39 Commits

Author SHA1 Message Date
Adhityaa Chandrasekar
7f9a39c330 .gitlab-ci.yml: specify AWS variables in job 2018-12-29 01:59:54 -05:00
Adhityaa Chandrasekar
633ccf427c .gitlab-ci.yml: environment: aws-upload-tags 2018-12-29 01:37:27 -05:00
Adhityaa Chandrasekar
51e4608c19 .gitlab-ci.yml: use environment for aws-upload-tags 2018-12-29 01:14:21 -05:00
Adhityaa Chandrasekar
612e620ffc .gitlab-ci.yml: remove ce suffix from CI scripts 2018-12-29 00:57:25 -05:00
Adhityaa Chandrasekar
642076a231 .gitlab-ci.yml: add automated aws push on release 2018-12-29 00:39:25 -05:00
Adhityaa Chandrasekar
f63639782c check-dco: add execute permission bit 2018-12-29 00:14:55 -05:00
Adhityaa Chandrasekar
02615088ff everywhere: remove -ce suffix 2018-12-28 12:58:01 -05:00
Adhityaa Chandrasekar
5aa3bc86eb release: v1.4.0 2018-12-28 12:18:54 -05:00
Adhityaa Chandrasekar
d5769d56c1 version.go: remove unused import 2018-12-28 12:18:54 -05:00
Adhityaa Chandrasekar
8eb0bc147c router_static.go: go fmt 2018-12-28 12:14:15 -05:00
Adhityaa Chandrasekar
96589a2658 commento.js: warn user if no div with id=commento
Closes https://gitlab.com/commento/commento/issues/42
2018-12-28 11:59:02 -05:00
Adhityaa Chandrasekar
34e39edcda version.go: remove ORIGIN and reduce frequency of logs 2018-12-28 11:47:54 -05:00
Adhityaa Chandrasekar
6d00a8e3aa frontend,api: refactor static router 2018-12-24 21:49:53 -05:00
Adhityaa Chandrasekar
c29b3a7a25 auth-common.js: add semicolon after use strict 2018-12-24 21:49:31 -05:00
Adhityaa Chandrasekar
a99bf15332 dashboard-installation.js: remove unnecessary commento namespace 2018-12-24 21:47:23 -05:00
Adhityaa Chandrasekar
7074800ecc gulpfile.js: remove redundant 'js' subpath 2018-12-24 21:40:28 -05:00
Adhityaa Chandrasekar
80fb09d941 dashboard-installation.js: use namespaced cdn variable 2018-12-24 21:40:28 -05:00
Adhityaa Chandrasekar
afabc25037 frontend: do not uglify devel JS files 2018-12-20 05:26:03 -05:00
Anton Linevych
c9e7a3f40a dashboard-setting.js: simplified if condition 2018-12-20 04:20:34 -05:00
Anton Linevych
4d82106aff gitlab-ci.yml: Install yarn for new F/E building system 2018-12-20 04:19:36 -05:00
Adhityaa Chandrasekar
4c0e261a8e autoserve: make it easy to switch between devel and prod 2018-12-20 04:18:43 -05:00
Anton Linevych
9e3935b3b2 frontend: use gulp, eslint, code refactor
Apologies in advance for the insanely huge commit. This commit is
primarily based on Anton Linevych's amazing work found here [1]. While
he had gone through the pains of making small, atomic changes to each
file, I had put off reviewing the PR for a long time. By the time I
finally got around to it, the project had changed so much that it didn't
make sense to keep the commits the same way. So I've cherry-picked most
of his commits, with some changes here and there, and I've squashed them
into one commit.

[1] https://gitlab.com/linevych/commento-ce/tree/feature/frontend_building_improvements
2018-12-20 04:14:48 -05:00
Adhityaa Chandrasekar
e4f71fe402 frontend: display options at the bottom on mobile 2018-12-20 01:27:55 -05:00
Adhityaa Chandrasekar
06c71f4e65 frontend: use commento namespace, event handlers, UI 2018-12-20 00:50:00 -05:00
Adhityaa Chandrasekar
9fcf67d667 api,frontend: add Akismet spam flagging integration 2018-12-19 22:57:02 -05:00
Adhityaa Chandrasekar
d1318daaca commento.scss: use yellow instead of blue for moderation 2018-12-19 22:14:36 -05:00
Adhityaa Chandrasekar
87a0c577bb frontend: display anonymous button in textarea 2018-12-18 19:46:43 -05:00
Adhityaa Chandrasekar
bcc81e1ad8 frontend: redesign score and timeago subtitle 2018-12-18 19:10:12 -05:00
Adhityaa Chandrasekar
1f8f3b3a36 commento.js: update avatar colors to be more distinct 2018-12-18 19:02:01 -05:00
Adhityaa Chandrasekar
610b61831d frontend,api: open source comment sticky 2018-12-18 18:57:32 -05:00
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
70 changed files with 5737 additions and 775 deletions

View File

@@ -3,6 +3,7 @@ stages:
- go-fmt
- go-test
- build-src
- aws-upload-tags
- build-docker
- docker-registry-master
- docker-registry-tags
@@ -21,22 +22,37 @@ check-dco:
build-src:
stage: build-src
image: debian:buster
variables:
GOPATH: $CI_PROJECT_DIR
except:
- master
- tags
before_script:
- bash $CI_PROJECT_DIR/scripts/gitlab-ci-build-prescript
script:
- apt update
- apt install -y curl gnupg git make golang
- 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
- export GOPATH=/go
- export PATH=$PATH:/go/bin
- cd /go/src/$CI_PROJECT_NAME
- make devel
- make prod
aws-upload-tags:
stage: aws-upload-tags
image: debian:buster
environment: aws-upload-tags
variables:
AWS_ACCESS_KEY_ID: $AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY: $AWS_SECRET_ACCESS_KEY
only:
- tags
before_script:
- bash $CI_PROJECT_DIR/scripts/gitlab-ci-build-prescript
script:
- export GOPATH=/go
- export PATH=$PATH:/go/bin
- cd /go/src/$CI_PROJECT_NAME
- make prod
- cd build/prod && tar -zcvf /commento-linux-amd64-$(git describe --tags).tgz .
- aws s3 cp /commento-linux-amd64-$(git describe --tags).tgz s3://commento-release/
build-docker:
stage: build-docker
image: docker:stable
@@ -46,7 +62,7 @@ build-docker:
- master
- tags
script:
- docker build -t commento-ce .
- docker build -t commento .
go-test:
stage: go-test
@@ -58,12 +74,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:
@@ -82,13 +103,13 @@ docker-registry-master:
services:
- docker:dind
only:
- master@commento/commento-ce
- master@commento/commento
before_script:
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN registry.gitlab.com
script:
- 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 pull registry.gitlab.com/commento/commento:latest || true
- docker build --cache-from registry.gitlab.com/commento/commento:latest --tag registry.gitlab.com/commento/commento:latest .
- docker push registry.gitlab.com/commento/commento:latest
docker-registry-tags:
stage: docker-registry-tags
@@ -101,5 +122,5 @@ docker-registry-tags:
- 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)
- docker build --tag registry.gitlab.com/commento/commento:$(git describe --tags) .
- docker push registry.gitlab.com/commento/commento:$(git describe --tags)

View File

