32 Commits

Author SHA1 Message Date
Adhityaa Chandrasekar
cf0b394b05 release: v1.3.1 2018-10-18 02:17:52 -04:00
Adhityaa Chandrasekar
a36b11f07d api: use dep instead of go get 2018-10-18 02:17:51 -04:00
Adhityaa Chandrasekar
93c9ce0cad release: v1.3.0 2018-10-18 01:05:01 -04:00
Adhityaa Chandrasekar
af88db42b2 owner_get.go: fix incorrent owner session selection field 2018-10-18 01:03:12 -04:00
Adhityaa Chandrasekar
0c6ccdc0a1 domain_delete.go: delete entries from pages on domain delete 2018-10-08 02:34:06 -04:00
Adhityaa Chandrasekar
6d3f8171e5 release: v1.2.0 2018-10-07 23:14:55 -04:00
Adhityaa Chandrasekar
3f1c570e84 db: store version in config table 2018-10-07 23:13:26 -04:00
Adhityaa Chandrasekar
cd88ae264e commento-input.scss: make create account button width 150px 2018-10-07 01:33:39 -04:00
Adhityaa Chandrasekar
b2abcae319 README.md: add Mozilla and DO 2018-10-05 22:48:52 -04:00
Adhityaa Chandrasekar
41a5c675bf release: v1.1.3 2018-09-28 09:39:38 -04:00
Adhityaa Chandrasekar
ac9f896a22 commento.js: reverse sorting algorithm 2018-09-28 09:39:07 -04:00
Adhityaa Chandrasekar
36d57914b2 release: v1.1.2 2018-09-27 17:13:05 -04:00
Adhityaa Chandrasekar
800ba5dd0d .gitlab-ci.yml: install git before usage 2018-09-27 17:11:57 -04:00
Adhityaa Chandrasekar
a793f7b3b4 release: v1.1.1 2018-09-26 04:01:11 -04:00
Adhityaa Chandrasekar
0d6dfb8319 .gitlab-ci.yml: auto-build docker image on tags 2018-09-26 04:01:10 -04:00
Adhityaa Chandrasekar
c5d2e17615 release: v1.1.0 2018-09-26 03:23:24 -04:00
Adhityaa Chandrasekar
93595f3877 router_static.go: do multiple rounds of templating 2018-09-26 03:18:36 -04:00
Adhityaa Chandrasekar
c21329ac4e commento.js: use a button for upvote and downvote 2018-09-23 02:32:28 -04:00
Adhityaa Chandrasekar
8500a3f7c6 commento.js: rename postComment to commentNew 2018-09-23 02:31:49 -04:00
Adhityaa Chandrasekar
4ffdd2cfb6 frontend: use better placeholders 2018-09-23 02:30:59 -04:00
Adhityaa Chandrasekar
b4f2ba41be commento.js: sort comments by score by default 2018-09-23 02:30:00 -04:00
Adhityaa Chandrasekar
8ebc0cd965 api: run go fmt 2018-09-23 01:12:52 -04:00
Adhityaa Chandrasekar
405d10766a utils.js: set Secure flag on cookies if using HTTPS
Related to https://gitlab.com/commento/commento-ce/issues/79
2018-09-23 01:09:59 -04:00
Adhityaa Chandrasekar
2a713c22f1 email-main.scss: use a smaller width in email boxes 2018-09-23 01:00:48 -04:00
Adhityaa Chandrasekar
d6ccb7338c frontend: rephrase dashboard pane titles and subtexts
Closes https://gitlab.com/commento/commento-ce/issues/82
2018-09-23 01:00:16 -04:00
Adhityaa Chandrasekar
4d799182da frontend: add min-height only when necessary
Fixes https://gitlab.com/commento/commento-ce/issues/81
2018-09-23 00:52:35 -04:00
Adhityaa Chandrasekar
f54f4d0afd db: build the comments count column 2018-09-23 00:47:38 -04:00
Adhityaa Chandrasekar
988a9fb1a1 router_api.go: expose /api/comment/count endpoint 2018-09-23 00:42:16 -04:00
Adhityaa Chandrasekar
283a32e2bb comment_list.go: allow empty path on new comments 2018-09-23 00:41:02 -04:00
Adhityaa Chandrasekar
330131f390 api,db: add comments count endpoint
Closes https://gitlab.com/commento/commento-ce/issues/27
2018-09-23 00:40:10 -04:00
Adhityaa
299649cea2 api,db: add page attributes and thread locking 2018-09-23 00:26:37 -04:00
Adhityaa Chandrasekar
0a03a2c6fc api/Makefile: use verbose when getting deps 2018-09-22 17:14:03 -04:00
37 changed files with 688 additions and 51 deletions

