Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e0cf9a89f9 | ||
|
|
bc92df8083 | ||
|
|
71947bbe2c | ||
|
|
5a029e2786 | ||
|
|
7f9a39c330 | ||
|
|
633ccf427c | ||
|
|
51e4608c19 | ||
|
|
612e620ffc | ||
|
|
642076a231 | ||
|
|
f63639782c | ||
|
|
02615088ff | ||
|
|
5aa3bc86eb | ||
|
|
d5769d56c1 | ||
|
|
8eb0bc147c | ||
|
|
96589a2658 | ||
|
|
34e39edcda | ||
|
|
6d00a8e3aa | ||
|
|
c29b3a7a25 | ||
|
|
a99bf15332 | ||
|
|
7074800ecc | ||
|
|
80fb09d941 | ||
|
|
afabc25037 | ||
|
|
c9e7a3f40a | ||
|
|
4d82106aff | ||
|
|
4c0e261a8e | ||
|
|
9e3935b3b2 | ||
|
|
e4f71fe402 | ||
|
|
06c71f4e65 | ||
|
|
9fcf67d667 | ||
|
|
d1318daaca | ||
|
|
87a0c577bb | ||
|
|
bcc81e1ad8 | ||
|
|
1f8f3b3a36 | ||
|
|
610b61831d |
@@ -3,6 +3,7 @@ stages:
|
||||
- go-fmt
|
||||
- go-test
|
||||
- build-src
|
||||
- aws-upload-tags
|
||||
- build-docker
|
||||
- docker-registry-master
|
||||
- docker-registry-tags
|
||||
@@ -21,28 +22,37 @@ check-dco:
|
||||
build-src:
|
||||
stage: build-src
|
||||
image: debian:buster
|
||||
variables:
|
||||
GOPATH: $CI_PROJECT_DIR
|
||||
except:
|
||||
- master
|
||||
- tags
|
||||
before_script:
|
||||
- apt update
|
||||
- apt install -y curl gnupg git make golang
|
||||
- mkdir -p /go/src /go/bin /go/pkg
|
||||
- bash $CI_PROJECT_DIR/scripts/gitlab-ci-build-prescript
|
||||
script:
|
||||
- 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
|
||||
script:
|
||||
- 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
|
||||
@@ -52,7 +62,7 @@ build-docker:
|
||||
- master
|
||||
- tags
|
||||
script:
|
||||
- docker build -t commento-ce .
|
||||
- docker build -t commento .
|
||||
|
||||
go-test:
|
||||
stage: go-test
|
||||
@@ -93,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
|
||||
@@ -112,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)
|
||||
|
||||
34
Dockerfile
34
Dockerfile
@@ -1,8 +1,8 @@
|
||||
# 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 curl
|
||||
RUN curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
||||
@@ -13,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
|
||||
@@ -25,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
|
||||
|
||||
@@ -36,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
|
||||
|
||||
@@ -47,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"]
|
||||
|
||||
64
README.md
64
README.md
@@ -4,43 +4,61 @@
|
||||
|
||||
<p align="center"><b>A bloat-free and privacy-focused discussion platform.</b></p>
|
||||
|
||||
Commento is a discussion platform that you can embed on your blog, news articles, and any place where you want your readers to add comments. Commento is fast, lightweight, and privacy-focused; we'll never sell your data, show ads, embed third-party tracking scripts, or inject affiliate links.
|
||||
### What is Commento?
|
||||
|
||||
#### Features
|
||||
Commento allows you to foster discussion on your website – if you have a blog, you can embed Commento if you want your readers to add comments. It's fast and bloat-free, has a modern interface, and is reasonably secure. Unlike most alternatives, Commento is lightweight and privacy-focused; I'll never sell your data, show ads, embed third-party tracking scripts, or inject affiliate links.
|
||||
|
||||
- Privacy-focused
|
||||
- Super lightweight, allowing for fast pageloads
|
||||
- Automatic spam filtering
|
||||
- Review and approve or delete comments through the moderation interface
|
||||
- Modern interface with a clean design
|
||||
- OAuth support (Google login, for example)
|
||||
- Custom CSS theming
|
||||
- Import from existing services (like Disqus)
|
||||
- Completely free and open source (MIT Expat license)
|
||||
### Frequently Asked Questions
|
||||
|
||||
#### Editions
|
||||
**I don't want to install and manage Commento on a server.**
|
||||
You can use [Commento.io](https://commento.io), the cloud version of Commento, where I do the server hosting, updates, and security and performance tuning for you. To make the hosted service self-sustainable, it is not free. You may choose the plan that best matches your financial situation and needs – all plans have all features.
|
||||
|
||||
There are three editions of Commento.
|
||||
**What features does Commento have?**
|
||||
Commento comes with a lot of useful features out-of-the-box: rich text support, upvotes and downvotes, automatic spam detection, moderation tools, sticky comments, thread locking, OAuth login, email notifications, and more!
|
||||
|
||||
- **Commento Community Edition (CE)** is open source software that's freely available under the MIT license.
|
||||
- [**Commento Enterprise Edition (EE)**](https://commento.io/pricing#self-hosted) includes extra features geared towards organizations that want to self-host.
|
||||
- [**Commento Hosted**](https://commento.io) is a hosted version of Commento for those who don't want to host and manage servers. This is currently in private beta and you can [add yourself to the waiting list here](https://commento.io).
|
||||
**What does Commento look like? Do you have a demo?**
|
||||
Check out [demo.commento.io](https://demo.commento.io) to play around with a live demo of Commento.
|
||||
|
||||
#### Installation and Configuration
|
||||
**How is Commento different from Disqus, Facebook Comments, and the rest?**
|
||||
Most other products in this space do not respect your privacy; showing adverts is their primary business model and that nearly always comes at the users' cost. There is no free lunch. Commento is also orders of magnitude lighter than alternatives – while Disqus and Facebook take megabytes of download to load, Commento is just 11 kB.
|
||||
|
||||
See our [documentation on how to install Commento](http://docs.commento.io/installation.html) to get started. We offer several ways to install the software, including a Docker image.
|
||||
**Is Commento free software?**
|
||||
Yes. Commento is made [freely available](https://gitlab.com/commento/commento) under the [MIT license](https://gitlab.com/commento/commento/blob/master/LICENSE). And it will always stay that way.
|
||||
|
||||
Once you've installed the software, you need to configure it with various environment variables before starting the service. To learn more about this, refer to our documentation on [configuring Commento](https://docs.commento.io/configuration.html).
|
||||
**Disqus has a free plan. Why is the [cloud version](https://commento.io) not free of cost?**
|
||||
When I say Commento is free, I mean [free as in freedom](https://www.gnu.org/philosophy/free-sw.en.html). The cloud version is not offered free of cost because servers cost money and offering the service for free would not be sustainable. Unlike most alternatives, Commento does not operate on adverts and shady tactics; you're the customer, not the product.
|
||||
|
||||
#### Contributing
|
||||
**I have nothing to hide. Why should I care about my privacy?**
|
||||
The thing about privacy is that once you give up control over your information, you can't get it back. You may be fine with having your personal information sold to unknown third-parties today, but when your insurance company uses this information against you tomorrow, you'll regret it. And you'll have no recourse to correct this. Read [this Wikipedia article](https://en.wikipedia.org/wiki/Nothing_to_hide_argument) for more information.
|
||||
|
||||
Commento is possible only because of its community. If this is your first contribution to Commento, please go through the [development documentation](https://docs.commento.io/contributing.html) before you begin.
|
||||
<div><p style="margin: 0px 0px"><b>As a blog owner, why should I worry about my readers' privacy?</b><br>
|
||||
Good question. For starters, your readers value their privacy. Not caring about them is disrespectful and you will end up alienating your audience; they won't come back. But even if you ignore this, you have bigger questions to answer:</p>
|
||||
<ul>
|
||||
<li><b>Legality</b>: Did you know that Disqus still isn't GDPR-compliant (according to their <a href="https://help.disqus.com/terms-and-policies/privacy-faq" title="At the time of writing (28 December 2018)" rel="nofollow">privacy policy</a>)?</li>
|
||||
<li><b>Security</b>: What happens when a random third-party script is injected into your website?</li>
|
||||
<li><b>Performance</b>: Did you know that half a second increase in page load time results in a 20% decrease in engagement and site traffic?</li>
|
||||
<li><b>Ownership</b>: Who owns the content when your readers create comments?</li>
|
||||
</ul></div>
|
||||
|
||||
**Who's behind this? Are you an evil corporation?**
|
||||
My name is <a href="https://adtac.in">Adhityaa</a>, and I created the project. As someone who's still a student, I promise you I'm neither evil nor a corporation. But I'm not the only one – dozens of people have contributed to the project and Commento would not exist without these wonderful people.
|
||||
|
||||
**Okay, how do I get started?**
|
||||
Glad you asked! You have two options – self-hosting Commento on your own server or using the [cloud version](https://commento.io). Start [from here](https://docs.commento.io/getting-started/) to decide which option is right for you and proceed from there.
|
||||
|
||||
### Installation
|
||||
|
||||
See our [documentation on how to install Commento](https://docs.commento.io/installation/) to get started.
|
||||
|
||||
### Contributing
|
||||
|
||||
Commento is possible only because of its community. If this is your first contribution to Commento, please go through the [documentation](https://docs.commento.io/contributing/) before you begin.
|
||||
|
||||
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.
|
||||
Commento development is partially 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>
|
||||
@@ -48,7 +66,7 @@ Commento CE development is sponsored by [Mozilla](https://mozilla.org) and [Digi
|
||||
<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
|
||||
### License
|
||||
|
||||
```
|
||||
Copyright 2018 Commento, Inc.
|
||||
|
||||
9
api/Gopkg.lock
generated
9
api/Gopkg.lock
generated
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
31
api/akismet.go
Normal file
31
api/akismet.go
Normal 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
|
||||
}
|
||||
@@ -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 {
|
||||
@@ -111,6 +115,9 @@ func commentNewHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if isModerator {
|
||||
state = "approved"
|
||||
} else {
|
||||
if isSpam(*x.Domain, getIp(r), getUserAgent(r), c.Name, c.Email, c.Link, *x.Markdown) {
|
||||
state = "flagged"
|
||||
} else {
|
||||
if d.RequireModeration {
|
||||
state = "unapproved"
|
||||
@@ -119,6 +126,7 @@ func commentNewHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
commentHex, err := commentNew(commenterHex, domain, path, *x.ParentHex, *x.Markdown, state, time.Now().UTC())
|
||||
if err != nil {
|
||||
@@ -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})
|
||||
}
|
||||
|
||||
@@ -43,6 +43,8 @@ func configParse() error {
|
||||
"SMTP_PORT": "",
|
||||
"SMTP_FROM_ADDRESS": "",
|
||||
|
||||
"AKISMET_KEY": "",
|
||||
|
||||
"GOOGLE_KEY": "",
|
||||
"GOOGLE_SECRET": "",
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
package main
|
||||
|
||||
var edition = "ce"
|
||||
var version = "v1.3.1"
|
||||
var version = "v1.4.1"
|
||||
|
||||
@@ -7,4 +7,5 @@ type page struct {
|
||||
Path string `json:"path"`
|
||||
IsLocked bool `json:"isLocked"`
|
||||
CommentCount int `json:"commentCount"`
|
||||
StickyCommentHex string `json:"stickyCommentHex"`
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
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)
|
||||
logger.Errorf("cannot detemplate %s%s: %v", os.Getenv("STATIC"), f, 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
|
||||
for p, _ := range asset {
|
||||
if path.Ext(p) != "" {
|
||||
contentType[p] = mime.TypeByExtension(path.Ext(p))
|
||||
} else {
|
||||
html[subdir+page] = result
|
||||
continue
|
||||
}
|
||||
}
|
||||
contentType[p] = mime.TypeByExtension("html")
|
||||
}
|
||||
|
||||
for _, page := range pages {
|
||||
router.HandleFunc("/"+page, func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprint(w, html[r.URL.Path])
|
||||
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])
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
2
db/20181218183803-sticky-comments.sql
Normal file
2
db/20181218183803-sticky-comments.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE pages
|
||||
ADD stickyCommentHex TEXT NOT NULL DEFAULT 'none';
|
||||
2
db/20181228114101-v1.4.0.sql
Normal file
2
db/20181228114101-v1.4.0.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
UPDATE config
|
||||
SET version = 'v1.4.0';
|
||||
2
db/20181228114101-v1.4.1.sql
Normal file
2
db/20181228114101-v1.4.1.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
UPDATE config
|
||||
SET version = 'v1.4.1';
|
||||
@@ -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
28
etc/bsd-rc/commento
Executable 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"
|
||||
@@ -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"
|
||||
@@ -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
33
frontend/.eslintrc
Normal 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
1
frontend/.gitignore
vendored
@@ -1 +1,2 @@
|
||||
.sass-cache
|
||||
node_modules/
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
139
frontend/gulpfile.js
Normal 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"]);
|
||||
@@ -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
13
frontend/js/constants.js
Normal 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));
|
||||
@@ -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));
|
||||
|
||||
@@ -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,9 +29,10 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Creates a new domain.
|
||||
@@ -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,7 +71,7 @@
|
||||
"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;
|
||||
@@ -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));
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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,9 +26,10 @@
|
||||
$(".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));
|
||||
|
||||
@@ -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,13 +80,13 @@
|
||||
|
||||
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", {
|
||||
labels: labels,
|
||||
@@ -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));
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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
@@ -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));
|
||||
|
||||
4
frontend/js/jquery.js
vendored
4
frontend/js/jquery.js
vendored
File diff suppressed because one or more lines are too long
@@ -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));
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
(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)
|
||||
return interval + " months ago";
|
||||
|
||||
interval = Math.floor(seconds / 86400);
|
||||
if (interval > 1)
|
||||
return interval + " days ago";
|
||||
|
||||
interval = Math.floor(seconds / 3600);
|
||||
if (interval > 1)
|
||||
return interval + " hours ago";
|
||||
|
||||
interval = Math.floor(seconds / 60);
|
||||
if (interval > 1)
|
||||
return interval + " minutes ago";
|
||||
|
||||
if (seconds > 5)
|
||||
return Math.floor(seconds) + " seconds ago";
|
||||
else
|
||||
return "just now";
|
||||
}
|
||||
|
||||
} (window, document));
|
||||
interval = Math.floor(seconds / 2592000);
|
||||
if (interval > 1) {
|
||||
return interval + " months ago";
|
||||
}
|
||||
|
||||
interval = Math.floor(seconds / 86400);
|
||||
if (interval > 1) {
|
||||
return interval + " days ago";
|
||||
}
|
||||
|
||||
interval = Math.floor(seconds / 3600);
|
||||
if (interval > 1) {
|
||||
return interval + " hours ago";
|
||||
}
|
||||
|
||||
interval = Math.floor(seconds / 60);
|
||||
if (interval > 1) {
|
||||
return interval + " minutes ago";
|
||||
}
|
||||
|
||||
if (seconds > 5) {
|
||||
return Math.floor(seconds) + " seconds ago";
|
||||
} else {
|
||||
return "just now";
|
||||
}
|
||||
}
|
||||
|
||||
} (window.commento, document));
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -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>
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
</head>
|
||||
|
||||
<script>
|
||||
window.onload = window.logout;
|
||||
window.onload = window.commento.logout;
|
||||
</script>
|
||||
</html>
|
||||
|
||||
33
frontend/package.json
Normal file
33
frontend/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
@@ -154,16 +159,22 @@ textarea {
|
||||
.commento-create-button {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
text-align: center;
|
||||
|
||||
.commento-login-link {
|
||||
font-size: 15px;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
overflow-x: hidden;
|
||||
padding: 0px;
|
||||
|
||||
* {
|
||||
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;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
@@ -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
4501
frontend/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
0
scripts/check-dco
Normal file → Executable file
17
scripts/gitlab-ci-build-prescript
Normal file
17
scripts/gitlab-ci-build-prescript
Normal 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
|
||||
Reference in New Issue
Block a user