@@ -1,10 +1,11 @@
# backend build (api server)
FROM golang:1.10.2-alpine AS api-build
COPY ./api /go/src/commento-ce/api
WORKDIR /go/src/commento-ce/api
COPY ./api /go/src/commento/api
WORKDIR /go/src/commento/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))
@@ -12,8 +13,8 @@ RUN make prod -j$(($(nproc) + 1))
# frontend build (html, js, css, images)
FROM node:10.3.0-alpine AS frontend-build
COPY ./frontend /commento-ce/frontend/
WORKDIR /commento-ce/frontend/
COPY ./frontend /commento/frontend/
WORKDIR /commento/frontend/
RUN apk update && apk add bash make
RUN npm install -g html-minifier@3.5.7 uglify-js@3.4.1 sass@1.5.1
@@ -24,8 +25,8 @@ RUN make prod -j$(($(nproc) + 1))
# templates build
FROM alpine:3.7 AS templates-build
COPY ./templates /commento-ce/templates
WORKDIR /commento-ce/templates
COPY ./templates /commento/templates
WORKDIR /commento/templates
RUN apk update && apk add bash make
@@ -35,8 +36,8 @@ RUN make prod -j$(($(nproc) + 1))
# db build
FROM alpine:3.7 AS db-build
COPY ./db /commento-ce/db
WORKDIR /commento-ce/db
COPY ./db /commento/db
WORKDIR /commento/db
RUN apk update && apk add bash make
@@ -46,19 +47,19 @@ RUN make prod -j$(($(nproc) + 1))
# final image
FROM alpine:3.7
COPY --from=api-build /go/src/commento-ce/api/build/prod/commento-ce /commento-ce/commento-ce
COPY --from=frontend-build /commento-ce/frontend/build/prod/*.html /commento-ce/
COPY --from=frontend-build /commento-ce/frontend/build/prod/css/*.css /commento-ce/css/
COPY --from=frontend-build /commento-ce/frontend/build/prod/js/*.js /commento-ce/js/
COPY --from=frontend-build /commento-ce/frontend/build/prod/images/* /commento-ce/images/
COPY --from=templates-build /commento-ce/templates/build/prod/templates/ /commento-ce/templates/
COPY --from=db-build /commento-ce/db/build/prod/db/ /commento-ce/db/
COPY --from=api-build /go/src/commento/api/build/prod/commento /commento/commento
COPY --from=frontend-build /commento/frontend/build/prod/*.html /commento/
COPY --from=frontend-build /commento/frontend/build/prod/css/*.css /commento/css/
COPY --from=frontend-build /commento/frontend/build/prod/js/*.js /commento/js/
COPY --from=frontend-build /commento/frontend/build/prod/images/* /commento/images/
COPY --from=templates-build /commento/templates/build/prod/templates/ /commento/templates/
COPY --from=db-build /commento/db/build/prod/db/ /commento/db/
RUN apk update && apk add ca-certificates --no-cache
EXPOSE 8080
WORKDIR /commento-ce/
WORKDIR /commento/
ENV COMMENTO_BIND_ADDRESS="0.0.0.0"
ENTRYPOINT ["/commento-ce/commento-ce"]
ENTRYPOINT ["/commento/commento"]

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

@@ -9,6 +9,14 @@
revision = "64a2037ec6be8a4b0c1d1f706ed35b428b989239"
version = "v0.26.0"
[[projects]]
branch = "master"
digest = "1:9769b231d8f5ff406a012aa7f293e45ed69d11617832a1c3c7b8c6ce1558a2a1"
name = "github.com/adtac/go-akismet"
packages = ["akismet"]
pruneopts = "UT"
revision = "0ca9e1023047c869ecd4bd3c20780511597a4a77"
[[projects]]
digest = "1:15042ad3498153684d09f393bbaec6b216c8eec6d61f63dff711de7d64ed8861"
name = "github.com/golang/protobuf"
@@ -143,6 +151,7 @@
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/adtac/go-akismet/akismet",
"github.com/gorilla/handlers",
"github.com/gorilla/mux",
"github.com/lib/pq",

View File

@@ -7,9 +7,9 @@ PROD_BUILD_DIR = $(BUILD_DIR)/prod
GO_SRC_DIR = .
GO_SRC_FILES = $(wildcard $(GO_SRC_DIR)/*.go)
GO_DEVEL_BUILD_DIR = $(DEVEL_BUILD_DIR)
GO_DEVEL_BUILD_BINARY = $(GO_DEVEL_BUILD_DIR)/commento-ce
GO_DEVEL_BUILD_BINARY = $(GO_DEVEL_BUILD_DIR)/commento
GO_PROD_BUILD_DIR = $(PROD_BUILD_DIR)
GO_PROD_BUILD_BINARY = $(GO_PROD_BUILD_DIR)/commento-ce
GO_PROD_BUILD_BINARY = $(GO_PROD_BUILD_DIR)/commento
devel: devel-go
@@ -25,15 +25,15 @@ clean:
# later down the line).
devel-go:
go get -v .
dep ensure
go build -i -v -o $(GO_DEVEL_BUILD_BINARY)
prod-go:
go get -v .
dep ensure
go build -i -v -o $(GO_PROD_BUILD_BINARY)
test-go:
go get -v .
dep ensure
go test -v .
$(shell mkdir -p $(GO_DEVEL_BUILD_DIR) $(GO_PROD_BUILD_DIR))

31
api/akismet.go Normal file
View File

@@ -0,0 +1,31 @@
package main
import (
"github.com/adtac/go-akismet/akismet"
"os"
)
func isSpam(domain string, userIp string, userAgent string, name string, email string, url string, markdown string) bool {
akismetKey := os.Getenv("AKISMET_KEY")
if akismetKey == "" {
return false
}
res, err := akismet.Check(&akismet.Comment{
Blog: domain,
UserIP: userIp,
UserAgent: userAgent,
CommentType: "comment",
CommentAuthor: name,
CommentAuthorEmail: email,
CommentAuthorURL: url,
CommentContent: markdown,
}, akismetKey)
if err != nil {
logger.Errorf("error: cannot validate commenet using Akismet: %v", err)
return true
}
return res
}

View File

@@ -89,8 +89,12 @@ func commentNewHandler(w http.ResponseWriter, r *http.Request) {
var state string
if *x.CommenterToken == "anonymous" {
state = "unapproved"
commenterHex = "anonymous"
if isSpam(*x.Domain, getIp(r), getUserAgent(r), "Anonymous", "", "", *x.Markdown) {
state = "flagged"
} else {
state = "unapproved"
}
} else {
c, err := commenterGetByCommenterToken(*x.CommenterToken)
if err != nil {
@@ -112,10 +116,14 @@ func commentNewHandler(w http.ResponseWriter, r *http.Request) {
if isModerator {
state = "approved"
} else {
if d.RequireModeration {
state = "unapproved"
if isSpam(*x.Domain, getIp(r), getUserAgent(r), c.Name, c.Email, c.Link, *x.Markdown) {
state = "flagged"
} else {
state = "approved"
if d.RequireModeration {
state = "unapproved"
} else {
state = "approved"
}
}
}
}
@@ -126,5 +134,5 @@ func commentNewHandler(w http.ResponseWriter, r *http.Request) {
return
}
bodyMarshal(w, response{"success": true, "commentHex": commentHex, "approved": state == "approved"})
bodyMarshal(w, response{"success": true, "commentHex": commentHex, "state": state})
}

View File

@@ -43,6 +43,8 @@ func configParse() error {
"SMTP_PORT": "",
"SMTP_FROM_ADDRESS": "",
"AKISMET_KEY": "",
"GOOGLE_KEY": "",
"GOOGLE_SECRET": "",
}

View File

@@ -1,4 +1,3 @@
package main
var edition = "ce"
var version = "v1.1.3"
var version = "v1.4.0"

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

@@ -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
);
`

View File

@@ -3,8 +3,9 @@ package main
import ()
type page struct {
Domain string `json:"domain"`
Path string `json:"path"`
IsLocked bool `json:"isLocked"`
CommentCount int `json:"commentCount"`
Domain string `json:"domain"`
Path string `json:"path"`
IsLocked bool `json:"isLocked"`
CommentCount int `json:"commentCount"`
StickyCommentHex string `json:"stickyCommentHex"`
}

View File

@@ -11,20 +11,21 @@ func pageGet(domain string, path string) (page, error) {
}
statement := `
SELECT isLocked, commentCount
SELECT isLocked, commentCount, stickyCommentHex
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 := row.Scan(&p.IsLocked, &p.CommentCount, &p.StickyCommentHex); 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
p.StickyCommentHex = "none"
} else {
logger.Errorf("error scanning page: %v", err)
return page{}, errorInternal

View File

@@ -13,12 +13,12 @@ func pageUpdate(p page) error {
// commentCount
statement := `
INSERT INTO
pages (domain, path, isLocked)
VALUES ($1, $2, $3 )
pages (domain, path, isLocked, stickyCommentHex)
VALUES ($1, $2, $3, $4 )
ON CONFLICT (domain, path) DO
UPDATE SET isLocked = $3;
UPDATE SET isLocked = $3, stickyCommentHex = $4;
`
_, err := db.Exec(statement, p.Domain, p.Path, p.IsLocked)
_, err := db.Exec(statement, p.Domain, p.Path, p.IsLocked, p.StickyCommentHex)
if err != nil {
logger.Errorf("error setting page attributes: %v", err)
return errorInternal

View File

@@ -1,41 +1,81 @@
package main
import (
"bytes"
"fmt"
"github.com/gorilla/mux"
"html/template"
"io/ioutil"
"mime"
"net/http"
"os"
"path"
"strings"
)
func redirectLogin(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, os.Getenv("ORIGIN")+"/login", 301)
}
type staticAssetPlugs struct {
Origin string
}
type staticHtmlPlugs struct {
type staticPlugs struct {
Origin string
CdnPrefix string
Footer template.HTML
Footer string
}
var asset map[string][]byte = make(map[string][]byte)
var contentType map[string]string = make(map[string]string)
var footer string
var compress bool
func fileDetemplate(f string) ([]byte, error) {
contents, err := ioutil.ReadFile(f)
if err != nil {
logger.Errorf("cannot read file %s: %v", f, err)
return []byte{}, err
}
x := string(contents)
x = strings.Replace(x, "[[[.Origin]]]", os.Getenv("ORIGIN"), -1)
x = strings.Replace(x, "[[[.CdnPrefix]]]", os.Getenv("CDN_PREFIX"), -1)
x = strings.Replace(x, "[[[.Footer]]]", footer, -1)
return []byte(x), nil
}
func footerInit() error {
contents, err := fileDetemplate(os.Getenv("STATIC") + "/footer.html")
if err != nil {
logger.Errorf("cannot init footer: %v", err)
return err
}
footer = string(contents)
return nil
}
func fileLoad(f string) ([]byte, error) {
b, err := fileDetemplate(f)
if err != nil {
logger.Errorf("cannot load file %s: %v", f, err)
return []byte{}, err
}
if !compress {
return b, nil
}
return gzipStatic(b)
}
func staticRouterInit(router *mux.Router) error {
var err error
subdir := pathStrip(os.Getenv("ORIGIN"))
asset := make(map[string][]byte)
gzippedAsset := make(map[string][]byte)
for _, dir := range []string{"js", "css", "images"} {
sl := string(os.PathSeparator)
dir = sl + dir
if err = footerInit(); err != nil {
logger.Errorf("error initialising static router: %v", err)
return err
}
for _, dir := range []string{"/js", "/css", "/images"} {
files, err := ioutil.ReadDir(os.Getenv("STATIC") + dir)
if err != nil {
logger.Errorf("cannot read directory %s%s: %v", os.Getenv("STATIC"), dir, err)
@@ -43,108 +83,47 @@ func staticRouterInit(router *mux.Router) error {
}
for _, file := range files {
p := dir + sl + file.Name()
contents, err := ioutil.ReadFile(os.Getenv("STATIC") + p)
f := dir + "/" + file.Name()
asset[subdir+f], err = fileLoad(os.Getenv("STATIC") + f)
if err != nil {
logger.Errorf("cannot read file %s%s: %v", os.Getenv("STATIC"), p, err)
logger.Errorf("cannot detemplate %s%s: %v", os.Getenv("STATIC"), f, err)
return err
}
prefix := ""
if dir == "/js" {
prefix = "window.commentoOrigin='" + os.Getenv("ORIGIN") + "';\n"
prefix += "window.commentoCdn='" + os.Getenv("CDN_PREFIX") + "';\n"
}
gzip := (os.Getenv("GZIP_STATIC") == "true")
asset[subdir+p] = []byte(prefix + string(contents))
if gzip {
gzippedAsset[subdir+p], err = gzipStatic(asset[subdir+p])
if err != nil {
logger.Errorf("error gzipping %s: %v", p, err)
return err
}
}
// faster than checking inside the handler
if !gzip {
router.HandleFunc(p, func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", mime.TypeByExtension(path.Ext(r.URL.Path)))
w.Write(asset[r.URL.Path])
})
} else {
router.HandleFunc(p, func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", mime.TypeByExtension(path.Ext(r.URL.Path)))
w.Header().Set("Content-Encoding", "gzip")
w.Write(gzippedAsset[r.URL.Path])
})
}
}
}
footer, err := ioutil.ReadFile(os.Getenv("STATIC") + string(os.PathSeparator) + "footer.html")
if err != nil {
logger.Errorf("cannot read file footer.html: %v", err)
return err
}
pages := []string{
"login",
"forgot",
"reset-password",
"signup",
"confirm-email",
"dashboard",
"logout",
}
html := make(map[string]string)
for _, page := range pages {
html[subdir+page] = ""
"/login",
"/forgot",
"/reset-password",
"/signup",
"/confirm-email",
"/dashboard",
"/logout",
}
for _, page := range pages {
sl := string(os.PathSeparator)
page = sl + page
file := page + ".html"
contents, err := ioutil.ReadFile(os.Getenv("STATIC") + file)
f := page + ".html"
asset[subdir+page], err = fileLoad(os.Getenv("STATIC") + f)
if err != nil {
logger.Errorf("cannot read file %s%s: %v", os.Getenv("STATIC"), file, err)
logger.Errorf("cannot detemplate %s%s: %v", os.Getenv("STATIC"), f, 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
}
}
}
for _, page := range pages {
router.HandleFunc("/"+page, func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, html[r.URL.Path])
for p, _ := range asset {
if path.Ext(p) != "" {
contentType[p] = mime.TypeByExtension(path.Ext(p))
} else {
contentType[p] = mime.TypeByExtension("html")
}
router.HandleFunc(p, func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", contentType[r.URL.Path])
if compress {
w.Header().Set("Content-Encoding", "gzip")
}
w.Write(asset[r.URL.Path])
})
}

View File

@@ -43,3 +43,16 @@ func bodyMarshal(w http.ResponseWriter, x map[string]interface{}) error {
w.Write(resp)
return nil
}
func getIp(r *http.Request) string {
ip := r.RemoteAddr
if r.Header.Get("X-Forwarded-For") != "" {
ip = r.Header.Get("X-Forwarded-For")
}
return ip
}
func getUserAgent(r *http.Request) string {
return r.Header.Get("User-Agent")
}

View File

@@ -6,7 +6,6 @@ import (
"io/ioutil"
"net/http"
"net/url"
"os"
"time"
)
@@ -14,13 +13,12 @@ func versionCheckStart() error {
go func() {
printedError := false
errorCount := 0
latestSeen := ""
for {
time.Sleep(5 * time.Minute)
data := url.Values{
"origin": {os.Getenv("ORIGIN")},
"edition": {edition},
"version": {version},
}
@@ -65,8 +63,9 @@ func versionCheckStart() error {
continue
}
if r.NewUpdate {
if r.NewUpdate && r.Latest != latestSeen {
logger.Infof("New update available! Latest version: %s", r.Latest)
latestSeen = r.Latest
}
errorCount = 0

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

@@ -0,0 +1,2 @@
ALTER TABLE pages
ADD stickyCommentHex TEXT NOT NULL DEFAULT 'none';

View File

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

View File

@@ -2,7 +2,7 @@ version: '3'
services:
server:
image: registry.gitlab.com/commento/commento-ce
image: registry.gitlab.com/commento/commento
ports:
- 8080:8080
environment:

28
etc/bsd-rc/commento Executable file
View File

@@ -0,0 +1,28 @@
#!/bin/sh
# PROVIDE: commento
# REQUIRE: LOGIN postgresql
# KEYWORD: shutdown
PATH="/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin"
. /etc/rc.subr
desc="Commento daemon"
name=commento
rcvar=commento_enable
load_rc_config $name
: ${commento_enable:=NO}
commento_env="COMMENTO_ORIGIN=https://commento.example.com \
COMMENTO_PORT=8080 \
COMMENTO_POSTGRES=postgres://commento:commento@db:5432/commento?sslmode=disable \
COMMENTO_STATIC=/usr/local/share/commento"
commento_user=www
command="/usr/local/bin/commento"
command_args=" >> /var/log/commento/${name}.log 2>&1 &"
run_rc_command "$1"

View File

@@ -1,28 +0,0 @@
#!/bin/sh
# PROVIDE: commento_ce
# REQUIRE: LOGIN postgresql
# KEYWORD: shutdown
PATH="/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin"
. /etc/rc.subr
desc="Commento CE daemon"
name=commento_ce
rcvar=commento_ce_enable
load_rc_config $name
: ${commento_ce_enable:=NO}
commento_ce_env="COMMENTO_ORIGIN=https://commento.example.com \
COMMENTO_PORT=8080 \
COMMENTO_POSTGRES=postgres://commento:commento@db:5432/commento?sslmode=disable \
COMMENTO_STATIC=/usr/local/share/commento-ce"
commento_ce_user=www
command="/usr/local/bin/commento-ce"
command_args=" >> /var/log/commento_ce/${name}.log 2>&1 &"
run_rc_command "$1"

View File

@@ -1,14 +1,14 @@
[Unit]
Description=Commento-CE daemon service
Description=Commento daemon service
After=network.target postgresql.service
[Service]
Type=simple
ExecStart=/usr/bin/commento-ce
ExecStart=/usr/bin/commento
Environment=COMMENTO_ORIGIN=https://commento.example.com
Environment=COMMENTO_PORT=8080
Environment=COMMENTO_POSTGRES=postgres://commento:commento@db:5432/commento?sslmode=disable
Environment=COMMENTO_STATIC=/usr/share/commento-ce
Environment=COMMENTO_STATIC=/usr/share/commento
[Install]
WantedBy=multi-user.target

33
frontend/.eslintrc Normal file
View File

@@ -0,0 +1,33 @@
{
"env": {
"browser": true
},
"globals": {
"$": true
},
"rules": {
"no-bitwise": 2,
"camelcase": 2,
"brace-style": ["error", "1tbs"],
"curly": ["error", "all"],
"eqeqeq": ["error", "smart"],
"indent": ["error", 2],
"no-use-before-define": [
2,
{
"functions": false
}
],
"new-cap": 2,
"no-caller": 2,
"quotes": [
2,
"double"
],
"no-unused-vars": 2,
"strict": [
2,
"function"
]
}
}

1
frontend/.gitignore vendored
View File

@@ -1 +1,2 @@
.sass-cache
node_modules/

View File

@@ -1,117 +1,13 @@
SHELL = bash
# list of JS files to be built
JS_BUILD = jquery.js vue.js highlight.js chartist.js login.js forgot.js reset.js signup.js dashboard.js logout.js commento.js
jquery.js = jquery.js
vue.js = vue.js
highlight.js = highlight.js
chartist.js = chartist.js
login.js = utils.js http.js auth-common.js login.js
forgot.js = utils.js http.js forgot.js
reset.js = utils.js http.js reset.js
signup.js = utils.js http.js auth-common.js signup.js
dashboard.js = utils.js http.js errors.js self.js dashboard.js dashboard-setting.js dashboard-domain.js dashboard-installation.js dashboard-general.js dashboard-moderation.js dashboard-statistics.js dashboard-import.js dashboard-danger.js
logout.js = utils.js logout.js
commento.js = commento.js
# for each file in $(JS_BUILD), list its composition
BUILD_DIR = build
DEVEL_BUILD_DIR = $(BUILD_DIR)/devel
PROD_BUILD_DIR = $(BUILD_DIR)/prod
GULP = node_modules/.bin/gulp
HTML_SRC_DIR = .
HTML_SRC_FILES = $(wildcard $(HTML_SRC_DIR)/*.html)
HTML_DEVEL_BUILD_DIR = $(DEVEL_BUILD_DIR)
HTML_DEVEL_BUILD_FILES = $(patsubst $(HTML_SRC_DIR)/%, $(HTML_DEVEL_BUILD_DIR)/%, $(HTML_SRC_FILES))
HTML_PROD_BUILD_DIR = $(PROD_BUILD_DIR)
HTML_PROD_BUILD_FILES = $(patsubst $(HTML_SRC_DIR)/%, $(HTML_PROD_BUILD_DIR)/%, $(HTML_SRC_FILES))
devel:
yarn install
$(GULP) devel
HTML_MINIFIER = html-minifier
HTML_MINIFIER_FLAGS = --collapse-whitespace --remove-comments
JS_SRC_DIR = js
JS_SRC_FILES = $(wildcard $(JS_SRC_DIR)/*.js)
JS_DEVEL_BUILD_DIR = $(DEVEL_BUILD_DIR)/js
JS_DEVEL_BUILD_FILES = $(addprefix $(JS_DEVEL_BUILD_DIR)/, $(JS_BUILD))
JS_PROD_BUILD_DIR = $(PROD_BUILD_DIR)/js
JS_PROD_BUILD_FILES = $(addprefix $(JS_PROD_BUILD_DIR)/, $(JS_BUILD))
JS_MINIFIER = uglifyjs
JS_MINIFIER_FLAGS = --compress --mangle
SASS_SRC_DIR = sass
SASS_SRC_FILES = $(wildcard $(SASS_SRC_DIR)/*.scss)
CSS_DEVEL_BUILD_DIR = $(DEVEL_BUILD_DIR)/css
CSS_DEVEL_BUILD_FILES = $(patsubst $(SASS_SRC_DIR)/%.scss, $(CSS_DEVEL_BUILD_DIR)/%.css, $(SASS_SRC_FILES))
CSS_PROD_BUILD_DIR = $(PROD_BUILD_DIR)/css
CSS_PROD_BUILD_FILES = $(patsubst $(SASS_SRC_DIR)/%.scss, $(CSS_PROD_BUILD_DIR)/%.css, $(SASS_SRC_FILES))
CSS = sass
CSS_DEVEL_FLAGS =
CSS_PROD_FLAGS = $(CSS_DEVEL_FLAGS) --style compressed
IMGS_SRC_DIR = images
IMGS_SRC_FILES = $(wildcard $(IMGS_SRC_DIR)/*)
IMGS_DEVEL_BUILD_DIR = $(DEVEL_BUILD_DIR)/images
IMGS_DEVEL_BUILD_FILES = $(patsubst $(IMGS_SRC_DIR)/%, $(IMGS_DEVEL_BUILD_DIR)/%, $(IMGS_SRC_FILES))
IMGS_PROD_BUILD_DIR = $(PROD_BUILD_DIR)/images
IMGS_PROD_BUILD_FILES = $(patsubst $(IMGS_SRC_DIR)/%, $(IMGS_PROD_BUILD_DIR)/%, $(IMGS_SRC_FILES))
devel: devel-html devel-js devel-css devel-imgs
prod: devel prod-html prod-js prod-css prod-imgs
prod:
yarn install
$(GULP) prod
clean:
-rm -rf $(BUILD_DIR);
devel-html: $(HTML_DEVEL_BUILD_FILES)
$(HTML_DEVEL_BUILD_FILES): $(HTML_DEVEL_BUILD_DIR)/%.html: $(HTML_SRC_DIR)/%.html
cp $^ $@;
prod-html: $(HTML_PROD_BUILD_FILES)
$(HTML_PROD_BUILD_FILES): $(HTML_PROD_BUILD_DIR)/%.html: $(HTML_DEVEL_BUILD_DIR)/%.html
$(HTML_MINIFIER) $(HTML_MINIFIER_FLAGS) -o $@ $^;
devel-js: $(JS_DEVEL_BUILD_FILES)
.SECONDEXPANSION:
$(JS_DEVEL_BUILD_FILES): $(JS_DEVEL_BUILD_DIR)/%.js: $$(addprefix $$(JS_SRC_DIR)/, $$(%.js))
>$@; \
for f in $^; do \
printf "// %s\n" "$$f" >>$@; \
cat $$f >>$@; \
printf "\n" >>$@; \
done;
prod-js: $(JS_PROD_BUILD_FILES)
$(JS_PROD_BUILD_FILES): $(JS_PROD_BUILD_DIR)/%.js: $(JS_DEVEL_BUILD_DIR)/%.js
$(JS_MINIFIER) $(JS_MINIFIER_FLAGS) -o $@ $^;
devel-css: $(CSS_DEVEL_BUILD_FILES)
$(CSS_DEVEL_BUILD_FILES): $(CSS_DEVEL_BUILD_DIR)/%.css: $(SASS_SRC_DIR)/%.scss $(SASS_SRC_FILES)
$(CSS) $(CSS_DEVEL_FLAGS) $< $@;
prod-css: $(CSS_PROD_BUILD_FILES)
$(CSS_PROD_BUILD_FILES): $(CSS_PROD_BUILD_DIR)/%.css: $(SASS_SRC_DIR)/%.scss
$(CSS) $(CSS_PROD_FLAGS) $^ $@;
$(shell mkdir -p $(HTML_DEVEL_BUILD_DIR) $(JS_DEVEL_BUILD_DIR) $(CSS_DEVEL_BUILD_DIR) $(HTML_PROD_BUILD_DIR) $(JS_PROD_BUILD_DIR) $(CSS_PROD_BUILD_DIR))
devel-imgs: $(IMGS_DEVEL_BUILD_FILES)
$(IMGS_DEVEL_BUILD_FILES): $(IMGS_DEVEL_BUILD_DIR)/%: $(IMGS_SRC_DIR)/%
cp $^ $@;
prod-imgs: $(IMGS_PROD_BUILD_FILES)
$(IMGS_PROD_BUILD_FILES): $(IMGS_PROD_BUILD_DIR)/%: $(IMGS_SRC_DIR)/%
cp $^ $@
$(shell mkdir -p $(HTML_DEVEL_BUILD_DIR) $(JS_DEVEL_BUILD_DIR) $(CSS_DEVEL_BUILD_DIR) $(IMGS_DEVEL_BUILD_DIR) $(HTML_PROD_BUILD_DIR) $(JS_PROD_BUILD_DIR) $(CSS_PROD_BUILD_DIR) $(IMGS_PROD_BUILD_DIR))

View File

@@ -21,10 +21,10 @@
<script>
window.onload = function() {
window.selfGet(function() {
window.vueConstruct(function() {
window.navbarFill();
window.domainRefresh();
window.commento.selfGet(function() {
window.commento.vueConstruct(function() {
window.commento.navbarFill();
window.commento.domainRefresh();
$(document).ready(function(){
$("ul.tabs li").click(function(){
var tab_id = $(this).attr("data-tab");
@@ -51,13 +51,13 @@
It's so quiet in here.
</div>
<div class="pane-setting" v-for="domain in domains" v-on:click="window.domainSelect(domain.domain)" id="{{domain.hex}}" v-bind:class="{selected: domain.selected}" v-if="domain.show">
<div class="pane-setting" v-for="domain in domains" v-on:click="window.commento.domainSelect(domain.domain)" id="{{domain.hex}}" v-bind:class="{selected: domain.selected}" v-if="domain.show">
<div class="pane-setting-inside">
<div class="setting-title">{{domain.name}}</div>
<div class="setting-subtitle">{{domain.domain}}</div>
</div>
</div>
<div class="pane-setting" id="domain-add" onclick="window.location.hash='#new-domain-modal'">
<div class="pane-setting" id="domain-add" onclick="document.location.hash='#new-domain-modal'">
<div class="pane-setting-inside super-setting">
<div class="super-setting-title">+</div>
<div class="super-setting-text">New Domain</div>
@@ -66,7 +66,7 @@
</div>
<div class="pane-middle">
<div v-if="showSettings" class="pane-setting" v-for="setting in settings" v-on:click="window.settingSelect(setting.id)" id="{{setting.id}}" v-bind:class="{selected: setting.selected}">
<div v-if="showSettings" class="pane-setting" v-for="setting in settings" v-on:click="window.commento.settingSelect(setting.id)" id="{{setting.id}}" v-bind:class="{selected: setting.selected}">
<div class="pane-setting-inside">
<div class="setting-title">{{setting.text}}</div>
<div class="setting-subtitle">{{setting.meaning}}</div>
@@ -156,14 +156,14 @@
<div class="commento-email-container">
<div class="commento-email">
<input class="commento-input" type="text" id="new-mod" placeholder="Email">
<button id="new-mod-button" class="commento-email-button" onclick="window.moderatorNewHandler()">Add moderator</button>
<button id="new-mod-button" class="commento-email-button" onclick="window.commento.moderatorNewHandler()">Add moderator</button>
</div>
</div>
<div class="mod-emails-container">
<div class="content">
<div class="mod-email" v-for="email in domains[cd].moderators" v-if="domains[cd].moderators.length > 0">
<div class="email">{{email.email}}</div>
<div class="delete" v-on:click="window.moderatorDeleteHandler(email.email)">Delete</div>
<div class="delete" v-on:click="window.commento.moderatorDeleteHandler(email.email)">Delete</div>
<div class="date">added {{email.timeAgo}}</div>
</div>
</div>
@@ -215,7 +215,7 @@
<div id="new-domain-error" class="modal-error-box"></div>
</div>
<div class="center">
<button id="save-general-button" onclick="window.generalSaveHandler()" class="button">Save Changes</button>
<button id="save-general-button" onclick="window.commento.generalSaveHandler()" class="button">Save Changes</button>
</div>
</div>
</div>
@@ -247,7 +247,7 @@
<div class="commento-email-container">
<div class="commento-email">
<input class="commento-input" type="text" id="disqus-url" placeholder="https://media.disqus.com/uploads/...">
<button id="disqus-import-button" class="commento-email-button" onclick="importDisqus()">Import</button>
<button id="disqus-import-button" class="commento-email-button" onclick="window.commento.importDisqus()">Import</button>
</div>
</div>
<div class="subtext-container">
@@ -286,7 +286,7 @@
If you desire to re-allow comments again on your website, you can do so. You can, of course, freeze the site again in the future.
</div>
<button onclick="window.location.hash='#unfreeze-domain-modal'" class="button green-button">Unfreeze Domain</button>
<button onclick="document.location.hash='#unfreeze-domain-modal'" class="button green-button">Unfreeze Domain</button>
</div>
<div class="box" v-if="domains[cd].state != 'frozen'">
@@ -294,7 +294,7 @@
If you desire to temporarily freeze new comments (domain-wide), thereby making it read-only, you can do so. You can choose to unfreeze later; this is temporary.
</div>
<button id="orange-button" onclick="window.location.hash='#freeze-domain-modal'" class="button orange-button">Freeze Domain</button>
<button id="orange-button" onclick="document.location.hash='#freeze-domain-modal'" class="button orange-button">Freeze Domain</button>
</div>
</div>
</div>
@@ -305,7 +305,7 @@
Want to completely remove Commento from your website? This will permanently delete all comments and there is literally no way to retrieve your data once you do this.
</div>
<button id="big-red-button" class="button big-red-button" onclick="window.location.hash='#delete-domain-modal'">Delete Domain</button>
<button id="big-red-button" class="button big-red-button" onclick="document.location.hash='#delete-domain-modal'">Delete Domain</button>
</div>
</div>
</div>
@@ -323,7 +323,7 @@
Are you absolutely sure you want to freeze your domain, thereby making it read-only? You can choose to unfreeze later; this is temporary.
</div>
<div class="modal-contents">
<button id="orange-button" class="button orange-button" onclick="window.domainFreezeHandler()">Freeze Domain</button>
<button id="orange-button" class="button orange-button" onclick="window.commento.domainFreezeHandler()">Freeze Domain</button>
</div>
</div>
</div>
@@ -336,7 +336,7 @@
Are you absolutely sure you want to unfreeze your domain? This will re-allow new comments. You can choose to freeze again in the future.
</div>
<div class="modal-contents">
<button id="blue-button" class="button green-button" onclick="window.domainUnfreezeHandler()">Unfreeze Domain</button>
<button id="blue-button" class="button green-button" onclick="window.commento.domainUnfreezeHandler()">Unfreeze Domain</button>
</div>
</div>
</div>
@@ -349,7 +349,7 @@
Are you absolutely sure? This will permanently delete all comments and there is literally no way to retrieve your data once you do this.
</div>
<div class="modal-contents">
<button id="big-red-button" class="button big-red-button" onclick="window.domainDeleteHandler()">Delete Domain</button>
<button id="big-red-button" class="button big-red-button" onclick="window.commento.domainDeleteHandler()">Delete Domain</button>
</div>
</div>
</div>
@@ -374,7 +374,7 @@
</div>
<div id="new-domain-error" class="modal-error-box"></div>
<div class="center">
<button id="add-site-button" onclick="window.domainNewHandler()" class="button">Add Domain</button>
<button id="add-site-button" onclick="window.commento.domainNewHandler()" class="button">Add Domain</button>
</div>
</div>
</div>

View File

@@ -25,7 +25,7 @@
</div>
<div class="err" id="err"></div>
<div class="msg" id="msg"></div>
<button id="reset-button" class="button" onclick="sendResetHex()">Send Reset Password Link</button>
<button id="reset-button" class="button" onclick="window.commento.sendResetHex()">Send Reset Password Link</button>
<a class="link" href="[[[.Origin]]]/login">Suddenly remembered your password? Login.</a>
</div>
</div>

139
frontend/gulpfile.js Normal file
View File

@@ -0,0 +1,139 @@
"use strict";
const gulp = require("gulp");
const sass = require("gulp-sass");
const sourcemaps = require("gulp-sourcemaps");
const cleanCss = require("gulp-clean-css");
const htmlMinifier = require("gulp-html-minifier");
const uglify = require("gulp-uglify");
const concat = require("gulp-concat");
const rename = require("gulp-rename");
const eslint = require("gulp-eslint");
const develPath = "build/devel/";
const prodPath = "build/prod/";
const scssSrc = "./sass/*.scss";
const cssDir = "css/";
const imagesDir = "images/";
const imagesGlob = imagesDir + "**/*";
const jsDir = "js/";
const jsGlob = jsDir + "*.js";
const htmlGlob = "./*.html";
const jsCompileMap = {
"js/jquery.js": ["node_modules/jquery/dist/jquery.min.js"],
"js/vue.js": ["node_modules/vue/dist/vue.min.js"],
"js/highlight.js": ["node_modules/highlightjs/highlight.pack.min.js"],
"js/chartist.js": ["node_modules/chartist/dist/chartist.min.js"],
"js/login.js": [
"js/constants.js",
"js/utils.js",
"js/http.js",
"js/auth-common.js",
"js/login.js"
],
"js/forgot.js": [
"js/constants.js",
"js/utils.js",
"js/http.js",
"js/forgot.js"
],
"js/reset.js": [
"js/constants.js",
"js/utils.js",
"js/http.js",
"js/reset.js"
],
"js/signup.js": [
"js/constants.js",
"js/utils.js",
"js/http.js",
"js/auth-common.js",
"js/signup.js"
],
"js/dashboard.js": [
"js/constants.js",
"js/utils.js",
"js/http.js",
"js/errors.js",
"js/self.js",
"js/dashboard.js",
"js/dashboard-setting.js",
"js/dashboard-domain.js",
"js/dashboard-installation.js",
"js/dashboard-general.js",
"js/dashboard-moderation.js",
"js/dashboard-statistics.js",
"js/dashboard-import.js",
"js/dashboard-danger.js",
],
"js/logout.js": [
"js/constants.js",
"js/utils.js",
"js/logout.js"
],
"js/commento.js": ["js/commento.js"],
};
gulp.task("scss-devel", function () {
return gulp.src(scssSrc)
.pipe(sourcemaps.init())
.pipe(sass({outputStyle: "expanded"}).on("error", sass.logError))
.pipe(sourcemaps.write())
.pipe(gulp.dest(develPath + cssDir));
});
gulp.task("scss-prod", function () {
return gulp.src(scssSrc)
.pipe(sass({outputStyle: "compressed"}).on("error", sass.logError))
.pipe(cleanCss({compatibility: "ie8", level: 2}))
.pipe(gulp.dest(prodPath + cssDir));
});
gulp.task("html-devel", function () {
gulp.src([htmlGlob]).pipe(gulp.dest(develPath));
});
gulp.task("html-prod", function () {
gulp.src(htmlGlob)
.pipe(htmlMinifier({collapseWhitespace: true, removeComments: true}))
.pipe(gulp.dest(prodPath))
});
gulp.task("images-devel", function () {
gulp.src([imagesGlob]).pipe(gulp.dest(develPath + imagesDir));
});
gulp.task("images-prod", function () {
gulp.src([imagesGlob]).pipe(gulp.dest(prodPath + imagesDir));
});
gulp.task("js-devel", function () {
for (let outputFile in jsCompileMap) {
gulp.src(jsCompileMap[outputFile])
.pipe(sourcemaps.init())
.pipe(concat(outputFile))
.pipe(rename(outputFile))
.pipe(sourcemaps.write())
.pipe(gulp.dest(develPath))
}
});
gulp.task("js-prod", function () {
for (let outputFile in jsCompileMap) {
gulp.src(jsCompileMap[outputFile])
.pipe(concat(outputFile))
.pipe(rename(outputFile))
.pipe(uglify())
.pipe(gulp.dest(prodPath))
}
});
gulp.task("lint", function () {
return gulp.src(jsGlob)
.pipe(eslint())
.pipe(eslint.failAfterError())
});
gulp.task("devel", ["scss-devel", "html-devel", "images-devel", "lint", "js-devel"]);
gulp.task("prod", ["scss-prod", "html-prod", "images-prod", "lint", "js-prod"]);