View File

@@ -4,13 +4,15 @@ stages:
- go-test
- build-src
- build-docker
- docker-registry
- docker-registry-master
- docker-registry-tags
check-dco:
stage: check-dco
image: debian:buster
except:
- master
- tags
script:
- apt update
- apt install -y curl git jq
@@ -23,14 +25,21 @@ build-src:
GOPATH: $CI_PROJECT_DIR
except:
- master
script:
- tags
before_script:
- apt update
- apt install -y curl gnupg git make golang
- mkdir -p /go/src /go/bin /go/pkg
- export GOPATH=/go
- export PATH=$PATH:/go/bin
- curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
- ln -s $CI_PROJECT_DIR /go/src/$CI_PROJECT_NAME
- curl -sL https://deb.nodesource.com/setup_10.x | bash -
- apt update
- apt install -y nodejs
- npm install -g html-minifier@3.5.7 uglify-js@3.4.1 sass@1.5.1
- mkdir -p src/gitlab.com/commento && cd src/gitlab.com/commento && ln -s $CI_PROJECT_DIR && cd $CI_PROJECT_NAME
script:
- cd /go/src/$CI_PROJECT_NAME
- make devel
- make prod
@@ -41,6 +50,7 @@ build-docker:
- docker:dind
except:
- master
- tags
script:
- docker build -t commento-ce .
@@ -54,11 +64,17 @@ go-test:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: commento_test
COMMENTO_POSTGRES: postgres://postgres:postgres@postgres/commento_test?sslmode=disable
GOPATH: $CI_PROJECT_DIR
except:
- master
- tags
before_script:
- mkdir -p /go/src /go/bin /go/pkg
- export GOPATH=/go
- export PATH=$PATH:/go/bin
- curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
- ln -s $CI_PROJECT_DIR /go/src/$CI_PROJECT_NAME
script:
- mkdir -p src/gitlab.com/commento && cd src/gitlab.com/commento && ln -s $CI_PROJECT_DIR && cd $CI_PROJECT_NAME
- cd /go/src/$CI_PROJECT_NAME
- make test
go-fmt:
@@ -66,12 +82,13 @@ go-fmt:
image: golang:1.10.2
except:
- master
- tags
script:
- cd api
- test -z $(go fmt)
docker-registry:
stage: docker-registry
docker-registry-master:
stage: docker-registry-master
image: docker:stable
services:
- docker:dind
@@ -83,3 +100,17 @@ docker-registry:
- docker pull registry.gitlab.com/commento/commento-ce:latest || true
- docker build --cache-from registry.gitlab.com/commento/commento-ce:latest --tag registry.gitlab.com/commento/commento-ce:latest .
- docker push registry.gitlab.com/commento/commento-ce:latest
docker-registry-tags:
stage: docker-registry-tags
image: docker:stable
services:
- docker:dind
only:
- tags
before_script:
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN registry.gitlab.com
script:
- apk add git
- docker build --tag registry.gitlab.com/commento/commento-ce:$(git describe --tags) .
- docker push registry.gitlab.com/commento/commento-ce:$(git describe --tags)

View File

@@ -4,7 +4,8 @@ FROM golang:1.10.2-alpine AS api-build
COPY ./api /go/src/commento-ce/api
WORKDIR /go/src/commento-ce/api
RUN apk update && apk add bash make git
RUN apk update && apk add bash make git curl
RUN curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
RUN make prod -j$(($(nproc) + 1))

View File

