Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e434f59f9a | ||
|
|
a4fbf67d73 | ||
|
|
1aea90cb07 | ||
|
|
20b6660fa9 | ||
|
|
815628c5ee | ||
|
|
6caa3e312c | ||
|
|
94829d9b83 | ||
|
|
7be22b091f | ||
|
|
fff5e5c0e1 | ||
|
|
f1ece27c99 | ||
|
|
5e48da6940 | ||
|
|
28fe1aaa89 | ||
|
|
f846935a2a | ||
|
|
42b452b9f8 | ||
|
|
514535a607 | ||
|
|
55f24b2de2 | ||
|
|
24d76c2fb6 | ||
|
|
f2ff2b4940 | ||
|
|
6d1563e22a | ||
|
|
9a3c181442 | ||
|
|
010b7336cd | ||
|
|
00c197e2ee | ||
|
|
c6a98d93e4 | ||
|
|
edd8aae7a7 | ||
|
|
3677d43aab | ||
|
|
0cdba65e48 | ||
|
|
022fc06257 | ||
|
|
61bc73e705 |
4
api/Gopkg.lock
generated
4
api/Gopkg.lock
generated
@@ -116,10 +116,11 @@
|
|||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
digest = "1:bea0314c10bd362ab623af4880d853b5bad3b63d0ab9945c47e461b8d04203ed"
|
digest = "1:82e6e4dc5ab71680d89684e4649be630fdeeaf81feb8e88e4a56273a0cd4d966"
|
||||||
name = "golang.org/x/oauth2"
|
name = "golang.org/x/oauth2"
|
||||||
packages = [
|
packages = [
|
||||||
".",
|
".",
|
||||||
|
"github",
|
||||||
"google",
|
"google",
|
||||||
"internal",
|
"internal",
|
||||||
"jws",
|
"jws",
|
||||||
@@ -161,6 +162,7 @@
|
|||||||
"github.com/russross/blackfriday",
|
"github.com/russross/blackfriday",
|
||||||
"golang.org/x/crypto/bcrypt",
|
"golang.org/x/crypto/bcrypt",
|
||||||
"golang.org/x/oauth2",
|
"golang.org/x/oauth2",
|
||||||
|
"golang.org/x/oauth2/github",
|
||||||
"golang.org/x/oauth2/google",
|
"golang.org/x/oauth2/google",
|
||||||
]
|
]
|
||||||
solver-name = "gps-cdcl"
|
solver-name = "gps-cdcl"
|
||||||
|
|||||||
@@ -78,12 +78,13 @@ func commentNewHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// logic: (empty column indicates the value doesn't matter)
|
// logic: (empty column indicates the value doesn't matter)
|
||||||
// | anonymous | moderator | requireIdentification | requireModeration | approved? |
|
// | anonymous | moderator | requireIdentification | requireModeration | moderateAllAnonymous | approved? |
|
||||||
// |-----------+-----------+-----------------------+-------------------+-----------|
|
// |-----------+-----------+-----------------------+-------------------+----------------------+-----------|
|
||||||
// | yes | | | | no |
|
// | yes | | | | no | yes |
|
||||||
// | no | yes | | | yes |
|
// | yes | | | | yes | no |
|
||||||
// | no | no | | yes | yes |
|
// | no | yes | | | | yes |
|
||||||
// | no | no | | no | no |
|
// | no | no | | yes | | yes |
|
||||||
|
// | no | no | | no | | no |
|
||||||
|
|
||||||
var commenterHex string
|
var commenterHex string
|
||||||
var state string
|
var state string
|
||||||
@@ -93,7 +94,11 @@ func commentNewHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
if isSpam(*x.Domain, getIp(r), getUserAgent(r), "Anonymous", "", "", *x.Markdown) {
|
if isSpam(*x.Domain, getIp(r), getUserAgent(r), "Anonymous", "", "", *x.Markdown) {
|
||||||
state = "flagged"
|
state = "flagged"
|
||||||
} else {
|
} else {
|
||||||
state = "unapproved"
|
if d.ModerateAllAnonymous {
|
||||||
|
state = "unapproved"
|
||||||
|
} else {
|
||||||
|
state = "approved"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
c, err := commenterGetByCommenterToken(*x.CommenterToken)
|
c, err := commenterGetByCommenterToken(*x.CommenterToken)
|
||||||
@@ -134,5 +139,5 @@ func commentNewHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
bodyMarshal(w, response{"success": true, "commentHex": commentHex, "state": state})
|
bodyMarshal(w, response{"success": true, "commentHex": commentHex, "state": state, "html": markdownToHtml(*x.Markdown)})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ func configParse() error {
|
|||||||
|
|
||||||
"GOOGLE_KEY": "",
|
"GOOGLE_KEY": "",
|
||||||
"GOOGLE_SECRET": "",
|
"GOOGLE_SECRET": "",
|
||||||
|
|
||||||
|
"GITHUB_KEY": "",
|
||||||
|
"GITHUB_SECRET": "",
|
||||||
}
|
}
|
||||||
|
|
||||||
for key, value := range defaults {
|
for key, value := range defaults {
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
var version = "v1.4.1"
|
var version = "v1.5.0"
|
||||||
|
|||||||
25
api/cron_domain_export_cleanup.go
Normal file
25
api/cron_domain_export_cleanup.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func domainExportCleanupBegin() error {
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
statement := `
|
||||||
|
DELETE FROM exports
|
||||||
|
WHERE creationDate < $1;
|
||||||
|
`
|
||||||
|
_, err := db.Exec(statement, time.Now().UTC().AddDate(0, 0, -7))
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("error cleaning up export rows: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(2 * time.Hour)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
25
api/cron_views_cleanup.go
Normal file
25
api/cron_views_cleanup.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func viewsCleanupBegin() error {
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
statement := `
|
||||||
|
DELETE FROM views
|
||||||
|
WHERE viewDate < $1;
|
||||||
|
`
|
||||||
|
_, err := db.Exec(statement, time.Now().UTC().AddDate(0, 0, -45))
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("error cleaning up views: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(24 * time.Hour)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -14,5 +14,6 @@ type domain struct {
|
|||||||
AutoSpamFilter bool `json:"autoSpamFilter"`
|
AutoSpamFilter bool `json:"autoSpamFilter"`
|
||||||
RequireModeration bool `json:"requireModeration"`
|
RequireModeration bool `json:"requireModeration"`
|
||||||
RequireIdentification bool `json:"requireIdentification"`
|
RequireIdentification bool `json:"requireIdentification"`
|
||||||
|
ModerateAllAnonymous bool `json:"moderateAllAnonymous"`
|
||||||
Moderators []moderator `json:"moderators"`
|
Moderators []moderator `json:"moderators"`
|
||||||
}
|
}
|
||||||
|
|||||||
151
api/domain_export.go
Normal file
151
api/domain_export.go
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func domainExportBeginError(email string, toName string, domain string, err error) {
|
||||||
|
// we're not using err at the moment because it's all errorInternal
|
||||||
|
if err2 := smtpDomainExportError(email, toName, domain); err2 != nil {
|
||||||
|
logger.Errorf("cannot send domain export error email for %s: %v", domain, err2)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func domainExportBegin(email string, toName string, domain string) {
|
||||||
|
type dataExport struct {
|
||||||
|
Version int `json:"version"`
|
||||||
|
Comments []comment `json:"comments"`
|
||||||
|
Commenters []commenter `json:"commenters"`
|
||||||
|
}
|
||||||
|
|
||||||
|
e := dataExport{Version: 1, Comments: []comment{}, Commenters: []commenter{}}
|
||||||
|
|
||||||
|
statement := `
|
||||||
|
SELECT commentHex, domain, path, commenterHex, markdown, parentHex, score, state, creationDate
|
||||||
|
FROM comments
|
||||||
|
WHERE domain = $1;
|
||||||
|
`
|
||||||
|
rows1, err := db.Query(statement, domain)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("cannot select comments while exporting %s: %v", domain, err)
|
||||||
|
domainExportBeginError(email, toName, domain, errorInternal)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer rows1.Close()
|
||||||
|
|
||||||
|
for rows1.Next() {
|
||||||
|
c := comment{}
|
||||||
|
if err = rows1.Scan(&c.CommentHex, &c.Domain, &c.Path, &c.CommenterHex, &c.Markdown, &c.ParentHex, &c.Score, &c.State, &c.CreationDate); err != nil {
|
||||||
|
logger.Errorf("cannot scan comment while exporting %s: %v", domain, err)
|
||||||
|
domainExportBeginError(email, toName, domain, errorInternal)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Comments = append(e.Comments, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
statement = `
|
||||||
|
SELECT commenters.commenterHex, commenters.email, commenters.name, commenters.link, commenters.photo, commenters.provider, commenters.joinDate
|
||||||
|
FROM commenters, comments
|
||||||
|
WHERE comments.domain = $1 AND commenters.commenterHex = comments.commenterHex;
|
||||||
|
`
|
||||||
|
rows2, err := db.Query(statement, domain)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("cannot select commenters while exporting %s: %v", domain, err)
|
||||||
|
domainExportBeginError(email, toName, domain, errorInternal)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer rows2.Close()
|
||||||
|
|
||||||
|
for rows2.Next() {
|
||||||
|
c := commenter{}
|
||||||
|
if err := rows2.Scan(&c.CommenterHex, &c.Email, &c.Name, &c.Link, &c.Photo, &c.Provider, &c.JoinDate); err != nil {
|
||||||
|
logger.Errorf("cannot scan commenter while exporting %s: %v", domain, err)
|
||||||
|
domainExportBeginError(email, toName, domain, errorInternal)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Commenters = append(e.Commenters, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
je, err := json.Marshal(e)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("cannot marshall JSON while exporting %s: %v", domain, err)
|
||||||
|
domainExportBeginError(email, toName, domain, errorInternal)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gje, err := gzipStatic(je)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("cannot gzip JSON while exporting %s: %v", domain, err)
|
||||||
|
domainExportBeginError(email, toName, domain, errorInternal)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
exportHex, err := randomHex(32)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("cannot generate exportHex while exporting %s: %v", domain, err)
|
||||||
|
domainExportBeginError(email, toName, domain, errorInternal)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
statement = `
|
||||||
|
INSERT INTO
|
||||||
|
exports (exportHex, binData, domain, creationDate)
|
||||||
|
VALUES ($1, $2, $3 , $4 );
|
||||||
|
`
|
||||||
|
_, err = db.Exec(statement, exportHex, gje, domain, time.Now().UTC())
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("error inserting expiry binary data while exporting %s: %v", domain, err)
|
||||||
|
domainExportBeginError(email, toName, domain, errorInternal)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = smtpDomainExport(email, toName, domain, exportHex)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("error sending data export email for %s: %v", domain, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func domainExportBeginHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
type request struct {
|
||||||
|
OwnerToken *string `json:"ownerToken"`
|
||||||
|
Domain *string `json:"domain"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var x request
|
||||||
|
if err := bodyUnmarshal(r, &x); err != nil {
|
||||||
|
bodyMarshal(w, response{"success": false, "message": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !smtpConfigured {
|
||||||
|
bodyMarshal(w, response{"success": false, "message": errorSmtpNotConfigured.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
o, err := ownerGetByOwnerToken(*x.OwnerToken)
|
||||||
|
if err != nil {
|
||||||
|
bodyMarshal(w, response{"success": false, "message": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isOwner, err := domainOwnershipVerify(o.OwnerHex, *x.Domain)
|
||||||
|
if err != nil {
|
||||||
|
bodyMarshal(w, response{"success": false, "message": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isOwner {
|
||||||
|
bodyMarshal(w, response{"success": false, "message": errorNotAuthorised.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go domainExportBegin(o.Email, o.Name, *x.Domain)
|
||||||
|
|
||||||
|
bodyMarshal(w, response{"success": true})
|
||||||
|
}
|
||||||
33
api/domain_export_download.go
Normal file
33
api/domain_export_download.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func domainExportDownloadHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
exportHex := r.FormValue("exportHex")
|
||||||
|
if exportHex == "" {
|
||||||
|
fmt.Fprintf(w, "Error: empty exportHex\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
statement := `
|
||||||
|
SELECT domain, binData, creationDate
|
||||||
|
FROM exports
|
||||||
|
WHERE exportHex = $1;
|
||||||
|
`
|
||||||
|
row := db.QueryRow(statement, exportHex)
|
||||||
|
|
||||||
|
var domain string
|
||||||
|
var binData []byte
|
||||||
|
var creationDate time.Time
|
||||||
|
if err := row.Scan(&domain, &binData, &creationDate); err != nil {
|
||||||
|
fmt.Fprintf(w, "Error: that exportHex does not exist\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Disposition", fmt.Sprintf(`inline; filename="%s-%v.gz"`, domain, creationDate.Unix()))
|
||||||
|
w.Header().Set("Content-Encoding", "gzip")
|
||||||
|
w.Write(binData)
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ func domainGet(dmn string) (domain, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
statement := `
|
statement := `
|
||||||
SELECT domain, ownerHex, name, creationDate, state, importedComments, autoSpamFilter, requireModeration, requireIdentification
|
SELECT domain, ownerHex, name, creationDate, state, importedComments, autoSpamFilter, requireModeration, requireIdentification, moderateAllAnonymous
|
||||||
FROM domains
|
FROM domains
|
||||||
WHERE domain = $1;
|
WHERE domain = $1;
|
||||||
`
|
`
|
||||||
@@ -16,7 +16,7 @@ func domainGet(dmn string) (domain, error) {
|
|||||||
|
|
||||||
var err error
|
var err error
|
||||||
d := domain{}
|
d := domain{}
|
||||||
if err = row.Scan(&d.Domain, &d.OwnerHex, &d.Name, &d.CreationDate, &d.State, &d.ImportedComments, &d.AutoSpamFilter, &d.RequireModeration, &d.RequireIdentification); err != nil {
|
if err = row.Scan(&d.Domain, &d.OwnerHex, &d.Name, &d.CreationDate, &d.State, &d.ImportedComments, &d.AutoSpamFilter, &d.RequireModeration, &d.RequireIdentification, &d.ModerateAllAnonymous); err != nil {
|
||||||
return d, errorNoSuchDomain
|
return d, errorNoSuchDomain
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ type disqusThread struct {
|
|||||||
|
|
||||||
type disqusAuthor struct {
|
type disqusAuthor struct {
|
||||||
XMLName xml.Name `xml:"author"`
|
XMLName xml.Name `xml:"author"`
|
||||||
IsAnonymous bool `xml:"isAnonymous"`
|
|
||||||
Name string `xml:"name"`
|
Name string `xml:"name"`
|
||||||
Email string `xml:"email"`
|
IsAnonymous bool `xml:"isAnonymous"`
|
||||||
|
Username string `xml:"username"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type disqusThreadId struct {
|
type disqusThreadId struct {
|
||||||
@@ -43,7 +43,6 @@ type disqusPost struct {
|
|||||||
Id string `xml:"http://disqus.com/disqus-internals id,attr"`
|
Id string `xml:"http://disqus.com/disqus-internals id,attr"`
|
||||||
ThreadId disqusThreadId `xml:"thread"`
|
ThreadId disqusThreadId `xml:"thread"`
|
||||||
ParentId disqusParentId `xml:"parent"`
|
ParentId disqusParentId `xml:"parent"`
|
||||||
PostId disqusPostId `xml:"post"`
|
|
||||||
Message string `xml:"message"`
|
Message string `xml:"message"`
|
||||||
CreationDate time.Time `xml:"createdAt"`
|
CreationDate time.Time `xml:"createdAt"`
|
||||||
IsDeleted bool `xml:"isDeleted"`
|
IsDeleted bool `xml:"isDeleted"`
|
||||||
@@ -98,24 +97,26 @@ func domainImportDisqus(domain string, url string) (int, error) {
|
|||||||
|
|
||||||
// Map Disqus emails to commenterHex (if not available, create a new one
|
// Map Disqus emails to commenterHex (if not available, create a new one
|
||||||
// with a random password that can be reset later).
|
// with a random password that can be reset later).
|
||||||
commenterHex := make(map[string]string)
|
commenterHex := map[string]string{}
|
||||||
for _, post := range x.Posts {
|
for _, post := range x.Posts {
|
||||||
if post.IsDeleted || post.IsSpam {
|
if post.IsDeleted || post.IsSpam {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := commenterHex[post.Author.Email]; ok {
|
email := post.Author.Username + "@disqus.com"
|
||||||
|
|
||||||
|
if _, ok := commenterHex[email]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := commenterGetByEmail("commento", post.Author.Email)
|
c, err := commenterGetByEmail("commento", email)
|
||||||
if err != nil && err != errorNoSuchCommenter {
|
if err != nil && err != errorNoSuchCommenter {
|
||||||
logger.Errorf("cannot get commenter by email: %v", err)
|
logger.Errorf("cannot get commenter by email: %v", err)
|
||||||
return 0, errorInternal
|
return 0, errorInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
commenterHex[post.Author.Email] = c.CommenterHex
|
commenterHex[email] = c.CommenterHex
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +126,7 @@ func domainImportDisqus(domain string, url string) (int, error) {
|
|||||||
return 0, errorInternal
|
return 0, errorInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
commenterHex[post.Author.Email], err = commenterNew(post.Author.Email, post.Author.Name, "undefined", "undefined", "commento", randomPassword)
|
commenterHex[email], err = commenterNew(email, post.Author.Name, "undefined", "undefined", "commento", randomPassword)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
@@ -134,12 +135,17 @@ func domainImportDisqus(domain string, url string) (int, error) {
|
|||||||
// For each Disqus post, create a Commento comment. Attempt to convert the
|
// For each Disqus post, create a Commento comment. Attempt to convert the
|
||||||
// HTML to markdown.
|
// HTML to markdown.
|
||||||
numImported := 0
|
numImported := 0
|
||||||
disqusIdMap := make(map[string]string)
|
disqusIdMap := map[string]string{}
|
||||||
for _, post := range x.Posts {
|
for _, post := range x.Posts {
|
||||||
if post.IsDeleted || post.IsSpam {
|
if post.IsDeleted || post.IsSpam {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cHex := "anonymous"
|
||||||
|
if !post.Author.IsAnonymous {
|
||||||
|
cHex = commenterHex[post.Author.Username+"@disqus.com"]
|
||||||
|
}
|
||||||
|
|
||||||
parentHex := "root"
|
parentHex := "root"
|
||||||
if val, ok := disqusIdMap[post.ParentId.Id]; ok {
|
if val, ok := disqusIdMap[post.ParentId.Id]; ok {
|
||||||
parentHex = val
|
parentHex = val
|
||||||
@@ -148,7 +154,7 @@ func domainImportDisqus(domain string, url string) (int, error) {
|
|||||||
// TODO: restrict the list of tags to just the basics: <a>, <b>, <i>, <code>
|
// TODO: restrict the list of tags to just the basics: <a>, <b>, <i>, <code>
|
||||||
// Especially remove <img> (convert it to <a>).
|
// Especially remove <img> (convert it to <a>).
|
||||||
commentHex, err := commentNew(
|
commentHex, err := commentNew(
|
||||||
commenterHex[post.Author.Email],
|
cHex,
|
||||||
domain,
|
domain,
|
||||||
pathStrip(threads[post.ThreadId.Id].URL),
|
pathStrip(threads[post.ThreadId.Id].URL),
|
||||||
parentHex,
|
parentHex,
|
||||||
@@ -159,7 +165,7 @@ func domainImportDisqus(domain string, url string) (int, error) {
|
|||||||
return numImported, err
|
return numImported, err
|
||||||
}
|
}
|
||||||
|
|
||||||
disqusIdMap[post.PostId.Id] = commentHex
|
disqusIdMap[post.Id] = commentHex
|
||||||
numImported += 1
|
numImported += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ func domainList(ownerHex string) ([]domain, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
statement := `
|
statement := `
|
||||||
SELECT domain, ownerHex, name, creationDate, state, importedComments, autoSpamFilter, requireModeration, requireIdentification
|
SELECT domain, ownerHex, name, creationDate, state, importedComments, autoSpamFilter, requireModeration, requireIdentification, moderateAllAnonymous
|
||||||
FROM domains
|
FROM domains
|
||||||
WHERE ownerHex=$1;
|
WHERE ownerHex=$1;
|
||||||
`
|
`
|
||||||
@@ -24,7 +24,7 @@ func domainList(ownerHex string) ([]domain, error) {
|
|||||||
domains := []domain{}
|
domains := []domain{}
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
d := domain{}
|
d := domain{}
|
||||||
if err = rows.Scan(&d.Domain, &d.OwnerHex, &d.Name, &d.CreationDate, &d.State, &d.ImportedComments, &d.AutoSpamFilter, &d.RequireModeration, &d.RequireIdentification); err != nil {
|
if err = rows.Scan(&d.Domain, &d.OwnerHex, &d.Name, &d.CreationDate, &d.State, &d.ImportedComments, &d.AutoSpamFilter, &d.RequireModeration, &d.RequireIdentification, &d.ModerateAllAnonymous); err != nil {
|
||||||
logger.Errorf("cannot Scan domain: %v", err)
|
logger.Errorf("cannot Scan domain: %v", err)
|
||||||
return nil, errorInternal
|
return nil, errorInternal
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ import (
|
|||||||
func domainUpdate(d domain) error {
|
func domainUpdate(d domain) error {
|
||||||
statement := `
|
statement := `
|
||||||
UPDATE domains
|
UPDATE domains
|
||||||
SET name=$2, state=$3, autoSpamFilter=$4, requireModeration=$5, requireIdentification=$6
|
SET name=$2, state=$3, autoSpamFilter=$4, requireModeration=$5, requireIdentification=$6, moderateAllAnonymous=$7
|
||||||
WHERE domain=$1;
|
WHERE domain=$1;
|
||||||
`
|
`
|
||||||
|
|
||||||
_, err := db.Exec(statement, d.Domain, d.Name, d.State, d.AutoSpamFilter, d.RequireModeration, d.RequireIdentification)
|
_, err := db.Exec(statement, d.Domain, d.Name, d.State, d.AutoSpamFilter, d.RequireModeration, d.RequireIdentification, d.ModerateAllAnonymous)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("cannot update non-moderators: %v", err)
|
logger.Errorf("cannot update non-moderators: %v", err)
|
||||||
return errorInternal
|
return errorInternal
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ func main() {
|
|||||||
exitIfError(markdownRendererCreate())
|
exitIfError(markdownRendererCreate())
|
||||||
exitIfError(sigintCleanupSetup())
|
exitIfError(sigintCleanupSetup())
|
||||||
exitIfError(versionCheckStart())
|
exitIfError(versionCheckStart())
|
||||||
|
exitIfError(domainExportCleanupBegin())
|
||||||
|
exitIfError(viewsCleanupBegin())
|
||||||
|
|
||||||
exitIfError(routesServe())
|
exitIfError(routesServe())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,5 +11,9 @@ func oauthConfigure() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := githubOauthConfigure(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
43
api/oauth_github.go
Normal file
43
api/oauth_github.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
"golang.org/x/oauth2/github"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var githubConfig *oauth2.Config
|
||||||
|
|
||||||
|
func githubOauthConfigure() error {
|
||||||
|
githubConfig = nil
|
||||||
|
if os.Getenv("GITHUB_KEY") == "" && os.Getenv("GITHUB_SECRET") == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if os.Getenv("GITHUB_KEY") == "" {
|
||||||
|
logger.Errorf("COMMENTO_GITHUB_KEY not configured, but COMMENTO_GITHUB_SECRET is set")
|
||||||
|
return errorOauthMisconfigured
|
||||||
|
}
|
||||||
|
|
||||||
|
if os.Getenv("GITHUB_SECRET") == "" {
|
||||||
|
logger.Errorf("COMMENTO_GITHUB_SECRET not configured, but COMMENTO_GITHUB_KEY is set")
|
||||||
|
return errorOauthMisconfigured
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Infof("loading github OAuth config")
|
||||||
|
|
||||||
|
githubConfig = &oauth2.Config{
|
||||||
|
RedirectURL: os.Getenv("ORIGIN") + "/api/oauth/github/callback",
|
||||||
|
ClientID: os.Getenv("GITHUB_KEY"),
|
||||||
|
ClientSecret: os.Getenv("GITHUB_SECRET"),
|
||||||
|
Scopes: []string{
|
||||||
|
"read:user",
|
||||||
|
"user:email",
|
||||||
|
},
|
||||||
|
Endpoint: github.Endpoint,
|
||||||
|
}
|
||||||
|
|
||||||
|
configuredOauths = append(configuredOauths, "github")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
115
api/oauth_github_callback.go
Normal file
115
api/oauth_github_callback.go
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func githubGetPrimaryEmail(accessToken string) (string, error) {
|
||||||
|
resp, err := http.Get("https://api.github.com/user/emails?access_token=" + accessToken)
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
contents, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", errorCannotReadResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
user := []map[string]interface{}{}
|
||||||
|
if err := json.Unmarshal(contents, &user); err != nil {
|
||||||
|
logger.Errorf("error unmarshaling github user: %v", err)
|
||||||
|
return "", errorInternal
|
||||||
|
}
|
||||||
|
|
||||||
|
nonPrimaryEmail := ""
|
||||||
|
for _, email := range user {
|
||||||
|
nonPrimaryEmail = email["email"].(string)
|
||||||
|
if email["primary"].(bool) {
|
||||||
|
return email["email"].(string), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nonPrimaryEmail, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func githubCallbackHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
commenterToken := r.FormValue("state")
|
||||||
|
code := r.FormValue("code")
|
||||||
|
|
||||||
|
_, err := commenterGetByCommenterToken(commenterToken)
|
||||||
|
if err != nil && err != errorNoSuchToken {
|
||||||
|
fmt.Fprintf(w, "Error: %s\n", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := githubConfig.Exchange(oauth2.NoContext, code)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(w, "Error: %s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
email, err := githubGetPrimaryEmail(token.AccessToken)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(w, "Error: %s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.Get("https://api.github.com/user?access_token=" + token.AccessToken)
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
contents, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(w, "Error: %s", errorCannotReadResponse.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user := make(map[string]interface{})
|
||||||
|
if err := json.Unmarshal(contents, &user); err != nil {
|
||||||
|
fmt.Fprintf(w, "Error: %s", errorInternal.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if email == "" {
|
||||||
|
if user["email"] == nil {
|
||||||
|
fmt.Fprintf(w, "Error: no email address returned by Github")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
email = user["email"].(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := commenterGetByEmail("github", email)
|
||||||
|
if err != nil && err != errorNoSuchCommenter {
|
||||||
|
fmt.Fprintf(w, "Error: %s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var commenterHex string
|
||||||
|
|
||||||
|
// TODO: in case of returning users, update the information we have on record?
|
||||||
|
if err == errorNoSuchCommenter {
|
||||||
|
var link string
|
||||||
|
if val, ok := user["html_url"]; ok {
|
||||||
|
link = val.(string)
|
||||||
|
} else {
|
||||||
|
link = "undefined"
|
||||||
|
}
|
||||||
|
|
||||||
|
commenterHex, err = commenterNew(email, user["name"].(string), link, user["avatar_url"].(string), "github", "")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(w, "Error: %s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
commenterHex = c.CommenterHex
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := commenterSessionUpdate(commenterToken, commenterHex); err != nil {
|
||||||
|
fmt.Fprintf(w, "Error: %s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(w, "<html><script>window.parent.close()</script></html>")
|
||||||
|
}
|
||||||
25
api/oauth_github_redirect.go
Normal file
25
api/oauth_github_redirect.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func githubRedirectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if githubConfig == nil {
|
||||||
|
logger.Errorf("github oauth access attempt without configuration")
|
||||||
|
fmt.Fprintf(w, "error: this website has not configured github OAuth")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
commenterToken := r.FormValue("commenterToken")
|
||||||
|
|
||||||
|
_, err := commenterGetByCommenterToken(commenterToken)
|
||||||
|
if err != nil && err != errorNoSuchToken {
|
||||||
|
fmt.Fprintf(w, "error: %s\n", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
url := githubConfig.AuthCodeURL(commenterToken)
|
||||||
|
http.Redirect(w, r, url, http.StatusFound)
|
||||||
|
}
|
||||||
@@ -39,7 +39,14 @@ func googleCallbackHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := commenterGetByEmail("google", user["email"].(string))
|
if user["email"] == nil {
|
||||||
|
fmt.Fprintf(w, "Error: no email address returned by Github")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
email := user["email"].(string)
|
||||||
|
|
||||||
|
c, err := commenterGetByEmail("google", email)
|
||||||
if err != nil && err != errorNoSuchCommenter {
|
if err != nil && err != errorNoSuchCommenter {
|
||||||
fmt.Fprintf(w, "Error: %s", err.Error())
|
fmt.Fprintf(w, "Error: %s", err.Error())
|
||||||
return
|
return
|
||||||
@@ -49,14 +56,6 @@ func googleCallbackHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// TODO: in case of returning users, update the information we have on record?
|
// TODO: in case of returning users, update the information we have on record?
|
||||||
if err == errorNoSuchCommenter {
|
if err == errorNoSuchCommenter {
|
||||||
var email string
|
|
||||||
if _, ok := user["email"]; ok {
|
|
||||||
email = user["email"].(string)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(w, "Error: %s", errorInvalidEmail.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var link string
|
var link string
|
||||||
if val, ok := user["link"]; ok {
|
if val, ok := user["link"]; ok {
|
||||||
link = val.(string)
|
link = val.(string)
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ func ownerNew(email string, name string, password string) (string, error) {
|
|||||||
return "", errorNewOwnerForbidden
|
return "", errorNewOwnerForbidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, err := ownerGetByEmail(email); err == nil {
|
||||||
|
return "", errorEmailAlreadyExists
|
||||||
|
}
|
||||||
|
|
||||||
ownerHex, err := randomHex(32)
|
ownerHex, err := randomHex(32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("cannot generate ownerHex: %v", err)
|
logger.Errorf("cannot generate ownerHex: %v", err)
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ func apiRouterInit(router *mux.Router) error {
|
|||||||
router.HandleFunc("/api/domain/moderator/delete", domainModeratorDeleteHandler).Methods("POST")
|
router.HandleFunc("/api/domain/moderator/delete", domainModeratorDeleteHandler).Methods("POST")
|
||||||
router.HandleFunc("/api/domain/statistics", domainStatisticsHandler).Methods("POST")
|
router.HandleFunc("/api/domain/statistics", domainStatisticsHandler).Methods("POST")
|
||||||
router.HandleFunc("/api/domain/import/disqus", domainImportDisqusHandler).Methods("POST")
|
router.HandleFunc("/api/domain/import/disqus", domainImportDisqusHandler).Methods("POST")
|
||||||
|
router.HandleFunc("/api/domain/export/begin", domainExportBeginHandler).Methods("POST")
|
||||||
|
router.HandleFunc("/api/domain/export/download", domainExportDownloadHandler).Methods("GET")
|
||||||
|
|
||||||
router.HandleFunc("/api/commenter/token/new", commenterTokenNewHandler).Methods("GET")
|
router.HandleFunc("/api/commenter/token/new", commenterTokenNewHandler).Methods("GET")
|
||||||
router.HandleFunc("/api/commenter/new", commenterNewHandler).Methods("POST")
|
router.HandleFunc("/api/commenter/new", commenterNewHandler).Methods("POST")
|
||||||
@@ -29,6 +31,9 @@ func apiRouterInit(router *mux.Router) error {
|
|||||||
router.HandleFunc("/api/oauth/google/redirect", googleRedirectHandler).Methods("GET")
|
router.HandleFunc("/api/oauth/google/redirect", googleRedirectHandler).Methods("GET")
|
||||||
router.HandleFunc("/api/oauth/google/callback", googleCallbackHandler).Methods("GET")
|
router.HandleFunc("/api/oauth/google/callback", googleCallbackHandler).Methods("GET")
|
||||||
|
|
||||||
|
router.HandleFunc("/api/oauth/github/redirect", githubRedirectHandler).Methods("GET")
|
||||||
|
router.HandleFunc("/api/oauth/github/callback", githubCallbackHandler).Methods("GET")
|
||||||
|
|
||||||
router.HandleFunc("/api/comment/new", commentNewHandler).Methods("POST")
|
router.HandleFunc("/api/comment/new", commentNewHandler).Methods("POST")
|
||||||
router.HandleFunc("/api/comment/list", commentListHandler).Methods("POST")
|
router.HandleFunc("/api/comment/list", commentListHandler).Methods("POST")
|
||||||
router.HandleFunc("/api/comment/count", commentCountHandler).Methods("POST")
|
router.HandleFunc("/api/comment/count", commentCountHandler).Methods("POST")
|
||||||
|
|||||||
29
api/smtp_domain_export.go
Normal file
29
api/smtp_domain_export.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"net/smtp"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type domainExportPlugs struct {
|
||||||
|
Origin string
|
||||||
|
Domain string
|
||||||
|
ExportHex string
|
||||||
|
}
|
||||||
|
|
||||||
|
func smtpDomainExport(to string, toName string, domain string, exportHex string) error {
|
||||||
|
var header bytes.Buffer
|
||||||
|
headerTemplate.Execute(&header, &headerPlugs{FromAddress: os.Getenv("SMTP_FROM_ADDRESS"), ToAddress: to, ToName: toName, Subject: "Commento Data Export"})
|
||||||
|
|
||||||
|
var body bytes.Buffer
|
||||||
|
templates["domain-export"].Execute(&body, &domainExportPlugs{Origin: os.Getenv("ORIGIN"), ExportHex: exportHex})
|
||||||
|
|
||||||
|
err := smtp.SendMail(os.Getenv("SMTP_HOST")+":"+os.Getenv("SMTP_PORT"), smtpAuth, os.Getenv("SMTP_FROM_ADDRESS"), []string{to}, concat(header, body))
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("cannot send data export email: %v", err)
|
||||||
|
return errorCannotSendEmail
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
28
api/smtp_domain_export_error.go
Normal file
28
api/smtp_domain_export_error.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"net/smtp"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type domainExportErrorPlugs struct {
|
||||||
|
Origin string
|
||||||
|
Domain string
|
||||||
|
}
|
||||||
|
|
||||||
|
func smtpDomainExportError(to string, toName string, domain string) error {
|
||||||
|
var header bytes.Buffer
|
||||||
|
headerTemplate.Execute(&header, &headerPlugs{FromAddress: os.Getenv("SMTP_FROM_ADDRESS"), ToAddress: to, ToName: toName, Subject: "Commento Data Export"})
|
||||||
|
|
||||||
|
var body bytes.Buffer
|
||||||
|
templates["data-export-error"].Execute(&body, &domainExportPlugs{Origin: os.Getenv("ORIGIN")})
|
||||||
|
|
||||||
|
err := smtp.SendMail(os.Getenv("SMTP_HOST")+":"+os.Getenv("SMTP_PORT"), smtpAuth, os.Getenv("SMTP_FROM_ADDRESS"), []string{to}, concat(header, body))
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("cannot send data export error email: %v", err)
|
||||||
|
return errorCannotSendEmail
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -2,8 +2,8 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
|
||||||
"os"
|
"os"
|
||||||
|
"text/template"
|
||||||
)
|
)
|
||||||
|
|
||||||
var headerTemplate *template.Template
|
var headerTemplate *template.Template
|
||||||
@@ -20,9 +20,9 @@ var templates map[string]*template.Template
|
|||||||
func smtpTemplatesLoad() error {
|
func smtpTemplatesLoad() error {
|
||||||
var err error
|
var err error
|
||||||
headerTemplate, err = template.New("header").Parse(`MIME-Version: 1.0
|
headerTemplate, err = template.New("header").Parse(`MIME-Version: 1.0
|
||||||
Content-Type: text/html; charset=UTF-8
|
From: Commento <{{.FromAddress}}>
|
||||||
From: {{.FromAddress}}
|
|
||||||
To: {{.ToName}} <{{.ToAddress}}>
|
To: {{.ToName}} <{{.ToAddress}}>
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
Subject: {{.Subject}}
|
Subject: {{.Subject}}
|
||||||
|
|
||||||
`)
|
`)
|
||||||
@@ -31,7 +31,7 @@ Subject: {{.Subject}}
|
|||||||
return errorMalformedTemplate
|
return errorMalformedTemplate
|
||||||
}
|
}
|
||||||
|
|
||||||
names := []string{"confirm-hex", "reset-hex"}
|
names := []string{"confirm-hex", "reset-hex", "domain-export", "domain-export-error"}
|
||||||
|
|
||||||
templates = make(map[string]*template.Template)
|
templates = make(map[string]*template.Template)
|
||||||
|
|
||||||
@@ -39,9 +39,9 @@ Subject: {{.Subject}}
|
|||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
var err error
|
var err error
|
||||||
templates[name] = template.New(name)
|
templates[name] = template.New(name)
|
||||||
templates[name], err = template.ParseFiles(fmt.Sprintf("%s/templates/%s.html", os.Getenv("STATIC"), name))
|
templates[name], err = template.ParseFiles(fmt.Sprintf("%s/templates/%s.txt", os.Getenv("STATIC"), name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("cannot parse %s/templates/%s.html: %v", os.Getenv("STATIC"), name, err)
|
logger.Errorf("cannot parse %s/templates/%s.txt: %v", os.Getenv("STATIC"), name, err)
|
||||||
return errorMalformedTemplate
|
return errorMalformedTemplate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
4
db/20190122235525-anonymous-moderation-default.sql
Normal file
4
db/20190122235525-anonymous-moderation-default.sql
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
-- Allow the owner to change whether anonymous comments are put into moderation by default.
|
||||||
|
|
||||||
|
ALTER TABLE domains
|
||||||
|
ADD COLUMN moderateAllAnonymous BOOLEAN DEFAULT true;
|
||||||
2
db/20190123002724-v1.4.2.sql
Normal file
2
db/20190123002724-v1.4.2.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
UPDATE config
|
||||||
|
SET version = 'v1.4.2';
|
||||||
8
db/20190131002240-export.sql
Normal file
8
db/20190131002240-export.sql
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
-- add export feature
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS exports (
|
||||||
|
exportHex TEXT NOT NULL UNIQUE PRIMARY KEY,
|
||||||
|
binData BYTEA NOT NULL,
|
||||||
|
domain TEXT NOT NULL,
|
||||||
|
creationDate TIMESTAMP NOT NULL
|
||||||
|
);
|
||||||
2
db/20190204180609-v1.5.0.sql
Normal file
2
db/20190204180609-v1.5.0.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
UPDATE config
|
||||||
|
SET version = 'v1.5.0';
|
||||||
@@ -24,7 +24,7 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- db_network
|
- db_network
|
||||||
volumes:
|
volumes:
|
||||||
- postgres_data_volume:/var/lib/postgres
|
- postgres_data_volume:/var/lib/postgresql/data
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
db_network:
|
db_network:
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta name="viewport" content="user-scalable=no, initial-scale=1.0">
|
<meta name="viewport" content="user-scalable=no, initial-scale=1.0">
|
||||||
<script src="[[[.CdnPrefix]]]/js/jquery.js"></script>
|
<script src="[[[.CdnPrefix]]]/js/jquery.js"></script>
|
||||||
|
<link rel="icon" href="[[[.CdnPrefix]]]/images/120x120.png">
|
||||||
<link rel="stylesheet" href="[[[.CdnPrefix]]]/css/auth.css">
|
<link rel="stylesheet" href="[[[.CdnPrefix]]]/css/auth.css">
|
||||||
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:400,700|Source+Sans+Pro:200,300,400,700" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:400,700|Source+Sans+Pro:200,300,400,700" rel="stylesheet">
|
||||||
<title>Commento: Email Confirmation</title>
|
<title>Commento: Email Confirmation</title>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
<script src="[[[.CdnPrefix]]]/js/highlight.js"></script>
|
<script src="[[[.CdnPrefix]]]/js/highlight.js"></script>
|
||||||
<script src="[[[.CdnPrefix]]]/js/chartist.js"></script>
|
<script src="[[[.CdnPrefix]]]/js/chartist.js"></script>
|
||||||
<script src="[[[.CdnPrefix]]]/js/dashboard.js"></script>
|
<script src="[[[.CdnPrefix]]]/js/dashboard.js"></script>
|
||||||
|
<link rel="icon" href="[[[.CdnPrefix]]]/images/120x120.png">
|
||||||
<link rel="stylesheet" href="[[[.CdnPrefix]]]/css/chartist.css">
|
<link rel="stylesheet" href="[[[.CdnPrefix]]]/css/chartist.css">
|
||||||
<link rel="stylesheet" href="[[[.CdnPrefix]]]/css/dashboard.css">
|
<link rel="stylesheet" href="[[[.CdnPrefix]]]/css/dashboard.css">
|
||||||
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:400,700|Source+Sans+Pro:200,300,400,700" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:400,700|Source+Sans+Pro:200,300,400,700" rel="stylesheet">
|
||||||
@@ -81,23 +82,29 @@
|
|||||||
<!-- Installation -->
|
<!-- Installation -->
|
||||||
<div id="installation-view" class="view hidden">
|
<div id="installation-view" class="view hidden">
|
||||||
<div class="view-inside">
|
<div class="view-inside">
|
||||||
<div class="large-view">
|
<div class="mid-view">
|
||||||
<div class="tabs-container">
|
<div class="tabs-container">
|
||||||
<div class="tab">
|
<div class="tab">
|
||||||
<ul class="tabs">
|
<ul class="tabs">
|
||||||
<li class="tab-link original current" data-tab="install-tab-1">Universal Snippet</li>
|
<li class="tab-link original current" data-tab="installation-tab-1">Universal Snippet</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div id="install-tab-1" class="content original current">
|
<div id="installation-tab-1" class="content original current">
|
||||||
<div class="import-text">
|
<div class="normal-text">
|
||||||
Copy the following piece of HTML code and paste it where you'd like Commento to load.
|
Copy the following piece of HTML code and paste it where you'd like Commento to load.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<pre><code id="code-div" class="html"></code></pre>
|
<pre><code id="code-div" class="html"></code></pre>
|
||||||
|
|
||||||
<div class="text">
|
<div class="normal-text">
|
||||||
And that's it. All your settings, themes, and comments would be automagically loaded. Commento is mobile-responsive too, as it simply fills the container it is put in.
|
And that's it. All your settings, themes, and comments would be automagically loaded. Commento is mobile-responsive too, as it simply fills the container it is put in.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<div class="normal-text">
|
||||||
|
Read the Commento documentation <a href="https://docs.commento.io/configuration/">on configuration</a>.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -112,6 +119,11 @@
|
|||||||
<div class="center center-title">
|
<div class="center center-title">
|
||||||
Analytics
|
Analytics
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="normal-text">
|
||||||
|
Anonymous statistics such as monthly pageviews and number of comments
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
<div class="number">
|
<div class="number">
|
||||||
<div class="digits gray-digits">{{domains[cd].viewsLast30Days.zeros}}</div>
|
<div class="digits gray-digits">{{domains[cd].viewsLast30Days.zeros}}</div>
|
||||||
@@ -142,16 +154,57 @@
|
|||||||
<!-- moderation -->
|
<!-- moderation -->
|
||||||
<div id="moderation-view" class="view hidden">
|
<div id="moderation-view" class="view hidden">
|
||||||
<div class="view-inside">
|
<div class="view-inside">
|
||||||
<div class="small-view mid-view">
|
<div class="mid-view">
|
||||||
<div class="tabs-container">
|
<div class="tabs-container">
|
||||||
<div class="tab">
|
<div class="tab">
|
||||||
<ul class="tabs">
|
<ul class="tabs">
|
||||||
<li class="tab-link original current" data-tab="mod-tab-1">Moderator List</li>
|
<li class="tab-link original current" data-tab="mod-tab-1">General</li>
|
||||||
|
<li class="tab-link" data-tab="mod-tab-2">Add/Remove Moderators</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div id="mod-tab-1" class="content original current">
|
<div id="mod-tab-1" class="content original current">
|
||||||
|
<div class="row no-border round-check">
|
||||||
|
<input type="checkbox" class="switch" v-model="domains[cd].autoSpamFilter" id="spam-filtering">
|
||||||
|
<label for="spam-filtering">Automatic spam filtering</label>
|
||||||
|
<div class="pitch">
|
||||||
|
Commento uses Akismet's advanced spam detection to automatically identify and remove spam comments. This is strongly recommended. Requires backend configuration.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row no-border round-check">
|
||||||
|
<input type="checkbox" class="switch" v-model="domains[cd].requireModeration" id="require-moderation">
|
||||||
|
<label for="require-moderation">Require all comments to be approved manually</label>
|
||||||
|
<div class="pitch">
|
||||||
|
Enabling this would require a moderator to approve all comments. This is generally recommended if your site doesn't receive too much traffic.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row no-border round-check">
|
||||||
|
<input type="checkbox" class="switch" v-model="domains[cd].allowAnonymous" id="allow-anonymous">
|
||||||
|
<label for="allow-anonymous">Allow anonymous comments</label>
|
||||||
|
<div class="pitch">
|
||||||
|
Enabling this would allow your readers to comment anonymously. Disabling would require the to authenticate themselves (using their Google account, for example). Recommended.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row no-border round-check indent" v-if="domains[cd].allowAnonymous">
|
||||||
|
<input type="checkbox" class="switch" v-model="domains[cd].moderateAllAnonymous" id="moderate-all-anonymous">
|
||||||
|
<label for="moderate-all-anonymous">Require anonymous comments to be approved manually</label>
|
||||||
|
<div class="pitch">
|
||||||
|
Enabling this would require a moderator to approve all anonymous comments. This is recommended if most of your spam comments are from anonymous users.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="center">
|
||||||
|
<button id="save-general-button" onclick="window.commento.generalSaveHandler()" class="button">Save Changes</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="mod-tab-2" class="content">
|
||||||
<div class="pitch">
|
<div class="pitch">
|
||||||
Moderators have the power to approve and delete comments. To make someone a moderator, add their email address down below. Once added, shiny new moderation buttons will appear on each comment for that person on each page on this domain.
|
Moderators have the power to approve/delete comments and lock threads. Once you add an user as a moderator, shiny new buttons will appear on each comment on each page when they log in.<br><br>
|
||||||
|
|
||||||
|
You're still the only administrator and the only person who can add and remove moderators. Moderators do not have access to this dashboard. Their access is restricted to pages on your website.
|
||||||
</div>
|
</div>
|
||||||
<div class="commento-email-container">
|
<div class="commento-email-container">
|
||||||
<div class="commento-email">
|
<div class="commento-email">
|
||||||
@@ -178,44 +231,44 @@
|
|||||||
<!-- Configure Domain -->
|
<!-- Configure Domain -->
|
||||||
<div id="general-view" class="view hidden">
|
<div id="general-view" class="view hidden">
|
||||||
<div class="view-inside">
|
<div class="view-inside">
|
||||||
<div class="small-mid-view">
|
<div class="mid-view">
|
||||||
<div class="center center-title">
|
<div class="tabs-container">
|
||||||
Configure Domain
|
<div class="tab">
|
||||||
</div>
|
<ul class="tabs">
|
||||||
<div class="box">
|
<li class="tab-link original current" data-tab="configure-tab-1">General</li>
|
||||||
<div class="row">
|
<!-- <li class="tab-link" data-tab="configure-tab-2">Email Settings</li> -->
|
||||||
<div class="label">Website Name</div>
|
<li class="tab-link" data-tab="configure-tab-3">Export Data</li>
|
||||||
<input class="input gray-input" id="cur-domain-name" type="text" :placeholder="domains[cd].origName" v-model="domains[cd].name">
|
</ul>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row no-border round-check">
|
<div id="configure-tab-1" class="content original current">
|
||||||
<input type="checkbox" class="switch" v-model="domains[cd].autoSpamFilter" id="spam-filtering">
|
<div class="box">
|
||||||
<label for="spam-filtering">Automatic spam filtering</label>
|
<div class="row">
|
||||||
<div class="pitch">
|
<div class="label">Website Name</div>
|
||||||
Commento uses Akismet's advanced spam detection to automatically identify and remove spam comments. We strongly recommended you have this enabled.
|
<input class="input gray-input" id="cur-domain-name" type="text" :placeholder="domains[cd].origName" v-model="domains[cd].name">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="center">
|
||||||
|
<button id="save-general-button" onclick="window.commento.generalSaveHandler()" class="button">Save Changes</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<div id="configure-tab-2" class="content">
|
||||||
|
</div>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<div id="configure-tab-3" class="content">
|
||||||
|
<div class="normal-text">
|
||||||
|
You can export an archive of this domain's data (which includes all comments and commenters) in the JSON format. To initiate and queue an archive request, click the button below. You will receive an email containing the archive once it's ready.<br><br>
|
||||||
|
|
||||||
|
Please note that this requires valid SMTP settings in order to send emails.<br><br>
|
||||||
|
|
||||||
|
<div class="center">
|
||||||
|
<button id="domain-export-button" onclick="window.commento.domainExportBegin()" class="button">Initiate Data Export</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row no-border round-check">
|
|
||||||
<input type="checkbox" class="switch" v-model="domains[cd].requireModeration" id="require-moderation">
|
|
||||||
<label for="require-moderation">Require all comments to be approved manually</label>
|
|
||||||
<div class="pitch">
|
|
||||||
Enabling this would require a moderator to approve every comment. Moderators can manually delete comments even if this is not enabled.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row no-border round-check">
|
|
||||||
<input type="checkbox" class="switch" v-model="domains[cd].requireIdentification" id="require-identification">
|
|
||||||
<label for="require-identification">Require identification</label>
|
|
||||||
<div class="pitch">
|
|
||||||
Enabling this would require all commenters to authenticate themselves (using their Google account, for example). Disabling would allow anonymous comments.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="new-domain-error" class="modal-error-box"></div>
|
|
||||||
</div>
|
|
||||||
<div class="center">
|
|
||||||
<button id="save-general-button" onclick="window.commento.generalSaveHandler()" class="button">Save Changes</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -224,7 +277,7 @@
|
|||||||
<!-- Import Comments -->
|
<!-- Import Comments -->
|
||||||
<div id="import-view" class="view hidden">
|
<div id="import-view" class="view hidden">
|
||||||
<div class="view-inside">
|
<div class="view-inside">
|
||||||
<div class="large-view">
|
<div class="mid-view">
|
||||||
<div class="tabs-container">
|
<div class="tabs-container">
|
||||||
<div class="tab">
|
<div class="tab">
|
||||||
<ul class="tabs">
|
<ul class="tabs">
|
||||||
@@ -232,15 +285,15 @@
|
|||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div id="install-tab-1" class="content original current">
|
<div id="install-tab-1" class="content original current">
|
||||||
<div class="import-text">
|
<div class="normal-text">
|
||||||
If you're currently using Disqus and want to import all your comments into Commento, you can do so:
|
If you're currently using Disqus, you can import all comments into Commento:
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
Go to <a href="http://disqus.com/admin/discussions/export/">the admin export section</a> in Disqus and click on <b>Export Comments</b>. This should start the process of exporting your comments.
|
Go to <a href="http://disqus.com/admin/discussions/export/">the admin export section</a> in Disqus and click on <b>Export Comments</b>. This should start the process of exporting your comments in the background.
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
After a while, you'll receive an email from Disqus with a link to a compressed archive of all comments and associated data. Copy and paste that link here and start the import process:
|
You'll receive an email from Disqus with a link to a compressed archive of all comments and associated data. Copy and paste that link here to start the import process:
|
||||||
|
|
||||||
<br><br>
|
<br><br>
|
||||||
|
|
||||||
@@ -250,14 +303,23 @@
|
|||||||
<button id="disqus-import-button" class="commento-email-button" onclick="window.commento.importDisqus()">Import</button>
|
<button id="disqus-import-button" class="commento-email-button" onclick="window.commento.importDisqus()">Import</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!--
|
||||||
<div class="subtext-container">
|
<div class="subtext-container">
|
||||||
<div class="subtext">
|
<div class="subtext">
|
||||||
<div>Note: it is strongly recommended you do this only once. Multiple imports for the same domain may have unintended effects.</div>
|
<div>By using this service, you grant Commento the permission to download and process your Disqus information.</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<br>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
We'll automatically download this file, extract it, parse it and import comments into Commento. The URL information will be preserved. By using this service, you grant Commento the permission to download and process your Disqus information.
|
Commento will automatically download this file, extract it, parse it and import comments into Commento. URL information, comment authors, text formatting, and nested replies will be preserved.
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
It is strongly recommended you do this only once. Importing multiple times may have unintended effects.
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
<meta name="viewport" content="user-scalable=no, initial-scale=1.0">
|
<meta name="viewport" content="user-scalable=no, initial-scale=1.0">
|
||||||
<script src="[[[.CdnPrefix]]]/js/jquery.js"></script>
|
<script src="[[[.CdnPrefix]]]/js/jquery.js"></script>
|
||||||
<script src="[[[.CdnPrefix]]]/js/forgot.js"></script>
|
<script src="[[[.CdnPrefix]]]/js/forgot.js"></script>
|
||||||
|
<link rel="icon" href="[[[.CdnPrefix]]]/images/120x120.png">
|
||||||
<link rel="stylesheet" href="[[[.CdnPrefix]]]/css/auth.css">
|
<link rel="stylesheet" href="[[[.CdnPrefix]]]/css/auth.css">
|
||||||
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:400,700|Source+Sans+Pro:200,300,400,700" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:400,700|Source+Sans+Pro:200,300,400,700" rel="stylesheet">
|
||||||
<title>Commento: Reset your Password</title>
|
<title>Commento: Reset your Password</title>
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ const jsCompileMap = {
|
|||||||
"js/dashboard-statistics.js",
|
"js/dashboard-statistics.js",
|
||||||
"js/dashboard-import.js",
|
"js/dashboard-import.js",
|
||||||
"js/dashboard-danger.js",
|
"js/dashboard-danger.js",
|
||||||
|
"js/dashboard-export.js",
|
||||||
],
|
],
|
||||||
"js/logout.js": [
|
"js/logout.js": [
|
||||||
"js/constants.js",
|
"js/constants.js",
|
||||||
|
|||||||
@@ -13,8 +13,8 @@
|
|||||||
|
|
||||||
// Prefills the email field from the URL parameter.
|
// Prefills the email field from the URL parameter.
|
||||||
global.prefillEmail = function() {
|
global.prefillEmail = function() {
|
||||||
if (paramGet("email") !== undefined) {
|
if (global.paramGet("email") !== undefined) {
|
||||||
$("#email").val(paramGet("email"));
|
$("#email").val(global.paramGet("email"));
|
||||||
$("#password").click();
|
$("#password").click();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -46,6 +46,7 @@
|
|||||||
var ID_TEXTAREA = "commento-textarea-";
|
var ID_TEXTAREA = "commento-textarea-";
|
||||||
var ID_CARD = "commento-comment-card-";
|
var ID_CARD = "commento-comment-card-";
|
||||||
var ID_BODY = "commento-comment-body-";
|
var ID_BODY = "commento-comment-body-";
|
||||||
|
var ID_TEXT = "commento-comment-text-";
|
||||||
var ID_SUBTITLE = "commento-comment-subtitle-";
|
var ID_SUBTITLE = "commento-comment-subtitle-";
|
||||||
var ID_TIMEAGO = "commento-comment-timeago-";
|
var ID_TIMEAGO = "commento-comment-timeago-";
|
||||||
var ID_SCORE = "commento-comment-score-";
|
var ID_SCORE = "commento-comment-score-";
|
||||||
@@ -72,7 +73,7 @@
|
|||||||
var autoInit;
|
var autoInit;
|
||||||
var isAuthenticated = false;
|
var isAuthenticated = false;
|
||||||
var comments = [];
|
var comments = [];
|
||||||
var commenters = [];
|
var commenters = {};
|
||||||
var requireIdentification = true;
|
var requireIdentification = true;
|
||||||
var isModerator = false;
|
var isModerator = false;
|
||||||
var isFrozen = false;
|
var isFrozen = false;
|
||||||
@@ -84,6 +85,7 @@
|
|||||||
var configuredOauths = [];
|
var configuredOauths = [];
|
||||||
var loginBoxType = "signup";
|
var loginBoxType = "signup";
|
||||||
var oauthButtonsShown = false;
|
var oauthButtonsShown = false;
|
||||||
|
var selfHex = undefined;
|
||||||
|
|
||||||
|
|
||||||
function $(id) {
|
function $(id) {
|
||||||
@@ -96,13 +98,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function prepend(root, el) {
|
||||||
|
root.prepend(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function append(root, el) {
|
function append(root, el) {
|
||||||
root.appendChild(el);
|
root.appendChild(el);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function prepend(root, el) {
|
function insertAfter(el1, el2) {
|
||||||
root.prepend(el);
|
el1.parentNode.insertBefore(el2, el1.nextSibling);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -239,12 +246,15 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
commenters[resp.commenter.commenterHex] = resp.commenter;
|
||||||
|
selfHex = resp.commenter.commenterHex;
|
||||||
|
|
||||||
var loggedContainer = create("div");
|
var loggedContainer = create("div");
|
||||||
var loggedInAs = create("div");
|
var loggedInAs = create("div");
|
||||||
var name = create("a");
|
var name = create("a");
|
||||||
var avatar;
|
var avatar;
|
||||||
var logout = create("div");
|
var logout = create("div");
|
||||||
var color = colorGet(resp.commenter.name);
|
var color = colorGet(resp.commenter.commenterHex + "-" + resp.commenter.name);
|
||||||
|
|
||||||
loggedContainer.id = ID_LOGGED_CONTAINER;
|
loggedContainer.id = ID_LOGGED_CONTAINER;
|
||||||
|
|
||||||
@@ -269,6 +279,8 @@
|
|||||||
avatar = create("img");
|
avatar = create("img");
|
||||||
if (resp.commenter.provider === "google") {
|
if (resp.commenter.provider === "google") {
|
||||||
attrSet(avatar, "src", resp.commenter.photo + "?sz=50");
|
attrSet(avatar, "src", resp.commenter.photo + "?sz=50");
|
||||||
|
} else if (resp.commenter.provider === "github") {
|
||||||
|
attrSet(avatar, "src", resp.commenter.photo + "&s=50");
|
||||||
} else {
|
} else {
|
||||||
attrSet(avatar, "src", resp.commenter.photo);
|
attrSet(avatar, "src", resp.commenter.photo);
|
||||||
}
|
}
|
||||||
@@ -352,7 +364,7 @@
|
|||||||
stickyCommentHex = resp.attributes.stickyCommentHex;
|
stickyCommentHex = resp.attributes.stickyCommentHex;
|
||||||
|
|
||||||
comments = resp.comments;
|
comments = resp.comments;
|
||||||
commenters = resp.commenters;
|
commenters = Object.assign({}, commenters, resp.commenters)
|
||||||
configuredOauths = resp.configuredOauths;
|
configuredOauths = resp.configuredOauths;
|
||||||
|
|
||||||
cssLoad(cdn + "/css/commento.css", "window.commento.loadCssOverride()");
|
cssLoad(cdn + "/css/commento.css", "window.commento.loadCssOverride()");
|
||||||
@@ -465,7 +477,7 @@
|
|||||||
commentsArea.innerHTML = "";
|
commentsArea.innerHTML = "";
|
||||||
|
|
||||||
if (isLocked || isFrozen) {
|
if (isLocked || isFrozen) {
|
||||||
if (isAuthenticated) {
|
if (isAuthenticated || chosenAnonymous) {
|
||||||
append(mainArea, messageCreate("This thread is locked. You cannot add new comments."));
|
append(mainArea, messageCreate("This thread is locked. You cannot add new comments."));
|
||||||
} else {
|
} else {
|
||||||
append(mainArea, textareaCreate("root"));
|
append(mainArea, textareaCreate("root"));
|
||||||
@@ -493,11 +505,13 @@
|
|||||||
|
|
||||||
|
|
||||||
global.commentNew = function(id) {
|
global.commentNew = function(id) {
|
||||||
|
var textareaSuperContainer = $(ID_SUPER_CONTAINER + id);
|
||||||
var textarea = $(ID_TEXTAREA + id);
|
var textarea = $(ID_TEXTAREA + id);
|
||||||
|
var replyButton = $(ID_REPLY + id);
|
||||||
|
|
||||||
var comment = textarea.value;
|
var markdown = textarea.value;
|
||||||
|
|
||||||
if (comment === "") {
|
if (markdown === "") {
|
||||||
classAdd(textarea, "red-border");
|
classAdd(textarea, "red-border");
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
@@ -509,7 +523,7 @@
|
|||||||
"domain": location.host,
|
"domain": location.host,
|
||||||
"path": location.pathname,
|
"path": location.pathname,
|
||||||
"parentHex": id,
|
"parentHex": id,
|
||||||
"markdown": comment,
|
"markdown": markdown,
|
||||||
};
|
};
|
||||||
|
|
||||||
post(origin + "/api/comment/new", json, function(resp) {
|
post(origin + "/api/comment/new", json, function(resp) {
|
||||||
@@ -518,27 +532,51 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$(ID_TEXTAREA + id).value = "";
|
var message = "";
|
||||||
|
if (resp.state === "unapproved") {
|
||||||
|
message = "Your comment is under moderation.";
|
||||||
|
} else if (resp.state === "flagged") {
|
||||||
|
message = "Your comment was flagged as spam and is under moderation.";
|
||||||
|
}
|
||||||
|
|
||||||
commentsGet(function() {
|
if (message !== "") {
|
||||||
$(ID_COMMENTS_AREA).innerHTML = "";
|
if (id === "root") {
|
||||||
commentsRender();
|
prepend($(ID_SUPER_CONTAINER + id), messageCreate(message));
|
||||||
|
} else {
|
||||||
var message = "";
|
append($(ID_BODY + id), messageCreate(message));
|
||||||
if (resp.state === "unapproved") {
|
|
||||||
message = "Your comment is under moderation.";
|
|
||||||
} else if (resp.state === "flagged") {
|
|
||||||
message = "Your comment was flagged as spam and is under moderation.";
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (message !== "") {
|
var newCard = commentsRecurse({
|
||||||
if (id === "root") {
|
"root": [{
|
||||||
prepend($(ID_SUPER_CONTAINER + id), messageCreate(message));
|
"commentHex": resp.commentHex,
|
||||||
} else {
|
"commenterHex": selfHex,
|
||||||
append($(ID_BODY + id), messageCreate(message));
|
"markdown": markdown,
|
||||||
}
|
"html": resp.html,
|
||||||
}
|
"parentHex": "root",
|
||||||
});
|
"score": 0,
|
||||||
|
"state": "approved",
|
||||||
|
"direction": 0,
|
||||||
|
"creationDate": (new Date()).toISOString(),
|
||||||
|
}],
|
||||||
|
}, "root")
|
||||||
|
|
||||||
|
if (id !== "root") {
|
||||||
|
textareaSuperContainer.replaceWith(newCard);
|
||||||
|
|
||||||
|
shownReply[id] = false;
|
||||||
|
shownSubmitButton[id] = false;
|
||||||
|
|
||||||
|
classAdd(replyButton, "option-reply");
|
||||||
|
classRemove(replyButton, "option-cancel");
|
||||||
|
|
||||||
|
replyButton.title = "Reply to this comment";
|
||||||
|
|
||||||
|
onclick(replyButton, global.replyShow, id)
|
||||||
|
} else {
|
||||||
|
textarea.value = "";
|
||||||
|
insertAfter(textareaSuperContainer, newCard);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -628,6 +666,7 @@
|
|||||||
var timeago = create("div");
|
var timeago = create("div");
|
||||||
var score = create("div");
|
var score = create("div");
|
||||||
var body = create("div");
|
var body = create("div");
|
||||||
|
var text = create("div");
|
||||||
var options = create("div");
|
var options = create("div");
|
||||||
var edit = create("button");
|
var edit = create("button");
|
||||||
var reply = create("button");
|
var reply = create("button");
|
||||||
@@ -649,6 +688,7 @@
|
|||||||
|
|
||||||
card.id = ID_CARD + comment.commentHex;
|
card.id = ID_CARD + comment.commentHex;
|
||||||
body.id = ID_BODY + comment.commentHex;
|
body.id = ID_BODY + comment.commentHex;
|
||||||
|
text.id = ID_TEXT + comment.commentHex;
|
||||||
subtitle.id = ID_SUBTITLE + comment.commentHex;
|
subtitle.id = ID_SUBTITLE + comment.commentHex;
|
||||||
timeago.id = ID_TIMEAGO + comment.commentHex;
|
timeago.id = ID_TIMEAGO + comment.commentHex;
|
||||||
score.id = ID_SCORE + comment.commentHex;
|
score.id = ID_SCORE + comment.commentHex;
|
||||||
@@ -686,7 +726,7 @@
|
|||||||
|
|
||||||
card.style["borderLeft"] = "2px solid " + color;
|
card.style["borderLeft"] = "2px solid " + color;
|
||||||
name.innerText = commenter.name;
|
name.innerText = commenter.name;
|
||||||
body.innerHTML = comment.html;
|
text.innerHTML = comment.html;
|
||||||
timeago.innerHTML = timeDifference((new Date()).getTime(), Date.parse(comment.creationDate));
|
timeago.innerHTML = timeDifference((new Date()).getTime(), Date.parse(comment.creationDate));
|
||||||
score.innerText = scorify(comment.score);
|
score.innerText = scorify(comment.score);
|
||||||
|
|
||||||
@@ -699,6 +739,8 @@
|
|||||||
avatar = create("img");
|
avatar = create("img");
|
||||||
if (commenter.provider === "google") {
|
if (commenter.provider === "google") {
|
||||||
attrSet(avatar, "src", commenter.photo + "?sz=50");
|
attrSet(avatar, "src", commenter.photo + "?sz=50");
|
||||||
|
} else if (commenter.provider === "github") {
|
||||||
|
attrSet(avatar, "src", commenter.photo + "&s=50");
|
||||||
} else {
|
} else {
|
||||||
attrSet(avatar, "src", commenter.photo);
|
attrSet(avatar, "src", commenter.photo);
|
||||||
}
|
}
|
||||||
@@ -809,6 +851,7 @@
|
|||||||
append(header, avatar);
|
append(header, avatar);
|
||||||
append(header, name);
|
append(header, name);
|
||||||
append(header, subtitle);
|
append(header, subtitle);
|
||||||
|
append(body, text);
|
||||||
append(contents, body);
|
append(contents, body);
|
||||||
if (mobileView) {
|
if (mobileView) {
|
||||||
append(contents, options);
|
append(contents, options);
|
||||||
@@ -938,8 +981,8 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var body = $(ID_BODY + id);
|
var text = $(ID_TEXT + id);
|
||||||
append(body, textareaCreate(id));
|
insertAfter(text, textareaCreate(id));
|
||||||
shownReply[id] = true;
|
shownReply[id] = true;
|
||||||
|
|
||||||
var replyButton = $(ID_REPLY + id);
|
var replyButton = $(ID_REPLY + id);
|
||||||
@@ -1408,15 +1451,9 @@
|
|||||||
isLocked = !isLocked;
|
isLocked = !isLocked;
|
||||||
|
|
||||||
lock.disabled = true;
|
lock.disabled = true;
|
||||||
pageUpdate(function(success) {
|
pageUpdate(function() {
|
||||||
if (success) {
|
lock.disabled = false;
|
||||||
lock.disabled = false;
|
refreshAll();
|
||||||
if (isLocked) {
|
|
||||||
lock.innerHTML = "Unlock Thread";
|
|
||||||
} else {
|
|
||||||
lock.innerHTML = "Lock Thread";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1434,16 +1471,14 @@
|
|||||||
stickyCommentHex = commentHex;
|
stickyCommentHex = commentHex;
|
||||||
}
|
}
|
||||||
|
|
||||||
pageUpdate(function(success) {
|
pageUpdate(function() {
|
||||||
if (success) {
|
var sticky = $(ID_STICKY + commentHex);
|
||||||
var sticky = $(ID_STICKY + commentHex);
|
if (stickyCommentHex === commentHex) {
|
||||||
if (stickyCommentHex === commentHex) {
|
classRemove(sticky, "option-sticky");
|
||||||
classRemove(sticky, "option-sticky");
|
classAdd(sticky, "option-unsticky");
|
||||||
classAdd(sticky, "option-unsticky");
|
} else {
|
||||||
} else {
|
classRemove(sticky, "option-unsticky");
|
||||||
classRemove(sticky, "option-unsticky");
|
classAdd(sticky, "option-sticky");
|
||||||
classAdd(sticky, "option-sticky");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,6 +92,8 @@
|
|||||||
resp.domains[i].viewsLast30Days = global.numberify(0);
|
resp.domains[i].viewsLast30Days = global.numberify(0);
|
||||||
resp.domains[i].commentsLast30Days = global.numberify(0);
|
resp.domains[i].commentsLast30Days = global.numberify(0);
|
||||||
|
|
||||||
|
resp.domains[i].allowAnonymous = !resp.domains[i].requireIdentification;
|
||||||
|
|
||||||
for (var j = 0; j < resp.domains[i].moderators.length; j++) {
|
for (var j = 0; j < resp.domains[i].moderators.length; j++) {
|
||||||
resp.domains[i].moderators[j].timeAgo = global.timeSince(
|
resp.domains[i].moderators[j].timeAgo = global.timeSince(
|
||||||
Date.parse(resp.domains[i].moderators[j].addDate));
|
Date.parse(resp.domains[i].moderators[j].addDate));
|
||||||
@@ -109,6 +111,7 @@
|
|||||||
|
|
||||||
// Updates a domain with the backend.
|
// Updates a domain with the backend.
|
||||||
global.domainUpdate = function(domain, callback) {
|
global.domainUpdate = function(domain, callback) {
|
||||||
|
domain.requireIdentification = !domain.allowAnonymous;
|
||||||
var json = {
|
var json = {
|
||||||
"ownerToken": global.cookieGet("commentoOwnerToken"),
|
"ownerToken": global.cookieGet("commentoOwnerToken"),
|
||||||
"domain": domain,
|
"domain": domain,
|
||||||
|
|||||||
26
frontend/js/dashboard-export.js
Normal file
26
frontend/js/dashboard-export.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
(function (global, document) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
(document);
|
||||||
|
|
||||||
|
global.domainExportBegin = function() {
|
||||||
|
var data = global.dashboard.$data;
|
||||||
|
|
||||||
|
var json = {
|
||||||
|
"ownerToken": global.cookieGet("commentoOwnerToken"),
|
||||||
|
"domain": data.domains[data.cd].domain,
|
||||||
|
}
|
||||||
|
|
||||||
|
global.buttonDisable("#domain-export-button");
|
||||||
|
global.post(global.origin + "/api/domain/export/begin", json, function(resp) {
|
||||||
|
global.buttonEnable("#domain-export-button");
|
||||||
|
if (!resp.success) {
|
||||||
|
global.globalErrorShow(resp.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
global.globalOKShow("Data export operation has been successfully queued. You will receive an email.");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
} (window.commento, document));
|
||||||
@@ -98,10 +98,10 @@
|
|||||||
series: [comments],
|
series: [comments],
|
||||||
}, options);
|
}, options);
|
||||||
|
|
||||||
data.domains[data.cd].viewsLast30Days = numberify(views.reduce(function(a, b) {
|
data.domains[data.cd].viewsLast30Days = global.numberify(views.reduce(function(a, b) {
|
||||||
return a + b;
|
return a + b;
|
||||||
}, 0));
|
}, 0));
|
||||||
data.domains[data.cd].commentsLast30Days = numberify(comments.reduce(function(a, b) {
|
data.domains[data.cd].commentsLast30Days = global.numberify(comments.reduce(function(a, b) {
|
||||||
return a + b;
|
return a + b;
|
||||||
}, 0));
|
}, 0));
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -23,29 +23,29 @@
|
|||||||
var settings = [
|
var settings = [
|
||||||
{
|
{
|
||||||
"id": "installation",
|
"id": "installation",
|
||||||
"text": "Installation",
|
"text": "Installation Guide",
|
||||||
"meaning": "Install Commento with HTML",
|
"meaning": "Install Commento with HTML",
|
||||||
"selected": false,
|
"selected": false,
|
||||||
"open": global.installationOpen,
|
"open": global.installationOpen,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "general",
|
"id": "general",
|
||||||
"text": "Configure Domain",
|
"text": "General",
|
||||||
"meaning": "Names, domains and the rest",
|
"meaning": "Email settings, data export",
|
||||||
"selected": false,
|
"selected": false,
|
||||||
"open": global.generalOpen,
|
"open": global.generalOpen,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "moderation",
|
"id": "moderation",
|
||||||
"text": "Moderation Settings",
|
"text": "Moderation Settings",
|
||||||
"meaning": "Manage list of moderators",
|
"meaning": "Manage moderators, spam filtering",
|
||||||
"selected": false,
|
"selected": false,
|
||||||
"open": global.moderationOpen,
|
"open": global.moderationOpen,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "statistics",
|
"id": "statistics",
|
||||||
"text": "View Activity",
|
"text": "Analytics",
|
||||||
"meaning": "Usage and comment statistics",
|
"meaning": "Anonymous statistics and graphs",
|
||||||
"selected": false,
|
"selected": false,
|
||||||
"open": global.statisticsOpen,
|
"open": global.statisticsOpen,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
var json = {
|
var json = {
|
||||||
"resetHex": paramGet("hex"),
|
"resetHex": global.paramGet("hex"),
|
||||||
"password": $("#password").val(),
|
"password": $("#password").val(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (resp.confirmEmail) {
|
if (resp.confirmEmail) {
|
||||||
document.locatidocumenton = global.origin + "/confirm-email";
|
document.location = global.origin + "/confirm-email";
|
||||||
} else {
|
} else {
|
||||||
document.location = global.origin + "/login?signedUp=true";
|
document.location = global.origin + "/login?signedUp=true";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
<meta name="viewport" content="user-scalable=no, initial-scale=1.0">
|
<meta name="viewport" content="user-scalable=no, initial-scale=1.0">
|
||||||
<script src="[[[.CdnPrefix]]]/js/jquery.js"></script>
|
<script src="[[[.CdnPrefix]]]/js/jquery.js"></script>
|
||||||
<script src="[[[.CdnPrefix]]]/js/login.js"></script>
|
<script src="[[[.CdnPrefix]]]/js/login.js"></script>
|
||||||
|
<link rel="icon" href="[[[.CdnPrefix]]]/images/120x120.png">
|
||||||
<link rel="stylesheet" href="[[[.CdnPrefix]]]/css/auth.css">
|
<link rel="stylesheet" href="[[[.CdnPrefix]]]/css/auth.css">
|
||||||
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:400,700|Source+Sans+Pro:200,300,400,700" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:400,700|Source+Sans+Pro:200,300,400,700" rel="stylesheet">
|
||||||
<title>Commento: Login</title>
|
<title>Commento: Login</title>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
<meta name="viewport" content="user-scalable=no, initial-scale=1.0">
|
<meta name="viewport" content="user-scalable=no, initial-scale=1.0">
|
||||||
<script src="[[[.CdnPrefix]]]/js/jquery.js"></script>
|
<script src="[[[.CdnPrefix]]]/js/jquery.js"></script>
|
||||||
<script src="[[[.CdnPrefix]]]/js/reset.js"></script>
|
<script src="[[[.CdnPrefix]]]/js/reset.js"></script>
|
||||||
|
<link rel="icon" href="[[[.CdnPrefix]]]/images/120x120.png">
|
||||||
<link rel="stylesheet" href="[[[.CdnPrefix]]]/css/auth.css">
|
<link rel="stylesheet" href="[[[.CdnPrefix]]]/css/auth.css">
|
||||||
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:400,700|Source+Sans+Pro:200,300,400,700" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:400,700|Source+Sans+Pro:200,300,400,700" rel="stylesheet">
|
||||||
<title>Commento: Reset your Password</title>
|
<title>Commento: Reset your Password</title>
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ textarea {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.commento-account-buttons-container {
|
.commento-account-buttons-container {
|
||||||
top: 3rem;
|
top: 45px;
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,13 +114,13 @@ textarea {
|
|||||||
.commento-google-button {
|
.commento-google-button {
|
||||||
background: #dd4b39;
|
background: #dd4b39;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
font-size: 12px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.commento-github-button {
|
.commento-github-button {
|
||||||
background: #000000;
|
background: #000000;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
font-size: 12px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.commento-anonymous-button {
|
.commento-anonymous-button {
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ body {
|
|||||||
|
|
||||||
.content{
|
.content{
|
||||||
display: none;
|
display: none;
|
||||||
padding: 25px;
|
padding: 12px;
|
||||||
|
|
||||||
.pitch {
|
.pitch {
|
||||||
color: $gray-6;
|
color: $gray-6;
|
||||||
@@ -201,8 +201,9 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.pitch {
|
.pitch {
|
||||||
font-size: 13px;
|
font-size: 14px;
|
||||||
color: #a0a0a0;
|
color: #a5a5a5;
|
||||||
|
line-height: 20px !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,7 +284,7 @@ body {
|
|||||||
.small-mid-view {
|
.small-mid-view {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
width: 70%;
|
width: 70%;
|
||||||
max-width: 500px;
|
max-width: 600px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mid-view {
|
.mid-view {
|
||||||
@@ -374,10 +375,10 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.import-text {
|
.normal-text {
|
||||||
font-size: 15px;
|
font-size: 14px;
|
||||||
color: $gray-6;
|
color: $gray-7;
|
||||||
line-height: 25px;
|
line-height: 22px;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: $blue-6;
|
color: $blue-6;
|
||||||
@@ -390,7 +391,7 @@ body {
|
|||||||
|
|
||||||
li {
|
li {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
li::before {
|
li::before {
|
||||||
@@ -540,7 +541,8 @@ body {
|
|||||||
.row {
|
.row {
|
||||||
padding-left: 8px;
|
padding-left: 8px;
|
||||||
padding-right: 8px;
|
padding-right: 8px;
|
||||||
margin-bottom: 32px;
|
margin-top: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
border-bottom: 1px solid $gray-2;
|
border-bottom: 1px solid $gray-2;
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
@@ -623,6 +625,11 @@ body {
|
|||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.indent {
|
||||||
|
margin-top: 0px;
|
||||||
|
padding-left: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
@extend .shadow;
|
@extend .shadow;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
@@ -689,7 +696,7 @@ body {
|
|||||||
|
|
||||||
.text {
|
.text {
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
color: $gray-5;
|
color: $gray-6;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
color: $blue-7;
|
color: $blue-7;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
|
width: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
.commento-email-button:hover {
|
.commento-email-button:hover {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
<meta name="viewport" content="user-scalable=no, initial-scale=1.0">
|
<meta name="viewport" content="user-scalable=no, initial-scale=1.0">
|
||||||
<script src="[[[.CdnPrefix]]]/js/jquery.js"></script>
|
<script src="[[[.CdnPrefix]]]/js/jquery.js"></script>
|
||||||
<script src="[[[.CdnPrefix]]]/js/signup.js"></script>
|
<script src="[[[.CdnPrefix]]]/js/signup.js"></script>
|
||||||
|
<link rel="icon" href="[[[.CdnPrefix]]]/images/120x120.png">
|
||||||
<link rel="stylesheet" href="[[[.CdnPrefix]]]/css/auth.css">
|
<link rel="stylesheet" href="[[[.CdnPrefix]]]/css/auth.css">
|
||||||
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:400,700|Source+Sans+Pro:200,300,400,700" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:400,700|Source+Sans+Pro:200,300,400,700" rel="stylesheet">
|
||||||
<title>Commento: Signup</title>
|
<title>Commento: Signup</title>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ DEVEL_BUILD_DIR = $(BUILD_DIR)/devel
|
|||||||
PROD_BUILD_DIR = $(BUILD_DIR)/prod
|
PROD_BUILD_DIR = $(BUILD_DIR)/prod
|
||||||
|
|
||||||
TEMPLATES_SRC_DIR = .
|
TEMPLATES_SRC_DIR = .
|
||||||
TEMPLATES_SRC_FILES = $(wildcard $(TEMPLATES_SRC_DIR)/*.html)
|
TEMPLATES_SRC_FILES = $(wildcard $(TEMPLATES_SRC_DIR)/*.txt)
|
||||||
TEMPLATES_DEVEL_BUILD_DIR = $(DEVEL_BUILD_DIR)/templates
|
TEMPLATES_DEVEL_BUILD_DIR = $(DEVEL_BUILD_DIR)/templates
|
||||||
TEMPLATES_DEVEL_BUILD_FILES = $(patsubst $(TEMPLATES_SRC_DIR)/%, $(TEMPLATES_DEVEL_BUILD_DIR)/%, $(TEMPLATES_SRC_FILES))
|
TEMPLATES_DEVEL_BUILD_FILES = $(patsubst $(TEMPLATES_SRC_DIR)/%, $(TEMPLATES_DEVEL_BUILD_DIR)/%, $(TEMPLATES_SRC_FILES))
|
||||||
TEMPLATES_PROD_BUILD_DIR = $(PROD_BUILD_DIR)/templates
|
TEMPLATES_PROD_BUILD_DIR = $(PROD_BUILD_DIR)/templates
|
||||||
@@ -20,12 +20,12 @@ clean:
|
|||||||
|
|
||||||
devel-templates: $(TEMPLATES_DEVEL_BUILD_FILES)
|
devel-templates: $(TEMPLATES_DEVEL_BUILD_FILES)
|
||||||
|
|
||||||
$(TEMPLATES_DEVEL_BUILD_FILES): $(TEMPLATES_DEVEL_BUILD_DIR)/%.html: $(TEMPLATES_SRC_DIR)/%.html
|
$(TEMPLATES_DEVEL_BUILD_FILES): $(TEMPLATES_DEVEL_BUILD_DIR)/%.txt: $(TEMPLATES_SRC_DIR)/%.txt
|
||||||
cp $^ $@;
|
cp $^ $@;
|
||||||
|
|
||||||
prod-templates: $(TEMPLATES_PROD_BUILD_FILES)
|
prod-templates: $(TEMPLATES_PROD_BUILD_FILES)
|
||||||
|
|
||||||
$(TEMPLATES_PROD_BUILD_FILES): $(TEMPLATES_PROD_BUILD_DIR)/%.html: $(TEMPLATES_SRC_DIR)/%.html
|
$(TEMPLATES_PROD_BUILD_FILES): $(TEMPLATES_PROD_BUILD_DIR)/%.txt: $(TEMPLATES_SRC_DIR)/%.txt
|
||||||
cp $^ $@;
|
cp $^ $@;
|
||||||
|
|
||||||
$(shell mkdir -p $(TEMPLATES_DEVEL_BUILD_DIR) $(TEMPLATES_PROD_BUILD_DIR))
|
$(shell mkdir -p $(TEMPLATES_DEVEL_BUILD_DIR) $(TEMPLATES_PROD_BUILD_DIR))
|
||||||
|
|||||||
@@ -1,90 +0,0 @@
|
|||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Commento: Verify your email address</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body leftmargin="0" marginwidth="0" topmargin="0" marginheight="0" offset="0" style="margin: 0pt auto; padding: 0px; background:#f8f9fa;">
|
|
||||||
<table id="main" width="100%" height="100%" cellpadding="0" cellspacing="0" border="0" bgcolor="#f8f9fa">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td valign="top">
|
|
||||||
<table cellpadding="0" width="580" cellspacing="0" border="0" bgcolor="#f8f9fa" align="center" style="margin:0 auto; table-layout: fixed;">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td colspan="4">
|
|
||||||
|
|
||||||
<table width="100%" cellpadding="0" cellspacing="0" border="0" style="background: white; border-radius: 4px; box-shadow: 0 1px 3px rgba(50, 50, 93, .15), 0 1px 0 rgba(0, 0, 0, .02); margin-top: 30px;">
|
|
||||||
<tbody>
|
|
||||||
<tr><td height="40"></td></tr>
|
|
||||||
|
|
||||||
<tr style='font-family: -apple-system,BlinkMacSystemFont,"Segoe UI","Roboto","Oxygen","Ubuntu","Cantarell","Fira Sans","Droid Sans","Helvetica Neue",sans-serif; color:#495057; font-size:14px; line-height:20px; margin-top:20px;'>
|
|
||||||
<td colspan="2" valign="top" align="center" style="padding-left:90px; padding-right:90px;">
|
|
||||||
|
|
||||||
<table width="100%" cellpadding="0" cellspacing="0" border="0" bgcolor="#ffffff">
|
|
||||||
<tbody>
|
|
||||||
<tr><td height="30"></td></tr>
|
|
||||||
|
|
||||||
<!-- Title -->
|
|
||||||
<tr>
|
|
||||||
<td align="center">
|
|
||||||
<span style="font-size:22px;line-height: 24px;">
|
|
||||||
Verify your email address
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr><td height="24"></td></tr>
|
|
||||||
|
|
||||||
<!-- Main content -->
|
|
||||||
<tr>
|
|
||||||
<td align="center">
|
|
||||||
<span style="color:#48545d;font-size:14px;line-height:24px;">
|
|
||||||
In order to start using Commento, you need to first confirm your email address. Click the link below to do that.
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr><td height="20"></td></tr>
|
|
||||||
|
|
||||||
<!-- Button -->
|
|
||||||
<tr>
|
|
||||||
<td valign="top" width="48%" align="center">
|
|
||||||
<span>
|
|
||||||
<a href="{{.Origin}}/api/owner/confirm-hex?confirmHex={{.ConfirmHex}}" style="display:block; padding:15px 25px; background-color:#007aff; color:#ffffff; border-radius:3px; text-decoration:none;">Verify Email Address</a>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr><td height="20"></td></tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr><td height="60"></td></tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<table width="100%" cellpadding="0" cellspacing="0" border="0">
|
|
||||||
<tbody>
|
|
||||||
<tr><td height="10"></td></tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td valign="top" align="center">
|
|
||||||
<span style='font-family: -apple-system,BlinkMacSystemFont,"Segoe UI","Roboto","Oxygen","Ubuntu","Cantarell","Fira Sans","Droid Sans","Helvetica Neue",sans-serif; color:#9EB0C9; font-size:10px;'>
|
|
||||||
If you did not sign up for this account you can safely ignore this email and the account will be automatically deleted.
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
7
templates/confirm-hex.txt
Normal file
7
templates/confirm-hex.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
Hi,
|
||||||
|
|
||||||
|
You recently registered a new Commento account with this email address. If you wish to complete registration, use the link below:
|
||||||
|
|
||||||
|
{{.Origin}}/api/owner/confirm-hex?confirmHex={{.ConfirmHex}}
|
||||||
|
|
||||||
|
If you did not do initiate this, you can ignore this email.
|
||||||
3
templates/domain-export-error.txt
Normal file
3
templates/domain-export-error.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
You recently requested a data export of your Commento domain {{.Domain}}. An
|
||||||
|
error was encountered while processing the request. Please contact support to
|
||||||
|
resolve this issue.
|
||||||
7
templates/domain-export.txt
Normal file
7
templates/domain-export.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
You recently requested a data export of your Commento domain {{.Domain}}. You
|
||||||
|
can download a GZipped archive of a JSON export of all the comments and
|
||||||
|
commenters associated with the domain here:
|
||||||
|
|
||||||
|
{{.Origin}}/api/domain/export/download?exportHex={{.ExportHex}}
|
||||||
|
|
||||||
|
The archive will be available for download for 7 days.
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Commento: Reset your password</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body leftmargin="0" marginwidth="0" topmargin="0" marginheight="0" offset="0" style="margin: 0pt auto; padding: 0px; background:#f8f9fa;">
|
|
||||||
<table id="main" width="100%" height="100%" cellpadding="0" cellspacing="0" border="0" bgcolor="#f8f9fa">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td valign="top">
|
|
||||||
<table cellpadding="0" width="580" cellspacing="0" border="0" bgcolor="#f8f9fa" align="center" style="margin:0 auto; table-layout: fixed;">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td colspan="4">
|
|
||||||
|
|
||||||
<table width="100%" cellpadding="0" cellspacing="0" border="0" style="background: white; border-radius: 4px; box-shadow: 0 1px 3px rgba(50, 50, 93, .15), 0 1px 0 rgba(0, 0, 0, .02); margin-top: 30px;">
|
|
||||||
<tbody>
|
|
||||||
<tr><td height="40"></td></tr>
|
|
||||||
|
|
||||||
<tr style='font-family: -apple-system,BlinkMacSystemFont,"Segoe UI","Roboto","Oxygen","Ubuntu","Cantarell","Fira Sans","Droid Sans","Helvetica Neue",sans-serif; color:#495057; font-size:14px; line-height:20px; margin-top:20px;'>
|
|
||||||
<td colspan="2" valign="top" align="center" style="padding-left:90px; padding-right:90px;">
|
|
||||||
|
|
||||||
<table width="100%" cellpadding="0" cellspacing="0" border="0" bgcolor="#ffffff">
|
|
||||||
<tbody>
|
|
||||||
<tr><td height="30"></td></tr>
|
|
||||||
|
|
||||||
<!-- Title -->
|
|
||||||
<tr>
|
|
||||||
<td align="center">
|
|
||||||
<span style="font-size:22px;line-height: 24px;">
|
|
||||||
Reset your Password
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr><td height="24"></td></tr>
|
|
||||||
|
|
||||||
<!-- Main content -->
|
|
||||||
<tr>
|
|
||||||
<td align="center">
|
|
||||||
<span style="color:#48545d;font-size:14px;line-height:24px;">
|
|
||||||
Someone (probably you) recently initiated the procedure to reset your Commento account password. To complete this, click the link below and set your new password.
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr><td height="20"></td></tr>
|
|
||||||
|
|
||||||
<!-- Button -->
|
|
||||||
<tr>
|
|
||||||
<td valign="top" width="48%" align="center">
|
|
||||||
<span>
|
|
||||||
<a href="{{.Origin}}/reset-password?hex={{.ResetHex}}" style="display:block; padding:15px 25px; background-color:#007aff; color:#ffffff; border-radius:3px; text-decoration:none;">Reset Password</a>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr><td height="20"></td></tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr><td height="60"></td></tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<table width="100%" cellpadding="0" cellspacing="0" border="0">
|
|
||||||
<tbody>
|
|
||||||
<tr><td height="10"></td></tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td valign="top" align="center">
|
|
||||||
<span style='font-family: -apple-system,BlinkMacSystemFont,"Segoe UI","Roboto","Oxygen","Ubuntu","Cantarell","Fira Sans","Droid Sans","Helvetica Neue",sans-serif; color:#9EB0C9; font-size:10px;'>
|
|
||||||
If you did not initiate this request, you can safely ignore this email.
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
7
templates/reset-hex.txt
Normal file
7
templates/reset-hex.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
Hi,
|
||||||
|
|
||||||
|
Someone (probably you) recently initiated the procedure to reset your Commento account password. To do this, use the link below:
|
||||||
|
|
||||||
|
{{.Origin}}/reset-password?hex={{.ResetHex}}
|
||||||
|
|
||||||
|
If you did not initiate this request, you can safely ignore this email.
|
||||||
Reference in New Issue
Block a user