View File

@@ -1,20 +1,22 @@
(function (global, document) {
"use strict";
// Redirect the user to the dashboard if there's a cookie. If the cookie is
// invalid, they would be redirected back to the login page *after* the
// cookie is deleted.
global.loggedInRedirect = function() {
if (global.cookieGet("commentoOwnerToken") !== undefined)
document.location = global.commentoOrigin + "/dashboard";
if (global.cookieGet("commentoOwnerToken") !== undefined) {
document.location = global.origin + "/dashboard";
}
}
// Prefills the email field from the URL parameter.
global.prefillEmail = function() {
if (paramGet("email") != undefined) {
if (paramGet("email") !== undefined) {
$("#email").val(paramGet("email"));
$("#password").click();
}
};
} (window, document));
} (window.commento, document));

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

13
frontend/js/constants.js Normal file
View File

@@ -0,0 +1,13 @@
(function (global, document) {
"use strict";
(document);
if (window.commento === undefined) {
window.commento = {};
}
window.commento.origin = "[[[.Origin]]]";
window.commento.cdn = "[[[.CdnPrefix]]]";
} (window, document));

View File

@@ -1,4 +1,5 @@
(function (global, document) {
"use strict";
// Opens the danger zone.
global.dangerOpen = function() {
@@ -11,9 +12,10 @@
global.domainDeleteHandler = function() {
var data = global.dashboard.$data;
domainDelete(data.domains[data.cd].domain, function(success) {
if (success)
document.location = global.commentoOrigin + '/dashboard';
global.domainDelete(data.domains[data.cd].domain, function(success) {
if (success) {
document.location = global.origin + "/dashboard";
}
});
}
@@ -23,7 +25,7 @@
var data = global.dashboard.$data;
data.domains[data.cd].state = "frozen"
domainUpdate(data.domains[data.cd])
global.domainUpdate(data.domains[data.cd])
document.location.hash = "#modal-close";
}
@@ -33,9 +35,9 @@
var data = global.dashboard.$data;
data.domains[data.cd].state = "unfrozen"
domainUpdate(data.domains[data.cd])
global.domainUpdate(data.domains[data.cd])
document.location.hash = "#modal-close";
}
} (window, document));
} (window.commento, document));