@@ -38,6 +38,16 @@ Commento is possible only because of its community. If this is your first contri
Help will always be given to those who ask for it. We use IRC for chat to collaborate with other developers. You're invited to [hang out with us](https://irc.commento.io) in the `#commento-dev` channel on freenode if you want to contribute to Commento!
### Sponsors
Commento CE development is sponsored by [Mozilla](https://mozilla.org) and [DigitalOcean](https://www.digitalocean.com/) independently.
<p align="center">
<a href="https://www.mozilla.org/en-US/"><img src="https://user-images.githubusercontent.com/7521600/32265838-d05b2d08-bf0a-11e7-92e1-2cb183eae616.png" title="Mozilla" height="40"></a>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
<a href="https://www.digitalocean.com"><img src="https://user-images.githubusercontent.com/7521600/32265839-d093c7da-bf0a-11e7-8d99-96a940041d06.png" title="DigitalOcean" height="40"></a>
</p>
#### License
```

View File

View File

@@ -25,15 +25,15 @@ clean:
# later down the line).
devel-go:
go get .
dep ensure
go build -i -v -o $(GO_DEVEL_BUILD_BINARY)
prod-go:
go get .
dep ensure
go build -i -v -o $(GO_PROD_BUILD_BINARY)
test-go:
go get .
dep ensure
go test -v .
$(shell mkdir -p $(GO_DEVEL_BUILD_DIR) $(GO_PROD_BUILD_DIR))

43
api/comment_count.go Normal file
View File

@@ -0,0 +1,43 @@
package main
import (
"net/http"
)
func commentCount(domain string, path string) (int, error) {
// path can be empty
if domain == "" {
return 0, errorMissingField
}
p, err := pageGet(domain, path)
if err != nil {
return 0, errorInternal
}
return p.CommentCount, nil
}
func commentCountHandler(w http.ResponseWriter, r *http.Request) {
type request struct {
Domain *string `json:"domain"`
Path *string `json:"path"`
}
var x request
if err := bodyUnmarshal(r, &x); err != nil {
bodyMarshal(w, response{"success": false, "message": err.Error()})
return
}
domain := domainStrip(*x.Domain)
path := *x.Path
count, err := commentCount(domain, path)
if err != nil {
bodyMarshal(w, response{"success": false, "message": err.Error()})
return
}
bodyMarshal(w, response{"success": true, "count": count})
}

54
api/comment_count_test.go Normal file
View File

@@ -0,0 +1,54 @@
package main
import (
"testing"
"time"
)
func TestCommentCountBasics(t *testing.T) {
failTestOnError(t, setupTestEnv())
commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "http://example.com/photo.jpg", "google", "")
commentNew(commenterHex, "example.com", "/path.html", "root", "**foo**", "approved", time.Now().UTC())
commentNew(commenterHex, "example.com", "/path.html", "root", "**bar**", "approved", time.Now().UTC())
commentNew(commenterHex, "example.com", "/path.html", "root", "**baz**", "unapproved", time.Now().UTC())
count, err := commentCount("example.com", "/path.html")
if err != nil {
t.Errorf("unexpected error counting comments: %v", err)
return
}
if count != 2 {
t.Errorf("expected count=2 got count=%d", count)
return
}
}
func TestCommentCountNewPage(t *testing.T) {
failTestOnError(t, setupTestEnv())
count, err := commentCount("example.com", "/path.html")
if err != nil {
t.Errorf("unexpected error counting comments: %v", err)
return
}
if count != 0 {
t.Errorf("expected count=0 got count=%d", count)
return
}
}
func TestCommentCountEmpty(t *testing.T) {
if _, err := commentCount("example.com", ""); err != nil {
t.Errorf("unexpected error counting comments on empty path: %v", err)
return
}
if _, err := commentCount("", ""); err == nil {
t.Errorf("expected error not found counting comments with empty everything")
return
}
}

View File

@@ -6,7 +6,8 @@ import (
)
func commentList(commenterHex string, domain string, path string, includeUnapproved bool) ([]comment, map[string]commenter, error) {
if commenterHex == "" || domain == "" || path == "" {
// path can be empty
if commenterHex == "" || domain == "" {
return nil, nil, errorMissingField
}
@@ -111,6 +112,12 @@ func commentListHandler(w http.ResponseWriter, r *http.Request) {
return
}
p, err := pageGet(domain, path)
if err != nil {
bodyMarshal(w, response{"success": false, "message": err.Error()})
return
}
commenterHex := "anonymous"
isModerator := false
if *x.CommenterToken != "anonymous" {
@@ -151,6 +158,7 @@ func commentListHandler(w http.ResponseWriter, r *http.Request) {
"requireIdentification": d.RequireIdentification,
"isFrozen": d.State == "frozen",
"isModerator": isModerator,
"attributes": p,
"configuredOauths": configuredOauths,
})
}

View File

@@ -13,6 +13,16 @@ func commentNew(commenterHex string, domain string, path string, parentHex strin
return "", errorMissingField
}
p, err := pageGet(domain, path)
if err != nil {
logger.Errorf("cannot get page attributes: %v", err)
return "", errorInternal
}
if p.IsLocked {
return "", errorThreadLocked
}
commentHex, err := randomHex(32)
if err != nil {
return "", err
@@ -31,6 +41,10 @@ func commentNew(commenterHex string, domain string, path string, parentHex strin
return "", errorInternal
}
if err = pageNew(domain, path); err != nil {
return "", err
}
return commentHex, nil
}

View File

@@ -56,3 +56,18 @@ func TestCommentNewUpvoted(t *testing.T) {
return
}
}
func TestCommentNewThreadLocked(t *testing.T) {
failTestOnError(t, setupTestEnv())
pageNew("example.com", "/path.html")
p, _ := pageGet("example.com", "/path.html")
p.IsLocked = true
pageUpdate(p)
_, err := commentNew("temp-commenter-hex", "example.com", "/path.html", "root", "**foo**", "approved", time.Now().UTC())
if err == nil {
t.Errorf("expected error not found creating a new comment on a locked thread")
return
}
}

View File

@@ -1,4 +1,4 @@
package main
var edition = "ce"
var version = "v1.0.0"
var version = "v1.3.1"

View File

@@ -60,6 +60,16 @@ func domainDelete(domain string) error {
return errorInternal
}
statement = `
DELETE FROM pages
WHERE pages.domain = $1;
`
_, err = db.Exec(statement, domain)
if err != nil {
logger.Errorf(statement, domain)
return errorInternal
}
return nil
}

View File

@@ -41,3 +41,4 @@ var errorSelfVote = errors.New("You cannot vote on your own comment.")
var errorInvalidConfigFile = errors.New("Invalid config file.")
var errorInvalidConfigValue = errors.New("Invalid config value.")
var errorNewOwnerForbidden = errors.New("New user registrations are forbidden and closed.")
var errorThreadLocked = errors.New("This thread is locked. You cannot add new comments.")

View File

@@ -31,8 +31,8 @@ func ownerGetByOwnerToken(ownerToken string) (owner, error) {
statement := `
SELECT ownerHex, email, name, confirmedEmail, joinDate
FROM owners
WHERE email IN (
SELECT email FROM ownerSessions
WHERE ownerHex IN (
SELECT ownerHex FROM ownerSessions
WHERE ownerToken = $1
);
`

10
api/page.go Normal file
View File

@@ -0,0 +1,10 @@
package main
import ()
type page struct {
Domain string `json:"domain"`
Path string `json:"path"`
IsLocked bool `json:"isLocked"`
CommentCount int `json:"commentCount"`
}

35
api/page_get.go Normal file
View File

@@ -0,0 +1,35 @@
package main
import (
"database/sql"
)
func pageGet(domain string, path string) (page, error) {
// path can be empty
if domain == "" {
return page{}, errorMissingField
}
statement := `
SELECT isLocked, commentCount
FROM pages
WHERE domain=$1 AND path=$2;
`
row := db.QueryRow(statement, domain, path)
p := page{Domain: domain, Path: path}
if err := row.Scan(&p.IsLocked, &p.CommentCount); err != nil {
if err == sql.ErrNoRows {
// If there haven't been any comments, there won't be a record for this
// page. The sane thing to do is return defaults.
// TODO: the defaults are hard-coded in two places: here and the schema
p.IsLocked = false
p.CommentCount = 0
} else {
logger.Errorf("error scanning page: %v", err)
return page{}, errorInternal
}
}
return p, nil
}

43
api/page_get_test.go Normal file
View File

@@ -0,0 +1,43 @@
package main
import (
"testing"
)
func TestPageGetBasics(t *testing.T) {
failTestOnError(t, setupTestEnv())
pageNew("example.com", "/path.html")
p, err := pageGet("example.com", "/path.html")
if err != nil {
t.Errorf("unexpected error getting page: %v", err)
return
}
if p.IsLocked != false {
t.Errorf("expected p.IsLocked=false got %v", p.IsLocked)
return
}
if _, err := pageGet("example.com", "/path2.html"); err != nil {
t.Errorf("unexpected error getting page with non-existant record: %v", err)
return
}
}
func TestPageGetEmpty(t *testing.T) {
failTestOnError(t, setupTestEnv())
pageNew("example.com", "")
if _, err := pageGet("example.com", ""); err != nil {
t.Errorf("unexpected error getting page with empty path: %v", err)
return
}
if _, err := pageGet("", "/path.html"); err == nil {
t.Errorf("exepected error not found when getting page with empty domain")
return
}
}

24
api/page_new.go Normal file
View File

@@ -0,0 +1,24 @@
package main
import ()
func pageNew(domain string, path string) error {
// path can be empty
if domain == "" {
return errorMissingField
}
statement := `
INSERT INTO
pages (domain, path)
VALUES ($1, $2 )
ON CONFLICT DO NOTHING;
`
_, err := db.Exec(statement, domain, path)
if err != nil {
logger.Errorf("error inserting new page: %v", err)
return errorInternal
}
return nil
}

43
api/page_new_test.go Normal file
View File

@@ -0,0 +1,43 @@
package main
import (
"testing"
)
func TestPageNewBasics(t *testing.T) {
failTestOnError(t, setupTestEnv())
if err := pageNew("example.com", "/path.html"); err != nil {
t.Errorf("unexpected error creating page: %v", err)
return
}
}
func TestPageNewEmpty(t *testing.T) {
failTestOnError(t, setupTestEnv())
if err := pageNew("example.com", ""); err != nil {
t.Errorf("unexpected error creating page with empty path: %v", err)
return
}
if err := pageNew("", "/path.html"); err == nil {
t.Errorf("expected error not found creating page with empty domain")
return
}
}
func TestPageNewUnique(t *testing.T) {
failTestOnError(t, setupTestEnv())
if err := pageNew("example.com", "/path.html"); err != nil {
t.Errorf("unexpected error creating page: %v", err)
return
}
// no error should be returned when trying to duplicate insert
if err := pageNew("example.com", "/path.html"); err != nil {
t.Errorf("unexpected error creating same page twice: %v", err)
return
}
}

72
api/page_update.go Normal file
View File

@@ -0,0 +1,72 @@
package main
import (
"net/http"
)
func pageUpdate(p page) error {
if p.Domain == "" {
return errorMissingField
}
// fields to not update:
// commentCount
statement := `
INSERT INTO
pages (domain, path, isLocked)
VALUES ($1, $2, $3 )
ON CONFLICT (domain, path) DO
UPDATE SET isLocked = $3;
`
_, err := db.Exec(statement, p.Domain, p.Path, p.IsLocked)
if err != nil {
logger.Errorf("error setting page attributes: %v", err)
return errorInternal
}
return nil
}
func pageUpdateHandler(w http.ResponseWriter, r *http.Request) {
type request struct {
CommenterToken *string `json:"commenterToken"`
Domain *string `json:"domain"`
Path *string `json:"path"`
Attributes *page `json:"attributes"`
}
var x request
if err := bodyUnmarshal(r, &x); err != nil {
bodyMarshal(w, response{"success": false, "message": err.Error()})
return
}
c, err := commenterGetByCommenterToken(*x.CommenterToken)
if err != nil {
bodyMarshal(w, response{"success": false, "message": err.Error()})
return
}
domain := domainStrip(*x.Domain)
isModerator, err := isDomainModerator(domain, c.Email)
if err != nil {
bodyMarshal(w, response{"success": false, "message": err.Error()})
return
}
if !isModerator {
bodyMarshal(w, response{"success": false, "message": errorNotModerator.Error()})
return
}
(*x.Attributes).Domain = *x.Domain
(*x.Attributes).Path = *x.Path
if err = pageUpdate(*x.Attributes); err != nil {
bodyMarshal(w, response{"success": false, "message": err.Error()})
return
}
bodyMarshal(w, response{"success": true})
}

43
api/page_update_test.go Normal file
View File

@@ -0,0 +1,43 @@
package main
import (
"testing"
"time"
)
func TestPageUpdateBasics(t *testing.T) {
failTestOnError(t, setupTestEnv())
commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "https://example.com/photo.jpg", "google", "")
commentNew(commenterHex, "example.com", "/path.html", "root", "**foo**", "unapproved", time.Now().UTC())
p, _ := pageGet("example.com", "/path.html")
if p.IsLocked != false {
t.Errorf("expected IsLocked=false got %v", p.IsLocked)
return
}
p.IsLocked = true
if err := pageUpdate(p); err != nil {
t.Errorf("unexpected error updating page: %v", err)
return
}
p, _ = pageGet("example.com", "/path.html")
if p.IsLocked != true {
t.Errorf("expected IsLocked=true got %v", p.IsLocked)
return
}
}
func TestPageUpdateEmpty(t *testing.T) {
failTestOnError(t, setupTestEnv())
p := page{Domain: "", Path: "", IsLocked: false}
if err := pageUpdate(p); err == nil {
t.Errorf("expected error not found updating page with empty everything")
return
}
}

View File

@@ -31,9 +31,12 @@ func apiRouterInit(router *mux.Router) error {
router.HandleFunc("/api/comment/new", commentNewHandler).Methods("POST")
router.HandleFunc("/api/comment/list", commentListHandler).Methods("POST")
router.HandleFunc("/api/comment/count", commentCountHandler).Methods("POST")
router.HandleFunc("/api/comment/vote", commentVoteHandler).Methods("POST")
router.HandleFunc("/api/comment/approve", commentApproveHandler).Methods("POST")
router.HandleFunc("/api/comment/delete", commentDeleteHandler).Methods("POST")
router.HandleFunc("/api/page/update", pageUpdateHandler).Methods("POST")
return nil
}

View File

@@ -27,6 +27,8 @@ type staticHtmlPlugs struct {
}
func staticRouterInit(router *mux.Router) error {
subdir := pathStrip(os.Getenv("ORIGIN"))
asset := make(map[string][]byte)
gzippedAsset := make(map[string][]byte)
@@ -57,8 +59,6 @@ func staticRouterInit(router *mux.Router) error {
gzip := (os.Getenv("GZIP_STATIC") == "true")
subdir := pathStrip(os.Getenv("ORIGIN"))
asset[subdir+p] = []byte(prefix + string(contents))
if gzip {
gzippedAsset[subdir+p], err = gzipStatic(asset[subdir+p])
@@ -101,6 +101,10 @@ func staticRouterInit(router *mux.Router) error {
}
html := make(map[string]string)
for _, page := range pages {
html[subdir+page] = ""
}
for _, page := range pages {
sl := string(os.PathSeparator)
page = sl + page
@@ -112,22 +116,30 @@ func staticRouterInit(router *mux.Router) error {
return err
}
t, err := template.New(page).Delims("[[[", "]]]").Parse(string(contents))
if err != nil {
logger.Errorf("cannot parse %s%s template: %v", os.Getenv("STATIC"), file, err)
return err
result := string(contents)
for {
t, err := template.New(page).Delims("[[[", "]]]").Parse(result)
if err != nil {
logger.Errorf("cannot parse %s%s template: %v", os.Getenv("STATIC"), file, err)
return err
}
var buf bytes.Buffer
t.Execute(&buf, &staticHtmlPlugs{
Origin: os.Getenv("ORIGIN"),
CdnPrefix: os.Getenv("CDN_PREFIX"),
Footer: template.HTML(string(footer)),
})
result = buf.String()
if result == html[subdir+page] {
break
} else {
html[subdir+page] = result
continue
}
}
var buf bytes.Buffer
t.Execute(&buf, &staticHtmlPlugs{
Origin: os.Getenv("ORIGIN"),
CdnPrefix: os.Getenv("CDN_PREFIX"),
Footer: template.HTML(string(footer)),
})
subdir := pathStrip(os.Getenv("ORIGIN"))
html[subdir+page] = buf.String()
}
for _, page := range pages {

View File

@@ -0,0 +1,9 @@
-- Introduces page attributes
CREATE TABLE IF NOT EXISTS pages (
domain TEXT NOT NULL ,
path TEXT NOT NULL ,
isLocked BOOLEAN NOT NULL DEFAULT false
);
CREATE UNIQUE INDEX pagesUniqueIndex ON pages(domain, path);

View File

@@ -0,0 +1,15 @@
ALTER TABLE pages
ADD commentCount INTEGER NOT NULL DEFAULT 0;
CREATE OR REPLACE FUNCTION commentsInsertTriggerFunction() RETURNS TRIGGER AS $trigger$
BEGIN
UPDATE pages
SET commentCount = commentCount + 1
WHERE domain = new.domain AND path = new.path;
RETURN NEW;
END;
$trigger$ LANGUAGE plpgsql;
CREATE TRIGGER commentsInsertTrigger AFTER INSERT ON comments
FOR EACH ROW EXECUTE PROCEDURE commentsInsertTriggerFunction();

View File

@@ -0,0 +1,10 @@
-- Build the comments count column
UPDATE pages
SET commentCount = subquery.commentCount
FROM (
SELECT COUNT(commentHex) as commentCount
FROM comments
WHERE state = 'approved'
GROUP BY (domain, path)
) as subquery;

View File

@@ -0,0 +1,7 @@
CREATE TABLE IF NOT EXISTS config (
version TEXT NOT NULL
);
INSERT INTO
config (version)
VALUES ('v1.1.3');

View File

@@ -0,0 +1,2 @@
UPDATE config
SET version = 'v1.2.0';

View File

@@ -175,12 +175,12 @@
</div>
</div>
<!-- General Settings -->
<!-- Configure Domain -->
<div id="general-view" class="view hidden">
<div class="view-inside">
<div class="small-mid-view">
<div class="center center-title">
General Settings
Configure Domain
</div>
<div class="box">
<div class="row">
@@ -365,7 +365,7 @@
</div>
<div class="row">
<div class="label">Website Name</div>
<input class="input gray-input" id="new-domain-name" type="text" placeholder="Billie Joe's Blog">
<input class="input gray-input" id="new-domain-name" type="text" placeholder="My Blog">
</div>
<div class="row">
<div class="label">Website Domain</div>

View File

@@ -27,6 +27,8 @@
var ID_LOGIN_BOX_HR = "commento-login-box-hr";
var ID_LOGIN_BOX_OAUTH_PRETEXT = "commento-login-box-oauth-pretext";
var ID_LOGIN_BOX_OAUTH_BUTTONS_CONTAINER = "commento-login-box-oauth-buttons-container";
var ID_MOD_TOOLS = "commento-mod-tools";
var ID_MOD_TOOLS_LOCK_BUTTON = "commento-mod-tools-lock-button";
var ID_ERROR = "commento-error";
var ID_LOGGED_CONTAINER = "commento-logged-container";
var ID_COMMENTS_AREA = "commento-comments-area";
@@ -62,8 +64,9 @@
var requireModeration = true;
var isModerator = false;
var isFrozen = false;
var chosenAnonymous = false;
var shownSubmitButton = {"root": false};
var chosenAnonymous = false;
var isLocked = false;
var shownReply = {};
var configuredOauths = [];
var loginBoxType = "signup";
@@ -336,6 +339,9 @@
requireIdentification = resp.requireIdentification;
isModerator = resp.isModerator;
isFrozen = resp.isFrozen;
isLocked = resp.attributes.isLocked;
comments = resp.comments;
commenters = resp.commenters;
configuredOauths = resp.configuredOauths;
@@ -443,7 +449,11 @@
commentsArea.innerHTML = "";
append(mainArea, textareaCreate("root"));
if (!isLocked)
append(mainArea, textareaCreate("root"));
else
append(mainArea, messageCreate("This thread is locked. You cannot create new comments."));
append(mainArea, commentsArea);
append(root, mainArea);
@@ -462,7 +472,7 @@
}
global.postComment = function(id) {
global.commentNew = function(id) {
var textarea = $(ID_TEXTAREA + id);
var comment = textarea.value;
@@ -577,6 +587,10 @@
return null;
}
cur.sort(function(a, b) {
return b.score - a.score;
});
var cards = create("div");
cur.forEach(function(comment) {
var commenter = commenters[comment.commenterHex];
@@ -590,8 +604,8 @@
var edit = create("button");
var reply = create("button");
var collapse = create("button");
var upvote = create("div");
var downvote = create("div");
var upvote = create("button");
var downvote = create("button");
var approve = create("button");
var remove = create("button");
var children = commentsRecurse(parentMap, comment.commentHex);
@@ -715,7 +729,10 @@
// append(options, edit); // uncomment when implemented
append(options, downvote);
append(options, upvote);
append(options, reply);
if (!isLocked)
append(options, reply);
if (isModerator) {
append(options, remove);
if (comment.state == "unapproved")
@@ -940,7 +957,7 @@
classAdd(submit, "submit-button");
classAdd(el, "button-margin");
attrSet(submit, "onclick", "postComment('" + id + "')");
attrSet(submit, "onclick", "commentNew('" + id + "')");
append(el, submit);
}
@@ -1037,6 +1054,7 @@
classAdd(oauthButtonsContainer, "oauth-buttons-container");
classAdd(oauthButtons, "oauth-buttons");
classAdd(close, "login-box-close");
classAdd(root, "root-min-height");
emailButton.innerText = "Continue";
loginLink.innerText = "Already have an account? Log in.";
@@ -1257,6 +1275,45 @@
}
function pageUpdate(callback) {
var attributes = {
"isLocked": isLocked,
};
var json = {
"commenterToken": commenterTokenGet(),
"domain": location.host,
"path": location.pathname,
"attributes": attributes,
};
post(origin + "/api/page/update", json, function(resp) {
if (!resp.success) {
errorShow(resp.message);
return
}
call(callback);
});
}
global.threadLockToggle = function() {
var lock = $(ID_MOD_TOOLS_LOCK_BUTTON);
isLocked = !isLocked;
lock.disabled = true;
pageUpdate(function(success) {
lock.disabled = false;
if (isLocked)
lock.innerHTML = "Unlock Thread";
else
lock.innerHTML = "Lock Thread";
});
}
function mainAreaCreate() {
var mainArea = create("div");
@@ -1270,6 +1327,29 @@
}
function modToolsCreate() {
var modTools = create("div");
var lock = create("button");
modTools.id = ID_MOD_TOOLS;
lock.id = ID_MOD_TOOLS_LOCK_BUTTON;
classAdd(modTools, "mod-tools");
classAdd(lock, "mod-tools-lock-button");
if (isLocked)
lock.innerHTML = "Unlock Thread";
else
lock.innerHTML = "Lock Thread";
attrSet(modTools, "style", "display: none");
attrSet(lock, "onclick", "threadLockToggle()");
append(modTools, lock);
append(root, modTools);
}
global.loadCssOverride = function() {
if (cssOverride === undefined)
global.allShow();
@@ -1280,12 +1360,18 @@
global.allShow = function() {
var mainArea = $(ID_MAIN_AREA);
var modTools = $(ID_MOD_TOOLS);
var loggedContainer = $(ID_LOGGED_CONTAINER);
var footer = $(ID_FOOTER);
attrSet(mainArea, "style", "");
if (isModerator)
attrSet(modTools, "style", "");
if (loggedContainer)
attrSet(loggedContainer, "style", "");
attrSet(footer, "style", "");
nameWidthFix();
@@ -1297,6 +1383,7 @@
var loginBoxContainer = $(ID_LOGIN_BOX_CONTAINER);
classRemove(mainArea, "blurred");
classRemove(root, "root-min-height");
attrSet(loginBoxContainer, "style", "display: none");
}
@@ -1346,6 +1433,7 @@
selfGet(function() {
commentsGet(function() {
modToolsCreate();
rootCreate(function() {
commentsRender();
footerLoad();

View File

@@ -27,7 +27,7 @@
},
{
"id": "general",
"text": "General Settings",
"text": "Configure Domain",
"meaning": "Names, domains and the rest",
"selected": false,
"open": generalOpen,
@@ -35,13 +35,13 @@
{
"id": "moderation",
"text": "Moderation Settings",
"meaning": "Approve and delete comments",
"meaning": "Manage list of moderators",
"selected": false,
"open": moderationOpen,
},
{
"id": "statistics",
"text": "Statistics",
"text": "View Activity",
"meaning": "Usage and comment statistics",
"selected": false,
"open": statisticsOpen,

View File

@@ -71,7 +71,11 @@
date.setTime(date.getTime() + (365*24*60*60*1000));
expires = "; expires=" + date.toUTCString();
document.cookie = name + "=" + value + expires + "; path=/";
var cookieString = name + "=" + value + expires + "; path=/";
if (/^https:\/\//i.test(commentoOrigin))
cookieString += "; secure";
document.cookie = cookieString;
}

View File

@@ -152,7 +152,7 @@ textarea {
}
.commento-create-button {
width: 120px;
width: 150px;
background: $pink-9;
}
@@ -177,3 +177,18 @@ textarea {
.commento-button-margin {
padding-bottom: 60px;
}
.commento-mod-tools-lock-button {
color: $gray-6;
background: none;
border: none;
font-weight: bold;
font-size: 12px;
text-transform: uppercase;
margin-left: 12px;
padding: 2px;
}
.commento-mod-tools-lock-button:hover {
cursor: pointer;
}

View File

@@ -1,5 +1,9 @@
@import url('https://fonts.googleapis.com/css?family=Source+Sans+Pro');
.commento-root-min-height {
min-height: 350px;
}
.commento-root {
font-family: "Source Sans Pro", "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
font-size: 15px;
@@ -8,7 +12,6 @@
overflow-x: hidden;
text-rendering: optimizeLegibility;
padding: 8px;
min-height: 350px;
@import "colors-main.scss";
@import "common-main.scss";
@@ -41,6 +44,18 @@
font-weight: bold;
}
.commento-mod-tools {
margin-bottom: 16px;
}
.commento-mod-tools::before {
content: "Moderators";
text-transform: uppercase;
color: $indigo-8;
font-size: 12px;
font-weight: bold;
}
.commento-moderation-notice {
width: 100%;
border-radius: 4px;
@@ -87,7 +102,7 @@
border-top: 1px solid #f0f0f0;
.commento-header {
padding-bottom: 12px;
padding-bottom: 4px;
}
.commento-avatar::after {

View File

@@ -21,7 +21,7 @@
outline: none;
padding: 5px;
padding-left: 10px;
width: calc(100% - 120px);
width: calc(100% - 150px);
}
.commento-input::placeholder {

View File

@@ -34,7 +34,7 @@
<div class="row">
<div class="label">Full Name</div>
<input class="input" type="text" name="name" id="name" placeholder="Billie Joe Armstrong">
<input class="input" type="text" name="name" id="name" placeholder="Full Name">
</div>
<div class="row">