View File

@@ -1,4 +1,5 @@
(function (global, document) {
"use strict";
// Selects a domain.
global.domainSelect = function(domain) {
@@ -6,19 +7,19 @@
var domains = data.domains;
for (var i = 0; i < domains.length; i++) {
if (domains[i].domain == domain) {
vs("frozen", domains[i].state == "frozen");
if (domains[i].domain === domain) {
global.vs("frozen", domains[i].state === "frozen");
domains[i].selected = true;
data.cd = i;
data.importedComments = domains[i].importedComments;
}
else
} else {
domains[i].selected = false;
}
}
data.showSettings = true;
settingDeselectAll();
global.settingDeselectAll();
$(".view").hide();
};
@@ -28,8 +29,9 @@
var data = global.dashboard.$data;
var domains = data.domains;
for (var i = 0; i < domains.length; i++)
for (var i = 0; i < domains.length; i++) {
domains[i].selected = false;
}
}
@@ -42,7 +44,7 @@
}
global.buttonDisable("#add-site-button");
global.post(global.commentoOrigin + "/api/domain/new", json, function(resp) {
global.post(global.origin + "/api/domain/new", json, function(resp) {
global.buttonEnable("#add-site-button");
$("#new-domain-name").val("");
@@ -69,15 +71,15 @@
"ownerToken": global.cookieGet("commentoOwnerToken"),
};
global.post(global.commentoOrigin + "/api/domain/list", json, function(resp) {
global.post(global.origin + "/api/domain/list", json, function(resp) {
if (!resp.success) {
global.globalErrorShow(resp.message);
return;
}
resp.domains = resp.domains.sort(function(a, b) {
var x = a.creationDate; var y = b.creationDate;
return ((x < y) ? -1 : ((x > y) ? 1 : 0));
var x = a.creationDate; var y = b.creationDate;
return ((x < y) ? -1 : ((x > y) ? 1 : 0));
});
for (var i = 0; i < resp.domains.length; i++) {
@@ -98,8 +100,9 @@
global.vs("domains", resp.domains);
if (callback !== undefined)
if (callback !== undefined) {
callback();
}
});
};
@@ -111,9 +114,10 @@
"domain": domain,
};
global.post(global.commentoOrigin + "/api/domain/update", json, function(resp) {
if (callback !== undefined)
global.post(global.origin + "/api/domain/update", json, function(resp) {
if (callback !== undefined) {
callback(resp.success);
}
if (!resp.success) {
global.globalErrorShow(resp.message);
@@ -130,15 +134,16 @@
"domain": domain,
};
global.post(global.commentoOrigin + "/api/domain/delete", json, function(resp) {
global.post(global.origin + "/api/domain/delete", json, function(resp) {
if (!resp.success) {
global.globalErrorShow(resp.message);
return;
}
if (callback !== undefined)
if (callback !== undefined) {
callback(resp.success);
}
});
}
} (window, document));
} (window.commento, document));

View File

@@ -1,4 +1,7 @@
(function (global, document) {
"use strict";
(document);
// Opens the general settings window.
global.generalOpen = function() {
@@ -16,4 +19,4 @@
});
};
} (window, document));
} (window.commento, document));

View File

@@ -1,4 +1,7 @@
(function (global, document) {
"use strict";
(document);
// Opens the import window.
global.importOpen = function() {
@@ -6,7 +9,6 @@
$("#import-view").show();
}
global.importDisqus = function() {
var url = $("#disqus-url").val();
var data = global.dashboard.$data;
@@ -18,7 +20,7 @@
}
global.buttonDisable("#disqus-import-button");
global.post(global.commentoOrigin + "/api/domain/import/disqus", json, function(resp) {
global.post(global.origin + "/api/domain/import/disqus", json, function(resp) {
global.buttonEnable("#disqus-import-button");
if (!resp.success) {
@@ -28,8 +30,8 @@
$("#disqus-import-button").hide();
globalOKShow("Imported " + resp.numImported + " comments!");
global.globalOKShow("Imported " + resp.numImported + " comments!");
});
}
} (window, document));
} (window.commento, document));

View File

@@ -1,17 +1,18 @@
(function (global, document) {
"use strict";
(document);
// Opens the installation view.
global.installationOpen = function() {
var data = global.dashboard.$data;
var html = '' +
'<div id="commento"></div>\n' +
'<script src="' + window.commentoCdn + '/js/commento.js"><\/script>\n' +
'';
var html = "" +
"<div id=\"commento\"></div>\n" +
"<script src=\"" + global.cdn + "/js/commento.js\"><\/script>\n" +
"";
$("#code-div").text(html);
$('pre code').each(function(i, block) {
$("pre code").each(function(i, block) {
hljs.highlightBlock(block);
});
@@ -19,4 +20,4 @@
$("#installation-view").show();
};
} (window, document));
} (window.commento, document));

View File

@@ -1,4 +1,7 @@
(function (global, document) {
"use strict";
(document);
// Opens the moderatiosn settings window.
global.moderationOpen = function() {
@@ -20,16 +23,16 @@
var idx = -1;
for (var i = 0; i < data.domains[data.cd].moderators.length; i++) {
if (data.domains[data.cd].moderators[i].email == email) {
if (data.domains[data.cd].moderators[i].email === email) {
idx = i;
break;
}
}
if (idx == -1) {
if (idx === -1) {
data.domains[data.cd].moderators.push({"email": email, "timeAgo": "just now"});
global.buttonDisable("#new-mod-button");
global.post(global.commentoOrigin + "/api/domain/moderator/new", json, function(resp) {
global.post(global.origin + "/api/domain/moderator/new", json, function(resp) {
global.buttonEnable("#new-mod-button");
if (!resp.success) {
@@ -41,8 +44,7 @@
$("#new-mod").val("");
$("#new-mod").focus();
});
}
else {
} else {
global.globalErrorShow("Already a moderator.");
}
}
@@ -60,23 +62,23 @@
var idx = -1;
for (var i = 0; i < data.domains[data.cd].moderators.length; i++) {
if (data.domains[data.cd].moderators[i].email == email) {
if (data.domains[data.cd].moderators[i].email === email) {
idx = i;
break;
}
}
if (idx != -1) {
if (idx !== -1) {
data.domains[data.cd].moderators.splice(idx, 1);
global.post(global.commentoOrigin + "/api/domain/moderator/delete", json, function(resp) {
global.post(global.origin + "/api/domain/moderator/delete", json, function(resp) {
if (!resp.success) {
global.globalErrorShow(resp.message);
return
}
globalOKShow("Removed!");
global.globalOKShow("Removed!");
});
}
}
} (window, document));
} (window.commento, document));

View File

@@ -1,4 +1,7 @@
(function (global, document) {
"use strict";
(document);
// Sets the vue.js toggle to select and deselect panes visually.
function settingSelectCSS(id) {
@@ -6,12 +9,7 @@
var settings = data.settings;
for (var i = 0; i < settings.length; i++) {
if (settings[i].id == id) {
settings[i].selected = true;
}
else {
settings[i].selected = false;
}
settings[i].selected = settings[i].id === id;
}
}
@@ -28,8 +26,9 @@
$(".original").addClass("current");
for (var i = 0; i < settings.length; i++) {
if (id == settings[i].id)
if (id === settings[i].id) {
settings[i].open();
}
}
};
@@ -39,8 +38,9 @@
var data = global.dashboard.$data;
var settings = data.settings;
for (var i = 0; i < settings.length; i++)
for (var i = 0; i < settings.length; i++) {
settings[i].selected = false;
}
}
} (window, document));
} (window.commento, document));

View File

@@ -1,35 +1,41 @@
(function (global, document) {
"use strict";
(document);
global.numberify = function(x) {
if (x == 0)
if (x === 0) {
return {"zeros": "000", "num": "", "units": ""}
}
if (x < 10)
if (x < 10) {
return {"zeros": "00", "num": x, "units": ""}
}
if (x < 100)
if (x < 100) {
return {"zeros": "0", "num": x, "units": ""}
}
if (x < 1000)
if (x < 1000) {
return {"zeros": "", "num": x, "units": ""}
}
var res;
if (x < 1000000) {
res = numberify((x/1000).toFixed(0))
res = global.numberify((x/1000).toFixed(0))
res.units = "K"
}
else if (x < 1000000000) {
res = numberify((x/1000000).toFixed(0))
} else if (x < 1000000000) {
res = global.numberify((x/1000000).toFixed(0))
res.units = "M"
}
else if (x < 1000000000000) {
res = numberify((x/1000000000).toFixed(0))
} else if (x < 1000000000000) {
res = global.numberify((x/1000000000).toFixed(0))
res.units = "B"
}
if (res.num*10 % 10 == 0)
if (res.num*10 % 10 === 0) {
res.num = Math.ceil(res.num);
}
return res;
}
@@ -43,11 +49,11 @@
}
$(".view").hide();
post(global.commentoOrigin + "/api/domain/statistics", json, function(resp) {
global.post(global.origin + "/api/domain/statistics", json, function(resp) {
$("#statistics-view").show();
if (!resp.success) {
globalErrorShow(resp.message);
global.globalErrorShow(resp.message);
return;
}
@@ -74,12 +80,12 @@
var labels = new Array();
for (var i = 0; i < views.length; i++) {
if ((views.length-i) % 7 == 0) {
if ((views.length-i) % 7 === 0) {
var x = (views.length-i)/7;
labels.push(x + " week" + (x > 1 ? "s" : "") + " ago");
}
else
} else {
labels.push("");
}
}
new Chartist.Line("#views-graph", {
@@ -92,9 +98,13 @@
series: [comments],
}, options);
data.domains[data.cd].viewsLast30Days = numberify(views.reduce(function(a, b) { return a + b; }, 0));
data.domains[data.cd].commentsLast30Days = numberify(comments.reduce(function(a, b) { return a + b; }, 0));
data.domains[data.cd].viewsLast30Days = numberify(views.reduce(function(a, b) {
return a + b;
}, 0));
data.domains[data.cd].commentsLast30Days = numberify(comments.reduce(function(a, b) {
return a + b;
}, 0));
});
}
} (window, document));
} (window.commento, document));

View File

@@ -1,4 +1,7 @@
(function (global, document) {
"use strict";
(document);
// Sets a vue.js field. Short for "vue set".
function vs(field, value) {
@@ -23,42 +26,42 @@
"text": "Installation",
"meaning": "Install Commento with HTML",
"selected": false,
"open": installationOpen,
"open": global.installationOpen,
},
{
"id": "general",
"text": "Configure Domain",
"meaning": "Names, domains and the rest",
"selected": false,
"open": generalOpen,
"open": global.generalOpen,
},
{
"id": "moderation",
"text": "Moderation Settings",
"meaning": "Manage list of moderators",
"selected": false,
"open": moderationOpen,
"open": global.moderationOpen,
},
{
"id": "statistics",
"text": "View Activity",
"meaning": "Usage and comment statistics",
"selected": false,
"open": statisticsOpen,
"open": global.statisticsOpen,
},
{
"id": "import",
"text": "Import Comments",
"meaning": "Import from a different service",
"selected": false,
"open": importOpen,
"open": global.importOpen,
},
{
"id": "danger",
"text": "Danger Zone",
"meaning": "Delete or freeze domain",
"selected": false,
"open": dangerOpen,
"open": global.dangerOpen,
},
];
@@ -82,8 +85,9 @@
data: reactiveData,
});
if (callback !== undefined)
if (callback !== undefined) {
callback();
}
};
} (window, document));
} (window.commento, document));

View File

@@ -1,9 +1,10 @@
(function (global, document) {
"use strict";
(document);
// Registers a given ID for a fade out after 5 seconds.
global.registerHide = function(id) {
var el = $(id);
setTimeout(function() {
$(id).fadeOut("fast");
}, 5000);
@@ -28,4 +29,4 @@
global.showGlobalMessage("#global-ok", text);
}
} (window, document));
} (window.commento, document));

View File

@@ -1,12 +1,15 @@
(function (global, document) {
"use strict";
(document);
// Talks to the API and sends an reset email.
global.sendResetHex = function() {
var all_ok = global.unfilledMark(["#email"], function(el) {
var allOk = global.unfilledMark(["#email"], function(el) {
el.css("border-bottom", "1px solid red");
});
if (!all_ok) {
if (!allOk) {
global.textSet("#err", "Please make sure all fields are filled.");
return;
}
@@ -16,7 +19,7 @@
};
global.buttonDisable("#reset-button");
global.post(global.commentoOrigin + "/api/owner/send-reset-hex", json, function(resp) {
global.post(global.origin + "/api/owner/send-reset-hex", json, function(resp) {
global.buttonEnable("#reset-button");
global.textSet("#err", "");
@@ -30,4 +33,4 @@
});
}
} (window, document));
} (window.commento, document));

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,7 @@
(function (global, document) {
"use strict";
(document);
// Performs a JSON POST request to the given url with the given data and
// calls the callback function with the JSON response.
@@ -28,4 +31,4 @@
});
}
} (window, document));
} (window.commento, document));

File diff suppressed because one or more lines are too long

View File

@@ -1,13 +1,15 @@
(function (global, document) {
"use strict";
(document);
// Shows messages produced from email confirmation attempts.
function displayConfirmedEmail() {
var confirmed = global.paramGet("confirmed");
if (confirmed == "true") {
if (confirmed === "true") {
$("#msg").html("Successfully confirmed! Login to continue.")
}
else if (confirmed == "false") {
} else if (confirmed === "false") {
$("#err").html("That link has expired.")
}
}
@@ -15,18 +17,18 @@
// Shows messages produced from password reset attempts.
function displayChangedPassword() {
var changed = paramGet("changed");
var changed = global.paramGet("changed");
if (changed == "true") {
if (changed === "true") {
$("#msg").html("Password changed successfully! Login to continue.")
}
}
// Shows messages produced from completed signups.
function displaySignedUp() {
var signedUp = paramGet("signedUp");
var signedUp = global.paramGet("signedUp");
if (signedUp == "true") {
if (signedUp === "true") {
$("#msg").html("Registration successful! Login to continue.")
}
}
@@ -42,11 +44,11 @@
// Logs the user in and redirects to the dashboard.
global.login = function() {
var all_ok = global.unfilledMark(["#email", "#password"], function(el) {
var allOk = global.unfilledMark(["#email", "#password"], function(el) {
el.css("border-bottom", "1px solid red");
});
if (!all_ok) {
if (!allOk) {
global.textSet("#err", "Please make sure all fields are filled");
return;
}
@@ -57,7 +59,7 @@
};
global.buttonDisable("#login-button");
global.post(global.commentoOrigin + "/api/owner/login", json, function(resp) {
global.post(global.origin + "/api/owner/login", json, function(resp) {
global.buttonEnable("#login-button");
if (!resp.success) {
@@ -66,8 +68,8 @@
}
global.cookieSet("commentoOwnerToken", resp.ownerToken);
document.location = global.commentoOrigin + "/dashboard";
document.location = global.origin + "/dashboard";
});
};
} (window, document));
} (window.commento, document));

View File

@@ -1,8 +1,9 @@
(function (global, document) {
"use strict";
global.logout = function() {
global.cookieDelete("commentoOwnerToken");
document.location = global.commentoOrigin + "/login";
document.location = global.origin + "/login";
}
} (window, document));
} (window.commento, document));

View File

@@ -1,16 +1,17 @@
(function (global, document) {
"use strict";
global.resetPassword = function() {
var all_ok = global.unfilledMark(["#password", "#password2"], function(el) {
var allOk = global.unfilledMark(["#password", "#password2"], function(el) {
el.css("border-bottom", "1px solid red");
});
if (!all_ok) {
if (!allOk) {
global.textSet("#err", "Please make sure all fields are filled.");
return;
}
if ($("#password").val() != $("#password2").val()) {
if ($("#password").val() !== $("#password2").val()) {
global.textSet("#err", "The two passwords do not match.");
return;
}
@@ -21,7 +22,7 @@
};
global.buttonDisable("#reset-button");
global.post(global.commentoOrigin + "/api/owner/reset-password", json, function(resp) {
global.post(global.origin + "/api/owner/reset-password", json, function(resp) {
global.buttonEnable("#reset-button");
global.textSet("#err", "");
@@ -30,8 +31,8 @@
return
}
document.location = global.commentoOrigin + "/login?changed=true";
document.location = global.origin + "/login?changed=true";
});
}
} (window, document));
} (window.commento, document));

View File

@@ -1,4 +1,5 @@
(function (global, document) {
"use strict";
// Get self details.
global.selfGet = function(callback) {
@@ -7,14 +8,14 @@
};
if (json.ownerToken === undefined) {
document.location = global.commentoOrigin + "/login";
document.location = global.origin + "/login";
return;
}
global.post(global.commentoOrigin + "/api/owner/self", json, function(resp) {
global.post(global.origin + "/api/owner/self", json, function(resp) {
if (!resp.success || !resp.loggedIn) {
global.cookieDelete("commentoOwnerToken");
document.location = global.commentoOrigin + "/login";
document.location = global.origin + "/login";
return;
}
@@ -23,4 +24,4 @@
});
};
}(window, document));
}(window.commento, document));

View File

@@ -1,19 +1,20 @@
(function (global, document) {
"use strict"
// Signs up the user and redirects to either the login page or the email
// confirmation, depending on whether or not SMTP is configured in the
// backend.
global.signup = function() {
if ($("#password").val() != $("#password2").val()) {
if ($("#password").val() !== $("#password2").val()) {
global.textSet("#err", "The two passwords don't match");
return;
}
var all_ok = unfilledMark(["#email", "#name", "#password", "#password2"], function(el) {
var allOk = global.unfilledMark(["#email", "#name", "#password", "#password2"], function(el) {
el.css("border-bottom", "1px solid red");
});
if (!all_ok) {
if (!allOk) {
global.textSet("#err", "Please make sure all fields are filled");
return;
}
@@ -25,7 +26,7 @@
};
global.buttonDisable("#signup-button");
post(global.commentoOrigin + "/api/owner/new", json, function(resp) {
global.post(global.origin + "/api/owner/new", json, function(resp) {
global.buttonEnable("#signup-button")
if (!resp.success) {
@@ -33,11 +34,12 @@
return;
}
if (resp.confirmEmail)
document.location = global.commentoOrigin + "/confirm-email";
else
document.location = global.commentoOrigin + "/login?signedUp=true";
if (resp.confirmEmail) {
document.locatidocumenton = global.origin + "/confirm-email";
} else {
document.location = global.origin + "/login?signedUp=true";
}
});
};
} (window, document));
} (window.commento, document));

View File

@@ -1,14 +1,16 @@
(function (global, document) {
"use strict";
// Gets a GET parameter in the current URL.
global.paramGet = function(param) {
var pageURL = decodeURIComponent(window.location.search.substring(1));
var urlVariables = pageURL.split('&');
var urlVariables = pageURL.split("&");
for (var i = 0; i < urlVariables.length; i++) {
var paramURL = urlVariables[i].split('=');
if (paramURL[0] === param)
var paramURL = urlVariables[i].split("=");
if (paramURL[0] === param) {
return paramURL[1] === undefined ? true : paramURL[1];
}
}
return null;
@@ -42,16 +44,16 @@
// Given an array of input IDs, this function calls a callback function with
// the first unfilled ID.
global.unfilledMark = function(fields, callback) {
var all_ok = true;
var allOk = true;
for (var i = 0; i < fields.length; i++) {
var el = $(fields[i]);
if (el.val() == "") {
if (el.val() === "") {
callback(el);
}
}
return all_ok;
return allOk;
}
@@ -59,8 +61,9 @@
global.cookieGet = function(name) {
var c = "; " + document.cookie;
var x = c.split("; " + name + "=");
if (x.length == 2)
if (x.length === 2) {
return x.pop().split(";").shift();
}
};
@@ -72,8 +75,9 @@
expires = "; expires=" + date.toUTCString();
var cookieString = name + "=" + value + expires + "; path=/";
if (/^https:\/\//i.test(commentoOrigin))
if (/^https:\/\//i.test(origin)) {
cookieString += "; secure";
}
document.cookie = cookieString;
}
@@ -81,7 +85,7 @@
// Deletes a cookie.
global.cookieDelete = function(name) {
document.cookie = name + '=;expires=Thu, 01 Jan 1970 00:00:01 GMT;';
document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:01 GMT;";
}
@@ -90,29 +94,35 @@
var seconds = Math.floor((new Date() - date) / 1000);
var interval = Math.floor(seconds / 31536000);
if (interval > 1)
if (interval > 1) {
return interval + " years ago";
}
interval = Math.floor(seconds / 2592000);
if (interval > 1)
if (interval > 1) {
return interval + " months ago";
}
interval = Math.floor(seconds / 86400);
if (interval > 1)
if (interval > 1) {
return interval + " days ago";
}
interval = Math.floor(seconds / 3600);
if (interval > 1)
if (interval > 1) {
return interval + " hours ago";
}
interval = Math.floor(seconds / 60);
if (interval > 1)
if (interval > 1) {
return interval + " minutes ago";
}
if (seconds > 5)
if (seconds > 5) {
return Math.floor(seconds) + " seconds ago";
else
} else {
return "just now";
}
}
} (window, document));
} (window.commento, document));

File diff suppressed because one or more lines are too long

View File

@@ -16,9 +16,9 @@
<script>
window.onload = function() {
window.loggedInRedirect();
window.prefillEmail();
window.displayMessages();
window.commento.loggedInRedirect();
window.commento.prefillEmail();
window.commento.displayMessages();
};
</script>
@@ -41,7 +41,7 @@
<div class="err" id="err"></div>
<div class="msg" id="msg"></div>
<button id="button" class="button" onclick="window.login()">Login</button>
<button id="button" class="button" onclick="window.commento.login()">Login</button>
<a class="link" href="[[[.Origin]]]/forgot">Trouble logging in? Reset your password.</a>
<a class="link" href="[[[.Origin]]]/signup">Don't have an account yet? Sign up.</a>

View File

@@ -5,6 +5,6 @@
</head>
<script>
window.onload = window.logout;
window.onload = window.commento.logout;
</script>
</html>

33
frontend/package.json Normal file
View File

@@ -0,0 +1,33 @@
{
"name": "commento",
"version": "1.0.0",
"main": "index.js",
"repository": "git@gitlab.com:commento/commento.git",
"author": "Adhityaa <c.adhityaa@gmail.com> Anton Linevych anton@linevich.net",
"license": "MIT",
"private": true,
"devDependencies": {
"chartist": "0.11.0",
"fixmyjs": "2.0.0",
"gulp": "3.9.1",
"gulp-clean-css": "3.9.4",
"gulp-concat": "2.6.1",
"gulp-eslint": "5.0.0",
"gulp-html-minifier": "0.1.8",
"gulp-rename": "1.3.0",
"gulp-sass": "4.0.1",
"gulp-sourcemaps": "2.6.4",
"gulp-uglify": "3.0.0",
"highlightjs": "9.10.0",
"html-minifier": "3.5.7",
"jquery": "3.2.1",
"natives": "^1.1.6",
"normalize-scss": "7.0.1",
"sass": "1.5.1",
"uglify-js": "3.4.1",
"vue": "2.5.16"
},
"dependencies": {
"eslint": "^5.10.0"
}
}

View File

@@ -32,7 +32,7 @@
<div class="err" id="err"></div>
<div class="msg" id="msg"></div>
<button id="reset-button" class="button" onclick="resetPassword()">Reset Password</button>
<button id="reset-button" class="button" onclick="window.commento.resetPassword()">Reset Password</button>
</div>
</div>

View File

@@ -83,6 +83,20 @@
background: $green-7;
}
.commento-option-sticky,
.commento-option-unsticky {
height: 14px;
width: 14px;
@include mask-image('data:image/svg+xml;utf8,<?xml version="1.0" encoding="UTF-8"?><svg enable-background="new 0 0 487.222 487.222" version="1.1" viewBox="0 0 487.22 487.22" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="m486.55 186.81c-1.6-4.9-5.8-8.4-10.9-9.2l-152-21.6-68.4-137.5c-2.3-4.6-7-7.5-12.1-7.5s-9.8 2.9-12.1 7.6l-67.5 137.9-152 22.6c-5.1 0.8-9.3 4.3-10.9 9.2s-0.2 10.3 3.5 13.8l110.3 106.9-25.5 151.4c-0.9 5.1 1.2 10.2 5.4 13.2 2.3 1.7 5.1 2.6 7.9 2.6 2.2 0 4.3-0.5 6.3-1.6l135.7-71.9 136.1 71.1c2 1 4.1 1.5 6.2 1.5 7.4 0 13.5-6.1 13.5-13.5 0-1.1-0.1-2.1-0.4-3.1l-26.3-150.5 109.6-107.5c3.9-3.6 5.2-9 3.6-13.9zm-137 107.1c-3.2 3.1-4.6 7.6-3.8 12l22.9 131.3-118.2-61.7c-3.9-2.1-8.6-2-12.6 0l-117.8 62.4 22.1-131.5c0.7-4.4-0.7-8.8-3.9-11.9l-95.6-92.8 131.9-19.6c4.4-0.7 8.2-3.4 10.1-7.4l58.6-119.7 59.4 119.4c2 4 5.8 6.7 10.2 7.4l132 18.8-95.3 93.3z" fill="%231e2127"/></svg>');
margin: 12px 6px 12px 6px;
background: $gray-5;
}
.commento-option-unsticky {
@include mask-image('data:image/svg+xml;utf8,<?xml version="1.0" encoding="UTF-8"?><svg viewBox="0 0 487.22 487.22" xmlns="http://www.w3.org/2000/svg"><g><title>background</title><rect x="-1" y="-1" fill="none"/></g><g><title>Layer 1</title><path d="m486.55 186.81c-1.6-4.9-5.8-8.4-10.9-9.2l-152-21.6-68.4-137.5c-2.3-4.6-7-7.5-12.1-7.5s-9.8 2.9-12.1 7.6l-67.5 137.9-152 22.6c-5.1 0.8-9.3 4.3-10.9 9.2s-0.2 10.3 3.5 13.8l110.3 106.9-25.5 151.4c-0.9 5.1 1.2 10.2 5.4 13.2 2.3 1.7 5.1 2.6 7.9 2.6 2.2 0 4.3-0.5 6.3-1.6l135.7-71.9 136.1 71.1c2 1 4.1 1.5 6.2 1.5 7.4 0 13.5-6.1 13.5-13.5 0-1.1-0.1-2.1-0.4-3.1l-26.3-150.5 109.6-107.5c3.9-3.6 5.2-9 3.6-13.9z" fill="%231e2127"/></g></svg>');
background: $yellow-7;
}
.commento-option-button:focus {
outline: none;
}

View File

@@ -16,7 +16,7 @@ textarea::placeholder {
color: #aaa;
font-size: 20px;
display: flex;
line-height: 80px;
line-height: 110px;
justify-content: center;
align-items: center;
text-align: center;
@@ -28,7 +28,7 @@ textarea {
padding: 8px;
outline: none;
overflow: auto;
min-height: 100px;
min-height: 130px;
width: 100%;
}
@@ -78,7 +78,7 @@ textarea {
.commento-account-buttons-question {
position: absolute;
top: 0.5rem;
top: 10px;
display: block;
text-align: center;
justify-content: center;
@@ -124,9 +124,14 @@ textarea {
}
.commento-anonymous-button {
background: #096fa6;
text-transform: uppercase;
display: block;
color: $blue-6;
font-weight: 700;
margin-top: 12px;
font-size: 12px;
cursor: pointer;
text-align: center;
text-transform: uppercase;
}
.commento-blurred-textarea {
@@ -152,18 +157,24 @@ textarea {
}
.commento-create-button {
width: 120px;
width: 150px;
background: $pink-9;
text-transform: uppercase;
font-size: 12px;
}
.commento-login-button {
width: 50px;
background: $cyan-9;
text-transform: uppercase;
font-size: 12px;
}
.commento-submit-button {
float: right;
background: $indigo-7;
text-transform: uppercase;
font-size: 12px;
}
.commento-approve-button {
@@ -177,18 +188,3 @@ 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

@@ -52,7 +52,7 @@
text-align: center;
.commento-login-link {
font-size: 15px;
font-size: 14px;
font-weight: bold;
border-bottom: none;
}

View File

@@ -1,17 +1,20 @@
@import url('https://fonts.googleapis.com/css?family=Source+Sans+Pro');
@import url('https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,700');
.commento-root-min-height {
min-height: 350px;
}
.commento-root {
font-family: "Source Sans Pro", "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
font-size: 15px;
line-height: 1.5;
color: #50596c;
overflow-x: hidden;
text-rendering: optimizeLegibility;
padding: 8px;
padding: 0px;
* {
font-family: "Source Sans Pro", "Segoe UI", "Roboto", "Helvetica Neue", sans-serif;
font-size: 15px;
line-height: 1.5;
color: #50596c;
text-rendering: optimizeLegibility;
}
@import "colors-main.scss";
@import "common-main.scss";
@@ -41,19 +44,30 @@
height: 32px;
text-align: center;
color: $red-7;
font-weight: bold;
font-weight: 700;
}
.commento-mod-tools {
margin-bottom: 16px;
button {
text-transform: uppercase;
color: $gray-7;
font-size: 13px;
font-weight: 700;
cursor: pointer;
margin-left: 12px;
background: none;
border: none;
}
}
.commento-mod-tools::before {
content: "Moderators";
content: "Moderator Tools";
text-transform: uppercase;
color: $indigo-8;
font-size: 12px;
font-weight: bold;
font-size: 13px;
font-weight: 700;
}
.commento-moderation-notice {
@@ -62,17 +76,17 @@
height: 32px;
text-align: center;
color: $orange-7;
font-weight: bold;
font-weight: 700;
margin-top: 16px;
}
.commento-dark-card {
background: $blue-1;
background: $yellow-0;
}
.commento-avatar {
width: 34px;
height: 34px;
width: 38px;
height: 38px;
border-radius: 50%;
display: flex;
justify-content: center;
@@ -81,8 +95,7 @@
font-size: 22px;
float: left;
margin-right: 10px;
border: 1px solid #fff;
box-shadow: 0px 0px 0px 2px #f00;
border: 0px transparent;
}
.commento-avatar-img {
@@ -111,7 +124,7 @@
}
.commento-name {
font-weight: bold;
font-weight: 700;
font-size: 14px;
color: #555;
border: none;
@@ -124,6 +137,17 @@
width: fit-content;
}
.commento-flagged::after {
content: "Flagged";
text-transform: uppercase;
font-size: 10px;
background: $red-7;
color: white;
margin-left: 8px;
padding: 2px 6px 2px 6px;
border-radius: 100px;
}
.commento-subtitle {
display: block;
color: #999;
@@ -131,13 +155,20 @@
margin-left: 48px;
}
.commento-score {
.commento-timeago {
display: inline;
color: #999;
color: #888;
font-size: 12px;
}
.commento-score::before {
.commento-score {
display: inline;
color: #888;
font-size: 12px;
font-weight: 700;
}
.commento-timeago::before {
content: "\00a0 \00a0 \00b7 \00a0 \00a0";
}
@@ -155,6 +186,16 @@
z-index: 2;
}
.commento-options-mobile {
margin-right: 12px;
}
.commento-options-clearfix {
height: 38px;
width: 1px;
display: block;
}
.commento-moderation {
height: 48px;
}

View File

@@ -16,8 +16,8 @@
<script>
window.onload = function() {
window.loggedInRedirect();
window.prefillEmail();
window.commento.loggedInRedirect();
window.commento.prefillEmail();
};
</script>
@@ -54,7 +54,7 @@
<p class="cent">
</p>
<button id="signup-button" class="button" onclick="window.signup()">Sign up</button>
<button id="signup-button" class="button" onclick="window.commento.signup()">Sign up</button>
<a class="link" href="[[[.Origin]]]/login">Already have an account? Login instead.</a>
</div>

4501
frontend/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash
binary_name=commento-ce
binary_name=commento
trap ctrl_c INT
ctrl_c() {
@@ -9,10 +9,12 @@ ctrl_c() {
exit
}
version=devel
binary_pid=
if make -j$(($(nproc) + 1)); then
if make $version -j$(($(nproc) + 1)); then
source devel.env
cd build/devel
cd build/$version
./$binary_name &
binary_pid=$!
cd ../../
@@ -50,9 +52,9 @@ while true; do
wait $binary_pid
fi
if make -j$(($(nproc) + 1)); then
if make $version -j$(($(nproc) + 1)); then
source devel.env
cd build/devel
cd build/$version
./$binary_name &
binary_pid=$!
cd ../../

0
scripts/check-dco Normal file → Executable file
View File

View File

@@ -0,0 +1,17 @@
#!/usr/bin/env bash
mkdir -p /go/src /go/bin /go/pkg
ln -s $CI_PROJECT_DIR /go/src/$CI_PROJECT_NAME
apt update
apt install -y curl gnupg git make golang python
export GOPATH=/go
export PATH=$PATH:/go/bin
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
curl -sL https://deb.nodesource.com/setup_10.x | bash -
apt install -y nodejs
npm install -g yarn@1.10.0
apt install -y python python-pip
pip install awscli