Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d25beede91 | ||
|
|
16b690c8c0 | ||
|
|
2036a0261e | ||
|
|
66c8f6b94b | ||
|
|
1bf21404bd | ||
|
|
9ec0f1c74a | ||
|
|
9b5e47d602 | ||
|
|
03a433d94a | ||
|
|
4a9b07e687 | ||
|
|
d00b63c0af | ||
|
|
97352b583e | ||
|
|
0b44989e5f | ||
|
|
a5308548ba | ||
|
|
9225fc32f8 | ||
|
|
37dc6a68d7 | ||
|
|
69613beb88 | ||
|
|
9c5bd9653f | ||
|
|
ca3d37fc2d | ||
|
|
f554e6c438 | ||
|
|
76066bbdc0 | ||
|
|
3950ef8532 | ||
|
|
3f6eb935e4 | ||
|
|
5364d191c7 | ||
|
|
5e1b04326b | ||
|
|
add379c82c | ||
|
|
d7565e5b33 | ||
|
|
d46326788b | ||
|
|
729fd9880a | ||
|
|
5b6a453349 | ||
|
|
b7b6700407 | ||
|
|
cb2860a778 | ||
|
|
705e220a81 | ||
|
|
ef2d1e8552 | ||
|
|
14e2d9e824 | ||
|
|
9dba2ae43f | ||
|
|
f1784a1bd0 | ||
|
|
bc30c4d26d | ||
|
|
ad9abd643c | ||
|
|
2e7a34fc9c | ||
|
|
082e9b7fed | ||
|
|
a95c1b9be6 | ||
|
|
dfe65ce621 | ||
|
|
da6556ec3c | ||
|
|
9fe237660c | ||
|
|
eff4a0b15d | ||
|
|
5b0e4c866e | ||
|
|
617de2cdf1 | ||
|
|
36c47104ca | ||
|
|
8460ae387f | ||
|
|
e6a5c2d4b9 | ||
|
|
1cd9cdb445 | ||
|
|
117908e57b | ||
|
|
b47eed5303 | ||
|
|
f7a2aeac9b | ||
|
|
5ff42483aa | ||
|
|
8de7157a91 | ||
|
|
747b184fa4 | ||
|
|
f875fc4023 | ||
|
|
cf06808170 | ||
|
|
3b00a5e356 | ||
|
|
3d9c484a5d | ||
|
|
dcac8cc883 | ||
|
|
b39ec2a0a6 | ||
|
|
5c3df4776e | ||
|
|
6ba19150df | ||
|
|
efe62e93e2 | ||
|
|
71a651153b | ||
|
|
b4c139bf9e | ||
|
|
3e45bb9506 | ||
|
|
64a2adbe1a | ||
|
|
99736db27f | ||
|
|
eb3310c537 | ||
|
|
ab2c2cf153 | ||
|
|
71b73a323a | ||
|
|
659023988e | ||
|
|
c2b9afa1a1 | ||
|
|
258dfde60e | ||
|
|
91ac907778 | ||
|
|
73da8740ee | ||
|
|
4ad0cc1366 | ||
|
|
15f2fb82a3 | ||
|
|
bd81b7c10c | ||
|
|
f6c99e949b | ||
|
|
dcde53f8ad | ||
|
|
d3aacf0d14 | ||
|
|
20be96f6f8 | ||
|
|
437892cdfa | ||
|
|
5065bb4763 | ||
|
|
baa63866f4 | ||
|
|
7c251fd067 | ||
|
|
02a369fe5c | ||
|
|
bd0db8cd57 | ||
|
|
206a26d0c1 | ||
|
|
f1ae566134 | ||
|
|
3571a18b42 | ||
|
|
abac3f0f36 | ||
|
|
db960aa024 | ||
|
|
1b5ef279bb | ||
|
|
754223571a | ||
|
|
a6eba361fc | ||
|
|
1d5811a59c | ||
|
|
ea9c5d9ecb | ||
|
|
8707f4f327 | ||
|
|
b89a1866f9 | ||
|
|
7e082158cd | ||
|
|
d5d458de11 | ||
|
|
c087400a66 | ||
|
|
34d793697c | ||
|
|
b9c3b2a1c3 | ||
|
|
bd121f8f94 | ||
|
|
6b0e46c86c | ||
|
|
53aaf27fdf | ||
|
|
a3d1ccc773 | ||
|
|
cf7725edba | ||
|
|
9cc3252c0f | ||
|
|
349eb6f5c5 | ||
|
|
55a96f12aa | ||
|
|
36580b5fc3 | ||
|
|
9afa7ca555 | ||
|
|
6d54b721da | ||
|
|
cb4596d59d | ||
|
|
4913e1aebd | ||
|
|
7ffaa04541 | ||
|
|
2a49c0e515 | ||
|
|
a98a50abbe | ||
|
|
4bd3537c0d | ||
|
|
9dcfd9cd91 | ||
|
|
9fde40d843 | ||
|
|
19c3317f64 | ||
|
|
22ff47f9ce | ||
|
|
4eb3dbaca2 | ||
|
|
90d8503d0e | ||
|
|
2354f7aa06 | ||
|
|
7bab69c4fd | ||
|
|
301928da4b | ||
|
|
6135c7e911 | ||
|
|
1ff0c4c10f | ||
|
|
9cc9a6d6ab | ||
|
|
d2225d565b | ||
|
|
e61911027c | ||
|
|
f29d2f81eb | ||
|
|
4b5ec39b8e | ||
|
|
5dc2752cc1 | ||
|
|
cf6e687a2d | ||
|
|
69ea7de22e | ||
|
|
8fb7027186 | ||
|
|
88615f9a88 | ||
|
|
f104629b40 | ||
|
|
2f6b30deaf | ||
|
|
2f7b9fa816 | ||
|
|
6d1041dc69 | ||
|
|
9fb191976c | ||
|
|
f20454bd00 | ||
|
|
64854107ef | ||
|
|
ac54eae179 | ||
|
|
aa92a92961 | ||
|
|
d54652264b | ||
|
|
d6ececd014 | ||
|
|
04ef002da1 | ||
|
|
a6b559d682 | ||
|
|
67b37b4612 | ||
|
|
3e81b2a8f2 | ||
|
|
8a9480d05a | ||
|
|
b3ed639be7 | ||
|
|
6a0e06ea75 | ||
|
|
92484cfa66 | ||
|
|
1b0eff9568 | ||
|
|
2efd34248d | ||
|
|
c0a3026890 | ||
|
|
392b6b15cf | ||
|
|
6b58fd8a9d | ||
|
|
80e5ca5dc7 | ||
|
|
50e967f71c | ||
|
|
cf58a545b5 | ||
|
|
270558129f | ||
|
|
059bac8b5f | ||
|
|
f1a86d59ca | ||
|
|
adcb75a2e2 | ||
|
|
4d7e157ff0 | ||
|
|
528847de7b | ||
|
|
2b9d6bb573 | ||
|
|
393224cdb1 | ||
|
|
a925806d29 | ||
|
|
b97167a718 | ||
|
|
587523f0df | ||
|
|
08f58cd38b | ||
|
|
0803e235a4 | ||
|
|
125a66611f | ||
|
|
92a0e23c85 | ||
|
|
c75b630e8a | ||
|
|
d22d34c223 | ||
|
|
6fe8ffd2c4 | ||
|
|
f4d33fcf24 | ||
|
|
640f9904fb | ||
|
|
df62de09f3 | ||
|
|
68bcdbe3e8 | ||
|
|
e663893fd1 | ||
|
|
b85d845b71 | ||
|
|
acf7480b91 | ||
|
|
be0dcd6b59 | ||
|
|
4d9445c84c | ||
|
|
1337e81cd4 | ||
|
|
d053b953a6 | ||
|
|
2549052558 | ||
|
|
b8da04e308 | ||
|
|
c4a7162302 | ||
|
|
6012993fe9 | ||
|
|
d4ed7f64b2 | ||
|
|
93e3b2f184 | ||
|
|
0e75457499 | ||
|
|
b8151bb789 | ||
|
|
67922f34ad | ||
|
|
7093e7e1c5 | ||
|
|
c3558b7beb | ||
|
|
5fdf3a27ce | ||
|
|
f8f509c0de | ||
|
|
437d943691 | ||
|
|
401cf5c21a | ||
|
|
77e9a687ac | ||
|
|
85f88cbba5 | ||
|
|
efb34c23e5 | ||
|
|
822a85c9e3 | ||
|
|
630a3b1ff8 | ||
|
|
ca6f8ed09c | ||
|
|
a0b0a33862 | ||
|
|
4868cd9992 | ||
|
|
2dfee533da | ||
|
|
dcaaefc71b | ||
|
|
499a57213e | ||
|
|
b7fc86b3c6 | ||
|
|
161ff8bfc3 | ||
|
|
1e36d45ec3 | ||
|
|
d2161cc0fb | ||
|
|
abbddfe9b4 | ||
|
|
860dd698e7 | ||
|
|
acb087887c | ||
|
|
dbb3c55148 | ||
|
|
fdfeef8042 | ||
|
|
824f122dcb | ||
|
|
53d46d82b6 | ||
|
|
bb218d0ba4 | ||
|
|
984071d4b6 | ||
|
|
34b24a1f36 | ||
|
|
58657524cf | ||
|
|
b5db161697 |
No files matched your search
@@ -1,79 +0,0 @@
|
||||
version: 2.1
|
||||
|
||||
orbs:
|
||||
node: circleci/node@5.2.0
|
||||
codecov: codecov/codecov@3.3.0
|
||||
|
||||
executors:
|
||||
base:
|
||||
docker:
|
||||
- image: cimg/base:stable
|
||||
node:
|
||||
docker:
|
||||
- image: "cimg/node:20.12.2"
|
||||
go:
|
||||
docker:
|
||||
- image: cimg/go:1.22.2
|
||||
|
||||
parameters:
|
||||
trigger-app:
|
||||
type: boolean
|
||||
default: false
|
||||
run-them-all:
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
jobs:
|
||||
## Backend
|
||||
test_app:
|
||||
executor: go
|
||||
working_directory: ~/go/src/github.com/ArtalkJS/Artalk
|
||||
environment:
|
||||
GO111MODULE: "on"
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: "Print the Go version"
|
||||
command: >
|
||||
go version
|
||||
- restore_cache:
|
||||
keys:
|
||||
- go-mod-1.22.2-{{ checksum "go.sum" }}
|
||||
- run:
|
||||
name: Install Dependencies
|
||||
command: go mod download
|
||||
- save_cache:
|
||||
key: go-mod-1.22.2-{{ checksum "go.sum" }}
|
||||
paths:
|
||||
- "~/go/pkg/mod"
|
||||
- run:
|
||||
name: Run tests
|
||||
command: |
|
||||
mkdir -p /tmp/test-reports
|
||||
gotestsum --junitfile /tmp/test-reports/unit-tests.xml -- -coverprofile=/tmp/test-reports/coverage.out ./...
|
||||
- store_test_results:
|
||||
path: /tmp/test-reports
|
||||
- codecov/upload:
|
||||
flags: go
|
||||
file: /tmp/test-reports/coverage.out
|
||||
|
||||
all_projects:
|
||||
executor: base
|
||||
steps:
|
||||
- run:
|
||||
command: |
|
||||
echo "all"
|
||||
|
||||
workflows:
|
||||
build-app:
|
||||
when:
|
||||
or:
|
||||
- << pipeline.parameters.trigger-app >>
|
||||
- << pipeline.parameters.run-them-all >>
|
||||
jobs:
|
||||
- test_app
|
||||
|
||||
build-shared-other:
|
||||
when: << pipeline.parameters.run-them-all >>
|
||||
jobs:
|
||||
- all_projects
|
||||
@@ -1,27 +0,0 @@
|
||||
# https://circleci.com/docs/2.0/configuration-reference
|
||||
version: 2.1
|
||||
|
||||
# this allows you to use CircleCI's dynamic configuration
|
||||
# https://circleci.com/docs/2.0/dynamic-config/
|
||||
setup: true
|
||||
|
||||
# the path-filtering orb is required to continue a pipeline based on
|
||||
# https://circleci.com/developer/orbs/orb/circleci/path-filtering
|
||||
orbs:
|
||||
path-filtering: circleci/path-filtering@1.0.0
|
||||
|
||||
workflows:
|
||||
setup:
|
||||
jobs:
|
||||
- path-filtering/filter:
|
||||
mapping: |
|
||||
(cmd|internal|server|pkg|test|conf)/.* trigger-app true
|
||||
main.go|go.mod|go.sum trigger-app true
|
||||
base-revision: master # git branch name
|
||||
config-path: .circleci/conditional_config.yml
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
- gh-pages
|
||||
- /release\/.+/
|
||||
- nightly
|
||||
@@ -1 +1,14 @@
|
||||
node_modules
|
||||
.git
|
||||
.github
|
||||
.DS_Store
|
||||
.vscode
|
||||
bin
|
||||
docs/docs/*
|
||||
!docs/docs/package.json
|
||||
!docs/docs/tsconfig.json
|
||||
local
|
||||
test
|
||||
ui/**/dist
|
||||
**/playwright-report
|
||||
**/test-results
|
||||
@@ -1,14 +0,0 @@
|
||||
# don't ever lint node_modules
|
||||
node_modules
|
||||
# don't lint build output (make sure it's set to your correct build folder name)
|
||||
dist
|
||||
# don't lint nyc coverage output
|
||||
coverage
|
||||
|
||||
vite*.config*
|
||||
|
||||
# ignore all js files
|
||||
**/*.*js
|
||||
|
||||
# ignore all config files
|
||||
*.config.ts
|
||||
@@ -1,48 +0,0 @@
|
||||
const path = require('node:path')
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
parser: '@typescript-eslint/parser',
|
||||
env: { browser: true },
|
||||
parserOptions: {
|
||||
project: ['./ui/*/tsconfig.json'],
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
extends: [
|
||||
'airbnb-base',
|
||||
'airbnb-typescript/base',
|
||||
'prettier',
|
||||
'plugin:import/recommended',
|
||||
'plugin:import/typescript',
|
||||
'plugin:compat/recommended',
|
||||
],
|
||||
plugins: ['@typescript-eslint', 'import'],
|
||||
rules: {
|
||||
'no-alert': 'warn',
|
||||
'no-unused-vars': 0,
|
||||
'no-plusplus': 0,
|
||||
'no-param-reassign': 0,
|
||||
'no-console': 0,
|
||||
'no-underscore-dangle': 0,
|
||||
'class-methods-use-this': 0,
|
||||
'spaced-comment': 0,
|
||||
'no-lonely-if': 0,
|
||||
'prefer-destructuring': 0,
|
||||
'import/prefer-default-export': 0,
|
||||
'import/no-extraneous-dependencies': 0,
|
||||
'@typescript-eslint/lines-between-class-members': 0,
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
'@typescript-eslint/no-use-before-define': 0,
|
||||
'@typescript-eslint/naming-convention': 0,
|
||||
'@typescript-eslint/no-useless-constructor': 0,
|
||||
'@typescript-eslint/no-unused-expressions': 0, // for `func && func()` expressions
|
||||
},
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
typescript: {
|
||||
project: ['ui/artalk/tsconfig.json'].map((p) => path.resolve(__dirname, p)),
|
||||
},
|
||||
},
|
||||
polyfills: ['AbortController'],
|
||||
},
|
||||
}
|
||||
+3
-2
@@ -2,7 +2,8 @@
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: artalk # Replace with a single Open Collective username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
buy_me_a_coffee: artalk
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
@@ -11,4 +12,4 @@ issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
- https://afdian.net/a/artalk
|
||||
- https://afdian.com/a/artalk
|
||||
@@ -12,13 +12,19 @@ inputs:
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- uses: actions/github-script@v7
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
ISSUE_NUMBER: ${{ inputs.issue_number }}
|
||||
STDOUT: ${{ inputs.content }}
|
||||
with:
|
||||
script: |
|
||||
const issueNumber = Number.parseInt(process.env.ISSUE_NUMBER, 10)
|
||||
if (!Number.isSafeInteger(issueNumber) || issueNumber <= 0) {
|
||||
throw new Error('Invalid issue number')
|
||||
}
|
||||
|
||||
github.rest.issues.createComment({
|
||||
issue_number: ${{ inputs.issue_number }},
|
||||
issue_number: issueNumber,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: process.env.STDOUT
|
||||
|
||||
@@ -13,6 +13,7 @@ options:
|
||||
- fix
|
||||
- perf
|
||||
- refactor
|
||||
- test
|
||||
- docs
|
||||
commit_groups:
|
||||
sort_by: Custom
|
||||
@@ -21,12 +22,14 @@ options:
|
||||
- fix
|
||||
- perf
|
||||
- refactor
|
||||
- test
|
||||
- docs
|
||||
title_maps:
|
||||
feat: Features
|
||||
fix: Bug Fixes
|
||||
perf: Performance Improvements
|
||||
refactor: Code Refactoring
|
||||
test: Testing
|
||||
docs: Documentation
|
||||
header:
|
||||
pattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s\\/]*)\\))?\\:\\s(.*)$"
|
||||
|
||||
+12
-13
@@ -9,6 +9,8 @@ updates:
|
||||
directory: /
|
||||
schedule:
|
||||
interval: monthly
|
||||
cooldown:
|
||||
default-days: 7
|
||||
commit-message:
|
||||
prefix: "chore(deps/go): "
|
||||
groups:
|
||||
@@ -18,9 +20,13 @@ updates:
|
||||
open-pull-requests-limit: 15
|
||||
|
||||
- package-ecosystem: npm
|
||||
directory: /ui
|
||||
# setup only for whole workspace to fix issues with package.lock,
|
||||
# SEE https://github.com/dependabot/dependabot-core/issues/4993#issuecomment-1739296001
|
||||
directory: /
|
||||
schedule:
|
||||
interval: monthly
|
||||
cooldown:
|
||||
default-days: 7
|
||||
commit-message:
|
||||
prefix: "chore(deps/ui): "
|
||||
groups:
|
||||
@@ -29,23 +35,14 @@ updates:
|
||||
dev-deps:
|
||||
dependency-type: development
|
||||
open-pull-requests-limit: 15
|
||||
|
||||
- package-ecosystem: npm
|
||||
directory: /docs
|
||||
schedule:
|
||||
interval: monthly
|
||||
commit-message:
|
||||
prefix: "chore(deps/docs): "
|
||||
groups:
|
||||
deps:
|
||||
patterns:
|
||||
- "*"
|
||||
open-pull-requests-limit: 15
|
||||
versioning-strategy: increase
|
||||
|
||||
- package-ecosystem: docker
|
||||
directory: /
|
||||
schedule:
|
||||
interval: monthly
|
||||
cooldown:
|
||||
default-days: 7
|
||||
commit-message:
|
||||
prefix: "chore(deps/docker): "
|
||||
groups:
|
||||
@@ -58,6 +55,8 @@ updates:
|
||||
directory: /.github/workflows
|
||||
schedule:
|
||||
interval: monthly
|
||||
cooldown:
|
||||
default-days: 7
|
||||
commit-message:
|
||||
prefix: "chore(deps/ci): "
|
||||
groups:
|
||||
|
||||
@@ -6,15 +6,15 @@ on:
|
||||
version:
|
||||
required: true
|
||||
type: string
|
||||
ref:
|
||||
required: true
|
||||
type: string
|
||||
dry_run:
|
||||
required: true
|
||||
type: boolean
|
||||
|
||||
env:
|
||||
GO_VERSION: 1.22.2
|
||||
PKG_NAME: github.com/ArtalkJS/Artalk
|
||||
DOCKER_IMG: ghcr.io/goreleaser/goreleaser-cross
|
||||
CACHE_DIR: /tmp/cache/docker-image
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build_publish:
|
||||
@@ -22,65 +22,83 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
ref: ${{ inputs.version }}
|
||||
ref: ${{ inputs.ref }}
|
||||
persist-credentials: false
|
||||
# `go-releaser` will read the semver version from git tag name
|
||||
# `git-chglog` will diff the tag and generate changelog
|
||||
fetch-depth: 0
|
||||
|
||||
# disable cache because the err `no space left on device`
|
||||
# - name: Docker Image Cache
|
||||
# id: docker-cache
|
||||
# uses: actions/cache@v3
|
||||
# with:
|
||||
# path: ${{ env.CACHE_DIR }}
|
||||
# key: docker-image-go-cross-${{ env.GO_VERSION }}
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
# Release inputs are verified from the lock files instead of shared caches.
|
||||
cache: false
|
||||
|
||||
- name: Pull Docker Image
|
||||
# if: steps.docker-cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
docker pull "${DOCKER_IMG}:v${GO_VERSION}"
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
with:
|
||||
version: 10.33.2
|
||||
|
||||
# mkdir -p "${CACHE_DIR}"
|
||||
# docker save -o "${CACHE_DIR}/go-cross.tar" "${DOCKER_IMG}:v${GO_VERSION}"
|
||||
|
||||
# - name: Restore Docker Image from Cache
|
||||
# if: steps.docker-cache.outputs.cache-hit == 'true'
|
||||
# run: docker load -i ${CACHE_DIR}/go-cross.tar
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: 22.x
|
||||
package-manager-cache: false
|
||||
|
||||
- name: Setup git-chglog
|
||||
run: |
|
||||
curl -sL $(curl -s https://api.github.com/repos/git-chglog/git-chglog/releases/latest \
|
||||
| grep -oP '"https://.+linux_amd64.tar.gz"' | tr -d \") | tar -C /usr/local/bin -xz git-chglog
|
||||
git-chglog --version
|
||||
GOBIN="$RUNNER_TEMP/bin" go install github.com/git-chglog/git-chglog/cmd/git-chglog@v0.15.4
|
||||
echo "$RUNNER_TEMP/bin" >> "$GITHUB_PATH"
|
||||
go version -m "$RUNNER_TEMP/bin/git-chglog"
|
||||
|
||||
- name: Create local tag for dry run
|
||||
if: ${{ inputs.dry_run }}
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
if git rev-parse --quiet --verify "refs/tags/$VERSION" > /dev/null; then
|
||||
tag_commit="$(git rev-list -n 1 "$VERSION")"
|
||||
head_commit="$(git rev-parse HEAD)"
|
||||
if [[ "$tag_commit" != "$head_commit" ]]; then
|
||||
echo "Tag $VERSION already points to $tag_commit, expected $head_commit."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
git tag "$VERSION"
|
||||
fi
|
||||
|
||||
- name: Pre Build
|
||||
run: |-
|
||||
mkdir -p local
|
||||
|
||||
# github token
|
||||
echo "GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}" > local/.release-env
|
||||
|
||||
# changelog
|
||||
git-chglog --config .github/chglog/config.yml ${{ inputs.version }} > local/release-notes.md
|
||||
git-chglog --config .github/chglog/config.yml "$VERSION" > local/release-notes.md
|
||||
|
||||
# copy config file
|
||||
cp conf/artalk.example.yml artalk.yml
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
|
||||
- name: Print Relase Notes
|
||||
- name: Print Release Notes
|
||||
run: cat local/release-notes.md
|
||||
|
||||
- name: Validate release build
|
||||
if: ${{ inputs.dry_run }}
|
||||
uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7.2.3
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: v2.17.0
|
||||
args: release --clean --release-notes local/release-notes.md --skip=publish
|
||||
|
||||
- name: Build and Release
|
||||
run: |
|
||||
docker run \
|
||||
--rm \
|
||||
--privileged \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v $(pwd)/sysroot:/sysroot \
|
||||
-v $(pwd):/go/src/${PKG_NAME} \
|
||||
-w /go/src/${PKG_NAME} \
|
||||
-e CGO_ENABLED=1 \
|
||||
--env-file local/.release-env \
|
||||
ghcr.io/goreleaser/goreleaser-cross:v${GO_VERSION} \
|
||||
release --release-notes local/release-notes.md ${{ inputs.dry_run && '--skip=publish' || '' }}
|
||||
if: ${{ !inputs.dry_run }}
|
||||
uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7.2.3
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: v2.17.0
|
||||
args: release --clean --release-notes local/release-notes.md
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
@@ -6,77 +6,283 @@ on:
|
||||
version:
|
||||
required: true
|
||||
type: string
|
||||
ref:
|
||||
required: true
|
||||
type: string
|
||||
commit:
|
||||
required: true
|
||||
type: string
|
||||
dry_run:
|
||||
required: true
|
||||
type: boolean
|
||||
target:
|
||||
required: true
|
||||
type: string
|
||||
secrets:
|
||||
DOCKERHUB_USERNAME:
|
||||
required: false
|
||||
DOCKERHUB_TOKEN:
|
||||
required: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
DIGEST_DIR: /tmp/digests
|
||||
DOCKER_IMG: artalk/artalk-go
|
||||
|
||||
jobs:
|
||||
build_publish:
|
||||
runs-on: ubuntu-latest
|
||||
ui:
|
||||
name: Build Docker UI assets
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
ref: ${{ inputs.ref }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Verify release source
|
||||
env:
|
||||
RELEASE_COMMIT: ${{ inputs.commit }}
|
||||
run: |
|
||||
if [[ ! "$RELEASE_COMMIT" =~ ^[0-9a-f]{40}$ ]]; then
|
||||
echo "Invalid release commit: $RELEASE_COMMIT"
|
||||
exit 1
|
||||
fi
|
||||
checkout_commit="$(git rev-parse HEAD)"
|
||||
if [[ "$checkout_commit" != "$RELEASE_COMMIT" ]]; then
|
||||
echo "Checked out $checkout_commit, expected $RELEASE_COMMIT."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
with:
|
||||
version: 10.33.2
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: 22.x
|
||||
cache: pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build UI
|
||||
run: make build-frontend
|
||||
|
||||
- name: Upload UI assets
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: docker-ui-src
|
||||
path: |
|
||||
public
|
||||
ui/artalk/dist
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
build:
|
||||
name: Build Docker (${{ matrix.platform }})
|
||||
needs: ui
|
||||
runs-on: ${{ matrix.runner }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- runner: ubuntu-24.04
|
||||
platform: linux/amd64
|
||||
platform_pair: linux_amd64
|
||||
- runner: ubuntu-24.04-arm
|
||||
platform: linux/arm64
|
||||
platform_pair: linux_arm64
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
ref: ${{ inputs.version }}
|
||||
ref: ${{ inputs.ref }}
|
||||
persist-credentials: false
|
||||
|
||||
# https://github.com/docker/metadata-action
|
||||
- name: Gen docker meta
|
||||
- name: Verify release source
|
||||
env:
|
||||
RELEASE_COMMIT: ${{ inputs.commit }}
|
||||
run: |
|
||||
if [[ ! "$RELEASE_COMMIT" =~ ^[0-9a-f]{40}$ ]]; then
|
||||
echo "Invalid release commit: $RELEASE_COMMIT"
|
||||
exit 1
|
||||
fi
|
||||
checkout_commit="$(git rev-parse HEAD)"
|
||||
if [[ "$checkout_commit" != "$RELEASE_COMMIT" ]]; then
|
||||
echo "Checked out $checkout_commit, expected $RELEASE_COMMIT."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Download UI assets
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: docker-ui-src
|
||||
|
||||
- name: Generate Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
|
||||
with:
|
||||
context: git
|
||||
images: |
|
||||
${{ env.DOCKER_IMG }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=sha
|
||||
images: ${{ env.DOCKER_IMG }}
|
||||
tags: type=raw,value=build-${{ matrix.platform_pair }}
|
||||
|
||||
# https://github.com/docker/login-action
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
if: ${{ !inputs.dry_run }}
|
||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
# https://github.com/docker/setup-qemu-action
|
||||
- name: Setup QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
with:
|
||||
platforms: ${{ inputs.target }}
|
||||
|
||||
# https://github.com/docker/setup-buildx-action
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||
|
||||
# Build UI outside of Docker to speed up cross-platform builds
|
||||
- name: Build UI
|
||||
run: |
|
||||
make build-frontend
|
||||
|
||||
# https://github.com/docker/build-push-action
|
||||
- name: Build and Push
|
||||
id: build
|
||||
uses: docker/build-push-action@v5
|
||||
- name: Build dry-run image
|
||||
if: ${{ inputs.dry_run }}
|
||||
id: build_dry_run
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||
with:
|
||||
push: ${{ !inputs.dry_run }}
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
platforms: ${{ matrix.platform }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
build-args: |
|
||||
APP_VERSION=${{ inputs.version }}
|
||||
APP_COMMIT_HASH=${{ inputs.commit }}
|
||||
SKIP_UI_BUILD=true
|
||||
platforms: ${{ inputs.target }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
outputs: type=docker
|
||||
cache-from: type=gha,scope=release-${{ matrix.platform_pair }}
|
||||
cache-to: type=gha,mode=max,scope=release-${{ matrix.platform_pair }}
|
||||
|
||||
- name: Print image digest
|
||||
run: echo ${{ steps.build.outputs.digest }}
|
||||
- name: Build and push image by digest
|
||||
if: ${{ !inputs.dry_run }}
|
||||
id: build
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
platforms: ${{ matrix.platform }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: |
|
||||
APP_VERSION=${{ inputs.version }}
|
||||
APP_COMMIT_HASH=${{ inputs.commit }}
|
||||
SKIP_UI_BUILD=true
|
||||
outputs: type=image,push=true,name=${{ env.DOCKER_IMG }},push-by-digest=true,name-canonical=true
|
||||
cache-from: type=gha,scope=release-${{ matrix.platform_pair }}
|
||||
cache-to: type=gha,mode=max,scope=release-${{ matrix.platform_pair }}
|
||||
provenance: mode=max
|
||||
sbom: true
|
||||
|
||||
- name: Smoke test dry-run image
|
||||
if: ${{ inputs.dry_run }}
|
||||
env:
|
||||
IMAGE: ${{ steps.meta.outputs.tags }}
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
output="$(docker run --rm --entrypoint /artalk "$IMAGE" version)"
|
||||
grep --fixed-strings "Artalk ($VERSION/" <<< "$output"
|
||||
|
||||
- name: Smoke test published image
|
||||
if: ${{ !inputs.dry_run }}
|
||||
env:
|
||||
IMAGE: ${{ env.DOCKER_IMG }}@${{ steps.build.outputs.digest }}
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
docker pull "$IMAGE"
|
||||
output="$(docker run --rm --entrypoint /artalk "$IMAGE" version)"
|
||||
grep --fixed-strings "Artalk ($VERSION/" <<< "$output"
|
||||
|
||||
- name: Export digest
|
||||
if: ${{ !inputs.dry_run }}
|
||||
env:
|
||||
DIGEST: ${{ steps.build.outputs.digest }}
|
||||
run: |
|
||||
mkdir -p "$DIGEST_DIR"
|
||||
touch "$DIGEST_DIR/${DIGEST#sha256:}"
|
||||
|
||||
- name: Upload digest
|
||||
if: ${{ !inputs.dry_run }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: release-digest-${{ matrix.platform_pair }}
|
||||
path: ${{ env.DIGEST_DIR }}/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
merge:
|
||||
name: Publish Docker manifest
|
||||
if: ${{ !inputs.dry_run }}
|
||||
needs: build
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
path: ${{ env.DIGEST_DIR }}
|
||||
pattern: release-digest-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||
|
||||
- name: Prepare release metadata
|
||||
env:
|
||||
RELEASE_COMMIT: ${{ inputs.commit }}
|
||||
run: |
|
||||
if [[ ! "$RELEASE_COMMIT" =~ ^[0-9a-f]{40}$ ]]; then
|
||||
echo "Invalid release commit: $RELEASE_COMMIT"
|
||||
exit 1
|
||||
fi
|
||||
echo "RELEASE_COMMIT_SHORT=${RELEASE_COMMIT:0:7}" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Generate Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
|
||||
with:
|
||||
images: ${{ env.DOCKER_IMG }}
|
||||
tags: |
|
||||
type=semver,pattern={{version}},value=${{ inputs.version }}
|
||||
type=semver,pattern={{major}}.{{minor}},value=${{ inputs.version }}
|
||||
type=raw,value=sha-${{ env.RELEASE_COMMIT_SHORT }}
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Create and push manifest
|
||||
env:
|
||||
METADATA: ${{ steps.meta.outputs.json }}
|
||||
run: |
|
||||
mapfile -t tags < <(jq -r '.tags[]' <<< "$METADATA")
|
||||
tag_args=()
|
||||
for tag in "${tags[@]}"; do
|
||||
tag_args+=(--tag "$tag")
|
||||
done
|
||||
|
||||
shopt -s nullglob
|
||||
digest_files=("$DIGEST_DIR"/*)
|
||||
if [[ "${#digest_files[@]}" -ne 2 ]]; then
|
||||
echo "Expected 2 platform digests, found ${#digest_files[@]}."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sources=()
|
||||
for digest_file in "${digest_files[@]}"; do
|
||||
digest="$(basename "$digest_file")"
|
||||
if [[ ! "$digest" =~ ^[0-9a-f]{64}$ ]]; then
|
||||
echo "Invalid image digest: $digest"
|
||||
exit 1
|
||||
fi
|
||||
sources+=("$DOCKER_IMG@sha256:$digest")
|
||||
done
|
||||
|
||||
docker buildx imagetools create "${tag_args[@]}" "${sources[@]}"
|
||||
|
||||
- name: Inspect image
|
||||
env:
|
||||
IMAGE: ${{ env.DOCKER_IMG }}:${{ steps.meta.outputs.version }}
|
||||
run: docker buildx imagetools inspect "$IMAGE"
|
||||
@@ -6,6 +6,9 @@ on:
|
||||
version:
|
||||
required: true
|
||||
type: string
|
||||
ref:
|
||||
required: true
|
||||
type: string
|
||||
dry_run:
|
||||
required: true
|
||||
type: boolean
|
||||
@@ -13,18 +16,24 @@ on:
|
||||
required: true
|
||||
type: number
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
done:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
ref: ${{ inputs.version }}
|
||||
ref: ${{ inputs.ref }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Comment done info
|
||||
uses: ./.github/actions/comment
|
||||
if: ${{ inputs.issue_number != 0 }}
|
||||
if: ${{ !inputs.dry_run && inputs.issue_number != 0 }}
|
||||
with:
|
||||
issue_number: ${{ inputs.issue_number }}
|
||||
content: |-
|
||||
|
||||
+220
-201
@@ -1,5 +1,5 @@
|
||||
name: Build Nightly
|
||||
run-name: Build Nightly
|
||||
run-name: Build Nightly ${{ inputs.dry_run && '(🧪 Dry-Run)' || '' }}
|
||||
|
||||
on:
|
||||
schedule:
|
||||
@@ -7,356 +7,375 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
dry_run:
|
||||
description: "Dry run"
|
||||
description: "Build without publishing"
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: build-nightly
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
DIST_DIR: /tmp/builds
|
||||
DIGEST_DIR: /tmp/digests
|
||||
DOCKER_IMG: artalk/artalk-go
|
||||
DOCKER_BUILD_ARGS: |-
|
||||
SKIP_UI_BUILD=true
|
||||
|
||||
jobs:
|
||||
# Prepare check should run
|
||||
prepare:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
outputs:
|
||||
skip: ${{ steps.check.outputs.skip }}
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Check should run
|
||||
- name: Check whether the scheduled build should run
|
||||
id: check
|
||||
if: github.event_name == 'schedule'
|
||||
run: |
|
||||
if [[ "$(git log --since='24 hours ago' | wc -l)" -eq 0 ]] || \
|
||||
if [[ "$(git log --since='24 hours ago' --oneline | wc -l)" -eq 0 ]] || \
|
||||
[[ "$GITHUB_REPOSITORY" != "ArtalkJS/Artalk" ]]; then
|
||||
echo "skip=true" >> $GITHUB_OUTPUT
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
# Bulid UI first
|
||||
# (build ui outside of docker to speed up cross-platform builds)
|
||||
ui:
|
||||
name: Build Nightly UI assets
|
||||
if: ${{ needs.prepare.outputs.skip != 'true' }}
|
||||
needs: prepare
|
||||
runs-on: macos-14
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
with:
|
||||
version: 9.1.0
|
||||
version: 10.33.2
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v4
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: 20.x
|
||||
registry-url: https://registry.npmjs.org/
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Get pnpm store directory
|
||||
shell: bash
|
||||
run: |
|
||||
echo "PNPM_STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/cache@v4
|
||||
name: Setup pnpm cache
|
||||
with:
|
||||
path: ${{ env.PNPM_STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
node-version: 22.x
|
||||
cache: pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build UI
|
||||
id: build_ui
|
||||
run: |
|
||||
make build-frontend
|
||||
echo "ui_tarball=$(make -s pack-frontend)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Pack UI
|
||||
id: pack
|
||||
run: |
|
||||
SRC_FILE=$(pnpm pack -C ui/artalk --pack-destination ../.. | tail -n 1)
|
||||
|
||||
mkdir -p bin
|
||||
PKG_FILE="bin/artalk_frontend_nigthly_$(date +'%Y%m%d').tar.gz"
|
||||
|
||||
mv $SRC_FILE $PKG_FILE
|
||||
echo "pkg_file=$PKG_FILE" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Upload UI
|
||||
uses: actions/upload-artifact@v4
|
||||
- name: Upload UI assets
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: ui-src
|
||||
name: nightly-ui-src
|
||||
path: |
|
||||
public
|
||||
ui/artalk/dist
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
- name: Upload UI package
|
||||
uses: actions/upload-artifact@v4
|
||||
- name: Upload UI tarball
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: build-ui-pkg
|
||||
path: ${{ steps.pack.outputs.pkg_file }}
|
||||
name: build-ui-tarball
|
||||
path: ${{ steps.build_ui.outputs.ui_tarball }}
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
#
|
||||
# Build app on different platforms
|
||||
#
|
||||
build:
|
||||
name: Build Nightly (${{ matrix.platform }})
|
||||
needs: ui
|
||||
runs-on: ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.runner }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-latest
|
||||
# - macos-14
|
||||
# :(
|
||||
# native Apple m1 arm64 is not supported nested virtualization yet, so docker qemu is not working.
|
||||
# https://github.com/actions/runner-images/issues/9460
|
||||
# https://docs.veertu.com/anka/anka-virtualization-cli/nested-virtualization/
|
||||
#
|
||||
# wait for arm64 linux runner support on github actions.
|
||||
platform:
|
||||
- linux/amd64
|
||||
- linux/arm64
|
||||
- linux/arm/v7
|
||||
# exclude:
|
||||
# # exclude build x86 on arm, and vice versa.
|
||||
# - { os: macos-14, platform: linux/amd64 }
|
||||
# - { os: ubuntu-latest, platform: linux/arm64 }
|
||||
include:
|
||||
- runner: ubuntu-24.04
|
||||
platform: linux/amd64
|
||||
platform_pair: linux_amd64
|
||||
- runner: ubuntu-24.04-arm
|
||||
platform: linux/arm64
|
||||
platform_pair: linux_arm64
|
||||
|
||||
steps:
|
||||
- name: Prepare
|
||||
run: |
|
||||
PF="${{ matrix.platform }}"
|
||||
echo "PLATFORM_PAIR=${PF//\//_}" >> $GITHUB_ENV
|
||||
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup docker on macos
|
||||
if: startsWith(matrix.os, 'macos')
|
||||
uses: crazy-max/ghaction-setup-docker@v3
|
||||
- name: Download UI assets
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: nightly-ui-src
|
||||
|
||||
- name: Gen docker meta
|
||||
- name: Generate Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
|
||||
with:
|
||||
images: ${{ env.DOCKER_IMG }}
|
||||
tags: type=raw,value=nightly
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
tags: type=raw,value=nightly-build-${{ matrix.platform_pair }}
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
if: ${{ github.event_name == 'schedule' || !inputs.dry_run }}
|
||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Download UI before build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ui-src
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||
|
||||
- name: Build and push by digest
|
||||
uses: docker/build-push-action@v5
|
||||
id: build
|
||||
- name: Build dry-run image
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run }}
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||
with:
|
||||
context: .
|
||||
platforms: ${{ matrix.platform }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: ${{ env.DOCKER_BUILD_ARGS }}
|
||||
# The `type=image,push=true` must the last one,
|
||||
# because the `build.outputs.digest` will output the last image's digest.
|
||||
# The digests will be used in the merge step.
|
||||
build-args: |
|
||||
APP_COMMIT_HASH=${{ github.sha }}
|
||||
SKIP_UI_BUILD=true
|
||||
outputs: type=docker,name=${{ steps.meta.outputs.tags }}
|
||||
cache-from: type=gha,scope=nightly-${{ matrix.platform_pair }}
|
||||
cache-to: type=gha,mode=max,scope=nightly-${{ matrix.platform_pair }}
|
||||
|
||||
- name: Build and push image by digest
|
||||
if: ${{ github.event_name == 'schedule' || !inputs.dry_run }}
|
||||
id: build
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||
with:
|
||||
context: .
|
||||
platforms: ${{ matrix.platform }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: |
|
||||
APP_COMMIT_HASH=${{ github.sha }}
|
||||
SKIP_UI_BUILD=true
|
||||
outputs: |
|
||||
type=docker,name=${{ env.DOCKER_IMG }}
|
||||
type=docker,name=${{ steps.meta.outputs.tags }}
|
||||
type=image,push=true,name=${{ env.DOCKER_IMG }},push-by-digest=true,name-canonical=true
|
||||
cache-from: type=gha,scope=nightly-${{ matrix.platform_pair }}
|
||||
cache-to: type=gha,mode=max,scope=nightly-${{ matrix.platform_pair }}
|
||||
|
||||
- name: Smoke test image
|
||||
env:
|
||||
IMAGE: ${{ steps.meta.outputs.tags }}
|
||||
run: docker run --rm --entrypoint /artalk "$IMAGE" version
|
||||
|
||||
- name: Export digest
|
||||
if: ${{ github.event_name == 'schedule' || !inputs.dry_run }}
|
||||
env:
|
||||
DIGEST: ${{ steps.build.outputs.digest }}
|
||||
run: |
|
||||
mkdir -p $DIGEST_DIR
|
||||
digest="${{ steps.build.outputs.digest }}"
|
||||
touch "$DIGEST_DIR/${digest#sha256:}"
|
||||
mkdir -p "$DIGEST_DIR"
|
||||
touch "$DIGEST_DIR/${DIGEST#sha256:}"
|
||||
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@v4
|
||||
if: ${{ github.event_name == 'schedule' || !inputs.dry_run }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: digests-${{ env.PLATFORM_PAIR }}
|
||||
name: nightly-digest-${{ matrix.platform_pair }}
|
||||
path: ${{ env.DIGEST_DIR }}/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
# Export docker image
|
||||
- name: Export Docker Image
|
||||
- name: Export Docker image
|
||||
id: docker
|
||||
env:
|
||||
IMAGE: ${{ steps.meta.outputs.tags }}
|
||||
PLATFORM_PAIR: ${{ matrix.platform_pair }}
|
||||
run: |
|
||||
IMG_FILE="artalk_docker_nigthly_$(date +'%Y%m%d')_${PLATFORM_PAIR}.tar.gz"
|
||||
docker save ${{ env.DOCKER_IMG }} | gzip > $IMG_FILE
|
||||
echo "img_file=$IMG_FILE" >> $GITHUB_OUTPUT
|
||||
image_file="artalk_docker_nigthly_$(date +'%Y%m%d')_${PLATFORM_PAIR}.tar.gz"
|
||||
docker save "$IMAGE" | gzip > "$image_file"
|
||||
echo "image_file=$image_file" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# App
|
||||
- name: Pack app
|
||||
id: app
|
||||
env:
|
||||
IMAGE: ${{ steps.meta.outputs.tags }}
|
||||
PLATFORM_PAIR: ${{ matrix.platform_pair }}
|
||||
run: |
|
||||
BASE_DIR="/tmp/app_pkg"
|
||||
NAME="artalk_nigthly_$(date +'%Y%m%d')_${PLATFORM_PAIR}"
|
||||
SUB_DIR="$BASE_DIR/$NAME"
|
||||
PKG_FILE="$BASE_DIR/$NAME.tar.gz"
|
||||
base_dir="/tmp/app_pkg"
|
||||
name="artalk_nigthly_$(date +'%Y%m%d')_${PLATFORM_PAIR}"
|
||||
package_dir="$base_dir/$name"
|
||||
package_file="$base_dir/$name.tar.gz"
|
||||
|
||||
# bin file
|
||||
mkdir -p $SUB_DIR
|
||||
docker run --rm --entrypoint cat ${{ env.DOCKER_IMG }} /artalk > "$SUB_DIR/artalk"
|
||||
chmod +x "$SUB_DIR/artalk"
|
||||
mkdir -p "$package_dir"
|
||||
docker run --rm --entrypoint cat "$IMAGE" /artalk > "$package_dir/artalk"
|
||||
chmod +x "$package_dir/artalk"
|
||||
|
||||
# doc file
|
||||
cp conf/artalk.example.yml "$SUB_DIR/artalk.yml"
|
||||
cp README.md LICENSE CHANGELOG.md "$SUB_DIR"
|
||||
cp conf/artalk.example.yml "$package_dir/artalk.yml"
|
||||
cp README.md LICENSE CHANGELOG.md "$package_dir"
|
||||
|
||||
# create package file
|
||||
tar -czf $PKG_FILE -C $BASE_DIR $NAME
|
||||
echo "app_pkg_file=$PKG_FILE" >> $GITHUB_OUTPUT
|
||||
tar -czf "$package_file" -C "$base_dir" "$name"
|
||||
echo "package_file=$package_file" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Move build artifacts
|
||||
- name: Move build artifacts
|
||||
- name: Collect build artifacts
|
||||
env:
|
||||
APP_PACKAGE: ${{ steps.app.outputs.package_file }}
|
||||
DOCKER_IMAGE: ${{ steps.docker.outputs.image_file }}
|
||||
run: |
|
||||
mkdir -p $DIST_DIR
|
||||
mv ${{ steps.app.outputs.app_pkg_file }} $DIST_DIR
|
||||
mv ${{ steps.docker.outputs.img_file }} $DIST_DIR
|
||||
mkdir -p "$DIST_DIR"
|
||||
mv "$APP_PACKAGE" "$DIST_DIR"
|
||||
mv "$DOCKER_IMAGE" "$DIST_DIR"
|
||||
|
||||
# Upload build artifacts
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: build-${{ env.PLATFORM_PAIR }}
|
||||
name: build-${{ matrix.platform_pair }}
|
||||
path: ${{ env.DIST_DIR }}/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
# Merge docker digests from different platforms
|
||||
# Reference: https://docs.docker.com/build/ci/github-actions/multi-platform/#distribute-build-across-multiple-runners
|
||||
docker_merge:
|
||||
runs-on: ubuntu-latest
|
||||
name: Publish Nightly Docker manifest
|
||||
if: ${{ github.event_name == 'schedule' || !inputs.dry_run }}
|
||||
needs: build
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
path: ${{ env.DIGEST_DIR }}
|
||||
pattern: digests-*
|
||||
pattern: nightly-digest-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||
|
||||
- name: Docker meta
|
||||
- name: Generate Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
|
||||
with:
|
||||
images: ${{ env.DOCKER_IMG }}
|
||||
tags: type=raw,value=nightly
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Create manifest list and push
|
||||
working-directory: ${{ env.DIGEST_DIR }}
|
||||
- name: Create and push manifest
|
||||
env:
|
||||
METADATA: ${{ steps.meta.outputs.json }}
|
||||
run: |
|
||||
TAGS_ARG=$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON")
|
||||
MINIFEST_SRCS=$(printf '${{ env.DOCKER_IMG }}@sha256:%s ' *)
|
||||
mapfile -t tags < <(jq -r '.tags[]' <<< "$METADATA")
|
||||
tag_args=()
|
||||
for tag in "${tags[@]}"; do
|
||||
tag_args+=(--tag "$tag")
|
||||
done
|
||||
|
||||
docker buildx imagetools create ${TAGS_ARG} ${MINIFEST_SRCS}
|
||||
shopt -s nullglob
|
||||
digest_files=("$DIGEST_DIR"/*)
|
||||
if [[ "${#digest_files[@]}" -ne 2 ]]; then
|
||||
echo "Expected 2 platform digests, found ${#digest_files[@]}."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sources=()
|
||||
for digest_file in "${digest_files[@]}"; do
|
||||
digest="$(basename "$digest_file")"
|
||||
if [[ ! "$digest" =~ ^[0-9a-f]{64}$ ]]; then
|
||||
echo "Invalid image digest: $digest"
|
||||
exit 1
|
||||
fi
|
||||
sources+=("$DOCKER_IMG@sha256:$digest")
|
||||
done
|
||||
|
||||
docker buildx imagetools create "${tag_args[@]}" "${sources[@]}"
|
||||
|
||||
- name: Inspect image
|
||||
run: |
|
||||
docker buildx imagetools inspect ${{ env.DOCKER_IMG }}:${{ steps.meta.outputs.version }}
|
||||
env:
|
||||
IMAGE: ${{ env.DOCKER_IMG }}:${{ steps.meta.outputs.version }}
|
||||
run: docker buildx imagetools inspect "$IMAGE"
|
||||
|
||||
# Release
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
name: Publish Nightly GitHub release
|
||||
if: ${{ github.event_name == 'schedule' || !inputs.dry_run }}
|
||||
needs: [build, docker_merge]
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Download builds
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
path: ${{ env.DIST_DIR }}
|
||||
pattern: build-*
|
||||
merge-multiple: true
|
||||
|
||||
# Generate CHANGELOG
|
||||
- name: Generate Changelog
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: false
|
||||
|
||||
- name: Setup git-chglog
|
||||
run: |
|
||||
# install git-chglog
|
||||
curl -sL $(curl -s https://api.github.com/repos/git-chglog/git-chglog/releases/latest \
|
||||
| grep -oP '"https://.+linux_amd64.tar.gz"' | tr -d \") | tar -C /usr/local/bin -xz git-chglog
|
||||
GOBIN="$RUNNER_TEMP/bin" go install github.com/git-chglog/git-chglog/cmd/git-chglog@v0.15.4
|
||||
echo "$RUNNER_TEMP/bin" >> "$GITHUB_PATH"
|
||||
go version -m "$RUNNER_TEMP/bin/git-chglog"
|
||||
|
||||
CHANGELOG=$(git-chglog --config .github/chglog/config.yml --next-tag nightly nightly)
|
||||
MD_FILE="/tmp/changelog.md"
|
||||
echo -e "$CHANGELOG" > $MD_FILE
|
||||
echo -e "\n> [!NOTE]\n" \
|
||||
"> The Nightly version, automatically built based on the latest code, " \
|
||||
"is not yet released and may be unstable. Please use it with caution. " \
|
||||
"Docker user can run \`docker pull artalk/artalk-go:nightly\` to get " \
|
||||
"the nightly build." >> $MD_FILE
|
||||
|
||||
# checksums.txt
|
||||
- name: Calculate checksums.txt
|
||||
- name: Generate changelog
|
||||
run: |
|
||||
cd $DIST_DIR
|
||||
sha256sum * > checksums.txt
|
||||
git-chglog --config .github/chglog/config.yml --next-tag nightly nightly > /tmp/changelog.md
|
||||
cat >> /tmp/changelog.md <<'EOF'
|
||||
|
||||
# print checksums.txt
|
||||
> [!NOTE]
|
||||
> The Nightly version is automatically built from the latest code and may be unstable.
|
||||
> Docker users can run `docker pull artalk/artalk-go:nightly` to get this build.
|
||||
EOF
|
||||
|
||||
- name: Calculate checksums
|
||||
run: |
|
||||
cd "$DIST_DIR"
|
||||
sha256sum -- * > checksums.txt
|
||||
cat checksums.txt
|
||||
|
||||
FILES=$(find ${DIST_DIR} -type f -exec readlink -f {} \;)
|
||||
echo -e "RELEASE_FILES<<EOF" >> $GITHUB_ENV
|
||||
echo -e "$FILES" >> $GITHUB_ENV
|
||||
echo -e "EOF" >> $GITHUB_ENV
|
||||
|
||||
- name: Create tag and push
|
||||
- name: Move Nightly tag
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
git config user.name github-actions[bot]
|
||||
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
|
||||
if gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/nightly" > /dev/null 2>&1; then
|
||||
gh api --method PATCH "repos/$GITHUB_REPOSITORY/git/refs/tags/nightly" \
|
||||
-f sha="$GITHUB_SHA" -F force=true > /dev/null
|
||||
else
|
||||
gh api --method POST "repos/$GITHUB_REPOSITORY/git/refs" \
|
||||
-f ref=refs/tags/nightly -f sha="$GITHUB_SHA" > /dev/null
|
||||
fi
|
||||
|
||||
tag_sha="$(gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/nightly" --jq '.object.sha')"
|
||||
if [[ "$tag_sha" != "$GITHUB_SHA" ]]; then
|
||||
echo "Nightly tag points to $tag_sha, expected $GITHUB_SHA."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Publish release
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
gh release delete nightly --yes || true
|
||||
git push origin :refs/tags/nightly || true
|
||||
|
||||
git tag -f nightly
|
||||
git push -f origin nightly
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
prerelease: true
|
||||
tag_name: nightly
|
||||
name: 🧪 Nightly Version
|
||||
body_path: /tmp/changelog.md
|
||||
files: ${{ env.RELEASE_FILES }}
|
||||
draft: false
|
||||
mapfile -d '' release_files < <(find "$DIST_DIR" -type f -print0)
|
||||
gh release create nightly "${release_files[@]}" \
|
||||
--title "🧪 Nightly Version" \
|
||||
--notes-file /tmp/changelog.md \
|
||||
--prerelease \
|
||||
--target "$GITHUB_SHA"
|
||||
@@ -9,53 +9,257 @@ on:
|
||||
issue_number:
|
||||
required: true
|
||||
type: number
|
||||
version:
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
release_commit:
|
||||
description: "Immutable commit to tag when the release tag does not exist"
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
outputs:
|
||||
version:
|
||||
description: "Version"
|
||||
value: ${{ jobs.tagging.outputs.version }}
|
||||
ref:
|
||||
description: "Git ref to build"
|
||||
value: ${{ jobs.tagging.outputs.ref }}
|
||||
commit:
|
||||
description: "Release source commit"
|
||||
value: ${{ jobs.tagging.outputs.commit }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
tagging:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Get release version
|
||||
id: version
|
||||
env:
|
||||
REF: ${{ github.head_ref || github.ref }}
|
||||
REQUESTED_VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
# `github.head_ref` only exists for pull_request events
|
||||
REF="${{ github.head_ref || github.ref }}"
|
||||
VERSION="${REF#refs/tags/}"
|
||||
VERSION="${VERSION#refs/heads/}"
|
||||
VERSION="${VERSION#release/}"
|
||||
|
||||
echo "Version: $VERSION"
|
||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||
if [[ -n "$REQUESTED_VERSION" ]]; then
|
||||
VERSION="$REQUESTED_VERSION"
|
||||
else
|
||||
VERSION="${REF#refs/tags/}"
|
||||
VERSION="${VERSION#refs/heads/}"
|
||||
VERSION="${VERSION#release/}"
|
||||
fi
|
||||
|
||||
if [[ ! "$VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "Invalid version format: $VERSION, please check git ref or tag."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Version: $VERSION"
|
||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Resolve release target
|
||||
id: target
|
||||
env:
|
||||
DEFAULT_COMMIT: ${{ github.sha }}
|
||||
DRY_RUN: ${{ inputs.dry_run }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
REQUESTED_COMMIT: ${{ inputs.release_commit }}
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
run: |
|
||||
resolve_tag_commit() {
|
||||
local object_type="$1"
|
||||
local object_sha="$2"
|
||||
local depth=0
|
||||
|
||||
while [[ "$object_type" == "tag" ]]; do
|
||||
depth=$((depth + 1))
|
||||
if (( depth > 8 )); then
|
||||
echo "Tag $VERSION has too many nested tag objects." >&2
|
||||
return 1
|
||||
fi
|
||||
IFS=$'\t' read -r object_type object_sha < <(
|
||||
gh api "repos/$GITHUB_REPOSITORY/git/tags/$object_sha" \
|
||||
--jq '.object | [.type, .sha] | @tsv'
|
||||
)
|
||||
done
|
||||
|
||||
if [[ "$object_type" != "commit" ]]; then
|
||||
echo "Tag $VERSION resolves to unsupported object type $object_type." >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
printf '%s\n' "$object_sha"
|
||||
}
|
||||
|
||||
requested_commit="$(tr '[:upper:]' '[:lower:]' <<< "$REQUESTED_COMMIT")"
|
||||
if [[ -n "$requested_commit" && ! "$requested_commit" =~ ^[0-9a-f]{40}$ ]]; then
|
||||
echo "release_commit must be a full 40-character commit SHA."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
tag_ref="$(
|
||||
gh api "repos/$GITHUB_REPOSITORY/git/matching-refs/tags/$VERSION" \
|
||||
--jq ".[] | select(.ref == \"refs/tags/$VERSION\") | [.object.type, .object.sha] | @tsv"
|
||||
)"
|
||||
|
||||
tag_exists=false
|
||||
if [[ -n "$tag_ref" ]]; then
|
||||
IFS=$'\t' read -r tag_type tag_sha <<< "$tag_ref"
|
||||
release_commit="$(resolve_tag_commit "$tag_type" "$tag_sha")"
|
||||
tag_exists=true
|
||||
if [[ -n "$requested_commit" && "$release_commit" != "$requested_commit" ]]; then
|
||||
echo "Tag $VERSION points to $release_commit, not requested commit $requested_commit."
|
||||
exit 1
|
||||
fi
|
||||
elif [[ -n "$requested_commit" ]]; then
|
||||
release_commit="$requested_commit"
|
||||
elif [[ "$DRY_RUN" == "true" ]]; then
|
||||
release_commit="$DEFAULT_COMMIT"
|
||||
else
|
||||
echo "Tag $VERSION does not exist and release_commit was not provided."
|
||||
echo "Use the reviewed release pull request merge commit for a production retry."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
verified_commit="$(
|
||||
gh api "repos/$GITHUB_REPOSITORY/git/commits/$release_commit" --jq '.sha'
|
||||
)"
|
||||
if [[ "$verified_commit" != "$release_commit" ]]; then
|
||||
echo "Unable to verify release commit $release_commit."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "commit=$release_commit" >> "$GITHUB_OUTPUT"
|
||||
echo "tag_exists=$tag_exists" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
ref: ${{ steps.target.outputs.commit }}
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Validate release source
|
||||
env:
|
||||
RELEASE_COMMIT: ${{ steps.target.outputs.commit }}
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
run: |
|
||||
checkout_commit="$(git rev-parse HEAD)"
|
||||
if [[ "$checkout_commit" != "$RELEASE_COMMIT" ]]; then
|
||||
echo "Checked out $checkout_commit, expected $RELEASE_COMMIT."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
git fetch --no-tags origin \
|
||||
"+refs/heads/master:refs/remotes/origin/master"
|
||||
if ! git merge-base --is-ancestor "$RELEASE_COMMIT" origin/master; then
|
||||
echo "Release commit $RELEASE_COMMIT is not reachable from master."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
package_version="${VERSION#v}"
|
||||
app_version="$(
|
||||
sed -n 's/^var Version = "\(v[0-9][0-9.]*\)"$/\1/p' internal/config/version.go
|
||||
)"
|
||||
ui_version="$(jq -r '.version' ui/artalk/package.json)"
|
||||
docs_version="$(jq -r '.latest' docs/docs/code/ArtalkVersion.json)"
|
||||
if [[ "$app_version" != "$VERSION" ||
|
||||
"$ui_version" != "$package_version" ||
|
||||
"$docs_version" != "$package_version" ]]; then
|
||||
echo "Release commit versions do not match $VERSION: app=$app_version ui=$ui_version docs=$docs_version."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Create Tag
|
||||
if: ${{ !inputs.dry_run }}
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
RELEASE_COMMIT: ${{ steps.target.outputs.commit }}
|
||||
TAG_EXISTS: ${{ steps.target.outputs.tag_exists }}
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
run: |
|
||||
if [ ! $(git tag -l "${VERSION}") ]; then
|
||||
git config user.name github-actions[bot]
|
||||
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
|
||||
git tag -a $VERSION -m "Release $VERSION"
|
||||
git push origin $VERSION
|
||||
else
|
||||
echo "Skip creating tag $VERSION, already exists."
|
||||
resolve_tag_commit() {
|
||||
local object_type="$1"
|
||||
local object_sha="$2"
|
||||
local depth=0
|
||||
|
||||
while [[ "$object_type" == "tag" ]]; do
|
||||
depth=$((depth + 1))
|
||||
if (( depth > 8 )); then
|
||||
echo "Tag $VERSION has too many nested tag objects." >&2
|
||||
return 1
|
||||
fi
|
||||
IFS=$'\t' read -r object_type object_sha < <(
|
||||
gh api "repos/$GITHUB_REPOSITORY/git/tags/$object_sha" \
|
||||
--jq '.object | [.type, .sha] | @tsv'
|
||||
)
|
||||
done
|
||||
|
||||
if [[ "$object_type" != "commit" ]]; then
|
||||
echo "Tag $VERSION resolves to unsupported object type $object_type." >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
printf '%s\n' "$object_sha"
|
||||
}
|
||||
|
||||
get_tag_ref() {
|
||||
gh api "repos/$GITHUB_REPOSITORY/git/matching-refs/tags/$VERSION" \
|
||||
--jq ".[] | select(.ref == \"refs/tags/$VERSION\") | [.object.type, .object.sha] | @tsv"
|
||||
}
|
||||
|
||||
if [[ "$TAG_EXISTS" != "true" ]]; then
|
||||
tag_object_sha="$(
|
||||
gh api --method POST "repos/$GITHUB_REPOSITORY/git/tags" \
|
||||
-f tag="$VERSION" \
|
||||
-f message="Release $VERSION" \
|
||||
-f object="$RELEASE_COMMIT" \
|
||||
-f type=commit \
|
||||
--jq '.sha'
|
||||
)"
|
||||
|
||||
if ! create_error="$(
|
||||
gh api --method POST "repos/$GITHUB_REPOSITORY/git/refs" \
|
||||
-f ref="refs/tags/$VERSION" \
|
||||
-f sha="$tag_object_sha" 2>&1
|
||||
)"; then
|
||||
tag_ref="$(get_tag_ref)"
|
||||
if [[ -z "$tag_ref" ]]; then
|
||||
printf '%s\n' "$create_error" >&2
|
||||
echo "GitHub rejected tag $VERSION. Check the active tag rulesets; tag targets must not require linear history." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Tag $VERSION was created concurrently; verifying it."
|
||||
else
|
||||
echo "Created annotated tag $VERSION at $RELEASE_COMMIT."
|
||||
fi
|
||||
fi
|
||||
|
||||
tag_ref="$(get_tag_ref)"
|
||||
if [[ -z "$tag_ref" ]]; then
|
||||
echo "Tag $VERSION is missing after creation."
|
||||
exit 1
|
||||
fi
|
||||
IFS=$'\t' read -r tag_type tag_sha <<< "$tag_ref"
|
||||
tag_commit="$(resolve_tag_commit "$tag_type" "$tag_sha")"
|
||||
if [[ "$tag_commit" != "$RELEASE_COMMIT" ]]; then
|
||||
echo "Tag $VERSION points to $tag_commit, expected $RELEASE_COMMIT."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Verified tag $VERSION at $RELEASE_COMMIT."
|
||||
|
||||
- name: Comment tag info to PR
|
||||
uses: ./.github/actions/comment
|
||||
if: ${{ inputs.issue_number != 0 }}
|
||||
if: ${{ !inputs.dry_run && inputs.issue_number != 0 }}
|
||||
with:
|
||||
issue_number: ${{ inputs.issue_number }}
|
||||
content: |-
|
||||
🔖 Tagged version [${{ env.VERSION }}](https://github.com/ArtalkJS/Artalk/tree/${{ env.VERSION }}) and updated the [CHANGELOG.md](https://github.com/ArtalkJS/Artalk/blob/master/CHANGELOG.md).
|
||||
🔖 Tagged version [${{ steps.version.outputs.version }}](https://github.com/ArtalkJS/Artalk/tree/${{ steps.version.outputs.version }}) and updated the [CHANGELOG.md](https://github.com/ArtalkJS/Artalk/blob/master/CHANGELOG.md).
|
||||
outputs:
|
||||
version: ${{ env.VERSION }}
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
ref: ${{ inputs.dry_run && steps.target.outputs.commit || steps.version.outputs.version }}
|
||||
commit: ${{ steps.target.outputs.commit }}
|
||||
@@ -6,31 +6,38 @@ on:
|
||||
version:
|
||||
required: true
|
||||
type: string
|
||||
ref:
|
||||
required: true
|
||||
type: string
|
||||
dry_run:
|
||||
required: true
|
||||
type: boolean
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
|
||||
jobs:
|
||||
build_publish:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
ref: ${{ inputs.version }}
|
||||
ref: ${{ inputs.ref }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Use pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
with:
|
||||
version: 9.1.0
|
||||
version: 10.33.2
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: 20.x
|
||||
registry-url: https://registry.npmjs.org/
|
||||
cache: "pnpm"
|
||||
node-version: 24.x
|
||||
package-manager-cache: false
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
@@ -38,15 +45,37 @@ jobs:
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
|
||||
- name: Publish
|
||||
run: pnpm publish -F artalk --no-git-checks ${{ inputs.dry_run && '--dry-run' || '' }}
|
||||
- name: Pack npm package
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
package_version="v$(node --print "require('./ui/artalk/package.json').version")"
|
||||
if [[ "$package_version" != "$VERSION" ]]; then
|
||||
echo "Package version $package_version does not match release version $VERSION."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
pnpm --filter artalk pack --pack-destination "$RUNNER_TEMP"
|
||||
package_file="$RUNNER_TEMP/artalk-${package_version#v}.tgz"
|
||||
if [[ ! -f "$package_file" ]]; then
|
||||
echo "Expected package tarball was not created: $package_file"
|
||||
exit 1
|
||||
fi
|
||||
echo "PACKAGE_FILE=$package_file" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Validate package
|
||||
if: ${{ inputs.dry_run }}
|
||||
run: npm publish "$PACKAGE_FILE" --dry-run --access public
|
||||
|
||||
- name: Publish
|
||||
if: ${{ !inputs.dry_run }}
|
||||
# npm validates build.yml, the calling workflow, for trusted publishing.
|
||||
run: npm publish "$PACKAGE_FILE" --access public
|
||||
|
||||
# GitHub Packages
|
||||
#
|
||||
# Setup .npmrc file to publish to GitHub Packages
|
||||
# - uses: actions/setup-node@v4
|
||||
# - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
# with:
|
||||
# registry-url: 'https://npm.pkg.github.com'
|
||||
# # Publish to GitHub Packages
|
||||
|
||||
+61
-19
@@ -1,5 +1,5 @@
|
||||
name: Build and Publish
|
||||
run-name: Build and Publish (${{ github.head_ref || github.ref }}) ${{ inputs.dry_run && '(🧪 Dry-Run)' || '' }}
|
||||
run-name: Build and Publish (${{ inputs.version || github.head_ref || github.ref }}) ${{ inputs.dry_run && '(🧪 Dry-Run)' || '' }}
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
@@ -12,11 +12,34 @@ on:
|
||||
description: "Dry run"
|
||||
type: boolean
|
||||
default: true
|
||||
version:
|
||||
description: "Release version (for example, v2.10.0)"
|
||||
type: string
|
||||
required: true
|
||||
release_commit:
|
||||
description: "Full release commit SHA (required when a production retry has no tag)"
|
||||
type: string
|
||||
required: false
|
||||
build_items:
|
||||
description: "Build items"
|
||||
type: string
|
||||
type: choice
|
||||
options:
|
||||
- "ui,app,docker"
|
||||
- "ui,app"
|
||||
- "ui,docker"
|
||||
- "app,docker"
|
||||
- ui
|
||||
- app
|
||||
- docker
|
||||
default: "ui,app,docker"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: release-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
# Tag the release version code before building
|
||||
# Getting the version code from the branch name or git ref.
|
||||
@@ -26,57 +49,76 @@ jobs:
|
||||
# (which the branch name start with `relase/`)
|
||||
if: |
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(github.event.pull_request.merged == true && startsWith(github.head_ref, 'release/'))
|
||||
(github.event.pull_request.merged == true &&
|
||||
github.event.pull_request.head.repo.full_name == github.repository &&
|
||||
startsWith(github.head_ref, 'release/'))
|
||||
uses: ./.github/workflows/build-tagging.yml
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
pull-requests: write
|
||||
with:
|
||||
issue_number: ${{ github.event.pull_request.number || 0 }}
|
||||
dry_run: ${{ inputs.dry_run || false }}
|
||||
secrets: inherit
|
||||
version: ${{ inputs.version || '' }}
|
||||
release_commit: ${{ github.event.pull_request.merge_commit_sha || inputs.release_commit || '' }}
|
||||
|
||||
# Build the UI
|
||||
ui:
|
||||
if: inputs.build_items == '' || contains(inputs.build_items, 'ui')
|
||||
if: inputs.build_items == '' || contains(format(',{0},', inputs.build_items), ',ui,')
|
||||
needs: tagging
|
||||
uses: ./.github/workflows/build-ui.yml
|
||||
with:
|
||||
version: ${{ needs.tagging.outputs.version }}
|
||||
ref: ${{ needs.tagging.outputs.ref }}
|
||||
dry_run: ${{ inputs.dry_run || false }}
|
||||
secrets: inherit
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
|
||||
# Build the Docker image
|
||||
docker:
|
||||
if: inputs.build_items == '' || contains(inputs.build_items, 'docker')
|
||||
if: inputs.build_items == '' || contains(format(',{0},', inputs.build_items), ',docker,')
|
||||
needs: tagging
|
||||
uses: ./.github/workflows/build-docker.yml
|
||||
# Parallel build the multi-arch docker image
|
||||
strategy:
|
||||
matrix:
|
||||
# since docker push actions will override the previous images,
|
||||
# so need to build `linux/amd64` and push first,
|
||||
# then build `linux/amd64` twice with other archs (build very slow so keep it last).
|
||||
target: ["linux/amd64", "linux/amd64,linux/arm64,linux/arm/v7"]
|
||||
with:
|
||||
version: ${{ needs.tagging.outputs.version }}
|
||||
ref: ${{ needs.tagging.outputs.ref }}
|
||||
commit: ${{ needs.tagging.outputs.commit }}
|
||||
dry_run: ${{ inputs.dry_run || false }}
|
||||
target: ${{ matrix.target }}
|
||||
secrets: inherit
|
||||
permissions:
|
||||
contents: read
|
||||
secrets:
|
||||
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
# Build the binary app
|
||||
app:
|
||||
if: inputs.build_items == '' || contains(inputs.build_items, 'app')
|
||||
if: inputs.build_items == '' || contains(format(',{0},', inputs.build_items), ',app,')
|
||||
needs: tagging
|
||||
uses: ./.github/workflows/build-app.yml
|
||||
with:
|
||||
version: ${{ needs.tagging.outputs.version }}
|
||||
ref: ${{ needs.tagging.outputs.ref }}
|
||||
dry_run: ${{ inputs.dry_run || false }}
|
||||
secrets: inherit
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
# All builds are complete
|
||||
done:
|
||||
needs: [tagging, ui, docker, app]
|
||||
if: |
|
||||
always() &&
|
||||
needs.tagging.result == 'success' &&
|
||||
!contains(needs.*.result, 'failure') &&
|
||||
!contains(needs.*.result, 'cancelled')
|
||||
uses: ./.github/workflows/build-done.yml
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
with:
|
||||
version: ${{ needs.tagging.outputs.version }}
|
||||
ref: ${{ needs.tagging.outputs.ref }}
|
||||
issue_number: ${{ github.event.pull_request.number || 0 }}
|
||||
dry_run: ${{ inputs.dry_run || false }}
|
||||
secrets: inherit
|
||||
@@ -22,6 +22,13 @@ on:
|
||||
schedule:
|
||||
- cron: "40 17 * * 4"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: codeql-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
@@ -42,11 +49,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
uses: github/codeql-action/init@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -59,7 +68,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
uses: github/codeql-action/autobuild@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
@@ -72,6 +81,6 @@ jobs:
|
||||
# ./location_of_script_within_repo/buildscript.sh
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
uses: github/codeql-action/analyze@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
@@ -5,33 +5,42 @@ on:
|
||||
branches: ["master"]
|
||||
paths: ["docs/**"]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: deploy-docs-cn
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Use pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
with:
|
||||
version: 9.1.0
|
||||
version: 10.33.2
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build Docs
|
||||
run: pnpm build:docs
|
||||
|
||||
- name: Deploy
|
||||
uses: TencentCloud/cos-action@v1
|
||||
uses: TencentCloud/cos-action@5ee3807d5ff6fddadbbb1d3e658c0690863de8ec # v1
|
||||
with:
|
||||
secret_id: ${{ secrets.TENCENT_CLOUD_SECRET_ID }}
|
||||
secret_key: ${{ secrets.TENCENT_CLOUD_SECRET_KEY }}
|
||||
|
||||
+181
-26
@@ -21,64 +21,219 @@ on:
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: prepare-release
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
ref: master
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.x
|
||||
registry-url: https://registry.npmjs.org/
|
||||
|
||||
- name: Setup semver
|
||||
run: npm install -g semver
|
||||
persist-credentials: false
|
||||
|
||||
- name: Handle Version Number
|
||||
env:
|
||||
CUSTOM_VERSION: ${{ inputs.custom_version }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
SEMVER_INCREMENT: ${{ inputs.semver }}
|
||||
run: |
|
||||
PREV_VERSION="$(git describe --tags --abbrev=0 --match 'v*')"
|
||||
|
||||
if [ -n "${{ inputs.custom_version }}" ]; then
|
||||
NEXT_VERSION="${{ inputs.custom_version }}"
|
||||
else
|
||||
NEXT_VERSION="v$(semver --increment ${{ inputs.semver }} ${PREV_VERSION})"
|
||||
if [[ ! "$PREV_VERSION" =~ ^v([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
|
||||
echo "Invalid previous version: $PREV_VERSION"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "PREV_VERSION=${PREV_VERSION}" >> $GITHUB_ENV
|
||||
echo "VERSION=${NEXT_VERSION}" >> $GITHUB_ENV
|
||||
if ! git merge-base --is-ancestor "$PREV_VERSION" HEAD; then
|
||||
echo "Latest release tag $PREV_VERSION is not an ancestor of master."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CURRENT_APP_VERSION="$(
|
||||
sed -n 's/^var Version = "\(v[0-9][0-9.]*\)"$/\1/p' internal/config/version.go
|
||||
)"
|
||||
CURRENT_UI_VERSION="$(jq -r '.version' ui/artalk/package.json)"
|
||||
CURRENT_DOCS_VERSION="$(jq -r '.latest' docs/docs/code/ArtalkVersion.json)"
|
||||
CURRENT_PACKAGE_VERSION="${CURRENT_APP_VERSION#v}"
|
||||
|
||||
if [[ -z "$CURRENT_APP_VERSION" ||
|
||||
"$CURRENT_UI_VERSION" != "$CURRENT_PACKAGE_VERSION" ||
|
||||
"$CURRENT_DOCS_VERSION" != "$CURRENT_PACKAGE_VERSION" ]]; then
|
||||
echo "Release versions are inconsistent: app=$CURRENT_APP_VERSION ui=$CURRENT_UI_VERSION docs=$CURRENT_DOCS_VERSION."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$CURRENT_APP_VERSION" != "$PREV_VERSION" ]]; then
|
||||
echo "Source version $CURRENT_APP_VERSION does not match the latest release tag $PREV_VERSION."
|
||||
echo "If the release PR was already merged, recover it with Build and Publish instead of preparing the release again."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -n "$CUSTOM_VERSION" ]]; then
|
||||
NEXT_VERSION="$CUSTOM_VERSION"
|
||||
else
|
||||
major="${BASH_REMATCH[1]}"
|
||||
minor="${BASH_REMATCH[2]}"
|
||||
patch="${BASH_REMATCH[3]}"
|
||||
case "$SEMVER_INCREMENT" in
|
||||
major) NEXT_VERSION="v$((major + 1)).0.0" ;;
|
||||
minor) NEXT_VERSION="v${major}.$((minor + 1)).0" ;;
|
||||
patch) NEXT_VERSION="v${major}.${minor}.$((patch + 1))" ;;
|
||||
*) echo "Invalid version increment: $SEMVER_INCREMENT"; exit 1 ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
if [[ ! "$NEXT_VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "Invalid version: $NEXT_VERSION"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
version_is_greater() {
|
||||
local next_version="${1#v}"
|
||||
local previous_version="${2#v}"
|
||||
local -a next_parts previous_parts
|
||||
local index next_part previous_part
|
||||
|
||||
IFS='.' read -r -a next_parts <<< "$next_version"
|
||||
IFS='.' read -r -a previous_parts <<< "$previous_version"
|
||||
for index in 0 1 2; do
|
||||
next_part=$((10#${next_parts[$index]}))
|
||||
previous_part=$((10#${previous_parts[$index]}))
|
||||
if (( next_part > previous_part )); then
|
||||
return 0
|
||||
fi
|
||||
if (( next_part < previous_part )); then
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
if ! version_is_greater "$NEXT_VERSION" "$PREV_VERSION"; then
|
||||
echo "Release version $NEXT_VERSION must be greater than $PREV_VERSION."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if git show-ref --verify --quiet "refs/tags/$NEXT_VERSION"; then
|
||||
echo "Release tag $NEXT_VERSION already exists."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
release_branch="release/$NEXT_VERSION"
|
||||
existing_pr="$(
|
||||
gh pr list \
|
||||
--repo "$GITHUB_REPOSITORY" \
|
||||
--head "$release_branch" \
|
||||
--base master \
|
||||
--state open \
|
||||
--json number,url \
|
||||
--jq 'if length == 0 then "" else "#\(.[0].number) \(.[0].url)" end'
|
||||
)"
|
||||
if [[ -n "$existing_pr" ]]; then
|
||||
echo "Release pull request $existing_pr already exists for $NEXT_VERSION."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "PREV_VERSION=${PREV_VERSION}" >> "$GITHUB_ENV"
|
||||
echo "VERSION=${NEXT_VERSION}" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Print Next Version
|
||||
run: echo "${VERSION}"
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: false
|
||||
|
||||
- name: Setup git-chglog
|
||||
run: |
|
||||
curl -sL $(curl -s https://api.github.com/repos/git-chglog/git-chglog/releases/latest \
|
||||
| grep -oP '"https://.+linux_amd64.tar.gz"' | tr -d \") | tar -C /usr/local/bin -xz git-chglog
|
||||
git-chglog --version
|
||||
GOBIN="$RUNNER_TEMP/bin" go install github.com/git-chglog/git-chglog/cmd/git-chglog@v0.15.4
|
||||
echo "$RUNNER_TEMP/bin" >> "$GITHUB_PATH"
|
||||
go version -m "$RUNNER_TEMP/bin/git-chglog"
|
||||
|
||||
- name: Version Changelog
|
||||
run: |
|
||||
# prepend changelog
|
||||
changelog=$(git-chglog --config .github/chglog/config.yml --next-tag "${VERSION}" "${VERSION}")
|
||||
echo -e "${changelog}\n" | cat - CHANGELOG.md > temp && mv temp CHANGELOG.md
|
||||
changelog="$(git-chglog --config .github/chglog/config.yml --next-tag "$VERSION" "$VERSION")"
|
||||
temp_file="$(mktemp CHANGELOG.md.XXXXXX)"
|
||||
printf '%s\n\n' "$changelog" | cat - CHANGELOG.md > "$temp_file"
|
||||
mv "$temp_file" CHANGELOG.md
|
||||
|
||||
# modify the version of `package.json`
|
||||
perl -i -pe 's/"version": ".*?"/"version": "'${VERSION#v}'"/g' ui/artalk/package.json
|
||||
# modify the version for `internal/config/version.go`
|
||||
perl -i -pe 's/var Version = ".*?"/var Version = "$ENV{VERSION}"/g' internal/config/version.go
|
||||
|
||||
# modify the version for `package.json`
|
||||
PACKAGE_VERSION="${VERSION#v}"
|
||||
PACKAGE_VERSION="$PACKAGE_VERSION" perl -i -pe \
|
||||
's/"version": ".*?"/"version": "$ENV{PACKAGE_VERSION}"/g' ui/artalk/package.json
|
||||
|
||||
# modify the version in docs
|
||||
perl -pi -e 's#"latest"(\W+)?:(\W+)?".*?"#"latest": "'${VERSION#v}'"#g' docs/docs/code/ArtalkVersion.json
|
||||
PACKAGE_VERSION="$PACKAGE_VERSION" perl -pi -e \
|
||||
's#"latest"(\W+)?:(\W+)?".*?"#"latest": "$ENV{PACKAGE_VERSION}"#g' docs/docs/code/ArtalkVersion.json
|
||||
|
||||
- name: Validate Release Changes
|
||||
run: |
|
||||
PACKAGE_VERSION="${VERSION#v}"
|
||||
APP_VERSION="$(
|
||||
sed -n 's/^var Version = "\(v[0-9][0-9.]*\)"$/\1/p' internal/config/version.go
|
||||
)"
|
||||
UI_VERSION="$(jq -r '.version' ui/artalk/package.json)"
|
||||
DOCS_VERSION="$(jq -r '.latest' docs/docs/code/ArtalkVersion.json)"
|
||||
|
||||
if [[ "$APP_VERSION" != "$VERSION" ||
|
||||
"$UI_VERSION" != "$PACKAGE_VERSION" ||
|
||||
"$DOCS_VERSION" != "$PACKAGE_VERSION" ]]; then
|
||||
echo "Generated release versions are inconsistent: app=$APP_VERSION ui=$UI_VERSION docs=$DOCS_VERSION."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
changelog_entries="$(grep -cF "## [$VERSION]" CHANGELOG.md || true)"
|
||||
if [[ "$changelog_entries" != "1" ]]; then
|
||||
echo "Expected one CHANGELOG entry for $VERSION, found $changelog_entries."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
first_changelog_version="$(
|
||||
sed -n 's/^## \[\(v[0-9][^]]*\)\].*/\1/p' CHANGELOG.md | head -n 1
|
||||
)"
|
||||
if [[ "$first_changelog_version" != "$VERSION" ]]; then
|
||||
echo "The first CHANGELOG release is $first_changelog_version, expected $VERSION."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
expected_changes="$(
|
||||
printf '%s\n' \
|
||||
CHANGELOG.md \
|
||||
docs/docs/code/ArtalkVersion.json \
|
||||
internal/config/version.go \
|
||||
ui/artalk/package.json |
|
||||
sort
|
||||
)"
|
||||
actual_changes="$(git diff --name-only | sort)"
|
||||
if [[ "$actual_changes" != "$expected_changes" ]]; then
|
||||
echo "Unexpected release file set:"
|
||||
git diff --name-only
|
||||
exit 1
|
||||
fi
|
||||
|
||||
git diff --check
|
||||
|
||||
- name: Print Git Diff
|
||||
run: git diff
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||
if: ${{ !inputs.dry_run }}
|
||||
with:
|
||||
branch: "release/${{ env.VERSION }}"
|
||||
|
||||
@@ -10,11 +10,23 @@ on:
|
||||
required: true
|
||||
default: ""
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Validate event type
|
||||
env:
|
||||
EVENT_TYPE: ${{ inputs.event_type }}
|
||||
run: |
|
||||
if [[ ! "$EVENT_TYPE" =~ ^[A-Za-z0-9_.-]{1,100}$ ]]; then
|
||||
echo "Invalid repository dispatch event type."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Repository Dispatch
|
||||
uses: peter-evans/repository-dispatch@v3
|
||||
uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v4.0.1
|
||||
with:
|
||||
event-type: ${{ inputs.event_type }}
|
||||
@@ -0,0 +1,119 @@
|
||||
name: Test App
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- ".github/workflows/test-app.yml"
|
||||
- "cmd/**"
|
||||
- "internal/**"
|
||||
- "server/**"
|
||||
- "test/**"
|
||||
- "main.go"
|
||||
- "go.mod"
|
||||
- "go.sum"
|
||||
branches-ignore: ["release/*", "nightly"]
|
||||
tags-ignore: ["v*"]
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/test-app.yml"
|
||||
- "cmd/**"
|
||||
- "internal/**"
|
||||
- "server/**"
|
||||
- "test/**"
|
||||
- "main.go"
|
||||
- "go.mod"
|
||||
- "go.sum"
|
||||
branches-ignore: ["release/*", "nightly"]
|
||||
|
||||
env:
|
||||
REPORT_DIR: "/tmp/test-reports"
|
||||
CGO_ENABLED: 0
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: test-app-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test_app:
|
||||
name: Test Go (${{ matrix.name }})
|
||||
runs-on: ${{ matrix.runner }}
|
||||
timeout-minutes: 20
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- name: Linux AMD64
|
||||
runner: ubuntu-24.04
|
||||
artifact: linux-amd64
|
||||
coverage: true
|
||||
- name: Linux ARM64
|
||||
runner: ubuntu-24.04-arm
|
||||
artifact: linux-arm64
|
||||
coverage: false
|
||||
- name: macOS
|
||||
runner: macos-latest
|
||||
artifact: macos
|
||||
coverage: false
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: false # https://github.com/actions/setup-go/issues/483
|
||||
|
||||
- id: go-vars
|
||||
name: Get Go variables for cache
|
||||
run: |
|
||||
echo "GOCACHE=$(go env GOCACHE)" >> "$GITHUB_OUTPUT"
|
||||
echo "GOMODCACHE=$(go env GOMODCACHE)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Cache go modules
|
||||
# v6.1.0; this test-only cache never feeds a publishing workflow.
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # zizmor: ignore[cache-poisoning]
|
||||
with:
|
||||
path: |
|
||||
${{ steps.go-vars.outputs.GOCACHE }}
|
||||
${{ steps.go-vars.outputs.GOMODCACHE }}
|
||||
key: go-test-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('go.sum') }}
|
||||
restore-keys: |
|
||||
go-test-${{ runner.os }}-${{ runner.arch }}-
|
||||
|
||||
- name: Install tools
|
||||
run: go install gotest.tools/gotestsum@v1.13.0
|
||||
|
||||
- name: Download Go modules
|
||||
run: go mod download
|
||||
|
||||
- name: Run vet
|
||||
run: go vet ./...
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
mkdir -p "$REPORT_DIR"
|
||||
gotestsum \
|
||||
--junitfile "$REPORT_DIR/unit-tests.xml" \
|
||||
--jsonfile "$REPORT_DIR/unit-tests.json" \
|
||||
-- -coverprofile="$REPORT_DIR/coverage.out" ./...
|
||||
|
||||
- name: Upload test results
|
||||
if: ${{ !cancelled() }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: Go-Test-Results-${{ matrix.artifact }}
|
||||
path: ${{ env.REPORT_DIR }}
|
||||
|
||||
- if: ${{ matrix.coverage && !cancelled() }}
|
||||
name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||
with:
|
||||
flags: go
|
||||
file: ${{ env.REPORT_DIR }}/coverage.out
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
@@ -2,12 +2,19 @@ name: Test Docs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["*", "!release/*", "!nightly"]
|
||||
paths: ["docs/**"]
|
||||
branches-ignore: ["release/*", "nightly"]
|
||||
tags-ignore: ["v*"]
|
||||
pull_request:
|
||||
paths: ["docs/**"]
|
||||
branches: ["*", "!release/*", "!nightly"]
|
||||
branches-ignore: ["release/*", "nightly"]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: test-docs-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test_docs:
|
||||
@@ -15,21 +22,26 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Use pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
with:
|
||||
version: 9.1.0
|
||||
version: 10.33.2
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: 20.x
|
||||
cache: pnpm
|
||||
node-version: 22.x
|
||||
cache: pnpm # zizmor: ignore[cache-poisoning] test-only cache
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build Docs
|
||||
run: pnpm build:docs
|
||||
|
||||
- name: Lint Docs
|
||||
run: pnpm eslint docs && pnpm prettier --check docs
|
||||
@@ -0,0 +1,86 @@
|
||||
name: Test End-to-End
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- ".github/workflows/test-e2e.yml"
|
||||
- "cmd/**"
|
||||
- "conf/**"
|
||||
- "internal/**"
|
||||
- "server/**"
|
||||
- "ui/**"
|
||||
- "go.mod"
|
||||
- "go.sum"
|
||||
- "package.json"
|
||||
- "pnpm-lock.yaml"
|
||||
branches-ignore: ["release/*", "nightly"]
|
||||
tags-ignore: ["v*"]
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/test-e2e.yml"
|
||||
- "cmd/**"
|
||||
- "conf/**"
|
||||
- "internal/**"
|
||||
- "server/**"
|
||||
- "ui/**"
|
||||
- "go.mod"
|
||||
- "go.sum"
|
||||
- "package.json"
|
||||
- "pnpm-lock.yaml"
|
||||
branches-ignore: ["release/*", "nightly"]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: test-e2e-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CGO_ENABLED: 0
|
||||
|
||||
jobs:
|
||||
test_e2e:
|
||||
name: Test browser UAT flow
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: true # zizmor: ignore[cache-poisoning] test-only cache
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
with:
|
||||
version: 10.33.2
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: 22.x
|
||||
cache: pnpm # zizmor: ignore[cache-poisoning] test-only cache
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Install Chromium
|
||||
run: pnpm exec playwright install --with-deps chromium
|
||||
|
||||
- name: Run end-to-end tests
|
||||
run: pnpm -F artalk test:e2e
|
||||
|
||||
- name: Upload Playwright report
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: Playwright-Report
|
||||
path: ui/artalk/playwright-report
|
||||
if-no-files-found: ignore
|
||||
retention-days: 7
|
||||
@@ -2,35 +2,60 @@ name: Test Frontend
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["*", "!release/*", "!nightly"]
|
||||
paths: ["ui/**"]
|
||||
paths: [".github/workflows/test-frontend.yml", "ui/**", "package.json", "pnpm-lock.yaml"]
|
||||
branches-ignore: ["release/*", "nightly"]
|
||||
tags-ignore: ["v*"]
|
||||
pull_request:
|
||||
paths: ["ui/**"]
|
||||
branches: ["*", "!release/*", "!nightly"]
|
||||
paths: [".github/workflows/test-frontend.yml", "ui/**", "package.json", "pnpm-lock.yaml"]
|
||||
branches-ignore: ["release/*", "nightly"]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: test-frontend-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test_ui:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node: [18, 20]
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
include:
|
||||
- os: ubuntu-24.04
|
||||
node: 22
|
||||
quality: true
|
||||
- os: ubuntu-24.04
|
||||
node: 24
|
||||
quality: false
|
||||
- os: windows-latest
|
||||
node: 22
|
||||
quality: false
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 20
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
- if: matrix.os == 'windows-latest'
|
||||
name: Set git to use LF (for Windows environment)
|
||||
run: |
|
||||
git config --global core.autocrlf false
|
||||
git config --global core.eol lf
|
||||
|
||||
- uses: pnpm/action-setup@v3
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
version: 9.1.0
|
||||
persist-credentials: false
|
||||
|
||||
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
with:
|
||||
version: 10.33.2
|
||||
|
||||
- name: Use Node.js ${{ matrix.node }}
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
cache: pnpm
|
||||
cache: pnpm # zizmor: ignore[cache-poisoning] test-only cache
|
||||
|
||||
- name: Install Dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
@@ -38,10 +63,26 @@ jobs:
|
||||
- name: Package Build Test
|
||||
run: pnpm build:all
|
||||
|
||||
- name: Unit test
|
||||
- name: Unit Test
|
||||
run: pnpm test
|
||||
|
||||
- name: Plugins Build Test
|
||||
if: ${{ matrix.quality }}
|
||||
run: pnpm build:plugins
|
||||
|
||||
- name: Plugins Type Test
|
||||
if: ${{ matrix.quality }}
|
||||
run: pnpm test:plugins
|
||||
|
||||
- name: Plugins Package Test
|
||||
if: ${{ matrix.quality }}
|
||||
run: pnpm test:plugins:attw
|
||||
|
||||
- name: Lint
|
||||
if: ${{ matrix.quality }}
|
||||
run: pnpm eslint ui && pnpm prettier --check ui
|
||||
|
||||
# https://github.com/arethetypeswrong/arethetypeswrong.github.io
|
||||
- name: "arethetypeswrong test"
|
||||
if: runner.os == 'ubuntu-latest'
|
||||
run: pnpm -F artalk attw || true
|
||||
if: ${{ matrix.quality }}
|
||||
run: pnpm -F artalk test:attw
|
||||
@@ -0,0 +1,46 @@
|
||||
name: Test Workflows
|
||||
|
||||
on:
|
||||
push:
|
||||
paths: [".github/**"]
|
||||
branches-ignore: ["release/*", "nightly"]
|
||||
tags-ignore: ["v*"]
|
||||
pull_request:
|
||||
paths: [".github/**"]
|
||||
branches-ignore: ["release/*", "nightly"]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: test-workflows-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Validate Actions and security policy
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: false
|
||||
|
||||
- name: Validate workflow syntax
|
||||
run: go run github.com/rhysd/actionlint/cmd/actionlint@v1.7.12 -color
|
||||
|
||||
- name: Install zizmor
|
||||
run: |
|
||||
python3 -m venv "$RUNNER_TEMP/zizmor"
|
||||
"$RUNNER_TEMP/zizmor/bin/pip" install --disable-pip-version-check zizmor==1.28.0
|
||||
|
||||
- name: Audit workflow security
|
||||
run: |
|
||||
"$RUNNER_TEMP/zizmor/bin/zizmor" --format github .github
|
||||
@@ -49,6 +49,7 @@ yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
__debug*
|
||||
vite.config.ts.timestamp-*
|
||||
|
||||
# frontend
|
||||
node_modules
|
||||
@@ -68,3 +69,6 @@ node_modules
|
||||
|
||||
playwright-report
|
||||
test-results
|
||||
|
||||
# TypeScript composite build declaration output
|
||||
.tsbuildinfo-dts/
|
||||
+10
-28
@@ -6,17 +6,20 @@
|
||||
# https://goreleaser.com/customization/fury/
|
||||
# https://goreleaser.com/customization/announce/telegram/
|
||||
|
||||
version: 2
|
||||
|
||||
project_name: artalk
|
||||
|
||||
env:
|
||||
- GO111MODULE=on
|
||||
- CGO_ENABLED=1
|
||||
- CGO_ENABLED=0
|
||||
|
||||
before:
|
||||
hooks:
|
||||
# install dependencies
|
||||
- make install
|
||||
- make build-frontend
|
||||
- make pack-frontend
|
||||
|
||||
# build multi-platform
|
||||
builds:
|
||||
@@ -30,14 +33,9 @@ builds:
|
||||
- linux
|
||||
goarch:
|
||||
- amd64
|
||||
env:
|
||||
- CC=x86_64-linux-gnu-gcc
|
||||
- CXX=x86_64-linux-gnu-g++
|
||||
binary: "{{.ProjectName}}"
|
||||
main: ./main.go
|
||||
ldflags: &common_ldflags |
|
||||
-X github.com/ArtalkJS/Artalk/internal/config.Version={{.Version}}
|
||||
-X github.com/ArtalkJS/Artalk/internal/config.CommitHash={{ .ShortCommit }}
|
||||
-s -w
|
||||
|
||||
# Linux (arm_64)
|
||||
@@ -46,9 +44,6 @@ builds:
|
||||
- linux
|
||||
goarch:
|
||||
- arm64
|
||||
env:
|
||||
- CC=aarch64-linux-gnu-gcc
|
||||
- CXX=aarch64-linux-gnu-g++
|
||||
binary: "{{.ProjectName}}"
|
||||
main: ./main.go
|
||||
ldflags: *common_ldflags
|
||||
@@ -61,9 +56,6 @@ builds:
|
||||
- arm
|
||||
goarm:
|
||||
- "7"
|
||||
env:
|
||||
- CC=arm-linux-gnueabihf-gcc
|
||||
- CXX=arm-linux-gnueabihf-g++
|
||||
binary: "{{.ProjectName}}"
|
||||
main: ./main.go
|
||||
ldflags: *common_ldflags
|
||||
@@ -78,9 +70,6 @@ builds:
|
||||
- darwin
|
||||
goarch:
|
||||
- amd64
|
||||
env:
|
||||
- CC=o64-clang
|
||||
- CXX=o64-clang++
|
||||
binary: "{{.ProjectName}}"
|
||||
main: ./main.go
|
||||
ldflags: *common_ldflags
|
||||
@@ -91,9 +80,6 @@ builds:
|
||||
- darwin
|
||||
goarch:
|
||||
- arm64
|
||||
env:
|
||||
- CC=oa64-clang
|
||||
- CXX=oa64-clang++
|
||||
binary: "{{.ProjectName}}"
|
||||
main: ./main.go
|
||||
ldflags: *common_ldflags
|
||||
@@ -108,9 +94,6 @@ builds:
|
||||
- windows
|
||||
goarch:
|
||||
- amd64
|
||||
env:
|
||||
- CC=x86_64-w64-mingw32-gcc
|
||||
- CXX=x86_64-w64-mingw32-g++
|
||||
binary: "{{.ProjectName}}"
|
||||
main: ./main.go
|
||||
ldflags: *common_ldflags
|
||||
@@ -126,9 +109,6 @@ builds:
|
||||
- windows
|
||||
goarch:
|
||||
- arm64
|
||||
env:
|
||||
- CC=/llvm-mingw/bin/aarch64-w64-mingw32-gcc
|
||||
- CXX=/llvm-mingw/bin/aarch64-w64-mingw32-g++
|
||||
binary: "{{.ProjectName}}"
|
||||
main: ./main.go
|
||||
ldflags: *common_ldflags
|
||||
@@ -136,7 +116,7 @@ builds:
|
||||
|
||||
archives:
|
||||
- id: artalk
|
||||
builds:
|
||||
ids:
|
||||
- linux-amd64
|
||||
- darwin-amd64
|
||||
- windows-amd64
|
||||
@@ -145,10 +125,12 @@ archives:
|
||||
- linux-arm64
|
||||
- darwin-arm64
|
||||
name_template: "{{.ProjectName}}_v{{.Version}}_{{.Os}}_{{.Arch}}{{.Arm}}"
|
||||
format: tar.gz
|
||||
formats:
|
||||
- tar.gz
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
formats:
|
||||
- zip
|
||||
wrap_in_directory: true
|
||||
files:
|
||||
- README*
|
||||
@@ -159,7 +141,7 @@ checksum:
|
||||
name_template: "checksums.txt"
|
||||
|
||||
snapshot:
|
||||
name_template: "{{.Version}}-SNAPSHOT-{{.ShortCommit}}"
|
||||
version_template: "{{.Version}}-SNAPSHOT-{{.ShortCommit}}"
|
||||
|
||||
# changelog:
|
||||
# sort: asc
|
||||
|
||||
+5
-1
@@ -9,9 +9,13 @@ pnpm-lock.yaml
|
||||
**/auto-imports.d.ts
|
||||
**/components.d.ts
|
||||
**/typed-router.d.ts
|
||||
**/vite.config.ts.timestamp-*
|
||||
ui/artalk/src/api/v2.ts
|
||||
docs/swagger/swagger.json
|
||||
docs/swagger/swagger.yaml
|
||||
docs/**
|
||||
docs/docs/**/*.md
|
||||
docs/docs/.vitepress/dist/**
|
||||
docs/docs/.vitepress/cache/**
|
||||
ui/artalk-sidebar/src/lib/md5.js
|
||||
ui/artalk/src/lib/detect.ts
|
||||
**/dist/**
|
||||
Vendored
+5
-1
@@ -8,7 +8,11 @@
|
||||
"mode": "exec",
|
||||
"program": "${workspaceFolder}/bin/artalk",
|
||||
"preLaunchTask": "Build for debugging",
|
||||
"args": ["server", "-c", "${workspaceFolder}/data/artalk.yml"]
|
||||
"env": {
|
||||
"ATK_SITE_DEFAULT": "ArtalkDocs",
|
||||
"ATK_TRUSTED_DOMAINS": "http://localhost:5173 http://localhost:23367"
|
||||
},
|
||||
"args": ["server", "-c", "${workspaceFolder}/artalk.yml"]
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
+44
@@ -1,4 +1,48 @@
|
||||
{
|
||||
// Enable the ESLint flat config support (ESLint 9+ / extension v3+)
|
||||
"eslint.useFlatConfig": true,
|
||||
|
||||
// Disable the default formatter, use eslint instead
|
||||
// "prettier.enable": false,
|
||||
"editor.formatOnSave": false,
|
||||
|
||||
// Auto fix
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit",
|
||||
"source.organizeImports": "never"
|
||||
},
|
||||
|
||||
"eslint.runtime": "node",
|
||||
|
||||
// Silent the stylistic rules in you IDE, but still auto fix them
|
||||
"eslint.rules.customizations": [
|
||||
{ "rule": "style/*", "severity": "off" },
|
||||
{ "rule": "*-indent", "severity": "off" },
|
||||
{ "rule": "*-spacing", "severity": "off" },
|
||||
{ "rule": "*-spaces", "severity": "off" },
|
||||
{ "rule": "*-order", "severity": "off" },
|
||||
{ "rule": "*-dangle", "severity": "off" },
|
||||
{ "rule": "*-newline", "severity": "off" },
|
||||
{ "rule": "*quotes", "severity": "off" },
|
||||
{ "rule": "*semi", "severity": "off" }
|
||||
],
|
||||
|
||||
// Enable eslint for all supported languages
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
"typescript",
|
||||
"typescriptreact",
|
||||
"vue",
|
||||
"html",
|
||||
"markdown",
|
||||
"json",
|
||||
"jsonc",
|
||||
"yaml",
|
||||
"toml",
|
||||
"xml"
|
||||
],
|
||||
|
||||
"cSpell.words": [
|
||||
"akismet",
|
||||
"artalk",
|
||||
|
||||
+250
@@ -1,4 +1,254 @@
|
||||
|
||||
## [v2.10.0](https://github.com/ArtalkJS/Artalk/compare/v2.9.1...v2.10.0) (2026-07-23)
|
||||
|
||||
### Features
|
||||
|
||||
* add 'ugc' to link rel attributes ([#1120](https://github.com/ArtalkJS/Artalk/issues/1120))
|
||||
* **api/vote:** add get vote status endpoint ([#998](https://github.com/ArtalkJS/Artalk/issues/998))
|
||||
* **auth:** add SSO token exchange endpoint ([#1134](https://github.com/ArtalkJS/Artalk/issues/1134))
|
||||
* **config:** enhance config file and data dir retrieval ([#992](https://github.com/ArtalkJS/Artalk/issues/992))
|
||||
* **db:** add TLS support for MySQL with Google Cloud SQL ([#1104](https://github.com/ArtalkJS/Artalk/issues/1104))
|
||||
* **go:** upgrade golang to v1.26.2 and some deps
|
||||
* **go:** upgrade golang to v1.26.5
|
||||
* **plugins:** add plugin registry fetch script
|
||||
* **ui:** add dependency injection container ([#1006](https://github.com/ArtalkJS/Artalk/issues/1006))
|
||||
* **ui:** add npm published version check script
|
||||
* **ui/conf:** add `preferRemoteConf` option ([#1000](https://github.com/ArtalkJS/Artalk/issues/1000)) ([#1011](https://github.com/ArtalkJS/Artalk/issues/1011))
|
||||
* **ui/eslint:** add eslint plugin for artalk ui development ([#995](https://github.com/ArtalkJS/Artalk/issues/995))
|
||||
* **ui/eslint:** update `eslint-plugin-artalk` to v1.0.2 ([#1010](https://github.com/ArtalkJS/Artalk/issues/1010))
|
||||
* **ui/page_vote:** add page voting feature ([#983](https://github.com/ArtalkJS/Artalk/issues/983))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api:** validate vote status targets
|
||||
* **api:** make vote updates transactional
|
||||
* **api/stats:** exclude pending comments
|
||||
* **api/vote:** return not found for deleted targets
|
||||
* **auth:** require verified email for SSO exchange
|
||||
* **build:** propagate image version metadata
|
||||
* **cache:** return database results on store failure
|
||||
* **ci:** satisfy workflow shell checks
|
||||
* **ci:** harden release supply chain
|
||||
* **ci:** harden release tag handling
|
||||
* **ci:** preserve nightly tag during release refresh
|
||||
* **cmd:** preserve config lookup before data chdir
|
||||
* **config:** preserve keyword file separators
|
||||
* **core:** initialize process timezone once
|
||||
* **db:** enforce Cloud SQL TLS verification
|
||||
* **docs:** support React 19 useRef types
|
||||
* **email:** add Message-ID header
|
||||
* **scripts:** import Buffer explicitly
|
||||
* **scripts:** harden npm publish check
|
||||
* **ui:** preserve generated list response contract
|
||||
* **ui:** restore deprecated context services
|
||||
* **ui:** reject direct context replacement
|
||||
* **ui:** align plugin lifecycle with final config
|
||||
* **ui:** accept scoped comment lists without page data
|
||||
* **ui:** replace vulnerable HTML sanitizer
|
||||
* **ui/editor:** restore deprecated aliases
|
||||
* **ui/sidebar:** handle null array preferences
|
||||
* **ui/types:** preserve atomic config values
|
||||
* **ui/types:** restore deprecated public aliases
|
||||
* **ui/ua:** support OpenHarmony and more browsers ([#1087](https://github.com/ArtalkJS/Artalk/issues/1087))
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
* **api/vote:** refactor vote http endpoint and logic ([#997](https://github.com/ArtalkJS/Artalk/issues/997))
|
||||
* **auth:** migrate JWT handling to v5
|
||||
* **ci:** accelerate nightly publishing
|
||||
* **ci:** optimize multi-architecture releases
|
||||
* **ui:** improve modularity, lifecycle, and types ([#1007](https://github.com/ArtalkJS/Artalk/issues/1007))
|
||||
* **ui/auth:** ensure compatibility with new client types ([#1009](https://github.com/ArtalkJS/Artalk/issues/1009))
|
||||
* **ui/comment:** update reply button to use component
|
||||
* **ui/conf:** introduce `DeepPartial` type for ui config ([#996](https://github.com/ArtalkJS/Artalk/issues/996))
|
||||
* **ui/sidebar:** ensure compatibility with new client types ([#1008](https://github.com/ArtalkJS/Artalk/issues/1008))
|
||||
|
||||
### Testing
|
||||
|
||||
* remove races from async tests
|
||||
* **ci:** expand release validation
|
||||
* **server:** verify plugin URL trust filtering
|
||||
* **ui:** update comment e2e flow
|
||||
* **ui:** isolate Playwright end-to-end specs
|
||||
* **ui:** cover Marked 18 integration
|
||||
* **ui:** cover sanitizer attribute allowlist
|
||||
* **ui:** isolate browser storage from Node
|
||||
|
||||
### Documentation
|
||||
|
||||
* document dependency and security updates
|
||||
* add v2.10.0 release notes
|
||||
* add Japanese README ([#1145](https://github.com/ArtalkJS/Artalk/issues/1145))
|
||||
* integrate git changelog plugin
|
||||
* **i18n:** add Turkish (tr-TR) language support ([#1119](https://github.com/ArtalkJS/Artalk/issues/1119))
|
||||
* **i18n:** add `zh-TW` translation for config template
|
||||
* **landing:** redesign landing page ([#1020](https://github.com/ArtalkJS/Artalk/issues/1020))
|
||||
* **landing:** add more translations
|
||||
* **landing:** optimize responsive font size
|
||||
|
||||
### BREAKING CHANGE
|
||||
|
||||
|
||||
Beyond [#997](https://github.com/ArtalkJS/Artalk/issues/997), [#1007](https://github.com/ArtalkJS/Artalk/issues/1007), and [#1011](https://github.com/ArtalkJS/Artalk/issues/1011), v2.10.0 changes default config/data discovery and relative-path semantics; raises source builds to Go 1.26.5; replaces the CGO-backed SQLite driver with a pure-Go implementation; fixes application timezone at process initialization, requiring a full restart to change it; removes the useBackendConf opt-out and remoteConfModifier; changes DI registration, Context mutation, update() return value, and created timing; and requires marked 18 with modern package resolution. Migration guide: https://artalk.js.org/en/guide/releases/v2.10.0.html
|
||||
|
||||
UI client now prioritizes local config in the latest version, whereas previously, the remote config would override it. (See [#1011](https://github.com/ArtalkJS/Artalk/issues/1011))
|
||||
|
||||
This update changes the UI client's export Types. Although we aim to maintain backward compatibility, it may still affect downstream UI programs. Please refer to the [TypeDoc](https://artalk.js.org/typedoc/).
|
||||
|
||||
Vote HTTP API endpoint have been updated. (See [#997](https://github.com/ArtalkJS/Artalk/issues/997))
|
||||
|
||||
|
||||
## [v2.9.1](https://github.com/ArtalkJS/Artalk/compare/v2.9.0...v2.9.1) (2024-09-18)
|
||||
|
||||
### Features
|
||||
|
||||
* **go:** upgrade golang to v1.22.7 and some deps
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **db:** sql error with custom table prefix ([#970](https://github.com/ArtalkJS/Artalk/issues/970))
|
||||
* **test:** separate test db to isolate concurrent test instances ([#975](https://github.com/ArtalkJS/Artalk/issues/975))
|
||||
* **transfer:** ensure consistent time format in artrans export
|
||||
* **ui/plugin:** prevent exceptions from blocking other plugins loading
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **plugin_katex:** add error message for missing katex deps
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
* **dao:** improve concurrent testing for `FindCreateAction` function
|
||||
* **go:** hardcode version and get commit_hash via runtime/debug
|
||||
* **go:** bump the module version and support go install ([#979](https://github.com/ArtalkJS/Artalk/issues/979))
|
||||
|
||||
### Testing
|
||||
|
||||
* **mutex:** add tests for keyed mutex
|
||||
|
||||
### Documentation
|
||||
|
||||
* refine deployment documentation
|
||||
* **i18n:** add English translations for docs ([#978](https://github.com/ArtalkJS/Artalk/issues/978))
|
||||
* **landing:** add multi-language and dark-mode for landing
|
||||
* **landing:** add demo video in landing page
|
||||
|
||||
### BREAKING CHANGE
|
||||
|
||||
|
||||
The original Go module name was `github.com/ArtalkJS/Artalk`, which has now been modified to `github.com/artalkjs/artalk/v2` (all lowercase). This change may affect the CI pipeline or other areas if you are compiling the Artalk server and have specified the fixed Go module name. Regarding the original `-ldflags -X github.com/ArtalkJS/Artalk/internal/config.Version` and `CommitHash` definitions, please remove them, as they are now obsolete. The `Version` has been hardcoded into the Go code, and the `CommitHash` is now read from the Go build info via runtime/debug’s vcs.revision value. This change only affects the server developers and does not impact the frontend UI client.
|
||||
|
||||
|
||||
## [v2.9.0](https://github.com/ArtalkJS/Artalk/compare/v2.8.7...v2.9.0) (2024-09-03)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add user profile and password changing ([#966](https://github.com/ArtalkJS/Artalk/issues/966))
|
||||
* **config:** add `site_url` config option ([#967](https://github.com/ArtalkJS/Artalk/issues/967))
|
||||
* **go:** upgrade golang to v1.22.6 and some deps
|
||||
* **ui/dashboard:** new dashboard ui refresh for desktop devices ([#957](https://github.com/ArtalkJS/Artalk/issues/957))
|
||||
* **ui/date:** add `dateFormatter` config option ([#940](https://github.com/ArtalkJS/Artalk/issues/940))
|
||||
* **ui/plugin:** make ui plugin configurable
|
||||
* **ui/sidebar:** add dark mode toggle for dashboard ([#790](https://github.com/ArtalkJS/Artalk/issues/790))
|
||||
* **ui/sidebar:** add page search by keywords in sidebar ([#603](https://github.com/ArtalkJS/Artalk/issues/603))
|
||||
* **ui/sidebar:** add user search by keywords in sidebar ([#710](https://github.com/ArtalkJS/Artalk/issues/710))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **anti_spam:** handle empty `referer` variable in anti-spam check ([#853](https://github.com/ArtalkJS/Artalk/issues/853))
|
||||
* **api/auth:** fix missing Microsoft oauth provider configuration ([#929](https://github.com/ArtalkJS/Artalk/issues/929))
|
||||
* **api/auth:** migrate auth_identities when account merging ([#920](https://github.com/ArtalkJS/Artalk/issues/920))
|
||||
* **api/auth:** register issue due to unremoved auth records after user deletion ([#919](https://github.com/ArtalkJS/Artalk/issues/919))
|
||||
* **api/comment_add:** improve the security of comment add api ([#964](https://github.com/ArtalkJS/Artalk/issues/964))
|
||||
* **auth:** correct email verification code send button
|
||||
* **plugin/lightbox:** fix import for non-browser env ([#931](https://github.com/ArtalkJS/Artalk/issues/931))
|
||||
* **plugin_kit:** fix typescript lib path retrieval on win
|
||||
* **plugin_kit:** import source path case error on linux
|
||||
* **sidebar:** correct total item count for pagination in sidebar
|
||||
* **ui/dark_mode:** apply dark mode config option before mounting ui ([#890](https://github.com/ArtalkJS/Artalk/issues/890))
|
||||
* **ui/plugin-kit:** fix typescript lib folder for api-extractor in monorepo
|
||||
* **ui/sidebar:** disable admin password check dialog in sidebar
|
||||
* **ui/sidebar:** show env variables in sidebar settings ui ([#946](https://github.com/ArtalkJS/Artalk/issues/946)) ([#956](https://github.com/ArtalkJS/Artalk/issues/956))
|
||||
* **ui/stat:** fix invalid backward compatible strategy ([#880](https://github.com/ArtalkJS/Artalk/issues/880))
|
||||
* **validator:** restrict to allow only HTTP protocol
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **api/auth:** add more specific error messages for login failures
|
||||
* **app/init:** generate translated config file by env variable `ATK_LOCALE`
|
||||
* **auth:** add loading skeleton for login methods list
|
||||
* **auth:** enhance visibility of skip button ([#863](https://github.com/ArtalkJS/Artalk/issues/863))
|
||||
* **auth:** perform optical adjustment for consistent icon sizing
|
||||
* **auth:** correct verify button position in register form
|
||||
* **i18n:** auto-load frontend i18n locale js sources
|
||||
* **notify:** improve error messages for notify ([#943](https://github.com/ArtalkJS/Artalk/issues/943))
|
||||
* **plugin_kit:** improve typescript diagnostics output
|
||||
* **style/sidebar:** modify font-family for sidebar
|
||||
* **ui/sidebar:** enhance i18n translations for sidebar
|
||||
* **ui/sidebar:** mask sensitive input values in sidebar settings ([#494](https://github.com/ArtalkJS/Artalk/issues/494))
|
||||
* **ui/sidebar:** optimize setup process and i18n lazy load ([#962](https://github.com/ArtalkJS/Artalk/issues/962))
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
* **plugin_katex:** use tokenizer extension of marked ([#955](https://github.com/ArtalkJS/Artalk/issues/955))
|
||||
* **ui/deps:** remove `abortcontroller-polyfill` ([#923](https://github.com/ArtalkJS/Artalk/issues/923))
|
||||
* **ui/marked:** replace pkg-level marked with new class instance ([#954](https://github.com/ArtalkJS/Artalk/issues/954))
|
||||
* **ui/types:** add `Layer` interface
|
||||
* **ui/user:** rename `nick` to `name` in `LocalUser` data structure ([#961](https://github.com/ArtalkJS/Artalk/issues/961))
|
||||
|
||||
### Documentation
|
||||
|
||||
* **auth:** set `auth.anonymous` to false as default
|
||||
* **ui/i18n:** add more locale support for sidebar
|
||||
* **ui/i18n:** add translations for French, Korean, and Russian
|
||||
|
||||
|
||||
## [v2.8.7](https://github.com/ArtalkJS/Artalk/compare/v2.8.6...v2.8.7) (2024-06-09)
|
||||
|
||||
### Features
|
||||
|
||||
* **go:** upgrade golang to v1.22.3 and some deps
|
||||
* **plugin_kit:** add vite plugin for developing artalk plugins ([#904](https://github.com/ArtalkJS/Artalk/issues/904))
|
||||
* **sync:** add keyed rw-mutex
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api/auth:** include render mustache params for verification email subject ([#910](https://github.com/ArtalkJS/Artalk/issues/910))
|
||||
* **api/stats:** fix `site_pv` response body and add unit tests ([#896](https://github.com/ArtalkJS/Artalk/issues/896))
|
||||
* **api/transfer_upload:** close temp file in transfer upload endpoint ([#877](https://github.com/ArtalkJS/Artalk/issues/877))
|
||||
* **auth:** failed to register by social account on mysql database ([#902](https://github.com/ArtalkJS/Artalk/issues/902))
|
||||
* **conf_env:** enhance environment variable loading for config ([#892](https://github.com/ArtalkJS/Artalk/issues/892))
|
||||
* **plugin_katex:** fix event issue causing katex integration failure ([#897](https://github.com/ArtalkJS/Artalk/issues/897))
|
||||
* **ui/height_limit:** height limit issue for collapsed comments ([#865](https://github.com/ArtalkJS/Artalk/issues/865))
|
||||
* **ui/sidebar:** unable to logout if user is not admin in control center
|
||||
* **ui/types:** remove marked dependency in dts ([#868](https://github.com/ArtalkJS/Artalk/issues/868))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **conf:** add cache for config metadata to boost startup speed ([#892](https://github.com/ArtalkJS/Artalk/issues/892))
|
||||
* **dao:** add singleflight for no-cache db find func
|
||||
* **gravatar:** support both sha256 and md5 hash for gravatar ([#912](https://github.com/ArtalkJS/Artalk/issues/912))
|
||||
* **gravatar:** convert email to lowercase before hashing for gravatar
|
||||
* **gravatar:** use sha256 hashing algorithm for gravatar ([#874](https://github.com/ArtalkJS/Artalk/issues/874))
|
||||
* **ui/conf:** exclude conf `pvEl`, `countEl` and `statPageKeyAttr` from remove server ([#905](https://github.com/ArtalkJS/Artalk/issues/905))
|
||||
* **ui/dev:** bump eslint to v9 ([#915](https://github.com/ArtalkJS/Artalk/issues/915))
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
* **dao:** add `date` field to the page json entity
|
||||
* **dao:** improve `QueryDBWithCache` func and concurrent unit tests ([#884](https://github.com/ArtalkJS/Artalk/issues/884))
|
||||
* **transfer:** improve the reliability of the transfer ([#883](https://github.com/ArtalkJS/Artalk/issues/883))
|
||||
* **transfer:** strip domain from page key by default ([#873](https://github.com/ArtalkJS/Artalk/issues/873))
|
||||
* **ui:** fix linter issues for ui and docs ([#917](https://github.com/ArtalkJS/Artalk/issues/917))
|
||||
* **ui/stat:** use class selector for pageview elements by default ([#880](https://github.com/ArtalkJS/Artalk/issues/880))
|
||||
|
||||
### Documentation
|
||||
|
||||
* refine documentation
|
||||
* **dev:** refine contributing.md and translate to chinese
|
||||
* **i18n:** revise French translation ([#913](https://github.com/ArtalkJS/Artalk/issues/913))
|
||||
* **ip_region:** add instructions for obtaining accurate IP under proxy
|
||||
|
||||
|
||||
## [v2.8.6](https://github.com/ArtalkJS/Artalk/compare/v2.8.5...v2.8.6) (2024-05-09)
|
||||
|
||||
### Features
|
||||
|
||||
+297
-87
@@ -1,127 +1,337 @@
|
||||
# Developer Contributing Guide
|
||||
|
||||
This guide is for developers who want to contribute to the project.
|
||||
This guide helps developers understand how to contribute code, documentation, tests, and feedback to the Artalk project. We welcome all forms of contributions, whether it's large feature development, small fixes, documentation improvements, or testing. We believe that every contribution is an important part of moving the project forward.
|
||||
|
||||
## Development Environment
|
||||
This guide is available in multiple languages: [English](https://github.com/ArtalkJS/Artalk/blob/master/CONTRIBUTING.md) • [简体中文](https://artalk.js.org/develop/contributing.html)
|
||||
|
||||
To develop Artalk, including the frontend and backend, you need to install the following tools:
|
||||
## Setup
|
||||
|
||||
- [Node.js](https://nodejs.org/en/) (>= 20.12.2)
|
||||
- [PNPM](https://pnpm.io/) (>= 9.1.0)
|
||||
- [Go](https://golang.org/) (>= 1.22)
|
||||
- [Docker](https://www.docker.com/) (>= 20.10.0) (optional)
|
||||
- [Docker Compose](https://docs.docker.com/compose/) (>= 1.29.0) (optional)
|
||||
|
||||
Then we'll setup the development environment.
|
||||
|
||||
### Get the source code
|
||||
|
||||
Run the following command to clone the repository:
|
||||
Clone the repository:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/ArtalkJS/Artalk
|
||||
git clone https://github.com/ArtalkJS/Artalk.git
|
||||
```
|
||||
|
||||
It is recommended to fork the repository first, and then clone the forked repository.
|
||||
It's recommended to fork the repository first, and then clone the forked repository.
|
||||
|
||||
Enter the directory:
|
||||
Navigate to the directory:
|
||||
|
||||
```sh
|
||||
cd Artalk
|
||||
```
|
||||
|
||||
### Build Backend
|
||||
## Development Environment
|
||||
|
||||
First, you need to install the dependencies for the backend written in Go. Simply run the `make install` command to install the dependencies.
|
||||
To develop Artalk, both frontend and backend, install the following tools:
|
||||
|
||||
Then, run the `make dev` to build and run `./bin/artalk`, and you can pass startup parameters to the program using `ARGS="version" make dev`.
|
||||
- [Node.js](https://nodejs.org/en/) (>= 22.19.0)
|
||||
- [PNPM](https://pnpm.io/) (>= 10.33.2)
|
||||
- [Go](https://golang.org/) (>= 1.26)
|
||||
- [Docker](https://www.docker.com/) (>= 20.10.0) (optional)
|
||||
- [Docker Compose](https://docs.docker.com/compose/) (>= 1.29.0) (optional)
|
||||
|
||||
This will build the backend with debugging symbols. The binary file will be placed under the `./bin` directory.
|
||||
## Development Workflow Overview
|
||||
|
||||
The backend program will run by default on port `23366`. You can access it through a browser at `http://localhost:23366`. It's recommended not to change this port number for testing the backend program.
|
||||
The development workflow consists of the following steps:
|
||||
|
||||
### Build Frontend
|
||||
1. **Set Up Complete Development Instance**:
|
||||
- Navigate to the Artalk Repo directory.
|
||||
- Execute `make dev` to run the backend on port `23366`.
|
||||
- Execute `pnpm dev` to run the frontend on port `5173`.
|
||||
- Optionally, execute `pnpm dev:sidebar` to run the sidebar frontend on port `23367`.
|
||||
|
||||
First, you need to install the dependencies for the frontend. Simply run the `pnpm install` command to install the dependencies.
|
||||
2. **Access Frontend for Development**:
|
||||
- Open your browser and go to `http://localhost:5173` to perform your development and testing.
|
||||
|
||||
Then, run the `pnpm dev` to build and run the frontend, and you can pass startup parameters to the program using `ARGS="--port 5173" pnpm dev`.
|
||||
3. **Testing**:
|
||||
- Run the backend unit tests using `make test`.
|
||||
- Run the ui unit tests using `pnpm test`.
|
||||
- Run the ui E2E tests using `make test-frontend-e2e`.
|
||||
|
||||
The frontend program will run by default on port `5173`, and you can access it in a browser at `http://localhost:5173`. The frontend testing client will, by default, request the backend on port `23366`, so it's essential to keep the backend on this port.
|
||||
4. **Building Frontend Code**:
|
||||
- When you make changes to the frontend code, build the complete frontend program using `pnpm build:all`.
|
||||
- The JavaScript and CSS code will be found in `ui/artalk/dist`.
|
||||
- The frontend code is automatically deployed to NPM by GitHub Actions.
|
||||
|
||||
The frontend program is divided into the main program and a sidebar program, with the sidebar program running on a separate port, which is `23367`.
|
||||
5. **Building Backend Code**:
|
||||
- When you make changes to the backend code, note that the backend program embeds the frontend code.
|
||||
- Before building the backend, run `make build-frontend` (which runs `scripts/build-frontend.sh`). This will place the embedded frontend main program and sidebar frontend program in `/public`.
|
||||
- Then, build the backend program using `make build`.
|
||||
|
||||
### Development Workflow
|
||||
More exploration:
|
||||
|
||||
In most cases, to set up a complete development instance, you need to navigate to the Artalk Repo directory and then execute `make dev`. This will run the backend on port `23366`. Then, execute `pnpm dev`, which will run the frontend on port `5173`. You can optionally execute `pnpm dev:sidebar` to run the sidebar frontend on port `23367`. For frontend development, you need to access `http://localhost:5173` in your browser to perform your development and testing.
|
||||
- **Developing Artalk Plugins or Themes**:
|
||||
Refer to the [Artalk Plugin Development Guide](https://artalk.js.org/develop/plugin.html).
|
||||
|
||||
When you make changes to the frontend code, you can build the complete frontend program using `pnpm build:all`. The JavaScript and CSS code can be found in `ui/artalk/dist`.
|
||||
- **Makefile**:
|
||||
You can explore the process and commands in the `Makefile`.
|
||||
|
||||
When you make changes to the backend code, running `make all` will build the complete backend program. Note that since the backend program also embeds the frontend code, the `scripts/build-frontend.sh` script will run during backend program building, which includes the embedded frontend main program and sidebar frontend program. If you are interested, you can explore the complete frontend build process in the `Makefile` code.
|
||||
- **Automated CI on GitHub**:
|
||||
The automated CI pipeline on GitHub, located in the `.github/workflows` directory, builds the frontend, backend, and Docker images. It publishes Docker images to Docker Hub, handles npm package publishing, and creates GitHub Releases. And also testing the frontend and backend.
|
||||
|
||||
Additionally, there is automated CI on GitHub for building. You can find the relevant code in the `.github/workflows` directory.
|
||||
|
||||
### Optional: Use a One-Key Script to Run a Demo Site
|
||||
|
||||
When you access `http://localhost:5173` during frontend development, you will get a minimalist interface that only contains the Artalk program interface and not a real blog environment. If you want to make testing closer to a real environment, you can create a demo blog site using the following steps.
|
||||
|
||||
Run the following command:
|
||||
|
||||
```sh
|
||||
./scripts/setup-example-site.sh
|
||||
```
|
||||
|
||||
This script sets up a local example site for testing at `data` folder, with Artalk integrated into its theme.
|
||||
|
||||
After running this script, run:
|
||||
|
||||
```sh
|
||||
./bin/artalk server -c ./data/artalk.yml
|
||||
```
|
||||
|
||||
to start the artalk server.
|
||||
|
||||
And open <http://localhost:1313/> in your browser to view the example site.
|
||||
|
||||
Here is the default admin account (only created in test mode):
|
||||
|
||||
```yaml
|
||||
name: 'admin'
|
||||
email: 'admin@test.com'
|
||||
password: 'admin'
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
The backend Go program can be tested by running `make test` for unit testing. The test results will be outputted in the terminal. You can also execute `test-coverage` to check the code test coverage.
|
||||
|
||||
Frontend testing is conducted using Playwright for end-to-end (E2E) testing. To start the E2E testing, run `make test-frontend-e2e`.
|
||||
|
||||
Both frontend and backend testing are automated and will be performed during Git pull requests and as part of the build process using CircleCI or GitHub Actions.
|
||||
The following sections provide more detailed information on the development workflow.
|
||||
|
||||
## Project Structure
|
||||
|
||||
Artalk is a monorepo project, which means all the source code is in the same repository. However, the frontend and backend are separated. The frontend part is located in `./ui` directory.
|
||||
Artalk is a monorepo project, meaning all the source code resides in a single repository. Despite this, the frontend and backend are kept separate. The frontend part is located in the `./ui` directory.
|
||||
|
||||
- `bin/` - The compiled binary files. This directory is ignored by git.
|
||||
- `cmd/` - The source code for the command line tools.
|
||||
- `conf/` - The sample configuration files.
|
||||
- `docs/` - The documentation site source code.
|
||||
- `i18n/` - The translation files.
|
||||
- `internal/` - The internal packages.
|
||||
- `data/` - The local data. This directory is ignored by git.
|
||||
- `public/` - The static files. Built frontend files will be copied here.
|
||||
- `scripts/` - The scripts for development.
|
||||
- `server/` - The source code for the server.
|
||||
- `ui/` - The source code for the frontend.
|
||||
### Directory Overview
|
||||
|
||||
## Translation
|
||||
- `bin/`: The compiled binary files. (This directory is ignored by git).
|
||||
- `cmd/`: The source code for the command line tools.
|
||||
- `conf/`: The sample configuration files.
|
||||
- `data/`: The local data. (This directory is ignored by git).
|
||||
- `docs/`: The documentation site source code.
|
||||
- `i18n/`: The translation files.
|
||||
- `local/`: The local files. (This directory is ignored by git).
|
||||
- `internal/`: The Go source code for the internal packages.
|
||||
- `public/`: The static files. Built frontend files will be copied here.
|
||||
- `scripts/`: The scripts for development and building.
|
||||
- `server/`: The Go source code for the backend.
|
||||
- `ui/`: The UI source code for the frontend.
|
||||
- `.github/`: The GitHub Actions workflows.
|
||||
- `.vscode/`: The VSCode settings.
|
||||
|
||||
Artalk aims to be a multilingual project. If you would like to contribute to the translation, here are some tips:
|
||||
## Backend Development
|
||||
|
||||
If you just write some new features or do some fixes/refactoring, use the following command to generate the translation template
|
||||
### Run Backend
|
||||
|
||||
1. **Create Configuration File**:
|
||||
- Copy `./conf/artalk.example.yml` to the root directory.
|
||||
- Rename it to `artalk.yml`.
|
||||
- Modify the file as needed.
|
||||
|
||||
2. **Start Backend Program**:
|
||||
- Run `make dev` to start the backend on port `23366`.
|
||||
- Access it via `http://localhost:23366`.
|
||||
- It's recommended to keep the default port for testing.
|
||||
- (Alternative) Use `ARGS="version" make dev` to pass startup parameters (default is `ARGS="server"`).
|
||||
|
||||
The `make dev` builds the backend with debugging symbols, which is convenient for debugging with GDB.
|
||||
|
||||
If you are using the VSCode, you can use the `F5` key (or RUN button) to start debugging the backend program directly.
|
||||
|
||||
### Build Backend
|
||||
|
||||
1. **Fetch Latest Git Tag**:
|
||||
Execute `git fetch --tags` to get the latest git tag information. This tag will be used as the version number of the backend app. Alternatively, specify it using environment variables: `VERSION="v1.0.0"` and `COMMIT_HASH="66128e"`.
|
||||
|
||||
2. **Build Frontend**:
|
||||
Run `make build-frontend` to build the frontend, as the backend app embeds the frontend code.
|
||||
|
||||
3. **Build Backend**:
|
||||
Execute `make build` to build the backend program.
|
||||
|
||||
The built binary file will be located in the `./bin` directory.
|
||||
|
||||
## Frontend Development (UI)
|
||||
|
||||
### Run Frontend
|
||||
|
||||
1. **Install Dependencies**:
|
||||
Run `pnpm install` to install all necessary dependencies.
|
||||
|
||||
2. **Start Development Server**:
|
||||
Execute `pnpm dev --port 5173` to start the frontend development server.
|
||||
|
||||
3. **Access the Frontend**:
|
||||
The frontend application will run on port `5173` by default. Access it in your browser at `http://localhost:5173`.
|
||||
|
||||
4. **Sidebar Development**:
|
||||
To run the sidebar part of the frontend, use `pnpm dev:sidebar`.
|
||||
|
||||
The ports `5173` and `23367` are used for frontend development and testing. These ports are included in the `ATK_TRUSTED_DOMAINS` configuration of the backend program.
|
||||
|
||||
### Build Frontend
|
||||
|
||||
To build the frontend, run the following command:
|
||||
|
||||
```sh
|
||||
go run ./internal/i18n/gen -w . -d .
|
||||
make build-frontend
|
||||
```
|
||||
|
||||
If you're not a programmer and would like to help us improve the translation, you can edit the translation files directly in the `.i18n' directory and then submit a pull request.
|
||||
The compiled JavaScript and CSS files will be located in the `/public` directory, embedded into the backend program.
|
||||
|
||||
For more details, refer to the `scripts/build-frontend.sh` script.
|
||||
|
||||
### Publish Frontend
|
||||
|
||||
The core artalk client code is automatically deployed to NPM by GitHub Actions. The deployment process is defined in the `.github/workflows/build-ui.yml` file.
|
||||
|
||||
If you are writing a artalk plugin in monorepo, it would not be automatically deployed. You should publish it manually. To publish the plugin, first ensure your version number is updated in the `package.json` file. Then, run the following command:
|
||||
|
||||
```sh
|
||||
pnpm publish --access public
|
||||
```
|
||||
|
||||
#### Check Published Versions
|
||||
|
||||
The `pnpm check:publish` script is designed to verify that all packages in the project have the latest versions published on npm. It skips private packages automatically and allows filtering to check specific packages using the `-F` option.
|
||||
|
||||
Check all packages:
|
||||
|
||||
```sh
|
||||
pnpm check:publish
|
||||
```
|
||||
|
||||
This will initiate the process of checking all public packages in the repository to ensure they are up to date on npm.
|
||||
|
||||
### Develop and Debug `@artalk/plugin-kit` and `eslint-plugin-artalk`
|
||||
|
||||
The monorepo will default to installing the latest version of `@artalk/plugin-kit` and `eslint-plugin-artalk` from NPM published packages. To develop and debug these packages, run the following commands from the root of the repository:
|
||||
|
||||
```sh
|
||||
pnpm link --global --dir ui/eslint-plugin-artalk
|
||||
pnpm link --global --dir ui/plugin-kit
|
||||
|
||||
pnpm link eslint-plugin-artalk
|
||||
pnpm link @artalk/plugin-kit
|
||||
```
|
||||
|
||||
After executing these commands, the `eslint-plugin-artalk` and `@artalk/plugin-kit` packages will be linked to the local development environment within the monorepo workspace. You can modify the source code located in the `ui/eslint-plugin-artalk` and `ui/plugin-kit` directories, and the changes will be automatically reflected in the monorepo workspace. If you wish to unlink the packages, run the following commands:
|
||||
|
||||
```sh
|
||||
pnpm unlink eslint-plugin-artalk
|
||||
pnpm unlink @artalk/plugin-kit
|
||||
|
||||
pnpm uninstall --global eslint-plugin-artalk
|
||||
pnpm uninstall --global @artalk/plugin-kit
|
||||
```
|
||||
|
||||
For more details, refer to the [pnpm link documentation](https://pnpm.io/cli/link).
|
||||
|
||||
## Docker Development
|
||||
|
||||
### Build Docker Image
|
||||
|
||||
To build the Docker image, run the following command:
|
||||
|
||||
```sh
|
||||
docker build -t artalk:TAG .
|
||||
```
|
||||
|
||||
Replace `TAG` with the desired tag name (e.g., `latest`).
|
||||
|
||||
### Skip Frontend Build
|
||||
|
||||
If you have already built the frontend outside the Docker container, you can skip the frontend build process inside the container to speed up the build. Use the following command:
|
||||
|
||||
```sh
|
||||
docker build --build-arg SKIP_UI_BUILD=true -t artalk:latest .
|
||||
```
|
||||
|
||||
For more details, refer to the `Dockerfile`.
|
||||
|
||||
## Testing
|
||||
|
||||
### Testing the Backend
|
||||
|
||||
To test the backend Go program, you can use the following commands:
|
||||
|
||||
- **Unit Testing**:
|
||||
Run `make test` to execute unit tests. The results will be outputted in the terminal.
|
||||
|
||||
- **Test Coverage**:
|
||||
Run `make test-coverage` to check the code test coverage.
|
||||
|
||||
- **HTML Coverage Report**:
|
||||
Run `make test-coverage-html` to generate and browse the test coverage report in HTML format.
|
||||
|
||||
- **Run Specific Tests**:
|
||||
To run specific tests, use the command with an environment variable:
|
||||
```sh
|
||||
TEST_PATHS="./server/..." make test
|
||||
```
|
||||
|
||||
### Testing the Frontend
|
||||
|
||||
Frontend testing includes unit tests and end-to-end (E2E) tests.
|
||||
|
||||
- **Unit Testing**:
|
||||
The unit tests for the frontend are conducted using Vitest. Run `pnpm test` to start the unit testing.
|
||||
|
||||
- **End-to-End Testing**:
|
||||
E2E testing is conducted using Playwright. To start the E2E testing, run:
|
||||
```sh
|
||||
make test-frontend-e2e
|
||||
```
|
||||
|
||||
### Continuous Integration for Testing
|
||||
|
||||
Both frontend and backend testing are automated and will be performed during Git pull requests and as part of the build process using CircleCI or GitHub Actions.
|
||||
|
||||
## Configurations
|
||||
|
||||
The template configuration files are located in the `/conf` directory, with templates annotated in multiple languages named in the format `artalk.example.[lang].yml` (e.g., `artalk.example.zh-CN.yml`). Artalk will parse these template configuration files to generate the settings interface, environment variable names, and documentation. To improve performance, some data will be cached during the program's runtime to eliminate parsing time.
|
||||
|
||||
In the `internal/config/config.go` file, there is a definition of the `Config` struct, which is used to parse the yml files into Go structs. This struct contains more precise type definitions. If you need to add, modify, or delete a configuration item, you must make changes to both the configuration file templates in `/conf` and the `Config` struct.
|
||||
|
||||
When you modify the configuration files, please run the following command to update the configuration data cache:
|
||||
|
||||
```sh
|
||||
make update-conf
|
||||
```
|
||||
|
||||
To update the environment variable documentation, you can run the command:
|
||||
|
||||
```sh
|
||||
make update-conf-docs
|
||||
```
|
||||
|
||||
## Documentations
|
||||
|
||||
The documentation consists of three parts, each associated with a specific package: **Guide documentation**, **Landing page**, and **Swagger API documentation**. Below is an organized summary of the relevant commands and their outputs for each package.
|
||||
|
||||
### Guide Documentation (`docs`)
|
||||
|
||||
- **Directory**: `docs/docs/guide`
|
||||
- **Dev Command**: `pnpm -F docs dev:docs`
|
||||
- **Build Command**: `pnpm -F docs build:docs`
|
||||
- **Output Directory**: `docs/docs/.vitepress/dist`
|
||||
|
||||
### Landing Page (`docs-landing`)
|
||||
|
||||
- **Directory**: `docs/landing`
|
||||
- **Dev Command**: `pnpm -F docs-landing dev:landing`
|
||||
- **Build Command**: `pnpm -F docs-landing build:landing`
|
||||
- **Output Directory**: `docs/landing/dist`
|
||||
|
||||
### Swagger API Documentation (`docs-swagger`)
|
||||
|
||||
- **Directory**: `docs/swagger`
|
||||
- **Build Command**: `make update-swagger`
|
||||
- **Output Directory**: `docs/swagger`
|
||||
|
||||
Swagger definitions are located in the backend code at `/server`. After modifying the Swagger definitions, running the build command will update the Swagger docs and generate HTTP client code at `/ui/artalk/src/api/v2.ts`.
|
||||
|
||||
### Combined Build
|
||||
|
||||
- **Command to Build All Packages**: `pnpm build:docs`
|
||||
- **Combined Output Directory**: `docs/docs/.vitepress/dist`
|
||||
|
||||
This command builds and combines all three packages into a single directory, ready for publishing.
|
||||
|
||||
### Environment Variable Documentation
|
||||
|
||||
- **File**: `docs/docs/guide/env.md`
|
||||
- **Command to Update**: `make update-conf-docs`
|
||||
|
||||
This command reads the configuration template file located in `/conf` to update the environment variable documentation. Run this command after modifying the configuration template file.
|
||||
|
||||
## Translation (i18n)
|
||||
|
||||
If you write new features or make fixes/refactoring, use the following command to incrementally generate the translation files by parsing the source code for `i18n.T` function calls:
|
||||
|
||||
```sh
|
||||
make update-i18n
|
||||
```
|
||||
|
||||
If you're not a programmer and would like to help improve the translation, you can edit the translation files directly in the `/i18n` directory and then submit a pull request.
|
||||
|
||||
## The End
|
||||
|
||||
Thank you for your patience in reading this and for your interest and support in Artalk. We understand that the success of an open-source project relies on the contributions of every developer. Whether through code, documentation, testing, or feedback, your participation is the driving force behind the project's continuous progress. We sincerely invite you to join us in improving and enhancing Artalk.
|
||||
|
||||
By collaborating, we can create a more robust and versatile platform that benefits the entire community. Your unique insights and expertise are invaluable, and together, we can achieve remarkable advancements. Whether you are a seasoned developer or just starting, there is always a place for your contributions in our project.
|
||||
|
||||
If you have any questions or need assistance, please feel free to ask in the issue section on our GitHub page: https://github.com/ArtalkJS/Artalk/issues. Thank you once again for your support, and we look forward to working with you.
|
||||
+7
-7
@@ -1,11 +1,11 @@
|
||||
### build Artalk
|
||||
FROM golang:1.22.2-alpine3.19 as builder
|
||||
FROM golang:1.26.5-alpine3.24 AS builder
|
||||
|
||||
WORKDIR /source
|
||||
|
||||
# install tools
|
||||
RUN set -ex \
|
||||
&& apk add --no-cache make git gcc musl-dev bash
|
||||
&& apk add --no-cache make git bash
|
||||
|
||||
# download go deps
|
||||
# (cache by separating the downloading of deps)
|
||||
@@ -22,7 +22,7 @@ ARG SKIP_UI_BUILD=false
|
||||
RUN set -ex \
|
||||
&& if [ "$SKIP_UI_BUILD" = "false" ]; then \
|
||||
apk add --no-cache nodejs npm \
|
||||
&& npm install -g pnpm@9.1.0 \
|
||||
&& npm install -g pnpm@10.33.2 \
|
||||
;fi
|
||||
|
||||
RUN set -ex \
|
||||
@@ -35,18 +35,18 @@ ARG APP_VERSION=""
|
||||
ARG APP_COMMIT_HASH=""
|
||||
|
||||
RUN set -ex \
|
||||
&& if [[ ! -z "$APP_VERSION" ]]; then export VERSION=$APP_VERSION ;fi \
|
||||
&& if [[ ! -z "$APP_COMMIT_HASH" ]]; then export COMMIT_HASH=$APP_COMMIT_HASH ;fi \
|
||||
&& if [[ -n "$APP_VERSION" ]]; then export VERSION="$APP_VERSION" ;fi \
|
||||
&& if [[ -n "$APP_COMMIT_HASH" ]]; then export COMMIT_HASH="$APP_COMMIT_HASH" ;fi \
|
||||
&& make build
|
||||
|
||||
### build final image
|
||||
FROM alpine:3.19
|
||||
FROM alpine:3.24
|
||||
|
||||
# we set the timezone `Asia/Shanghai` by default, you can be modified
|
||||
# by `docker build --build-arg="TZ=Other_Timezone ..."`
|
||||
ARG TZ="Asia/Shanghai"
|
||||
|
||||
ENV TZ ${TZ}
|
||||
ENV TZ=${TZ}
|
||||
|
||||
COPY --from=builder /source/bin/artalk /artalk
|
||||
|
||||
|
||||
@@ -1,55 +1,79 @@
|
||||
PKG_NAME := github.com/ArtalkJS/Artalk
|
||||
PKG_NAME := github.com/artalkjs/artalk/v2
|
||||
BIN_NAME := ./bin/artalk
|
||||
VERSION ?= $(shell git describe --tags --abbrev=0 --match 'v*')
|
||||
COMMIT_HASH ?= $(shell git rev-parse --short HEAD)
|
||||
LDFLAGS := -s -w
|
||||
|
||||
ifneq ($(strip $(VERSION)),)
|
||||
LDFLAGS += -X '$(PKG_NAME)/internal/config.Version=$(VERSION)'
|
||||
endif
|
||||
|
||||
ifneq ($(strip $(COMMIT_HASH)),)
|
||||
LDFLAGS += -X '$(PKG_NAME)/internal/config.buildCommitHash=$(COMMIT_HASH)'
|
||||
endif
|
||||
|
||||
HAS_RICHGO := $(shell which richgo)
|
||||
GOTEST ?= $(if $(HAS_RICHGO), richgo test, go test)
|
||||
ARGS ?= server
|
||||
|
||||
export CGO_ENABLED := 0
|
||||
|
||||
all: install build
|
||||
|
||||
install:
|
||||
go mod tidy
|
||||
|
||||
run: all
|
||||
$(BIN_NAME) $(ARGS)
|
||||
|
||||
build:
|
||||
go build \
|
||||
-ldflags "-s -w -X $(PKG_NAME)/internal/config.Version=$(VERSION) \
|
||||
-X $(PKG_NAME)/internal/config.CommitHash=$(COMMIT_HASH)" \
|
||||
-ldflags "$(LDFLAGS)" \
|
||||
-o $(BIN_NAME) \
|
||||
$(PKG_NAME)
|
||||
|
||||
build-frontend:
|
||||
./scripts/build-frontend.sh
|
||||
@./scripts/build-frontend.sh
|
||||
|
||||
run: all
|
||||
$(BIN_NAME) $(ARGS)
|
||||
pack-frontend:
|
||||
@./scripts/pack-frontend.sh
|
||||
|
||||
build-debug:
|
||||
@echo "Building Artalk $(VERSION) for debugging..."
|
||||
@echo "Building Artalk for debugging..."
|
||||
@go build \
|
||||
-ldflags "-X $(PKG_NAME)/internal/config.Version=$(VERSION) \
|
||||
-X $(PKG_NAME)/internal/config.CommitHash=$(COMMIT_HASH)" \
|
||||
-gcflags "all=-N -l" \
|
||||
-o $(BIN_NAME) \
|
||||
$(PKG_NAME)
|
||||
|
||||
dev: build-debug
|
||||
ATK_SITE_DEFAULT="ArtalkDocs" \
|
||||
ATK_TRUSTED_DOMAINS="http://localhost:5173 http://localhost:23367" \
|
||||
$(BIN_NAME) $(ARGS)
|
||||
|
||||
test:
|
||||
$(GOTEST) -timeout 20m ./internal/...
|
||||
$(GOTEST) -timeout 20m $(or $(TEST_PATHS), ./...)
|
||||
|
||||
test-coverage:
|
||||
$(GOTEST) -cover ./...
|
||||
$(GOTEST) -cover $(or $(TEST_PATHS), ./...)
|
||||
|
||||
test-coverage-html:
|
||||
$(GOTEST) -v -coverprofile=coverage.out ./...
|
||||
$(GOTEST) -v -coverprofile=coverage.out $(or $(TEST_PATHS), ./...)
|
||||
go tool cover -html=coverage.out
|
||||
|
||||
test-frontend-e2e:
|
||||
./scripts/frontend-e2e-test.sh $(if $(REPORT), --show-report)
|
||||
|
||||
update-i18n:
|
||||
go generate ./internal/i18n
|
||||
|
||||
update-conf:
|
||||
go generate ./internal/config
|
||||
|
||||
update-conf-docs:
|
||||
go run ./internal/config/meta/gen --format markdown --locale en -o ./docs/docs/en/guide/env.md
|
||||
go run ./internal/config/meta/gen --format markdown --locale zh-CN -o ./docs/docs/zh/guide/env.md
|
||||
|
||||
update-docs-features:
|
||||
pnpm -F docs-landing update:readme
|
||||
|
||||
update-swagger:
|
||||
go install github.com/swaggo/swag/cmd/swag@latest
|
||||
swag init -g server/server.go --output ./docs/swagger --requiredByDefault
|
||||
@@ -61,9 +85,8 @@ docker-build:
|
||||
docker-push:
|
||||
./scripts/docker-build.sh --push
|
||||
|
||||
test-frontend-e2e:
|
||||
./scripts/frontend-e2e-test.sh $(if $(REPORT), --show-report)
|
||||
|
||||
.PHONY: all install build build-frontend build-debug \
|
||||
dev test test-coverage test-coverage-html update-i18n \
|
||||
docker-build docker-push test-frontend-e2e;
|
||||
.PHONY: all install run build build-frontend pack-frontend build-debug dev \
|
||||
test test-coverage test-coverage-html test-frontend-e2e \
|
||||
update-i18n update-conf update-conf-docs \
|
||||
update-docs-features update-swagger \
|
||||
docker-build docker-push;
|
||||
-121
@@ -1,121 +0,0 @@
|
||||
<p align="center">
|
||||
<img src="https://user-images.githubusercontent.com/22412567/171680920-6e74b77c-c565-487b-bff1-4f94976ecbe7.png" alt="Artalk" width="100%">
|
||||
</p>
|
||||
|
||||
# Artalk
|
||||
|
||||
[](https://www.npmjs.com/package/artalk)
|
||||
[](https://www.npmjs.com/package/artalk)
|
||||
[](https://hub.docker.com/r/artalk/artalk-go)
|
||||
[](https://goreportcard.com/report/github.com/ArtalkJS/Artalk)
|
||||
[](https://circleci.com/gh/ArtalkJS/Artalk/tree/master)
|
||||
[](https://codecov.io/gh/ArtalkJS/Artalk)
|
||||
[](https://bundlephobia.com/package/artalk)
|
||||
|
||||
[中文](./README.md) • [Official Website](https://artalk.js.org) • [Latest Release](https://github.com/ArtalkJS/Artalk/releases) • [Changelog](https://github.com/ArtalkJS/Artalk/blob/master/CHANGELOG.md)
|
||||
|
||||
🌌 Artalk is a simple yet feature-rich commenting system that you can deploy out of the box and integrate into any blog, website, or web application.
|
||||
|
||||
- 🍃 Frontend ~40KB, pure Vanilla JS
|
||||
- 🍱 Backend Golang, efficient lightweight cross-platform
|
||||
- 🐳 One-click deployment via Docker, convenient and fast
|
||||
- 🌈 Open-source program, self-hosted, privacy-first
|
||||
|
||||
## Features
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
<!-- features -->
|
||||
* [Sidebar](https://artalk.js.org/guide/frontend/sidebar.html): Quick management, intuitive browsing
|
||||
* [Social Login](https://artalk.js.org/guide/frontend/auth.html): Quick login via social accounts
|
||||
* [Email Notification](https://artalk.js.org/guide/backend/email.html): Multiple sending methods, email templates
|
||||
* [Multi-channel Push](https://artalk.js.org/guide/backend/admin_notify.html): Multiple push methods, notification templates
|
||||
* [In-site Notifications](https://artalk.js.org/guide/frontend/sidebar.html): Red dot marking, mention list
|
||||
* [CAPTCHA](https://artalk.js.org/guide/backend/captcha.html): Multiple verification types, frequency limitation
|
||||
* [Comment Moderation](https://artalk.js.org/guide/backend/moderator.html): Content detection, spam interception
|
||||
* [Image Upload](https://artalk.js.org/guide/backend/img-upload.html): Custom upload, support for image hosting
|
||||
* [Markdown](https://artalk.js.org/guide/intro.html): Support Markdown syntax
|
||||
* [Emoticons](https://artalk.js.org/guide/frontend/emoticons.html): Compatible with OwO, quick integration
|
||||
* [Multi-site](https://artalk.js.org/guide/backend/multi-site.html): Site isolation, centralized management
|
||||
* [Admin](https://artalk.js.org/guide/backend/multi-site.html): Password verification, badge identification
|
||||
* [Page Management](https://artalk.js.org/guide/frontend/sidebar.html): Quick view, one-click title jump
|
||||
* [Page Views Statistics](https://artalk.js.org/guide/frontend/pv.html): Easily track page views
|
||||
* [Hierarchy](https://artalk.js.org/guide/frontend/config.html#nestmax): Nested paginated lists, scroll loading
|
||||
* [Comment Voting](https://artalk.js.org/guide/frontend/config.html#vote): Upvote or downvote comments
|
||||
* [Comment Sorting](https://artalk.js.org/guide/frontend/config.html#listsort): Multiple sorting options, freedom to choose
|
||||
* [Comment Search](https://artalk.js.org/guide/frontend/sidebar.html): Quickly search comment content
|
||||
* [Comment Pinning](https://artalk.js.org/guide/frontend/sidebar.html): Pin important messages
|
||||
* [Author-only View](https://artalk.js.org/guide/frontend/config.html): Show only comments from the author
|
||||
* [Comment Jumping](https://artalk.js.org/guide/intro.html): Quickly jump to referenced comments
|
||||
* [Auto Save](https://artalk.js.org/guide/frontend/config.html): Content input auto-saving
|
||||
* [IP Geolocation](https://artalk.js.org/guide/frontend/ip-region.html): User IP location display
|
||||
* [Data Migration](https://artalk.js.org/guide/transfer.html): Free migration, quick backup
|
||||
* [Image Lightbox](https://artalk.js.org/guide/frontend/lightbox.html): Quickly integrate image lightbox
|
||||
* [Image Lazy Loading](https://artalk.js.org/guide/frontend/img-lazy-load.html): Delay loading images, optimize experience
|
||||
* [Latex](https://artalk.js.org/guide/frontend/latex.html): Latex formula parsing integration
|
||||
* [Dark Mode](https://artalk.js.org/guide/frontend/config.html#darkmode): Dark mode switching
|
||||
* [Extension Plugins](https://artalk.js.org/develop/): Create more possibilities
|
||||
* [Multi-language](https://artalk.js.org/guide/frontend/i18n.html): Multi-language switching
|
||||
* [Command Line](https://artalk.js.org/guide/backend/config.html): Command line operation management capability
|
||||
* [API Documentation](https://artalk.js.org/develop/): Provides OpenAPI format documentation
|
||||
* [Program Upgrade](https://artalk.js.org/guide/backend/update.html): Version detection, one-click upgrade
|
||||
<!-- /features -->
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
## Installation
|
||||
|
||||
Deploy via Docker with one click:
|
||||
|
||||
```bash
|
||||
docker run -d --name artalk -p 8080:23366 -v $(pwd)/data:/data artalk/artalk-go
|
||||
```
|
||||
|
||||
Integrate Artalk into your webpage:
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
```ts
|
||||
Artalk.init({
|
||||
el: '#Comments',
|
||||
site: 'Artalk Blog',
|
||||
server: 'https://artalk.example.com',
|
||||
pageKey: '/2018/10/02/hello-world.html'
|
||||
})
|
||||
```
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
[**Learn More →**](https://artalk.js.org/guide/deploy.html)
|
||||
|
||||
## For Developers
|
||||
|
||||
Pull requests are welcome!
|
||||
|
||||
See [Development](https://artalk.js.org/develop/) and [Contributing](./CONTRIBUTING.md) for information on working with the codebase, getting a local development setup, and contributing changes.
|
||||
|
||||
## Contributors
|
||||
|
||||
Your contributions enrich the open-source community, fostering learning, inspiration, and innovation. We deeply value your involvement. Thank you for being a vital part of our community! 🥰
|
||||
|
||||
[](https://github.com/ArtalkJS/Artalk/graphs/contributors)
|
||||
|
||||
## Supporters
|
||||
|
||||
[](https://github.com/ArtalkJS/Artalk/stargazers)
|
||||
|
||||
## Repobeats Analytics
|
||||
|
||||

|
||||
|
||||
## Stargazers over time
|
||||
|
||||
[](https://starchart.cc/ArtalkJS/Artalk)
|
||||
|
||||
## License
|
||||
|
||||
[MIT](./LICENSE)
|
||||
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2FArtalkJS%2FArtalk?ref=badge_shield)
|
||||
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2FArtalkJS%2FArtalk?ref=badge_large)
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
<p align="center">
|
||||
<img src="https://user-images.githubusercontent.com/22412567/171680920-6e74b77c-c565-487b-bff1-4f94976ecbe7.png" alt="Artalk" width="100%">
|
||||
</p>
|
||||
|
||||
# Artalk
|
||||
|
||||
[](https://www.npmjs.com/package/artalk)
|
||||
[](https://www.npmjs.com/package/artalk)
|
||||
[](https://hub.docker.com/r/artalk/artalk-go)
|
||||
[](https://pkg.go.dev/github.com/artalkjs/artalk/v2)
|
||||
[](https://goreportcard.com/report/github.com/ArtalkJS/Artalk)
|
||||
[](https://circleci.com/gh/ArtalkJS/Artalk/tree/master)
|
||||
[](https://codecov.io/gh/ArtalkJS/Artalk)
|
||||
[](https://bundlephobia.com/package/artalk)
|
||||
|
||||
[ホームページ](https://artalk.js.org) • [ドキュメント](https://artalk.js.org/en/guide/deploy.html) • [最新リリース](https://github.com/ArtalkJS/Artalk/releases) • [変更履歴](https://github.com/ArtalkJS/Artalk/blob/master/CHANGELOG.md) • [English](./README.md) • [简体中文](./README.zh.md)
|
||||
|
||||
Artalk は直感的かつ高機能なコメントシステムで、あらゆるブログ、ウェブサイト、ウェブアプリケーションにすぐに導入できます。
|
||||
|
||||
- 🍃 クライアントは約 40KB、純粋な Vanilla JS で作られており、フレームワークに依存しません
|
||||
- 🍱 サーバーは Golang を採用し、効率的かつ軽量なクロスプラットフォーム性能を提供します
|
||||
- 🐳 Docker によるワンクリックデプロイで、手軽かつ高速に導入できます
|
||||
- 🌈 オープンソースソフトウェアで、プライバシーを最優先にセルフホストできます
|
||||
|
||||
## 機能
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
<!-- features -->
|
||||
* [サイドバー](https://artalk.js.org/guide/frontend/sidebar.html): 素早い管理と直感的な閲覧
|
||||
* [ソーシャルログイン](https://artalk.js.org/guide/frontend/auth.html): ソーシャルアカウントによる高速ログイン
|
||||
* [メール通知](https://artalk.js.org/guide/backend/email.html): 多様な送信方法とメールテンプレート
|
||||
* [多様なプッシュ通知](https://artalk.js.org/guide/backend/admin_notify.html): 複数のプッシュ方法と通知テンプレート
|
||||
* [サイト内通知](https://artalk.js.org/guide/frontend/sidebar.html): 未読マークとメンション一覧
|
||||
* [キャプチャ](https://artalk.js.org/guide/backend/captcha.html): 多様な認証タイプと頻度制限
|
||||
* [コメントモデレーション](https://artalk.js.org/guide/backend/moderator.html): コンテンツ検出とスパム遮断
|
||||
* [画像アップロード](https://artalk.js.org/guide/backend/img-upload.html): カスタムアップロード、画像ホスティング対応
|
||||
* [Markdown](https://artalk.js.org/guide/intro.html): Markdown 構文に対応
|
||||
* [絵文字パック](https://artalk.js.org/guide/frontend/emoticons.html): OwO 互換、簡単に統合
|
||||
* [マルチサイト](https://artalk.js.org/guide/backend/multi-site.html): サイトの分離と一元管理
|
||||
* [管理者](https://artalk.js.org/guide/backend/multi-site.html): パスワード認証とバッジ識別
|
||||
* [ページ管理](https://artalk.js.org/guide/frontend/sidebar.html): 素早い閲覧とワンクリックでのタイトル遷移
|
||||
* [ページビュー統計](https://artalk.js.org/guide/frontend/pv.html): ページビューを簡単に追跡
|
||||
* [階層構造](https://artalk.js.org/guide/frontend/config.html#nestmax): ネストされたページネーション一覧と無限スクロール
|
||||
* [コメント投票](https://artalk.js.org/guide/frontend/config.html#vote): コメントの賛成・反対投票
|
||||
* [コメント並び替え](https://artalk.js.org/guide/frontend/config.html#listsort): 多様な並び替えオプションを自由に選択
|
||||
* [コメント検索](https://artalk.js.org/guide/frontend/sidebar.html): コメント内容を素早く検索
|
||||
* [コメントのピン留め](https://artalk.js.org/guide/frontend/sidebar.html): 重要なメッセージをピン留め
|
||||
* [作成者のみ表示](https://artalk.js.org/guide/frontend/config.html): 作成者のコメントのみを表示
|
||||
* [コメントジャンプ](https://artalk.js.org/guide/intro.html): 引用されたコメントへ素早く移動
|
||||
* [自動保存](https://artalk.js.org/guide/frontend/config.html): 入力内容の消失を防止
|
||||
* [IP 地域表示](https://artalk.js.org/guide/frontend/ip-region.html): ユーザーの IP 地域を表示
|
||||
* [データ移行](https://artalk.js.org/guide/transfer.html): 自由な移行と素早いバックアップ
|
||||
* [画像ライトボックス](https://artalk.js.org/guide/frontend/lightbox.html): 画像ライトボックスを簡単に統合
|
||||
* [画像の遅延読み込み](https://artalk.js.org/guide/frontend/img-lazy-load.html): 画像を遅延読み込みし、体験を最適化
|
||||
* [LaTeX](https://artalk.js.org/guide/frontend/latex.html): LaTeX 数式の解析を統合
|
||||
* [ナイトモード](https://artalk.js.org/guide/frontend/config.html#darkmode): ナイトモードへの切り替え
|
||||
* [拡張プラグイン](https://artalk.js.org/develop/plugin.html): さらなる可能性を創出
|
||||
* [多言語対応](https://artalk.js.org/guide/frontend/i18n.html): 複数の言語を切り替え
|
||||
* [コマンドライン](https://artalk.js.org/guide/backend/config.html): コマンドラインによる操作管理
|
||||
* [API ドキュメント](https://artalk.js.org/http-api.html): OpenAPI 形式のドキュメントを提供
|
||||
* [プログラムのアップグレード](https://artalk.js.org/guide/backend/update.html): バージョンチェックとワンクリックアップグレード
|
||||
<!-- /features -->
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
## インストール
|
||||
|
||||
Docker でワンステップで Artalk サーバーをデプロイ:
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name artalk \
|
||||
-p 8080:23366 \
|
||||
-v $(pwd)/data:/data \
|
||||
-e "TZ=America/New_York" \
|
||||
-e "ATK_LOCALE=en" \
|
||||
-e "ATK_SITE_DEFAULT=Artalk Blog" \
|
||||
-e "ATK_SITE_URL=https://example.com" \
|
||||
artalk/artalk-go
|
||||
```
|
||||
|
||||
Artalk クライアントをウェブページに組み込む:
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
```ts
|
||||
Artalk.init({
|
||||
el: '#Comments',
|
||||
site: 'Artalk Blog',
|
||||
server: 'https://artalk.example.com',
|
||||
pageKey: '/2018/10/02/hello-world.html'
|
||||
})
|
||||
```
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
バイナリファイル、go install、Linux ディストリビューション向けのパッケージマネージャーなど、さまざまなインストール方法を提供しています。
|
||||
|
||||
[**詳しく見る →**](https://artalk.js.org/en/guide/deploy.html)
|
||||
|
||||
## 開発者の方へ
|
||||
|
||||
プルリクエストを歓迎します!
|
||||
|
||||
コードベースの扱い方、ローカル開発環境のセットアップ、変更の貢献については [開発](https://artalk.js.org/en/develop/) と [コントリビューション](./CONTRIBUTING.md) をご覧ください。
|
||||
|
||||
## コントリビューター
|
||||
|
||||
皆さんの貢献はオープンソースコミュニティを豊かにし、学び、ひらめき、イノベーションを育みます。私たちは皆さんの参加を心から大切にしています。私たちのコミュニティに欠かせない存在でいてくれて、ありがとうございます!🥰
|
||||
|
||||
[](https://github.com/ArtalkJS/Artalk/graphs/contributors)
|
||||
|
||||
## サポーター
|
||||
|
||||
[](https://github.com/ArtalkJS/Artalk/stargazers)
|
||||
|
||||
## Repobeats アナリティクス
|
||||
|
||||

|
||||
|
||||
## Stargazer の推移
|
||||
|
||||
<a href="https://trendshift.io/repositories/6290" target="_blank"><img src="https://trendshift.io/api/badge/repositories/6290" alt="ArtalkJS%2FArtalk | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||
|
||||
[](https://starchart.cc/ArtalkJS/Artalk)
|
||||
|
||||
## ライセンス
|
||||
|
||||
[MIT](./LICENSE)
|
||||
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2FArtalkJS%2FArtalk?ref=badge_shield)
|
||||
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2FArtalkJS%2FArtalk?ref=badge_large)
|
||||
@@ -7,78 +7,87 @@
|
||||
[](https://www.npmjs.com/package/artalk)
|
||||
[](https://www.npmjs.com/package/artalk)
|
||||
[](https://hub.docker.com/r/artalk/artalk-go)
|
||||
[](https://pkg.go.dev/github.com/artalkjs/artalk/v2)
|
||||
[](https://goreportcard.com/report/github.com/ArtalkJS/Artalk)
|
||||
[](https://circleci.com/gh/ArtalkJS/Artalk/tree/master)
|
||||
[](https://codecov.io/gh/ArtalkJS/Artalk)
|
||||
[](https://bundlephobia.com/package/artalk)
|
||||
|
||||
[English](./README.en.md) • [官方网站](https://artalk.js.org) • [最新版本](https://github.com/ArtalkJS/Artalk/releases) • [更新日志](https://github.com/ArtalkJS/Artalk/blob/master/CHANGELOG.md)
|
||||
[Homepage](https://artalk.js.org) • [Documentation](https://artalk.js.org/en/guide/deploy.html) • [Latest Release](https://github.com/ArtalkJS/Artalk/releases) • [Changelog](https://github.com/ArtalkJS/Artalk/blob/master/CHANGELOG.md) • [简体中文](./README.zh.md) • [日本語](./README.ja.md)
|
||||
|
||||
Artalk 是一款简单易用但功能丰富的评论系统,你可以开箱即用地部署并置入任何博客、网站、Web 应用。
|
||||
Artalk is an intuitive yet feature-rich comment system, ready for immediate deployment into any blog, website, or web application.
|
||||
|
||||
- 🍃 前端 ~40KB,纯天然 Vanilla JS
|
||||
- 🍱 后端 Golang,高效轻量跨平台
|
||||
- 🐳 通过 Docker 一键部署,方便快捷
|
||||
- 🌈 开源程序,自托管,隐私至上
|
||||
- 🍃 Client ~40KB, crafted with pure Vanilla JS, framework-agnostic
|
||||
- 🍱 Server powered by Golang, offering efficient and lightweight cross-platform performance
|
||||
- 🐳 One-click deployment via Docker, ensuring ease and speed
|
||||
- 🌈 Open-source software, self-hosted with privacy as a priority
|
||||
|
||||
## 特性
|
||||
## Features
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
<!-- features -->
|
||||
* [侧边栏](https://artalk.js.org/guide/frontend/sidebar.html): 快速管理、直观浏览
|
||||
* [社交登录](https://artalk.js.org/guide/frontend/auth.html): 通过社交账号快速登录
|
||||
* [邮件通知](https://artalk.js.org/guide/backend/email.html): 多种发送方式、邮件模板
|
||||
* [多元推送](https://artalk.js.org/guide/backend/admin_notify.html): 多种推送方式、通知模版
|
||||
* [站内通知](https://artalk.js.org/guide/frontend/sidebar.html): 红点标记、提及列表
|
||||
* [验证码](https://artalk.js.org/guide/backend/captcha.html): 多种验证类型、频率限制
|
||||
* [评论审核](https://artalk.js.org/guide/backend/moderator.html): 内容检测、垃圾拦截
|
||||
* [图片上传](https://artalk.js.org/guide/backend/img-upload.html): 自定义上传、支持图床
|
||||
* [Markdown](https://artalk.js.org/guide/intro.html): 支持 Markdown 语法
|
||||
* [表情包](https://artalk.js.org/guide/frontend/emoticons.html): 兼容 OwO,快速集成
|
||||
* [多站点](https://artalk.js.org/guide/backend/multi-site.html): 站点隔离、集中管理
|
||||
* [管理员](https://artalk.js.org/guide/backend/multi-site.html): 密码验证、徽章标识
|
||||
* [页面管理](https://artalk.js.org/guide/frontend/sidebar.html): 快速查看、标题一键跳转
|
||||
* [浏览量统计](https://artalk.js.org/guide/frontend/pv.html): 轻松统计网页浏览量
|
||||
* [层级结构](https://artalk.js.org/guide/frontend/config.html#nestmax): 嵌套分页列表、滚动加载
|
||||
* [评论投票](https://artalk.js.org/guide/frontend/config.html#vote): 赞同或反对评论
|
||||
* [评论排序](https://artalk.js.org/guide/frontend/config.html#listsort): 多种排序方式,自由选择
|
||||
* [评论搜索](https://artalk.js.org/guide/frontend/sidebar.html): 快速搜索评论内容
|
||||
* [评论置顶](https://artalk.js.org/guide/frontend/sidebar.html): 重要消息置顶显示
|
||||
* [仅看作者](https://artalk.js.org/guide/frontend/config.html): 仅显示作者的评论
|
||||
* [评论跳转](https://artalk.js.org/guide/intro.html): 快速跳转到引用的评论
|
||||
* [自动保存](https://artalk.js.org/guide/frontend/config.html): 输入内容防丢功能
|
||||
* [IP 属地](https://artalk.js.org/guide/frontend/ip-region.html): 用户 IP 属地展示
|
||||
* [数据迁移](https://artalk.js.org/guide/transfer.html): 自由迁移、快速备份
|
||||
* [图片灯箱](https://artalk.js.org/guide/frontend/lightbox.html): 图片灯箱快速集成
|
||||
* [图片懒加载](https://artalk.js.org/guide/frontend/img-lazy-load.html): 延迟加载图片,优化体验
|
||||
* [Latex](https://artalk.js.org/guide/frontend/latex.html): Latex 公式解析集成
|
||||
* [夜间模式](https://artalk.js.org/guide/frontend/config.html#darkmode): 夜间模式切换
|
||||
* [扩展插件](https://artalk.js.org/develop/): 创造更多可能性
|
||||
* [多语言](https://artalk.js.org/guide/frontend/i18n.html): 多国语言切换
|
||||
* [命令行](https://artalk.js.org/guide/backend/config.html): 命令行操作管理能力
|
||||
* [API 文档](https://artalk.js.org/develop/): 提供 OpenAPI 格式文档
|
||||
* [程序升级](https://artalk.js.org/guide/backend/update.html): 版本检测,一键升级
|
||||
* [Sidebar](https://artalk.js.org/guide/frontend/sidebar.html): Quick management, intuitive browsing
|
||||
* [Social Login](https://artalk.js.org/guide/frontend/auth.html): Fast login via social accounts
|
||||
* [Email Notification](https://artalk.js.org/guide/backend/email.html): Various sending methods, email templates
|
||||
* [Diverse Push](https://artalk.js.org/guide/backend/admin_notify.html): Multiple push methods, notification templates
|
||||
* [Site Notification](https://artalk.js.org/guide/frontend/sidebar.html): Red dot marks, mention list
|
||||
* [Captcha](https://artalk.js.org/guide/backend/captcha.html): Various verification types, frequency limits
|
||||
* [Comment Moderation](https://artalk.js.org/guide/backend/moderator.html): Content detection, spam interception
|
||||
* [Image Upload](https://artalk.js.org/guide/backend/img-upload.html): Custom upload, supports image hosting
|
||||
* [Markdown](https://artalk.js.org/guide/intro.html): Supports Markdown syntax
|
||||
* [Emoji Pack](https://artalk.js.org/guide/frontend/emoticons.html): Compatible with OwO, quick integration
|
||||
* [Multi-Site](https://artalk.js.org/guide/backend/multi-site.html): Site isolation, centralized management
|
||||
* [Admin](https://artalk.js.org/guide/backend/multi-site.html): Password verification, badge identification
|
||||
* [Page Management](https://artalk.js.org/guide/frontend/sidebar.html): Quick view, one-click title navigation
|
||||
* [Page View Statistics](https://artalk.js.org/guide/frontend/pv.html): Easily track page views
|
||||
* [Hierarchical Structure](https://artalk.js.org/guide/frontend/config.html#nestmax): Nested paginated list, infinite scroll
|
||||
* [Comment Voting](https://artalk.js.org/guide/frontend/config.html#vote): Upvote or downvote comments
|
||||
* [Comment Sorting](https://artalk.js.org/guide/frontend/config.html#listsort): Various sorting options, freely selectable
|
||||
* [Comment Search](https://artalk.js.org/guide/frontend/sidebar.html): Quick comment content search
|
||||
* [Comment Pinning](https://artalk.js.org/guide/frontend/sidebar.html): Pin important messages
|
||||
* [View Author Only](https://artalk.js.org/guide/frontend/config.html): Show only the author's comments
|
||||
* [Comment Jump](https://artalk.js.org/guide/intro.html): Quickly jump to quoted comment
|
||||
* [Auto Save](https://artalk.js.org/guide/frontend/config.html): Content loss prevention
|
||||
* [IP Region](https://artalk.js.org/guide/frontend/ip-region.html): Display user's IP region
|
||||
* [Data Migration](https://artalk.js.org/guide/transfer.html): Free migration, quick backup
|
||||
* [Image Lightbox](https://artalk.js.org/guide/frontend/lightbox.html): Quick integration of image lightbox
|
||||
* [Image Lazy Load](https://artalk.js.org/guide/frontend/img-lazy-load.html): Lazy load images, optimize experience
|
||||
* [Latex](https://artalk.js.org/guide/frontend/latex.html): Integrate Latex formula parsing
|
||||
* [Night Mode](https://artalk.js.org/guide/frontend/config.html#darkmode): Switch to night mode
|
||||
* [Extension Plugin](https://artalk.js.org/develop/plugin.html): Create more possibilities
|
||||
* [Multi-Language](https://artalk.js.org/guide/frontend/i18n.html): Switch between multiple languages
|
||||
* [Command Line](https://artalk.js.org/guide/backend/config.html): Command line operation management
|
||||
* [API Documentation](https://artalk.js.org/http-api.html): Provides OpenAPI format documentation
|
||||
* [Program Upgrade](https://artalk.js.org/guide/backend/update.html): Version check, one-click upgrade
|
||||
<!-- /features -->
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
## 安装
|
||||
## Installation
|
||||
|
||||
通过 Docker 一键部署:
|
||||
Deploy Artalk Server with Docker in one step:
|
||||
|
||||
```bash
|
||||
docker run -d --name artalk -p 8080:23366 -v $(pwd)/data:/data artalk/artalk-go
|
||||
docker run -d \
|
||||
--name artalk \
|
||||
-p 8080:23366 \
|
||||
-v $(pwd)/data:/data \
|
||||
-e "TZ=America/New_York" \
|
||||
-e "ATK_LOCALE=en" \
|
||||
-e "ATK_SITE_DEFAULT=Artalk Blog" \
|
||||
-e "ATK_SITE_URL=https://example.com" \
|
||||
artalk/artalk-go
|
||||
```
|
||||
|
||||
在网页中引入 Artalk:
|
||||
Integrate Artalk Client into your webpage:
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
```ts
|
||||
Artalk.init({
|
||||
el: '#Comments',
|
||||
site: 'Artalk 的博客',
|
||||
site: 'Artalk Blog',
|
||||
server: 'https://artalk.example.com',
|
||||
pageKey: '/2018/10/02/hello-world.html'
|
||||
})
|
||||
@@ -86,13 +95,15 @@ Artalk.init({
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
[**了解更多 →**](https://artalk.js.org/guide/deploy.html)
|
||||
We offer various installation methods, including binary files, go install, and package managers for Linux distributions.
|
||||
|
||||
[**Learn More →**](https://artalk.js.org/en/guide/deploy.html)
|
||||
|
||||
## For Developers
|
||||
|
||||
Pull requests are welcome!
|
||||
|
||||
See [Development](https://artalk.js.org/develop/) and [Contributing](./CONTRIBUTING.md) for information on working with the codebase, getting a local development setup, and contributing changes.
|
||||
See [Development](https://artalk.js.org/en/develop/) and [Contributing](./CONTRIBUTING.md) for information on working with the codebase, getting a local development setup, and contributing changes.
|
||||
|
||||
## Contributors
|
||||
|
||||
@@ -110,6 +121,8 @@ Your contributions enrich the open-source community, fostering learning, inspira
|
||||
|
||||
## Stargazers over time
|
||||
|
||||
<a href="https://trendshift.io/repositories/6290" target="_blank"><img src="https://trendshift.io/api/badge/repositories/6290" alt="ArtalkJS%2FArtalk | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||
|
||||
[](https://starchart.cc/ArtalkJS/Artalk)
|
||||
|
||||
## License
|
||||
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
<p align="center">
|
||||
<img src="https://user-images.githubusercontent.com/22412567/171680920-6e74b77c-c565-487b-bff1-4f94976ecbe7.png" alt="Artalk" width="100%">
|
||||
</p>
|
||||
|
||||
# Artalk
|
||||
|
||||
[](https://www.npmjs.com/package/artalk)
|
||||
[](https://www.npmjs.com/package/artalk)
|
||||
[](https://hub.docker.com/r/artalk/artalk-go)
|
||||
[](https://pkg.go.dev/github.com/artalkjs/artalk/v2)
|
||||
[](https://goreportcard.com/report/github.com/ArtalkJS/Artalk)
|
||||
[](https://circleci.com/gh/ArtalkJS/Artalk/tree/master)
|
||||
[](https://codecov.io/gh/ArtalkJS/Artalk)
|
||||
[](https://bundlephobia.com/package/artalk)
|
||||
|
||||
[官方网站](https://artalk.js.org) • [最新版本](https://github.com/ArtalkJS/Artalk/releases) • [更新日志](https://github.com/ArtalkJS/Artalk/blob/master/CHANGELOG.md) • [English](./README.md) • [日本語](./README.ja.md)
|
||||
|
||||
Artalk 是一款简单易用但功能丰富的评论系统,你可以开箱即用地部署并置入任何博客、网站、Web 应用。
|
||||
|
||||
- 🍃 前端 ~40KB,纯天然 Vanilla JS
|
||||
- 🍱 后端 Golang,高效轻量跨平台
|
||||
- 🐳 通过 Docker 一键部署,方便快捷
|
||||
- 🌈 开源程序,自托管,隐私至上
|
||||
|
||||
## 特性
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
<!-- features -->
|
||||
* [侧边栏](https://artalk.js.org/guide/frontend/sidebar.html): 快速管理、直观浏览
|
||||
* [社交登录](https://artalk.js.org/guide/frontend/auth.html): 通过社交账号快速登录
|
||||
* [邮件通知](https://artalk.js.org/guide/backend/email.html): 多种发送方式、邮件模板
|
||||
* [多元推送](https://artalk.js.org/guide/backend/admin_notify.html): 多种推送方式、通知模版
|
||||
* [站内通知](https://artalk.js.org/guide/frontend/sidebar.html): 红点标记、提及列表
|
||||
* [验证码](https://artalk.js.org/guide/backend/captcha.html): 多种验证类型、频率限制
|
||||
* [评论审核](https://artalk.js.org/guide/backend/moderator.html): 内容检测、垃圾拦截
|
||||
* [图片上传](https://artalk.js.org/guide/backend/img-upload.html): 自定义上传、支持图床
|
||||
* [Markdown](https://artalk.js.org/guide/intro.html): 支持 Markdown 语法
|
||||
* [表情包](https://artalk.js.org/guide/frontend/emoticons.html): 兼容 OwO,快速集成
|
||||
* [多站点](https://artalk.js.org/guide/backend/multi-site.html): 站点隔离、集中管理
|
||||
* [管理员](https://artalk.js.org/guide/backend/multi-site.html): 密码验证、徽章标识
|
||||
* [页面管理](https://artalk.js.org/guide/frontend/sidebar.html): 快速查看、标题一键跳转
|
||||
* [浏览量统计](https://artalk.js.org/guide/frontend/pv.html): 轻松统计网页浏览量
|
||||
* [层级结构](https://artalk.js.org/guide/frontend/config.html#nestmax): 嵌套分页列表、滚动加载
|
||||
* [评论投票](https://artalk.js.org/guide/frontend/config.html#vote): 赞同或反对评论
|
||||
* [评论排序](https://artalk.js.org/guide/frontend/config.html#listsort): 多种排序方式,自由选择
|
||||
* [评论搜索](https://artalk.js.org/guide/frontend/sidebar.html): 快速搜索评论内容
|
||||
* [评论置顶](https://artalk.js.org/guide/frontend/sidebar.html): 重要消息置顶显示
|
||||
* [仅看作者](https://artalk.js.org/guide/frontend/config.html): 仅显示作者的评论
|
||||
* [评论跳转](https://artalk.js.org/guide/intro.html): 快速跳转到引用的评论
|
||||
* [自动保存](https://artalk.js.org/guide/frontend/config.html): 输入内容防丢功能
|
||||
* [IP 属地](https://artalk.js.org/guide/frontend/ip-region.html): 用户 IP 属地展示
|
||||
* [数据迁移](https://artalk.js.org/guide/transfer.html): 自由迁移、快速备份
|
||||
* [图片灯箱](https://artalk.js.org/guide/frontend/lightbox.html): 图片灯箱快速集成
|
||||
* [图片懒加载](https://artalk.js.org/guide/frontend/img-lazy-load.html): 延迟加载图片,优化体验
|
||||
* [Latex](https://artalk.js.org/guide/frontend/latex.html): Latex 公式解析集成
|
||||
* [夜间模式](https://artalk.js.org/guide/frontend/config.html#darkmode): 夜间模式切换
|
||||
* [扩展插件](https://artalk.js.org/develop/plugin.html): 创造更多可能性
|
||||
* [多语言](https://artalk.js.org/guide/frontend/i18n.html): 多国语言切换
|
||||
* [命令行](https://artalk.js.org/guide/backend/config.html): 命令行操作管理能力
|
||||
* [API 文档](https://artalk.js.org/http-api.html): 提供 OpenAPI 格式文档
|
||||
* [程序升级](https://artalk.js.org/guide/backend/update.html): 版本检测,一键升级
|
||||
<!-- /features -->
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
## 安装
|
||||
|
||||
通过 Docker 一键部署 Artalk 服务器:
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name artalk \
|
||||
-p 8080:23366 \
|
||||
-v $(pwd)/data:/data \
|
||||
-e "TZ=Asia/Shanghai" \
|
||||
-e "ATK_LOCALE=zh-CN" \
|
||||
-e "ATK_SITE_DEFAULT=Artalk 的博客" \
|
||||
-e "ATK_SITE_URL=https://example.com" \
|
||||
artalk/artalk-go
|
||||
```
|
||||
|
||||
在你的网页中引入 Artalk 客户端:
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
```ts
|
||||
Artalk.init({
|
||||
el: '#Comments',
|
||||
site: 'Artalk 的博客',
|
||||
server: 'https://artalk.example.com',
|
||||
pageKey: '/2018/10/02/hello-world.html'
|
||||
})
|
||||
```
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
我们提供多种安装方法,包括二进制文件、`go install` 和通过 Linux 发行版的包管理器安装。
|
||||
|
||||
[**了解更多 →**](https://artalk.js.org/zh/guide/deploy.html)
|
||||
|
||||
## 参与开发
|
||||
|
||||
我们欢迎你的 Pull Request!
|
||||
|
||||
有关如何使用代码库、设置本地开发环境和贡献更改的信息,请参阅 [开发文档](https://artalk.js.org/zh/develop/) 和 [贡献指南](./CONTRIBUTING.md)。
|
||||
|
||||
## 贡献者们
|
||||
|
||||
你的贡献丰富了开源社区,促进了学习、灵感和创新。我们非常重视你的参与。感谢你成为我们社区的重要一员!🥰
|
||||
|
||||
[](https://github.com/ArtalkJS/Artalk/graphs/contributors)
|
||||
|
||||
## 支持者们
|
||||
|
||||
[](https://github.com/ArtalkJS/Artalk/stargazers)
|
||||
|
||||
## Repobeats 分析
|
||||
|
||||

|
||||
|
||||
## Star 趋势
|
||||
|
||||
<a href="https://trendshift.io/repositories/6290" target="_blank"><img src="https://trendshift.io/api/badge/repositories/6290" alt="ArtalkJS%2FArtalk | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||
|
||||
[](https://starchart.cc/ArtalkJS/Artalk)
|
||||
|
||||
## 开源许可协议
|
||||
|
||||
[MIT](./LICENSE) (麻省理工学院许可证)
|
||||
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2FArtalkJS%2FArtalk?ref=badge_shield)
|
||||
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2FArtalkJS%2FArtalk?ref=badge_large)
|
||||
@@ -0,0 +1,3 @@
|
||||
# Reporting Security Issues
|
||||
|
||||
The features of Artalk are continuously being improved, and we are also striving to enhance security. However, potential issues may still exist. If you discover any security vulnerabilities in Artalk, please promptly contact us via [GitHub Issues](https://github.com/ArtalkJS/Artalk/issues) or email at artalkjs@gmail.com. We will make every effort to address the issue as quickly as possible. Thank you for your support!
|
||||
+4
-4
@@ -7,10 +7,10 @@ import (
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/ArtalkJS/Artalk/internal/entity"
|
||||
"github.com/ArtalkJS/Artalk/internal/i18n"
|
||||
"github.com/ArtalkJS/Artalk/internal/log"
|
||||
"github.com/ArtalkJS/Artalk/internal/utils"
|
||||
"github.com/artalkjs/artalk/v2/internal/entity"
|
||||
"github.com/artalkjs/artalk/v2/internal/i18n"
|
||||
"github.com/artalkjs/artalk/v2/internal/log"
|
||||
"github.com/artalkjs/artalk/v2/internal/utils"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
+79
-15
@@ -4,17 +4,19 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/ArtalkJS/Artalk/internal/config"
|
||||
"github.com/ArtalkJS/Artalk/internal/core"
|
||||
"github.com/ArtalkJS/Artalk/internal/log"
|
||||
"github.com/adrg/xdg"
|
||||
"github.com/artalkjs/artalk/v2/internal/config"
|
||||
"github.com/artalkjs/artalk/v2/internal/core"
|
||||
"github.com/artalkjs/artalk/v2/internal/log"
|
||||
"github.com/artalkjs/artalk/v2/internal/utils"
|
||||
"github.com/fatih/color"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var Version = config.Version + `/` + config.CommitHash
|
||||
|
||||
var Banner = `
|
||||
________ ________ _________ ________ ___ ___ __
|
||||
|\ __ \|\ __ \|\___ ___\\ __ \|\ \ |\ \|\ \
|
||||
@@ -24,7 +26,7 @@ var Banner = `
|
||||
\ \__\ \__\ \__\\ _\ \ \__\ \ \__\ \__\ \_______\ \__\\ \__\
|
||||
\|__|\|__|\|__|\|__| \|__| \|__|\|__|\|_______|\|__| \|__|
|
||||
|
||||
Artalk (` + Version + `)
|
||||
Artalk (` + config.VersionString() + `)
|
||||
|
||||
-> A Self-hosted Comment System.
|
||||
-> https://artalk.js.org
|
||||
@@ -52,7 +54,7 @@ func New() *ArtalkCmd {
|
||||
Use: "artalk",
|
||||
Short: "Artalk: A self-hosted comment system",
|
||||
Long: Banner,
|
||||
Version: Version,
|
||||
Version: config.VersionString(),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Println(Banner)
|
||||
fmt.Print("-------------------------------\n\n")
|
||||
@@ -66,16 +68,44 @@ func New() *ArtalkCmd {
|
||||
// Parse base flags
|
||||
cmd.eagerParseFlags()
|
||||
|
||||
// Change work directory
|
||||
if cmd.workDir != "" {
|
||||
if err := os.Chdir(cmd.workDir); err != nil {
|
||||
log.Fatal("Working directory change error: ", err)
|
||||
}
|
||||
// Resolve config from the invocation directory before an implicit data-dir chdir.
|
||||
if cfgFile, err := resolveConfigFileBeforeDataDir(cmd.cfgFile, cmd.workDir); err == nil {
|
||||
cmd.cfgFile = cfgFile
|
||||
} else {
|
||||
log.Fatal("Config path fail: ", err)
|
||||
}
|
||||
|
||||
// Init data directory (work directory)
|
||||
if workDir, err := initDataDir(cmd.workDir); err == nil {
|
||||
cmd.workDir = workDir
|
||||
} else {
|
||||
log.Fatal("Data directory fail: ", err)
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func resolveConfigFileBeforeDataDir(cfgFile string, workDir string) (string, error) {
|
||||
// An explicit work directory keeps the historical behavior: relative config
|
||||
// paths and default config discovery are resolved after changing to workDir.
|
||||
if workDir != "" {
|
||||
return cfgFile, nil
|
||||
}
|
||||
|
||||
if cfgFile == "" {
|
||||
cfgFile = config.RetrieveConfigFile()
|
||||
}
|
||||
if cfgFile == "" || filepath.IsAbs(cfgFile) {
|
||||
return cfgFile, nil
|
||||
}
|
||||
|
||||
absPath, err := filepath.Abs(cfgFile)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("resolve config file %q: %w", cfgFile, err)
|
||||
}
|
||||
return absPath, nil
|
||||
}
|
||||
|
||||
// Parses the global app flags before calling atk.RootCmd.Execute().
|
||||
// so we can have all flags ready for use on initialization.
|
||||
func (atk *ArtalkCmd) eagerParseFlags() error {
|
||||
@@ -169,16 +199,50 @@ func (atk *ArtalkCmd) Launch() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func initDataDir(workDir string) (string, error) {
|
||||
// If work directory is not specified, try to find it
|
||||
if workDir == "" {
|
||||
// If `data` in current directory exists, not change work directory
|
||||
if utils.CheckDirExist("./data") {
|
||||
// log.Warn("[DEPRECATED] The `data` directory is deprecated, please move to `~/.local/share/artalk/data`")
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Retrieve data directory assuming it's already exists
|
||||
workDir = config.RetrieveDataDir()
|
||||
}
|
||||
|
||||
// Create new data directory if not exists
|
||||
if workDir == "" {
|
||||
// default data path is `~/.local/share/artalk`
|
||||
workDir = path.Join(xdg.DataHome, "artalk")
|
||||
if err := utils.EnsureDir(workDir); err != nil {
|
||||
return "", fmt.Errorf("data directory creation error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Change work directory
|
||||
if workDir != "" {
|
||||
if err := os.Chdir(workDir); err != nil {
|
||||
return "", fmt.Errorf("working directory change error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return workDir, nil
|
||||
}
|
||||
|
||||
// Create new config instance by specific config filename
|
||||
func getConfig(cfgFile string) (*config.Config, error) {
|
||||
// Retrieve config file by default names when specific filename is empty
|
||||
// If config file is not specified, try to find it
|
||||
if cfgFile == "" {
|
||||
// Retrieve config file assuming it's already exists
|
||||
cfgFile = config.RetrieveConfigFile()
|
||||
}
|
||||
|
||||
// Generate new config file when retrieve failed
|
||||
// Generate new config file if not exists
|
||||
if cfgFile == "" {
|
||||
cfgFile = config.CONF_DEFAULT_FILENAMES[0]
|
||||
// default config path is `~/.config/artalk/artalk.yml`
|
||||
cfgFile = path.Join(xdg.ConfigHome, "artalk", config.CONF_DEFAULT_FILENAMES[0])
|
||||
core.Gen("config", cfgFile, false)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/adrg/xdg"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestResolveConfigFileBeforeDataDir(t *testing.T) {
|
||||
originalDir, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
originalDataHome := xdg.DataHome
|
||||
defer func() {
|
||||
require.NoError(t, os.Chdir(originalDir))
|
||||
xdg.DataHome = originalDataHome
|
||||
}()
|
||||
|
||||
invocationDir := t.TempDir()
|
||||
dataHome := t.TempDir()
|
||||
invocationDir, err = filepath.EvalSymlinks(invocationDir)
|
||||
require.NoError(t, err)
|
||||
dataHome, err = filepath.EvalSymlinks(dataHome)
|
||||
require.NoError(t, err)
|
||||
dataDir := filepath.Join(dataHome, "artalk")
|
||||
require.NoError(t, os.Mkdir(dataDir, 0o755))
|
||||
require.NoError(t, os.WriteFile(
|
||||
filepath.Join(invocationDir, "artalk.yml"),
|
||||
[]byte("timezone: UTC\n"),
|
||||
0o600,
|
||||
))
|
||||
require.NoError(t, os.Chdir(invocationDir))
|
||||
xdg.DataHome = dataHome
|
||||
|
||||
cfgFile, err := resolveConfigFileBeforeDataDir("", "")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, filepath.Join(invocationDir, "artalk.yml"), cfgFile)
|
||||
|
||||
workDir, err := initDataDir("")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, dataDir, workDir)
|
||||
|
||||
currentDir, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, dataDir, currentDir)
|
||||
|
||||
conf, err := getConfig(cfgFile)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "UTC", conf.TimeZone)
|
||||
}
|
||||
|
||||
func TestResolveConfigFileWithExplicitPaths(t *testing.T) {
|
||||
originalDir, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
defer func() { require.NoError(t, os.Chdir(originalDir)) }()
|
||||
|
||||
invocationDir := t.TempDir()
|
||||
invocationDir, err = filepath.EvalSymlinks(invocationDir)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, os.Chdir(invocationDir))
|
||||
|
||||
cfgFile, err := resolveConfigFileBeforeDataDir("custom.yml", "")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, filepath.Join(invocationDir, "custom.yml"), cfgFile)
|
||||
|
||||
cfgFile, err = resolveConfigFileBeforeDataDir("custom.yml", "./runtime")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "custom.yml", cfgFile)
|
||||
}
|
||||
+1
-1
@@ -4,7 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/ArtalkJS/Artalk/internal/log"
|
||||
"github.com/artalkjs/artalk/v2/internal/log"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
||||
+4
-4
@@ -7,10 +7,10 @@ import (
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/ArtalkJS/Artalk/internal/artransfer"
|
||||
"github.com/ArtalkJS/Artalk/internal/i18n"
|
||||
"github.com/ArtalkJS/Artalk/internal/log"
|
||||
"github.com/ArtalkJS/Artalk/internal/utils"
|
||||
"github.com/artalkjs/artalk/v2/internal/artransfer"
|
||||
"github.com/artalkjs/artalk/v2/internal/i18n"
|
||||
"github.com/artalkjs/artalk/v2/internal/log"
|
||||
"github.com/artalkjs/artalk/v2/internal/utils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/ArtalkJS/Artalk/internal/core"
|
||||
"github.com/artalkjs/artalk/v2/internal/core"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
||||
+33
-12
@@ -1,12 +1,13 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
"github.com/ArtalkJS/Artalk/internal/artransfer"
|
||||
"github.com/ArtalkJS/Artalk/internal/i18n"
|
||||
"github.com/ArtalkJS/Artalk/internal/log"
|
||||
"github.com/artalkjs/artalk/v2/internal/artransfer"
|
||||
"github.com/artalkjs/artalk/v2/internal/i18n"
|
||||
"github.com/artalkjs/artalk/v2/internal/log"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -16,24 +17,44 @@ func NewImportCommand(app *ArtalkCmd) *cobra.Command {
|
||||
Aliases: []string{},
|
||||
Short: "Artransfer import",
|
||||
Long: "\n# Artransfer - Import\n\n See the documentation to learn more: https://artalk.js.org/guide/transfer.html",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
parcelFile := args[0]
|
||||
if _, err := os.Stat(parcelFile); errors.Is(err, os.ErrNotExist) {
|
||||
log.Fatal(i18n.T("{{name}} not found", map[string]interface{}{"name": i18n.T("File")}))
|
||||
// Prepare params
|
||||
params := &artransfer.ImportParams{}
|
||||
|
||||
// Parse JSON parameters from flags
|
||||
if jsonParams, _ := cmd.Flags().GetString("parameters"); jsonParams != "" {
|
||||
if err := json.Unmarshal([]byte(jsonParams), params); err != nil {
|
||||
log.Fatal("Failed to parse JSON parameters: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
payload := args[1:]
|
||||
payload = append(payload, "json_file:"+parcelFile)
|
||||
// If JSON file or JSON data is not provided in flags, try to get it from arguments
|
||||
if params.JsonFile == "" && params.JsonData == "" {
|
||||
if len(args) == 0 {
|
||||
log.Fatal(i18n.T("{{name}} is required", map[string]interface{}{"name": "FILENAME"}))
|
||||
}
|
||||
params.JsonFile = args[0]
|
||||
}
|
||||
|
||||
// import Artrans
|
||||
params := artransfer.ArrToImportParams(payload)
|
||||
params.Assumeyes, _ = cmd.Flags().GetBool("assumeyes")
|
||||
// Parse flags to params
|
||||
if flagAssumeyes, err := cmd.Flags().GetBool("assumeyes"); err == nil {
|
||||
params.Assumeyes = flagAssumeyes
|
||||
}
|
||||
|
||||
// Check if file exists if JsonFile is provided
|
||||
if params.JsonFile != "" {
|
||||
if _, err := os.Stat(params.JsonFile); errors.Is(err, os.ErrNotExist) {
|
||||
log.Fatal(i18n.T("{{name}} not found", map[string]interface{}{"name": i18n.T("File")}))
|
||||
}
|
||||
}
|
||||
|
||||
// Run import
|
||||
artransfer.RunImportArtrans(app.Dao(), params)
|
||||
},
|
||||
}
|
||||
|
||||
flagPV(importCmd, "assumeyes", "y", false, "Automatically answer yes for all questions.")
|
||||
flagPV(importCmd, "parameters", "p", "", "JSON format parameters for the import command.")
|
||||
|
||||
return importCmd
|
||||
}
|
||||
+2
-2
@@ -3,8 +3,8 @@ package cmd
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/ArtalkJS/Artalk/internal/log"
|
||||
"github.com/ArtalkJS/Artalk/server"
|
||||
"github.com/artalkjs/artalk/v2/internal/log"
|
||||
"github.com/artalkjs/artalk/v2/server"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
+3
-3
@@ -5,9 +5,9 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/ArtalkJS/Artalk/internal/config"
|
||||
"github.com/ArtalkJS/Artalk/internal/i18n"
|
||||
"github.com/ArtalkJS/Artalk/internal/log"
|
||||
"github.com/artalkjs/artalk/v2/internal/config"
|
||||
"github.com/artalkjs/artalk/v2/internal/i18n"
|
||||
"github.com/artalkjs/artalk/v2/internal/log"
|
||||
"github.com/blang/semver"
|
||||
"github.com/rhysd/go-github-selfupdate/selfupdate"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
+2
-1
@@ -3,6 +3,7 @@ package cmd
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/artalkjs/artalk/v2/internal/config"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -11,7 +12,7 @@ func NewVersionCommand() *cobra.Command {
|
||||
Use: "version",
|
||||
Short: "Output version information",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Println("Artalk (" + Version + ")")
|
||||
fmt.Println("Artalk (" + config.VersionString() + ")")
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
BootModeKey: MODE_MINI_BOOT,
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
ignore:
|
||||
- "docs/*"
|
||||
@@ -5,6 +5,7 @@ debug: false
|
||||
locale: en
|
||||
timezone: Asia/Shanghai
|
||||
site_default: Default Site
|
||||
site_url: ""
|
||||
login_timeout: 259200
|
||||
db:
|
||||
type: sqlite
|
||||
@@ -145,7 +146,7 @@ admin_notify:
|
||||
- "GROUP_ID_1"
|
||||
auth:
|
||||
enabled: false
|
||||
anonymous: true
|
||||
anonymous: false
|
||||
callback: "http://localhost:23366/api/v2/auth/{provider}/callback"
|
||||
email:
|
||||
enabled: true
|
||||
@@ -219,6 +220,9 @@ auth:
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
domain: ""
|
||||
sso:
|
||||
enabled: false
|
||||
issuer: ""
|
||||
frontend:
|
||||
placeholder: ""
|
||||
noComment: ""
|
||||
@@ -229,8 +233,6 @@ frontend:
|
||||
voteDown: false
|
||||
uaBadge: false
|
||||
listSort: true
|
||||
pvEl: "#ArtalkPV"
|
||||
countEl: "#ArtalkCount"
|
||||
preview: true
|
||||
flatMode: auto
|
||||
darkMode: inherit
|
||||
@@ -238,7 +240,7 @@ frontend:
|
||||
nestSort: DATE_ASC
|
||||
gravatar:
|
||||
mirror: https://www.gravatar.com/avatar/
|
||||
params: d=mp&s=240
|
||||
params: sha256=1&d=mp&s=240
|
||||
pagination:
|
||||
pageSize: 20
|
||||
readMore: true
|
||||
|
||||
@@ -0,0 +1,456 @@
|
||||
# Dinleme adresi
|
||||
host: "0.0.0.0"
|
||||
|
||||
# Dinleme portu
|
||||
port: 23366
|
||||
|
||||
# Uygulama Anahtarı (JWT üretimi için)
|
||||
app_key: ""
|
||||
|
||||
# Hata ayıklama modu
|
||||
debug: false
|
||||
|
||||
# Dil (Unicode BCP 47 formatında) ["en", "zh-CN", "zh-TW", "ja", "fr", "ko", "ru", "tr"]
|
||||
# -- bkz. https://www.techonthenet.com/js/language_tags.php --
|
||||
locale: tr
|
||||
|
||||
# Zaman dilimi (IANA Zaman Dilimi Veritabanı formatında)
|
||||
# -- bkz. https://en.wikipedia.org/wiki/List_of_tz_database_time_zones --
|
||||
timezone: Europe/Istanbul
|
||||
|
||||
# Varsayılan site adı (uygulama ilk başlatıldığında oluşturulur)
|
||||
site_default: Varsayılan Site
|
||||
|
||||
# Varsayılan site URL'si
|
||||
site_url: ""
|
||||
|
||||
# Giriş zaman aşımı (saniye cinsinden)
|
||||
login_timeout: 259200
|
||||
|
||||
# Veritabanı
|
||||
db:
|
||||
# Veritabanı türü ["sqlite", "mysql", "pgsql", "mssql"]
|
||||
type: sqlite
|
||||
# Veritabanı dosyası (yalnızca SQLite için)
|
||||
file: ./data/artalk.db
|
||||
# Tablo öneki (örn. "atk_")
|
||||
table_prefix: ""
|
||||
# -- Aşağıdakiler SQLite için gerekli değildir --
|
||||
# Veritabanı adı
|
||||
name: artalk
|
||||
# Ana makine adresi
|
||||
host: localhost
|
||||
# Ana makine portu
|
||||
port: 3306
|
||||
# Veritabanı kullanıcısı
|
||||
user: root
|
||||
# Veritabanı şifresi
|
||||
password: ""
|
||||
# Veritabanı karakter seti
|
||||
charset: utf8mb4
|
||||
# SSL modunu etkinleştir
|
||||
ssl: false
|
||||
# Hazır İfadeler (Prepared Statement)
|
||||
prepare_stmt: true
|
||||
|
||||
# Web sunucusu
|
||||
http:
|
||||
# Gövde boyutu sınırı (birim: MB)
|
||||
body_limit: 100
|
||||
# Proxy Başlığı (güvenilir bir ters proxy veya CDN arkasındaysa kullanıcının gerçek IP'sini almak için `X-Forwarded-For` doldurun)
|
||||
proxy_header: ""
|
||||
|
||||
# Günlük Kaydı (Logging)
|
||||
log:
|
||||
# Günlük kaydını etkinleştir
|
||||
enabled: true
|
||||
# Günlük dosyası yolu
|
||||
filename: ./data/artalk.log
|
||||
|
||||
# Önbellek (Cache)
|
||||
cache:
|
||||
# Önbelleği etkinleştir
|
||||
enabled: false
|
||||
# Önbellek türü ["redis", "memcache", "builtin"]
|
||||
type: builtin
|
||||
# Önbellek geçerlilik süresi (dakika cinsinden)
|
||||
expires: 30
|
||||
# Önbellek ön yükleme (program başladığında önbelleği ısıtır)
|
||||
warm_up: false
|
||||
# -- Aşağıdakiler `builtin` (yerleşik) önbellek için gerekli değildir --
|
||||
# Önbellek sunucu adresi (örn. "localhost:6379")
|
||||
server: ""
|
||||
# Redis yapılandırması
|
||||
redis:
|
||||
# Bağlantı türü ["tcp", "unix"]
|
||||
network: tcp
|
||||
# Redis kullanıcı adı
|
||||
username: ""
|
||||
# Redis şifresi
|
||||
password: ""
|
||||
# Redis veritabanı numarası (örn. 0)
|
||||
db: 0
|
||||
|
||||
# Güvenilir alan adları
|
||||
# -- örn. ["https://artalk.example.com:23366"] sitenizin URL'sini buraya ekleyin --
|
||||
trusted_domains: []
|
||||
|
||||
# SSL
|
||||
ssl:
|
||||
# SSL'i etkinleştir
|
||||
enabled: false
|
||||
# Sertifika dosyası yolu
|
||||
# (örn. "/etc/letsencrypt/live/example.com/fullchain.pem")
|
||||
cert_path: ""
|
||||
# Anahtar dosyası yolu
|
||||
# (örn. "/etc/letsencrypt/live/example.com/privkey.pem")
|
||||
key_path: ""
|
||||
|
||||
# Moderatör
|
||||
# -- Yorumların herkese açık olmadan önce incelenmesi --
|
||||
moderator:
|
||||
# Varsayılan bekleme durumu (yeni yorumların yönetici tarafından onaylanması gerekir)
|
||||
pending_default: false
|
||||
# API isteği başarısız olduğunda engelle (API isteği başarısız olduğunda yorumların geçmesine izin vermek için false yapın)
|
||||
api_fail_block: false
|
||||
# Akismet Anahtarı
|
||||
# (Akismet spam önleme servisi, https://akismet.com)
|
||||
akismet_key: ""
|
||||
# Tencent Bulut İçerik Güvenliği
|
||||
# (Tencent Bulut İçerik Güvenliği ile yorumları otomatik inceleyin)
|
||||
# -- bkz. https://cloud.tencent.com/document/product/1124/64508 --
|
||||
tencent:
|
||||
enabled: false
|
||||
secret_id: ""
|
||||
secret_key: ""
|
||||
region: ap-guangzhou
|
||||
# Aliyun İçerik Güvenliği
|
||||
# (Aliyun İçerik Güvenliği ile yorumları otomatik inceleyin)
|
||||
# -- bkz. https://help.aliyun.com/document_detail/28417.html --
|
||||
aliyun:
|
||||
enabled: false
|
||||
access_key_id: ""
|
||||
access_key_secret: ""
|
||||
region: cn-shanghai
|
||||
# Kelime filtresi (yerel çevrimdışı sözlük)
|
||||
keywords:
|
||||
# Kelime filtresini etkinleştir
|
||||
enabled: false
|
||||
# Eşleşme olduğunda beklemeye al
|
||||
pending: false
|
||||
# Sözlük dosyası (birden fazla sözlük dosyasını destekler)
|
||||
files:
|
||||
- ./data/keywords_1.txt
|
||||
file_sep: "\n"
|
||||
replace_to: x
|
||||
|
||||
# Doğrulama (Captcha)
|
||||
captcha:
|
||||
# Doğrulamayı etkinleştir
|
||||
enabled: true
|
||||
# Her zaman doğrulama gerekli
|
||||
always: false
|
||||
# Doğrulama türü ["image", "turnstile", "recaptcha", "hcaptcha", "geetest"]
|
||||
captcha_type: image
|
||||
# İşlem sınırı
|
||||
# (doğrulamayı etkinleştirmek için gereken işlem sayısı)
|
||||
action_limit: 3
|
||||
# Sıfırlama Zaman Aşımı (işlem sayacını sıfırlama süresi. birim: saniye, devre dışı bırakmak için -1 yapın)
|
||||
action_reset: 60
|
||||
# Turnstile
|
||||
# (https://www.cloudflare.com/products/turnstile/)
|
||||
turnstile:
|
||||
site_key: ""
|
||||
secret_key: ""
|
||||
# reCaptcha
|
||||
# (https://www.google.com/recaptcha/about/)
|
||||
recaptcha:
|
||||
site_key: ""
|
||||
secret_key: ""
|
||||
# hCaptcha (https://www.hcaptcha.com/)
|
||||
hcaptcha:
|
||||
site_key: ""
|
||||
secret_key: ""
|
||||
# Geetest (https://www.geetest.com)
|
||||
geetest:
|
||||
captcha_id: ""
|
||||
captcha_key: ""
|
||||
|
||||
# Yükleme
|
||||
img_upload:
|
||||
# Resim yüklemeyi etkinleştir
|
||||
enabled: true
|
||||
# Resim depolama yolu
|
||||
path: ./data/artalk-img/
|
||||
# Resim boyutu sınırı (birim: MB)
|
||||
max_size: 5
|
||||
# Resim bağlantısı temel yolu (varsayılan: "/static/images/")
|
||||
public_path: null
|
||||
# Upgit yapılandırması
|
||||
upgit:
|
||||
# Upgit'i etkinleştir
|
||||
enabled: false
|
||||
# Komut satırı argümanları
|
||||
exec: upgit -c UPGIT_CONF_FILE_PATH -t /artalk-img
|
||||
# Yükleme başarılı olduktan sonra yerel resmi sil
|
||||
del_local: true
|
||||
|
||||
# E-posta
|
||||
email:
|
||||
# E-posta bildirimini etkinleştir
|
||||
enabled: false
|
||||
# Gönderim yöntemi ["smtp", "ali_dm", "sendmail"]
|
||||
send_type: smtp
|
||||
# Gönderen takma adı
|
||||
send_name: "{{reply_nick}}"
|
||||
# Gönderen e-posta adresi
|
||||
send_addr: noreply@example.com
|
||||
# E-posta konusu
|
||||
mail_subject: "[{{site_name}}] @{{reply_nick}} tarafından bir yanıt aldınız"
|
||||
# E-posta şablon dosyası (özel şablon kullanmak için dosya yolunu ayarlayın)
|
||||
mail_tpl: default
|
||||
# SMTP gönderimi (etkinleştirmek için gönderim yöntemini "smtp" yapın)
|
||||
smtp:
|
||||
# Gönderen e-posta sunucusu
|
||||
host: smtp.qq.com
|
||||
# E-posta portu
|
||||
port: 587
|
||||
# Kullanıcı adı
|
||||
username: example@qq.com
|
||||
# Şifre
|
||||
password: ""
|
||||
# Aliyun posta push
|
||||
# (etkinleştirmek için gönderim yöntemini "ali_dm" yapın; bkz: https://help.aliyun.com/document_detail/29444.html)
|
||||
ali_dm:
|
||||
access_key_id: ""
|
||||
access_key_secret: ""
|
||||
account_name: noreply@example.com
|
||||
|
||||
# Çoklu Push
|
||||
admin_notify:
|
||||
# Bildirim şablonu (özel şablon kullanmak için dosya yolunu ayarlayın)
|
||||
notify_tpl: default
|
||||
# Bekleyen yorumlar için hala bildirim gönder (yorumlar engellendiğinde bile bildirimler gönderilir)
|
||||
notify_pending: false
|
||||
# Gürültü modu
|
||||
# -- gürültü modu varsayılan olarak devre dışıdır. --
|
||||
# -- Bu seçenek `false` olarak ayarlandığında, yalnızca yöneticiye gönderilen mesajlar bildirilir, --
|
||||
# -- "kullanıcı A"nın "kullanıcı B"ye yanıt vermesi gibi iki kullanıcı arasındaki iletişim yöneticiye bildirilmez. --
|
||||
noise_mode: false
|
||||
# Yöneticiyi bildir
|
||||
email:
|
||||
# Etkinleştir (diğer push yöntemlerini kullanırken devre dışı bırakılabilir)
|
||||
enabled: true
|
||||
# E-posta konusu (yöneticiye gönderilen e-posta konusu)
|
||||
mail_subject: '[{{site_name}}] "{{page_title}}" sayfası yeni bir yoruma sahip'
|
||||
# Yönetici e-posta şablon dosyası (özel şablon kullanmak için dosya yolunu ayarlayın)
|
||||
mail_tpl: ""
|
||||
# Telegram
|
||||
telegram:
|
||||
enabled: false
|
||||
api_token: ""
|
||||
receivers:
|
||||
- 7777777
|
||||
# Bark
|
||||
bark:
|
||||
enabled: false
|
||||
server: http://day.app/xxxxxxx/
|
||||
# Lark
|
||||
lark:
|
||||
enabled: false
|
||||
webhook_url: ""
|
||||
# Mesaj türü ["text", "card"]
|
||||
msg_type: "text"
|
||||
# WebHook
|
||||
webhook:
|
||||
enabled: false
|
||||
url: ""
|
||||
# DingTalk
|
||||
ding_talk:
|
||||
enabled: false
|
||||
token: ""
|
||||
secret: ""
|
||||
# Slack
|
||||
slack:
|
||||
enabled: false
|
||||
oauth_token: ""
|
||||
receivers:
|
||||
- "CHANNEL_ID"
|
||||
# LINE
|
||||
line:
|
||||
enabled: false
|
||||
channel_secret: ""
|
||||
channel_access_token: ""
|
||||
receivers:
|
||||
- USER_ID_1
|
||||
- GROUP_ID_1
|
||||
|
||||
# Sosyal Giriş
|
||||
auth:
|
||||
# Sosyal Girişi Etkinleştir
|
||||
enabled: false
|
||||
# Anonim yorum yapmaya izin ver (Doğrulamayı atlamaya izin ver, yalnızca anonim bir takma ad ve e-posta doldurmak yeterlidir)
|
||||
anonymous: false
|
||||
# Geri dönüş URL'si (https://example.com/api/v2/auth/{provider}/callback)
|
||||
callback: "http://localhost:23366/api/v2/auth/{provider}/callback"
|
||||
# E-posta
|
||||
email:
|
||||
# E-posta şifre girişini etkinleştir
|
||||
enabled: true
|
||||
# Doğrulama e-postası konusu
|
||||
verify_subject: "Kodunuz - {{code}}"
|
||||
# Doğrulama e-postası şablonu (özel şablon kullanmak için dosya yolunu ayarlayın)
|
||||
verify_tpl: default
|
||||
# GitHub
|
||||
github:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# GitLab
|
||||
gitlab:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# Gitea
|
||||
gitea:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# Google
|
||||
google:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# Mastodon
|
||||
mastodon:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# Twitter
|
||||
twitter:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# Facebook
|
||||
facebook:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# Discord
|
||||
discord:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# Steam
|
||||
steam:
|
||||
enabled: false
|
||||
api_key: ""
|
||||
# Apple
|
||||
apple:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# Microsoft
|
||||
microsoft:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# WeChat
|
||||
wechat:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# Tiktok
|
||||
tiktok:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# Slack
|
||||
slack:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# Line
|
||||
line:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# Patreon
|
||||
patreon:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# Auth0
|
||||
auth0:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
domain: ""
|
||||
# SSO (Harici OIDC erişim belirteçlerini Artalk oturumuyla değiştirir, IdP'nin /userinfo uç noktası ile doğrulanır)
|
||||
# -- Gömülü uygulama zaten OIDC çalıştırıyorsa ve Artalk'ın kendi açılır giriş ekranını göstermeden bu oturumu devralmasını istiyorsanız kullanın. --
|
||||
sso:
|
||||
# SSO belirteç değişimini etkinleştir
|
||||
enabled: false
|
||||
# OIDC issuer URL'si (örn. "tenant.auth0.com" veya "https://tenant.auth0.com")
|
||||
issuer: ""
|
||||
|
||||
# Kullanıcı Arayüzü Ayarları
|
||||
frontend:
|
||||
# Yorum kutusu yer tutucu metni
|
||||
placeholder: ""
|
||||
# Yorum olmadığında görüntülenecek metin
|
||||
noComment: ""
|
||||
# Gönder düğmesi metni
|
||||
sendBtn: ""
|
||||
# Hareketli yorum kutusu
|
||||
editorTravel: true
|
||||
# İfadeler (Emoticonlar)
|
||||
emoticons: https://cdn.jsdelivr.net/gh/ArtalkJS/Emoticons/grps/default.json
|
||||
# Oy verme düğmesi
|
||||
vote: true
|
||||
# Beğenmeme düğmesi
|
||||
voteDown: false
|
||||
# Kullanıcı UA rozeti
|
||||
uaBadge: false
|
||||
# Yorum sıralama
|
||||
listSort: true
|
||||
# Editör gerçek zamanlı önizleme
|
||||
preview: true
|
||||
# Düzleştirme modu ["auto", true, false]
|
||||
flatMode: auto
|
||||
# Karanlık mod ["inherit", "auto"]
|
||||
darkMode: inherit
|
||||
# Maksimum yuvalama seviyesi
|
||||
nestMax: 2
|
||||
# Yuvalama yorum sıralama kuralları ["DATE_ASC", "DATE_DESC", "VOTE_UP_DESC"]
|
||||
nestSort: DATE_ASC
|
||||
# Gravatar
|
||||
gravatar:
|
||||
# API URL'si
|
||||
mirror: https://www.gravatar.com/avatar/
|
||||
# API parametreleri
|
||||
params: sha256=1&d=mp&s=240
|
||||
# Yorum sayfalama
|
||||
pagination:
|
||||
# Sayfa başına yorum sayısı
|
||||
pageSize: 20
|
||||
# Daha fazla yükle modu (sayfalama çubuğunu kullanmak için devre dışı bırakın)
|
||||
readMore: true
|
||||
# Kaydırmalı yükleme
|
||||
autoLoad: true
|
||||
# İçerik yükseklik sınırı
|
||||
heightLimit:
|
||||
# Yorum içeriği yükseklik sınırı (birim: px)
|
||||
content: 300
|
||||
# Alt yorum alanı yükseklik sınırı (birim: px)
|
||||
children: 400
|
||||
# Kaydırılabilir (yükseklik sınırı alanı kaydırılabilir olsun)
|
||||
scrollable: false
|
||||
# Resim geç yükleme [false, "native", "data-src"]
|
||||
imgLazyLoad: false
|
||||
# İstek zaman aşımı (birim: ms)
|
||||
reqTimeout: 15000
|
||||
# Sürüm kontrolü
|
||||
versionCheck: true
|
||||
# Eklentiler
|
||||
pluginURLs: []
|
||||
+14
-8
@@ -10,7 +10,7 @@ app_key: ""
|
||||
# Debug mode
|
||||
debug: false
|
||||
|
||||
# Language (follow Unicode BCP 47) ["en", "zh-CN", "zh-TW", "jp", "fr", "ko", "ru"]
|
||||
# Language (follow Unicode BCP 47) ["en", "zh-CN", "zh-TW", "ja", "fr", "ko", "ru", "tr"]
|
||||
# -- see https://www.techonthenet.com/js/language_tags.php --
|
||||
locale: en
|
||||
|
||||
@@ -21,6 +21,9 @@ timezone: Asia/Shanghai
|
||||
# Default site name (create when app is first launched)
|
||||
site_default: Default Site
|
||||
|
||||
# Default site url
|
||||
site_url: ""
|
||||
|
||||
# Login timeout (in seconds)
|
||||
login_timeout: 259200
|
||||
|
||||
@@ -287,7 +290,7 @@ auth:
|
||||
# Enable Social Login
|
||||
enabled: false
|
||||
# Allow anonymous commenting (Allow skipping verification, only fill in an anonymous nickname and email)
|
||||
anonymous: true
|
||||
anonymous: false
|
||||
# Callback URL (https://example.com/api/v2/auth/{provider}/callback)
|
||||
callback: "http://localhost:23366/api/v2/auth/{provider}/callback"
|
||||
# Email
|
||||
@@ -383,6 +386,13 @@ auth:
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
domain: ""
|
||||
# SSO (Exchange external OIDC access tokens for Artalk login sessions, verified via the IdP's /userinfo endpoint)
|
||||
# -- Use this when the embedding app already runs OIDC and you want Artalk to inherit that session without its own popup login UI. --
|
||||
sso:
|
||||
# Enable SSO token exchange
|
||||
enabled: false
|
||||
# OIDC issuer URL (e.g. "tenant.auth0.com" or "https://tenant.auth0.com")
|
||||
issuer: ""
|
||||
|
||||
# UI Settings
|
||||
frontend:
|
||||
@@ -398,16 +408,12 @@ frontend:
|
||||
emoticons: https://cdn.jsdelivr.net/gh/ArtalkJS/Emoticons/grps/default.json
|
||||
# Vote button
|
||||
vote: true
|
||||
# Dislike button
|
||||
# Vote down button
|
||||
voteDown: false
|
||||
# User UA badge
|
||||
uaBadge: false
|
||||
# Comment sorting
|
||||
listSort: true
|
||||
# Page PV binding element
|
||||
pvEl: "#ArtalkPV"
|
||||
# Comment count binding element
|
||||
countEl: "#ArtalkCount"
|
||||
# Editor real-time preview
|
||||
preview: true
|
||||
# Flatten mode ["auto", true, false]
|
||||
@@ -423,7 +429,7 @@ frontend:
|
||||
# API URL
|
||||
mirror: https://www.gravatar.com/avatar/
|
||||
# API parameters
|
||||
params: d=mp&s=240
|
||||
params: sha256=1&d=mp&s=240
|
||||
# Comment pagination
|
||||
pagination:
|
||||
# Number of comments per page
|
||||
|
||||
@@ -10,7 +10,7 @@ app_key: ""
|
||||
# 调试模式
|
||||
debug: false
|
||||
|
||||
# 语言 ["en", "zh-CN", "zh-TW", "jp", "fr", "ko", "ru"]
|
||||
# 语言 ["en", "zh-CN", "zh-TW", "ja", "fr", "ko", "ru"]
|
||||
locale: zh-CN
|
||||
|
||||
# 时间区域
|
||||
@@ -19,6 +19,9 @@ timezone: Asia/Shanghai
|
||||
# 默认站点名
|
||||
site_default: 默认站点
|
||||
|
||||
# 默认站点地址
|
||||
site_url: ""
|
||||
|
||||
# 登录有效时长 (单位:秒)
|
||||
login_timeout: 259200
|
||||
|
||||
@@ -85,7 +88,8 @@ cache:
|
||||
db: 0
|
||||
|
||||
# 可信域名
|
||||
trusted_domains: [] # 例如:["https://artalk.example.com:23366"]
|
||||
# -- 例如:["https://artalk.example.com:23366"] --
|
||||
trusted_domains: []
|
||||
|
||||
# SSL
|
||||
ssl:
|
||||
@@ -97,12 +101,14 @@ ssl:
|
||||
key_path: ""
|
||||
|
||||
# 管理员账户
|
||||
admin_users:
|
||||
# - name: "admin"
|
||||
# email: "admin@example.com"
|
||||
# password: "" # 支持 bcrypt 或 md5 加密,如:"(md5)50c21190c6e4e5418c6a90d2b5031119"
|
||||
# badge_name: "管理员"
|
||||
# badge_color: "#0083FF"
|
||||
# --
|
||||
# - name: "admin"
|
||||
# email: "admin@example.com"
|
||||
# password: "" # 支持 bcrypt 或 md5 加密,如:"(bcrypt)$2y$10$ti4vZYIrxVN8rLcYXVgXCO.GJND0dyI49r7IoF3xqIx8bBRmIBZRm"
|
||||
# badge_name: "管理员"
|
||||
# badge_color: "#0083FF"
|
||||
# --
|
||||
admin_users: []
|
||||
|
||||
# 评论审核
|
||||
moderator:
|
||||
@@ -292,7 +298,7 @@ auth:
|
||||
# 启用社交登录
|
||||
enabled: false
|
||||
# 允许匿名评论 (允许跳过验证,仅填写匿名的昵称和邮箱)
|
||||
anonymous: true
|
||||
anonymous: false
|
||||
# 回调地址 (https://example.com/api/v2/auth/{provider}/callback)
|
||||
callback: "http://localhost:23366/api/v2/auth/{provider}/callback"
|
||||
# Email
|
||||
@@ -388,6 +394,13 @@ auth:
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
domain: ""
|
||||
# SSO (用外部 OIDC 身份提供商的访问令牌换取 Artalk 登录会话,通过调用 IdP 的 /userinfo 端点校验)
|
||||
# -- 当嵌入页面已运行 OIDC 且希望 Artalk 复用该登录态、不显示自身弹窗登录界面时使用。 --
|
||||
sso:
|
||||
# 启用 SSO 令牌交换
|
||||
enabled: false
|
||||
# OIDC issuer 地址 (如 "tenant.auth0.com" 或 "https://tenant.auth0.com")
|
||||
issuer: ""
|
||||
|
||||
# 界面配置
|
||||
frontend:
|
||||
@@ -409,10 +422,6 @@ frontend:
|
||||
uaBadge: false
|
||||
# 评论排序功能
|
||||
listSort: true
|
||||
# 页面 PV 绑定元素
|
||||
pvEl: "#ArtalkPV"
|
||||
# 评论数绑定元素
|
||||
countEl: "#ArtalkCount"
|
||||
# 编辑器实时预览功能
|
||||
preview: true
|
||||
# 平铺模式 ["auto", true, false]
|
||||
@@ -426,9 +435,9 @@ frontend:
|
||||
# 头像 Gravatar
|
||||
gravatar:
|
||||
# API 地址
|
||||
mirror: https://cravatar.cn/avatar/
|
||||
mirror: https://weavatar.com/avatar/
|
||||
# API 参数
|
||||
params: d=mp&s=240
|
||||
params: sha256=1&d=mp&s=240
|
||||
# 评论分页
|
||||
pagination:
|
||||
# 每页评论数
|
||||
|
||||
@@ -0,0 +1,464 @@
|
||||
# 伺服器地址
|
||||
host: "0.0.0.0"
|
||||
|
||||
# 伺服器端口
|
||||
port: 23366
|
||||
|
||||
# 加密密鑰
|
||||
app_key: ""
|
||||
|
||||
# 偵錯模式
|
||||
debug: false
|
||||
|
||||
# 語言 ["en", "zh-CN", "zh-TW", "ja", "fr", "ko", "ru"]
|
||||
locale: zh-TW
|
||||
|
||||
# 時區
|
||||
timezone: Asia/Taipei
|
||||
|
||||
# 預設站點名稱
|
||||
site_default: 預設站點
|
||||
|
||||
# 預設站點地址
|
||||
site_url: ""
|
||||
|
||||
# 登入有效時長 (單位:秒)
|
||||
login_timeout: 259200
|
||||
|
||||
# 資料庫
|
||||
db:
|
||||
# 資料庫類型 ["sqlite", "mysql", "pgsql", "mssql"]
|
||||
type: sqlite
|
||||
# 資料庫文件 (僅 SQLite 資料庫需填寫)
|
||||
file: ./data/artalk.db
|
||||
# 資料庫名稱
|
||||
name: artalk
|
||||
# 資料庫地址
|
||||
host: localhost
|
||||
# 資料庫端口
|
||||
port: 3306
|
||||
# 資料庫帳號
|
||||
user: root
|
||||
# 資料庫密碼
|
||||
password: ""
|
||||
# 編碼格式
|
||||
charset: utf8mb4
|
||||
# 表前綴 (例如:"atk_")
|
||||
table_prefix: ""
|
||||
# 啟用 SSL
|
||||
ssl: false
|
||||
# 預編譯語句
|
||||
prepare_stmt: true
|
||||
|
||||
# 伺服器
|
||||
http:
|
||||
# 請求體大小限制 (單位:MB)
|
||||
body_limit: 100
|
||||
# 代理標頭名 (當使用 CDN 時填寫 `X-Forwarded-For` 獲取用戶真實 IP)
|
||||
proxy_header: ""
|
||||
|
||||
# 日誌
|
||||
log:
|
||||
# 啟用日誌
|
||||
enabled: true
|
||||
# 日誌文件路徑
|
||||
filename: ./data/artalk.log
|
||||
|
||||
# 快取
|
||||
cache:
|
||||
# 啟用快取
|
||||
enabled: false
|
||||
# 快取類型 ["redis", "memcache", "builtin"]
|
||||
type: builtin
|
||||
# 快取過期時間 (單位:分鐘)
|
||||
expires: 30
|
||||
# 快取啟動預熱 (程式啟動時預熱快取)
|
||||
warm_up: false
|
||||
# 快取伺服器地址 (例如:"localhost:6379")
|
||||
server: ""
|
||||
# Redis 配置
|
||||
redis:
|
||||
# 連接方式 ["tcp", "unix"]
|
||||
network: tcp
|
||||
# 用戶名
|
||||
username: ""
|
||||
# 密碼
|
||||
password: ""
|
||||
# 資料庫編號 (例如使用零號資料庫填寫 0)
|
||||
db: 0
|
||||
|
||||
# 可信網域
|
||||
# -- 例如:["https://artalk.example.com:23366"] --
|
||||
trusted_domains: []
|
||||
|
||||
# SSL
|
||||
ssl:
|
||||
# 啟用 SSL
|
||||
enabled: false
|
||||
# 憑證文件路徑
|
||||
cert_path: ""
|
||||
# 密鑰文件路徑
|
||||
key_path: ""
|
||||
|
||||
# 管理員帳戶
|
||||
# --
|
||||
# - name: "admin"
|
||||
# email: "admin@example.com"
|
||||
# password: "" # 支持 bcrypt 或 md5 加密,如:"(bcrypt)$2y$10$ti4vZYIrxVN8rLcYXVgXCO.GJND0dyI49r7IoF3xqIx8bBRmIBZRm"
|
||||
# badge_name: "管理員"
|
||||
# badge_color: "#0083FF"
|
||||
# --
|
||||
admin_users: []
|
||||
|
||||
# 評論審核
|
||||
moderator:
|
||||
# 預設待審 (發表新評論需要後台人工審核後才能顯示)
|
||||
pending_default: false
|
||||
# API 請求錯誤時攔截 (關閉此項當請求錯誤時讓評論放行)
|
||||
api_fail_block: false
|
||||
# Akismet Key
|
||||
# (Akismet 反垃圾服務,https://akismet.com)
|
||||
akismet_key: ""
|
||||
# 騰訊雲文本內容安全
|
||||
# (https://cloud.tencent.com/document/product/1124/64508)
|
||||
tencent:
|
||||
enabled: false
|
||||
secret_id: ""
|
||||
secret_key: ""
|
||||
region: ap-guangzhou
|
||||
# 阿里雲內容安全
|
||||
# (https://help.aliyun.com/document_detail/28417.html)
|
||||
aliyun:
|
||||
enabled: false
|
||||
access_key_id: ""
|
||||
access_key_secret: ""
|
||||
region: cn-shanghai
|
||||
# 關鍵詞過濾 (本地離線詞庫)
|
||||
keywords:
|
||||
enabled: false
|
||||
# 匹配成功設為待審狀態
|
||||
pending: false
|
||||
# 詞庫文件 (支持多個詞庫文件)
|
||||
files:
|
||||
- ./data/詞庫_1.txt
|
||||
# 詞庫文件內容分割符 (例如填寫 "\n" 文件中一行一個關鍵詞)
|
||||
file_sep: "\n"
|
||||
# 替換字符
|
||||
replace_to: x
|
||||
|
||||
# 驗證碼
|
||||
captcha:
|
||||
# 啟用驗證碼
|
||||
enabled: true
|
||||
# 總是需要驗證碼
|
||||
always: false
|
||||
# 驗證類型 ["image", "turnstile", "recaptcha", "hcaptcha", "geetest"]
|
||||
captcha_type: image
|
||||
# 激活驗證碼所需操作次數
|
||||
action_limit: 3
|
||||
# 重置操作計數器超時 (單位:s, 設為 -1 不重置)
|
||||
action_reset: 60
|
||||
# Turnstile
|
||||
# (https://www.cloudflare.com/products/turnstile/)
|
||||
turnstile:
|
||||
site_key: ""
|
||||
secret_key: ""
|
||||
# reCaptcha
|
||||
# (https://www.google.com/recaptcha/about/)
|
||||
recaptcha:
|
||||
site_key: ""
|
||||
secret_key: ""
|
||||
# hCaptcha (https://www.hcaptcha.com/)
|
||||
hcaptcha:
|
||||
site_key: ""
|
||||
secret_key: ""
|
||||
# Geetest 極驗 (https://www.geetest.com)
|
||||
geetest:
|
||||
captcha_id: ""
|
||||
captcha_key: ""
|
||||
|
||||
# IP 屬地
|
||||
ip_region:
|
||||
# 啟用 IP 屬地展示
|
||||
enabled: false
|
||||
# 數據文件路徑 (.xdb 格式)
|
||||
db_path: ./data/ip2region.xdb
|
||||
# 顯示精度 ["province", "city", "country"]
|
||||
precision: province
|
||||
|
||||
# 圖片上傳
|
||||
img_upload:
|
||||
# 啟用圖片上傳
|
||||
enabled: true
|
||||
# 圖片存放路徑
|
||||
path: ./data/artalk-img/
|
||||
# 圖片大小限制 (單位:MB)
|
||||
max_size: 5
|
||||
# 圖片連結基礎路徑 (默認為 "/static/images/")
|
||||
public_path: null
|
||||
# Upgit 配置
|
||||
# (使用 Upgit 將圖片上傳到 GitHub 或圖床:https://github.com/pluveto/upgit)
|
||||
upgit:
|
||||
# 啟用 Upgit
|
||||
enabled: false
|
||||
# 命令行參數
|
||||
exec: upgit -c <upgit配置文件路徑> -t /artalk-img
|
||||
# 上傳後刪除本地的圖片
|
||||
del_local: true
|
||||
|
||||
# 郵件通知
|
||||
email:
|
||||
# 啟用郵件通知
|
||||
enabled: false
|
||||
# 發送方式 ["smtp", "ali_dm", "sendmail"]
|
||||
send_type: smtp
|
||||
# 發信人暱稱
|
||||
send_name: "{{reply_nick}}"
|
||||
# 發信人地址
|
||||
send_addr: noreply@example.com
|
||||
# 郵件標題
|
||||
mail_subject: "[{{site_name}}] 您收到了來自 @{{reply_nick}} 的回覆"
|
||||
# 郵件模板文件 (填入文件路徑使用自定義模板)
|
||||
mail_tpl: default
|
||||
# SMTP 發送 (啟用請將發送方式設為 "smtp")
|
||||
smtp:
|
||||
# 發件地址
|
||||
host: smtp.qq.com
|
||||
# 發件端口
|
||||
port: 587
|
||||
# 用戶名
|
||||
username: example@qq.com
|
||||
# 密碼
|
||||
password: ""
|
||||
# 阿里雲郵件推送
|
||||
# (啟用請將發送方式設為 "ali_dm";參考:https://help.aliyun.com/document_detail/29444.html)
|
||||
ali_dm:
|
||||
access_key_id: ""
|
||||
access_key_secret: ""
|
||||
account_name: noreply@example.com
|
||||
|
||||
# 多元推送
|
||||
admin_notify:
|
||||
# 通知模板 (填入文件路徑使用自定義模板)
|
||||
notify_tpl: default
|
||||
# 待審評論仍然發送通知 (當評論被攔截時仍然發送通知)
|
||||
notify_pending: false
|
||||
# 嘈雜模式
|
||||
noise_mode: false
|
||||
# 郵件通知管理員
|
||||
email:
|
||||
# 開啟 (當使用其他推送方式時,可以關閉管理員郵件通知)
|
||||
enabled: true
|
||||
# 郵件標題 (發送給管理員的郵件標題)
|
||||
mail_subject: "[{{site_name}}] 您的文章「{{page_title}}」有新回覆"
|
||||
# 管理員郵件模板文件 (填入文件路徑使用自定義模板)
|
||||
mail_tpl: ""
|
||||
# Telegram
|
||||
telegram:
|
||||
enabled: false
|
||||
api_token: ""
|
||||
receivers:
|
||||
- 7777777
|
||||
# Bark
|
||||
bark:
|
||||
enabled: false
|
||||
server: http://day.app/xxxxxxx/
|
||||
# 飛書
|
||||
lark:
|
||||
enabled: false
|
||||
webhook_url: ""
|
||||
# 消息類型 ["text", "card"]
|
||||
msg_type: "text"
|
||||
# WebHook
|
||||
webhook:
|
||||
enabled: false
|
||||
url: ""
|
||||
# 釘釘
|
||||
ding_talk:
|
||||
enabled: false
|
||||
token: ""
|
||||
secret: ""
|
||||
# Slack
|
||||
slack:
|
||||
enabled: false
|
||||
oauth_token: ""
|
||||
receivers:
|
||||
- CHANNEL_ID
|
||||
# LINE
|
||||
line:
|
||||
enabled: false
|
||||
channel_secret: ""
|
||||
channel_access_token: ""
|
||||
receivers:
|
||||
- USER_ID_1
|
||||
- GROUP_ID_1
|
||||
|
||||
# 社交登錄
|
||||
auth:
|
||||
# 啟用社交登錄
|
||||
enabled: false
|
||||
# 允許匿名評論 (允許跳過驗證,僅填寫匿名的暱稱和郵箱)
|
||||
anonymous: false
|
||||
# 回調地址 (https://example.com/api/v2/auth/{provider}/callback)
|
||||
callback: "http://localhost:23366/api/v2/auth/{provider}/callback"
|
||||
# Email
|
||||
email:
|
||||
# 啟用郵箱密碼登錄
|
||||
enabled: true
|
||||
# 郵箱驗證郵件標題
|
||||
verify_subject: "您的驗證碼是 - {{code}}"
|
||||
# 郵箱驗證郵件模板 (填入文件路徑使用自定義模板)
|
||||
verify_tpl: default
|
||||
# GitHub
|
||||
github:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# GitLab
|
||||
gitlab:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# Gitea
|
||||
gitea:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# Google
|
||||
google:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# Mastodon
|
||||
mastodon:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# Twitter
|
||||
twitter:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# Facebook
|
||||
facebook:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# Discord
|
||||
discord:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# Steam
|
||||
steam:
|
||||
enabled: false
|
||||
api_key: ""
|
||||
# Apple
|
||||
apple:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# Microsoft
|
||||
microsoft:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# 微信
|
||||
wechat:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# Tiktok
|
||||
tiktok:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# Slack
|
||||
slack:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# Line
|
||||
line:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# Patreon
|
||||
patreon:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# Auth0
|
||||
auth0:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
domain: ""
|
||||
# SSO (用外部 OIDC 身分提供商的存取權杖換取 Artalk 登入工作階段,透過呼叫 IdP 的 /userinfo 端點驗證)
|
||||
# -- 當嵌入頁面已執行 OIDC 且希望 Artalk 沿用該登入狀態、不顯示自身彈窗登入介面時使用。 --
|
||||
sso:
|
||||
# 啟用 SSO 權杖交換
|
||||
enabled: false
|
||||
# OIDC issuer 位址 (如 "tenant.auth0.com" 或 "https://tenant.auth0.com")
|
||||
issuer: ""
|
||||
|
||||
# 介面配置
|
||||
frontend:
|
||||
# 評論框占位文字
|
||||
placeholder: ""
|
||||
# 無評論顯示文字
|
||||
noComment: ""
|
||||
# 發送按鈕文字
|
||||
sendBtn: ""
|
||||
# 評論框穿梭
|
||||
editorTravel: true
|
||||
# 表情包
|
||||
emoticons: https://cdn.jsdelivr.net/gh/ArtalkJS/Emoticons/grps/default.json
|
||||
# 投票按鈕
|
||||
vote: true
|
||||
# 反對按鈕
|
||||
voteDown: false
|
||||
# 用戶 UA 徽章
|
||||
uaBadge: false
|
||||
# 評論排序功能
|
||||
listSort: true
|
||||
# 編輯器實時預覽功能
|
||||
preview: true
|
||||
# 平鋪模式 ["auto", true, false]
|
||||
flatMode: auto
|
||||
# 夜間模式 ["inherit", "auto"]
|
||||
darkMode: inherit
|
||||
# 最大嵌套層數
|
||||
nestMax: 2
|
||||
# 嵌套評論排序規則 ["DATE_ASC", "DATE_DESC", "VOTE_UP_DESC"]
|
||||
nestSort: DATE_ASC
|
||||
# 頭像 Gravatar
|
||||
gravatar:
|
||||
# API 地址
|
||||
mirror: https://weavatar.com/avatar/
|
||||
# API 參數
|
||||
params: sha256=1&d=mp&s=240
|
||||
# 評論分頁
|
||||
pagination:
|
||||
# 每頁評論數
|
||||
pageSize: 20
|
||||
# 加載更多模式 (關閉則使用分頁條)
|
||||
readMore: true
|
||||
# 滾動加載
|
||||
autoLoad: true
|
||||
# 內容限高
|
||||
heightLimit:
|
||||
# 評論內容限高 (單位:px)
|
||||
content: 300
|
||||
# 子評論區域限高 (單位:px)
|
||||
children: 400
|
||||
# 滾動限高 (允許限高區域滾動)
|
||||
scrollable: false
|
||||
# 圖片懶加載 [false, "native", "data-src"]
|
||||
imgLazyLoad: false
|
||||
# 請求超時 (單位:毫秒)
|
||||
reqTimeout: 15000
|
||||
# 版本檢測
|
||||
versionCheck: true
|
||||
# 插件
|
||||
pluginURLs: []
|
||||
+7
-1
@@ -1,8 +1,9 @@
|
||||
version: "3.5"
|
||||
version: "3.8"
|
||||
services:
|
||||
artalk:
|
||||
container_name: artalk
|
||||
image: artalk/artalk-go
|
||||
restart: unless-stopped
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: Dockerfile
|
||||
@@ -10,3 +11,8 @@ services:
|
||||
- 8080:23366
|
||||
volumes:
|
||||
- ./data:/data
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
- ATK_LOCALE=zh-CN
|
||||
- ATK_SITE_DEFAULT=Artalk 的博客
|
||||
- ATK_SITE_URL=https://example.com
|
||||
@@ -1,2 +1,3 @@
|
||||
.vitepress/dist/**
|
||||
.vitepress/cache/**
|
||||
.vitepress/.temp/**
|
||||
@@ -1,254 +0,0 @@
|
||||
import { defineConfig } from 'vitepress'
|
||||
import iterator from 'markdown-it-for-inline'
|
||||
import * as Version from '../code/ArtalkVersion.json'
|
||||
|
||||
export default defineConfig({
|
||||
title: 'Artalk',
|
||||
description: '一款简洁的自托管评论系统',
|
||||
lang: 'zh-CN',
|
||||
|
||||
head: [
|
||||
['link', { rel: 'icon', href: '/favicon.png' }],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
name: 'viewport',
|
||||
content:
|
||||
'width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, target-densitydpi=device-dpi',
|
||||
},
|
||||
],
|
||||
// light gallery
|
||||
[
|
||||
'link',
|
||||
{
|
||||
href: 'https://cdnjs.cloudflare.com/ajax/libs/lightgallery/2.3.0/css/lightgallery.css',
|
||||
rel: 'stylesheet',
|
||||
},
|
||||
],
|
||||
[
|
||||
'script',
|
||||
{
|
||||
src: 'https://cdnjs.cloudflare.com/ajax/libs/lightgallery/2.3.0/lightgallery.min.js',
|
||||
},
|
||||
],
|
||||
// katex
|
||||
[
|
||||
'link',
|
||||
{
|
||||
href: 'https://unpkg.com/katex@0.15.3/dist/katex.min.css',
|
||||
rel: 'stylesheet',
|
||||
},
|
||||
],
|
||||
['script', { src: 'https://unpkg.com/katex@0.15.3/dist/katex.min.js' }],
|
||||
// ['script', { src: 'https://unpkg.com/@artalk/plugin-katex/dist/artalk-plugin-katex.js' }],
|
||||
],
|
||||
|
||||
lastUpdated: true,
|
||||
|
||||
markdown: {
|
||||
// @link https://github.com/shikijs/shiki
|
||||
theme: {
|
||||
light: 'github-light',
|
||||
dark: 'github-dark',
|
||||
},
|
||||
config: (md) => {
|
||||
md.use(iterator, 'artalk_version', 'text', function (tokens, idx) {
|
||||
tokens[idx].content = tokens[idx].content.replace(
|
||||
/:ArtalkVersion:/g,
|
||||
Version.latest,
|
||||
)
|
||||
})
|
||||
md.use(iterator, 'artalk_version_link', 'link_open', (tokens, idx) => {
|
||||
const href = tokens[idx].attrGet('href')
|
||||
tokens[idx].attrSet(
|
||||
'href',
|
||||
href.replace(/:ArtalkVersion:/g, Version.latest),
|
||||
)
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
locales: {
|
||||
root: {
|
||||
label: '简体中文',
|
||||
lang: 'zh',
|
||||
link: 'https://artalk.js.org/',
|
||||
},
|
||||
en: {
|
||||
label: 'English',
|
||||
lang: 'en',
|
||||
link: 'https://artalk-js-org.translate.goog/guide/intro?_x_tr_sl=auto&_x_tr_tl=en-US&_x_tr_hl=en-US&_x_tr_pto=wapp',
|
||||
},
|
||||
},
|
||||
|
||||
themeConfig: {
|
||||
sidebar: {
|
||||
'/guide/': [
|
||||
{
|
||||
text: '快速开始',
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: '项目介绍', link: '/guide/intro.md' },
|
||||
{ text: '程序部署', link: '/guide/deploy.md' },
|
||||
{ text: '数据迁移', link: '/guide/transfer.md' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: '核心指南',
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: '侧边栏', link: '/guide/frontend/sidebar.md' },
|
||||
{ text: '邮件通知', link: '/guide/backend/email.md' },
|
||||
{ text: '多元推送', link: '/guide/backend/admin_notify.md' },
|
||||
{ text: '社交登录', link: '/guide/frontend/auth.md' },
|
||||
{ text: '评论审核', link: '/guide/backend/moderator.md' },
|
||||
{ text: '验证码', link: '/guide/backend/captcha.md' },
|
||||
{ text: '图片上传', link: '/guide/backend/img-upload.md' },
|
||||
{ text: '账户与多站点', link: '/guide/backend/multi-site.md' },
|
||||
{ text: '解析相对路径', link: '/guide/backend/relative-path.md' },
|
||||
{ text: '如何配置界面', link: '/guide/backend/fe-control.md' },
|
||||
{ text: 'English (i18n)', link: '/guide/frontend/i18n.md' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: '进阶指南',
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: '表情包', link: '/guide/frontend/emoticons.md' },
|
||||
{ text: '浏览量统计', link: '/guide/frontend/pv.md' },
|
||||
{ text: 'Latex', link: '/guide/frontend/latex.md' },
|
||||
{ text: '图片灯箱', link: '/guide/frontend/lightbox.md' },
|
||||
{ text: '图片懒加载', link: '/guide/frontend/img-lazy-load.md' },
|
||||
{ text: 'IP 属地', link: '/guide/frontend/ip-region.md' },
|
||||
{ text: '精简版本', link: '/guide/frontend/artalk-lite.md' },
|
||||
{ text: '置入博客', link: '/guide/frontend/import-blog.md' },
|
||||
{ text: '置入框架', link: '/guide/frontend/import-framework.md' },
|
||||
{ text: '前端 API', link: '/develop/fe-api.md' },
|
||||
{ text: '开发文档', link: '/develop/index.md' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: '配置文档',
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: '后端配置', link: '/guide/backend/config.md' },
|
||||
{ text: '前端配置', link: '/guide/frontend/config.md' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: '部署说明',
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: '守护进程', link: '/guide/backend/daemon.md' },
|
||||
{ text: '反向代理', link: '/guide/backend/reverse-proxy.md' },
|
||||
{ text: '编译构建', link: '/guide/backend/build.md' },
|
||||
{ text: '程序升级', link: '/guide/backend/update.md' },
|
||||
{ text: 'Docker', link: '/guide/backend/docker.md' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: '更多内容',
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: '安全防范', link: '/guide/security.md' },
|
||||
{ text: '扩展阅读', link: '/guide/extras.md' },
|
||||
{ text: '案例展示', link: '/guide/cases.md' },
|
||||
{ text: '关于我们', link: '/guide/about.md' },
|
||||
],
|
||||
},
|
||||
],
|
||||
'/develop/': [
|
||||
{
|
||||
text: '开发文档',
|
||||
items: [
|
||||
{ text: '开发说明', link: '/develop/index.md' },
|
||||
{ text: '前端 API', link: '/develop/fe-api.md' },
|
||||
{ text: '前端 Event', link: '/develop/event.md' },
|
||||
{ text: '插件开发', link: '/develop/plugs.md' },
|
||||
{
|
||||
text: '🔬 HTTP API',
|
||||
link: 'https://artalk.js.org/http-api.html',
|
||||
},
|
||||
{
|
||||
text: '📖 Contributing Guide',
|
||||
link: 'https://github.com/ArtalkJS/Artalk/blob/master/CONTRIBUTING.md',
|
||||
},
|
||||
{
|
||||
text: '🔖 Code of Conduct',
|
||||
link: 'https://github.com/ArtalkJS/Artalk/blob/master/CODE_OF_CONDUCT.md',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
nav: [
|
||||
// NavbarItem
|
||||
{
|
||||
text: '介绍',
|
||||
link: '/guide/intro',
|
||||
},
|
||||
{
|
||||
text: '部署',
|
||||
link: '/guide/deploy',
|
||||
},
|
||||
{
|
||||
text: '配置',
|
||||
link: '/guide/backend/config',
|
||||
},
|
||||
{
|
||||
text: '迁移',
|
||||
link: '/guide/transfer',
|
||||
},
|
||||
{
|
||||
text: '案例',
|
||||
link: '/guide/cases',
|
||||
},
|
||||
{
|
||||
text: '开发',
|
||||
link: '/develop/',
|
||||
},
|
||||
// NavbarGroup
|
||||
{
|
||||
text: '传送',
|
||||
items: [
|
||||
{
|
||||
text: '代码仓库',
|
||||
link: 'https://github.com/ArtalkJS/Artalk',
|
||||
},
|
||||
// {
|
||||
// text: "文档镜像 (国内)",
|
||||
// link: "https://artalk-docs.qwqaq.com",
|
||||
// },
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
socialLinks: [
|
||||
{ icon: 'github', link: 'https://github.com/ArtalkJS/Artalk' },
|
||||
],
|
||||
|
||||
search: {
|
||||
provider: 'algolia',
|
||||
options: {
|
||||
appId: '2WNJ32WVTY',
|
||||
apiKey: '6c6ebc345a87b738264f19095b78c91c',
|
||||
indexName: 'artalk-js',
|
||||
searchParameters: {
|
||||
facetFilters: ['lang:zh-CN'],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
editLink: {
|
||||
pattern: 'https://github.com/ArtalkJS/Artalk/edit/master/docs/:path',
|
||||
text: '完善文档',
|
||||
},
|
||||
},
|
||||
|
||||
vite: {
|
||||
server: {
|
||||
open: '/guide/intro.html',
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,127 @@
|
||||
import { defineConfig } from 'vitepress'
|
||||
|
||||
export const en = defineConfig({
|
||||
lang: 'en-US',
|
||||
description: 'A Self-hosted Comment System.',
|
||||
|
||||
themeConfig: {
|
||||
sidebar: {
|
||||
'/en/guide/': [
|
||||
{
|
||||
text: 'Quick Start',
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: 'Introduction', link: '/en/guide/intro.md' },
|
||||
{ text: 'Getting Started', link: '/en/guide/deploy.md' },
|
||||
{ text: 'Data Migration', link: '/en/guide/transfer.md' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Basic Usage',
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: 'Sidebar', link: '/en/guide/frontend/sidebar.md' },
|
||||
{ text: 'Email Notification', link: '/en/guide/backend/email.md' },
|
||||
{ text: 'Multi-channel Notification', link: '/en/guide/backend/admin_notify.md' },
|
||||
{ text: 'Social Login', link: '/en/guide/frontend/auth.md' },
|
||||
{ text: 'Comment Moderation', link: '/en/guide/backend/moderator.md' },
|
||||
{ text: 'Captcha', link: '/en/guide/backend/captcha.md' },
|
||||
{ text: 'Image Upload', link: '/en/guide/backend/img-upload.md' },
|
||||
{ text: 'Admins and Multi-Site', link: '/en/guide/backend/multi-site.md' },
|
||||
{ text: 'Resolve Relative Path', link: '/en/guide/backend/relative-path.md' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Extensions',
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: 'Emoticons', link: '/en/guide/frontend/emoticons.md' },
|
||||
{ text: 'Page View Statistics', link: '/en/guide/frontend/pv.md' },
|
||||
{ text: 'LaTeX', link: '/en/guide/frontend/latex.md' },
|
||||
{ text: 'Image Lightbox', link: '/en/guide/frontend/lightbox.md' },
|
||||
{ text: 'Image Lazy Load', link: '/en/guide/frontend/img-lazy-load.md' },
|
||||
{ text: 'Voting', link: '/zh/guide/frontend/voting.md' },
|
||||
{ text: 'IP Region', link: '/en/guide/frontend/ip-region.md' },
|
||||
{ text: 'Localization', link: '/en/guide/frontend/i18n.md' },
|
||||
{ text: 'Development Documentation', link: '/en/develop/index.md' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Configurations',
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: 'Environment Variables', link: '/en/guide/env.md' },
|
||||
{ text: 'Configuration File', link: '/en/guide/backend/config.md' },
|
||||
{ text: 'UI Configuration', link: '/en/guide/frontend/config.md' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Deployment details',
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: 'Daemon Process', link: '/en/guide/backend/daemon.md' },
|
||||
{ text: 'Reverse Proxy', link: '/en/guide/backend/reverse-proxy.md' },
|
||||
{
|
||||
text: 'Compile Source',
|
||||
link: 'https://github.com/ArtalkJS/Artalk/blob/master/CONTRIBUTING.md',
|
||||
},
|
||||
{ text: 'Program Upgrade', link: '/en/guide/backend/update.md' },
|
||||
{ text: 'Docker', link: '/en/guide/backend/docker.md' },
|
||||
],
|
||||
},
|
||||
],
|
||||
'/en/develop/': [
|
||||
{
|
||||
text: 'Development Documentation',
|
||||
items: [
|
||||
{ text: 'Development Instructions', link: '/en/develop/index.md' },
|
||||
{
|
||||
text: 'Contribution Guide',
|
||||
link: 'https://github.com/ArtalkJS/Artalk/blob/master/CONTRIBUTING.md',
|
||||
target: '_blank',
|
||||
},
|
||||
{ text: 'Import to Blog', link: '/en/develop/import-blog.md' },
|
||||
{ text: 'Import to Framework', link: '/en/develop/import-framework.md' },
|
||||
{ text: 'Frontend API', link: '/en/develop/fe-api.md' },
|
||||
{ text: 'Frontend Events', link: '/en/develop/event.md' },
|
||||
{ text: 'Frontend Types', link: 'https://artalk.js.org/typedoc/' },
|
||||
{ text: 'Plugin Development', link: '/en/develop/plugin.md' },
|
||||
{ text: 'Compatibility', link: '/en/develop/compatibility.md' },
|
||||
{
|
||||
text: 'HTTP API',
|
||||
link: 'https://artalk.js.org/http-api.html',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
nav: [
|
||||
{
|
||||
text: 'Introduction',
|
||||
link: '/en/guide/intro',
|
||||
},
|
||||
{
|
||||
text: 'Installation',
|
||||
link: '/en/guide/deploy',
|
||||
},
|
||||
{
|
||||
text: 'Configuration',
|
||||
link: '/en/guide/backend/config',
|
||||
},
|
||||
{
|
||||
text: 'Migration',
|
||||
link: '/en/guide/transfer',
|
||||
},
|
||||
{
|
||||
text: 'Development',
|
||||
link: '/en/develop/',
|
||||
},
|
||||
],
|
||||
|
||||
editLink: {
|
||||
pattern: 'https://github.com/ArtalkJS/Artalk/edit/master/docs/docs/:path',
|
||||
text: 'Improve this document',
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,12 @@
|
||||
import { defineConfig } from 'vitepress'
|
||||
import { shared } from './shared'
|
||||
import { zh } from './zh'
|
||||
import { en } from './en'
|
||||
|
||||
export default defineConfig({
|
||||
...shared,
|
||||
locales: {
|
||||
en: { label: 'English', ...en },
|
||||
zh: { label: '简体中文', ...zh },
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,98 @@
|
||||
import { defineConfig } from 'vitepress'
|
||||
import {
|
||||
GitChangelog,
|
||||
GitChangelogMarkdownSection,
|
||||
} from '@nolebase/vitepress-plugin-git-changelog/vite'
|
||||
import * as Version from '../../code/ArtalkVersion.json'
|
||||
|
||||
export const shared = defineConfig({
|
||||
title: 'Artalk',
|
||||
|
||||
/* prettier-ignore */
|
||||
head: [
|
||||
['link', { rel: 'icon', type: 'image/png', href: '/favicon.png' }],
|
||||
['meta', { property: 'og:type', content: 'website' }],
|
||||
['meta', { property: 'og:locale', content: 'en' }],
|
||||
['meta', { property: 'og:title', content: 'Artalk - A Self-hosted Comment System'}],
|
||||
['meta', { property: 'og:site_name', content: 'Artalk' }],
|
||||
['meta', { property: 'og:image', content: 'https://artalk.js.org/assets/images/artalk-banner.png' }],
|
||||
['meta', { property: 'og:url', content: 'https://artalk.js.org/' }],
|
||||
['meta', { name: 'viewport', content: 'width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, target-densitydpi=device-dpi' }],
|
||||
],
|
||||
|
||||
markdown: {
|
||||
// @link https://github.com/shikijs/shiki
|
||||
theme: {
|
||||
light: 'github-light',
|
||||
dark: 'github-dark',
|
||||
},
|
||||
config: (md) => {
|
||||
const renderVersion = (c: string) => String(c).replace(/:ArtalkVersion:/g, Version.latest)
|
||||
md.core.ruler.push('artalk_version', (state) => {
|
||||
state.tokens?.forEach((token) => {
|
||||
if (token.type === 'inline') {
|
||||
token.children?.forEach((child) => {
|
||||
if (['text', 'link_open', 'code_inline'].includes(child.type))
|
||||
child.content = renderVersion(child.content)
|
||||
})
|
||||
} else if (token.type === 'fence') {
|
||||
token.content = renderVersion(token.content)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
sitemap: {
|
||||
hostname: 'https://artalk.js.org',
|
||||
transformItems(items) {
|
||||
return items.filter((item) => !item.url.includes('migration'))
|
||||
},
|
||||
},
|
||||
|
||||
lastUpdated: true,
|
||||
cleanUrls: false,
|
||||
metaChunk: true,
|
||||
|
||||
themeConfig: {
|
||||
socialLinks: [{ icon: 'github', link: 'https://github.com/ArtalkJS/Artalk' }],
|
||||
|
||||
search: {
|
||||
provider: 'algolia',
|
||||
options: {
|
||||
appId: '2WNJ32WVTY',
|
||||
apiKey: '6c6ebc345a87b738264f19095b78c91c',
|
||||
indexName: 'artalk-js',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
vite: {
|
||||
server: {
|
||||
open: '/zh/guide/intro.html',
|
||||
},
|
||||
plugins: [
|
||||
GitChangelog({
|
||||
repoURL: () => 'https://github.com/ArtalkJS/Artalk',
|
||||
mapAuthors: [
|
||||
{
|
||||
name: 'qwqcode',
|
||||
username: 'qwqcode',
|
||||
mapByEmailAliases: ['qwqcode@gmail.com', '22412567+qwqcode@users.noreply.github.com'],
|
||||
},
|
||||
{
|
||||
name: 'pluveto',
|
||||
username: 'pluveto',
|
||||
mapByEmailAliases: ['i@pluvet.com', '50045289+pluveto@users.noreply.github.com'],
|
||||
},
|
||||
{
|
||||
name: 'Mr.Hope',
|
||||
username: 'Mister-Hope',
|
||||
mapByEmailAliases: ['mister-hope@outlook.com'],
|
||||
},
|
||||
],
|
||||
}),
|
||||
GitChangelogMarkdownSection(),
|
||||
],
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,135 @@
|
||||
import { defineConfig } from 'vitepress'
|
||||
|
||||
export const zh = defineConfig({
|
||||
lang: 'zh-CN',
|
||||
description: '由 Golang 驱动的自托管评论系统',
|
||||
|
||||
themeConfig: {
|
||||
sidebar: {
|
||||
'/zh/guide/': [
|
||||
{
|
||||
text: '快速开始',
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: '项目介绍', link: '/zh/guide/intro.md' },
|
||||
{ text: '程序部署', link: '/zh/guide/deploy.md' },
|
||||
{ text: '数据迁移', link: '/zh/guide/transfer.md' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: '核心指南',
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: '侧边栏', link: '/zh/guide/frontend/sidebar.md' },
|
||||
{ text: '邮件通知', link: '/zh/guide/backend/email.md' },
|
||||
{ text: '多元推送', link: '/zh/guide/backend/admin_notify.md' },
|
||||
{ text: '社交登录', link: '/zh/guide/frontend/auth.md' },
|
||||
{ text: '评论审核', link: '/zh/guide/backend/moderator.md' },
|
||||
{ text: '验证码', link: '/zh/guide/backend/captcha.md' },
|
||||
{ text: '图片上传', link: '/zh/guide/backend/img-upload.md' },
|
||||
{ text: '账户与多站点', link: '/zh/guide/backend/multi-site.md' },
|
||||
{ text: '解析相对路径', link: '/zh/guide/backend/relative-path.md' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: '进阶指南',
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: '表情包', link: '/zh/guide/frontend/emoticons.md' },
|
||||
{ text: '浏览量统计', link: '/zh/guide/frontend/pv.md' },
|
||||
{ text: 'LaTeX', link: '/zh/guide/frontend/latex.md' },
|
||||
{ text: '图片灯箱', link: '/zh/guide/frontend/lightbox.md' },
|
||||
{ text: '图片懒加载', link: '/zh/guide/frontend/img-lazy-load.md' },
|
||||
{ text: '投票功能', link: '/zh/guide/frontend/voting.md' },
|
||||
{ text: 'IP 属地', link: '/zh/guide/frontend/ip-region.md' },
|
||||
{ text: '多语言', link: '/zh/guide/frontend/i18n.md' },
|
||||
{ text: '开发文档', link: '/zh/develop/index.md' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: '配置文档',
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: '环境变量', link: '/zh/guide/env.md' },
|
||||
{ text: '配置文件', link: '/zh/guide/backend/config.md' },
|
||||
{ text: '界面配置', link: '/zh/guide/frontend/config.md' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: '部署说明',
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: '守护进程', link: '/zh/guide/backend/daemon.md' },
|
||||
{ text: '反向代理', link: '/zh/guide/backend/reverse-proxy.md' },
|
||||
{ text: '编译构建', link: '/zh/develop/contributing.md' },
|
||||
{ text: '程序升级', link: '/zh/guide/backend/update.md' },
|
||||
{ text: 'Docker', link: '/zh/guide/backend/docker.md' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: '更多内容',
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: '安全防范', link: '/zh/guide/security.md' },
|
||||
{ text: '扩展阅读', link: '/zh/guide/extras.md' },
|
||||
{ text: '案例展示', link: '/zh/guide/cases.md' },
|
||||
{ text: '关于我们', link: '/zh/guide/about.md' },
|
||||
],
|
||||
},
|
||||
],
|
||||
'/zh/develop/': [
|
||||
{
|
||||
text: '开发文档',
|
||||
items: [
|
||||
{ text: '开发说明', link: '/zh/develop/index.md' },
|
||||
{ text: '贡献指南', link: '/zh/develop/contributing.md' },
|
||||
{ text: '置入博客', link: '/zh/develop/import-blog.md' },
|
||||
{ text: '置入框架', link: '/zh/develop/import-framework.md' },
|
||||
{ text: '前端 API', link: '/zh/develop/fe-api.md' },
|
||||
{ text: '前端 Events', link: '/zh/develop/event.md' },
|
||||
{ text: '前端 Types', link: 'https://artalk.js.org/typedoc/' },
|
||||
{ text: '插件开发', link: '/zh/develop/plugin.md' },
|
||||
{ text: '兼容性', link: '/zh/develop/compatibility.md' },
|
||||
{
|
||||
text: 'HTTP API',
|
||||
link: 'https://artalk.js.org/http-api.html',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
nav: [
|
||||
// NavbarItem
|
||||
{
|
||||
text: '介绍',
|
||||
link: '/zh/guide/intro',
|
||||
},
|
||||
{
|
||||
text: '部署',
|
||||
link: '/zh/guide/deploy',
|
||||
},
|
||||
{
|
||||
text: '配置',
|
||||
link: '/zh/guide/backend/config',
|
||||
},
|
||||
{
|
||||
text: '迁移',
|
||||
link: '/zh/guide/transfer',
|
||||
},
|
||||
{
|
||||
text: '案例',
|
||||
link: '/zh/guide/cases',
|
||||
},
|
||||
{
|
||||
text: '开发',
|
||||
link: '/zh/develop/',
|
||||
},
|
||||
],
|
||||
|
||||
editLink: {
|
||||
pattern: 'https://github.com/ArtalkJS/Artalk/edit/master/docs/docs/:path',
|
||||
text: '完善文档',
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -6,7 +6,11 @@
|
||||
import { watch, nextTick, ref, onMounted, onUnmounted } from 'vue'
|
||||
import { useData, useRouter } from 'vitepress'
|
||||
import Artalk from 'artalk'
|
||||
import 'artalk/dist/Artalk.css'
|
||||
import { ArtalkKatexPlugin } from '@artalk/plugin-katex'
|
||||
import { ArtalkLightboxPlugin } from '@artalk/plugin-lightbox'
|
||||
import 'lightgallery/css/lightgallery.css'
|
||||
import 'katex/dist/katex.min.css'
|
||||
import 'artalk/Artalk.css'
|
||||
|
||||
const el = ref<HTMLElement | null>(null)
|
||||
|
||||
@@ -36,12 +40,24 @@ onUnmounted(() => {
|
||||
})
|
||||
|
||||
function initArtalk(conf: any) {
|
||||
Artalk.use(ArtalkKatexPlugin)
|
||||
Artalk.use(ArtalkLightboxPlugin, {
|
||||
lightGallery: {
|
||||
lib: async () => (await import('lightgallery')).default,
|
||||
},
|
||||
})
|
||||
|
||||
Artalk.use((ctx) => {
|
||||
ctx.on('mounted', () => {
|
||||
// TODO: Optimize the locale setting, which is overwritten by the remote server config
|
||||
const locale = document.documentElement.lang.includes('zh') ? 'zh-CN' : 'en'
|
||||
if (locale !== ctx.getConf().locale) ctx.updateConf({ locale })
|
||||
})
|
||||
})
|
||||
|
||||
artalk = Artalk.init({
|
||||
el: el.value,
|
||||
emoticons: '/assets/emoticons/default.json',
|
||||
gravatar: {
|
||||
mirror: 'https://cravatar.cn/avatar/',
|
||||
},
|
||||
...conf,
|
||||
})
|
||||
|
||||
@@ -50,7 +66,10 @@ function initArtalk(conf: any) {
|
||||
|
||||
function getConfByPage() {
|
||||
return {
|
||||
pageKey: 'https://artalk.js.org' + router.route.path,
|
||||
pageKey: `https://artalk.js.org/${router.route.path
|
||||
.replace(/^\/+/, '')
|
||||
.replace(/^zh\//, '')
|
||||
.replace(/\.html$/, '')}.html`,
|
||||
pageTitle: page.value.title,
|
||||
server: 'https://artalk.qwqaq.com',
|
||||
site: 'ArtalkDocs',
|
||||
@@ -58,28 +77,6 @@ function getConfByPage() {
|
||||
}
|
||||
|
||||
function loadExtraFuncs() {
|
||||
// 图片灯箱插件
|
||||
artalk.on('list-loaded', () => {
|
||||
document
|
||||
.querySelectorAll('.atk-comment .atk-content')
|
||||
.forEach(($content) => {
|
||||
const imgEls = $content.querySelectorAll<HTMLImageElement>(
|
||||
'img:not([atk-emoticon]):not([atk-lightbox])',
|
||||
)
|
||||
imgEls.forEach((imgEl) => {
|
||||
imgEl.setAttribute('atk-lightbox', '')
|
||||
const linkEl = document.createElement('a')
|
||||
linkEl.setAttribute('class', 'atk-img-link')
|
||||
linkEl.setAttribute('href', imgEl.src)
|
||||
linkEl.setAttribute('data-src', imgEl.src)
|
||||
linkEl.append(imgEl.cloneNode())
|
||||
imgEl.replaceWith(linkEl)
|
||||
})
|
||||
// @ts-ignore
|
||||
if (imgEls.length) lightGallery($content, { selector: '.atk-img-link' })
|
||||
})
|
||||
})
|
||||
|
||||
// 夜间模式
|
||||
const darkMode = document.querySelector('html').classList.contains('dark')
|
||||
artalk.setDarkMode(darkMode)
|
||||
@@ -88,8 +85,7 @@ function loadExtraFuncs() {
|
||||
mList.forEach((m) => {
|
||||
if (m.attributeName !== 'class') return
|
||||
|
||||
// @ts-ignore
|
||||
const darkMode = m.target.classList.contains('dark')
|
||||
const darkMode = (<HTMLElement>m.target).classList.contains('dark')
|
||||
artalk.setDarkMode(darkMode)
|
||||
})
|
||||
}).observe(document.querySelector('html'), { attributes: true })
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<iframe
|
||||
id="artransferIframe"
|
||||
src="https://artransfer.netlify.app/?iframe=1"
|
||||
style="width: 100%; height: 520px; border: 0"
|
||||
id="artransferIframe"
|
||||
></iframe>
|
||||
</template>
|
||||
|
||||
@@ -28,8 +28,7 @@ export default defineComponent({
|
||||
mList.forEach((m) => {
|
||||
if (m.attributeName !== 'class') return
|
||||
|
||||
// @ts-ignore
|
||||
const darkMode = m.target.classList.contains('dark')
|
||||
const darkMode = (<HTMLElement>m.target).classList.contains('dark')
|
||||
setArtransferIframeDarkMode(darkMode)
|
||||
})
|
||||
}).observe(document.querySelector('html'), { attributes: true })
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
<script setup>
|
||||
import DefaultTheme from 'vitepress/theme'
|
||||
import { onMounted } from 'vue'
|
||||
import { inBrowser, useData } from 'vitepress'
|
||||
import { watchEffect } from 'vue'
|
||||
import './style.scss'
|
||||
|
||||
const { Layout } = DefaultTheme
|
||||
|
||||
const { lang } = useData()
|
||||
watchEffect(() => {
|
||||
if (inBrowser)
|
||||
document.cookie = `nf_lang=${lang.value}; expires=Mon, 1 Jan 2030 00:00:00 UTC; path=/`
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import DefaultTheme from 'vitepress/theme'
|
||||
import { Theme } from 'vitepress'
|
||||
import { NolebaseGitChangelogPlugin } from '@nolebase/vitepress-plugin-git-changelog/client'
|
||||
import Layout from './Layout.vue'
|
||||
import Artalk from './Artalk.vue'
|
||||
import Artransfer from './Artransfer.vue'
|
||||
import { Theme } from 'vitepress'
|
||||
|
||||
import '@nolebase/vitepress-plugin-git-changelog/client/style.css'
|
||||
|
||||
export default {
|
||||
...DefaultTheme,
|
||||
@@ -12,6 +15,7 @@ export default {
|
||||
enhanceApp({ app, router, siteData }) {
|
||||
app.component('Artransfer', Artransfer)
|
||||
app.component('Artalk', Artalk)
|
||||
app.use(NolebaseGitChangelogPlugin)
|
||||
|
||||
// app is the Vue 3 app instance from `createApp()`.
|
||||
// router is VitePress' custom router. `siteData` is
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"latest": "2.8.6"
|
||||
"latest": "2.10.0"
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
<!-- CSS -->
|
||||
<link href="https://unpkg.com/artalk@2.4.4/dist/Artalk.css" rel="stylesheet">
|
||||
<link href="http://localhost:8080/dist/Artalk.css" rel="stylesheet">
|
||||
|
||||
<!-- JS -->
|
||||
<script src="https://unpkg.com/artalk@2.4.4/dist/Artalk.js"></script>
|
||||
<script src="http://localhost:8080/dist/Artalk.js"></script>
|
||||
|
||||
<!-- Artalk -->
|
||||
<div id="Comments"></div>
|
||||
<script>
|
||||
Artalk.init({
|
||||
el: '#Comments', // 绑定元素的 Selector
|
||||
pageKey: '/post/1', // 固定链接 (留空自动获取)
|
||||
pageKey: '/post/1', // 固定链接
|
||||
pageTitle: '关于引入 Artalk 这档子事', // 页面标题 (留空自动获取)
|
||||
server: 'http://localhost:8080', // 后端地址
|
||||
site: 'Artalk 的博客', // 你的站点名
|
||||
|
||||
@@ -1,592 +0,0 @@
|
||||
# HTTP API
|
||||
|
||||
::: warning
|
||||
该 HTTP API 文档已过时
|
||||
:::
|
||||
|
||||
::: tip 无特殊说明时 API 调用响应格式
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
| --------- | ------- | --------------------------------------------- |
|
||||
| `success` | Boolean | 接口调用结果 |
|
||||
| `msg` | String | 返回消息,出错时此字段包含错误消息 |
|
||||
| `data` | Any | 返回数据,可能为 Object / String / Array 类型 |
|
||||
|
||||
:::
|
||||
|
||||
## Normal API
|
||||
|
||||
### 评论新增
|
||||
|
||||
**POST** `/api/add`
|
||||
|
||||
**参数**
|
||||
|
||||
| 字段名 | 数据类型 | 必需 | 说明 |
|
||||
| ------------ | -------- | :--: | ------------------------------------------------- |
|
||||
| `name` | String | 是 | 待新增评论者昵称 |
|
||||
| `email` | String | 是 | 待新增评论者邮箱 |
|
||||
| `link` | String | 否 | 待新增评论者链接 |
|
||||
| `content` | String | 是 | 待新增评论内容 |
|
||||
| `rid` | Number | 否 | 待新增评论为回复评论时传入父评论 `ID`,否则为 `0` |
|
||||
| `page_key` | String | 是 | 待新增评论目标页面唯一标识符 |
|
||||
| `page_title` | String | 否 | 待新增评论目标页面标题 |
|
||||
| `token` | String | 否 | 评论请求 Token |
|
||||
| `site_name` | String | 否 | 待新增评论目标站点名称 |
|
||||
|
||||
**响应**
|
||||
|
||||
| 字段名 | 数据类型 | 默认 | 说明 |
|
||||
| ------------------------------ | -------- | :----: | -------------------------------------- |
|
||||
| `success` | Boolean | `true` | 评论新增成功 |
|
||||
| `data.comment` | Object | - | 评论数据 |
|
||||
| `data.comment.id` | Number | - | 评论 ID |
|
||||
| `data.comment.content` | String | - | 评论正文 |
|
||||
| `data.comment.nick` | String | - | 评论者昵称 |
|
||||
| `data.comment.email_encrypted` | String | - | 评论者邮箱 MD5 加密值 |
|
||||
| `data.comment.link` | String | - | 评论者链接 |
|
||||
| `data.comment.ua` | String | - | 评论者 User-Agent |
|
||||
| `data.comment.date` | String | - | 评论时间,格式为 `1970-01-01 00:00:00` |
|
||||
| `data.comment.is_collapsed` | Boolean | - | 评论是否折叠 |
|
||||
| `data.comment.is_pending` | Boolean | - | 评论是否待审 |
|
||||
| `data.comment.is_allow_reply` | Boolean | - | 评论是否允许回复 |
|
||||
| `data.comment.rid` | Number | `0` | 待新增评论为回复评论时返回父评论 `ID` |
|
||||
| `data.comment.badge_name` | String | - | 评论者徽章文字 |
|
||||
| `data.comment.badge_color` | String | - | 评论者徽章颜色 |
|
||||
| `data.comment.visible` | Boolean | `true` | 评论是否可见 |
|
||||
| `data.comment.vote_up` | Number | `0` | 评论赞同数 |
|
||||
| `data.comment.vote_down` | Number | `0` | 评论反对数 |
|
||||
| `data.comment.page_key` | String | - | 评论所在页面唯一标识符 |
|
||||
| `data.comment.site_name` | String | - | 评论所在站点名称 |
|
||||
|
||||
### 评论获取
|
||||
|
||||
**POST** `/api/get`
|
||||
|
||||
**参数**
|
||||
|
||||
| 字段名 | 数据类型 | 必需 | 说明 |
|
||||
| ----------- | -------- | :--: | -------------------------- |
|
||||
| `page_key` | String | 是 | 待获取评论页面的唯一标识符 |
|
||||
| `limit` | Number | 否 | 待获取评论的数量限制 |
|
||||
| `offset` | Number | 否 | 待获取评论的起始位置偏移 |
|
||||
| `type` | String | 否 | 获取指定类型的评论 |
|
||||
| `name` | String | 否 | 获取指定昵称的评论 |
|
||||
| `email` | String | 否 | 获取指定邮箱的评论 |
|
||||
| `site_name` | String | 否 | 获取指定站点名称的评论 |
|
||||
| `flat_mode` | Boolean | 否 | 待获取评论是否平铺模式 |
|
||||
|
||||
**响应**
|
||||
|
||||
| 字段名 | 数据类型 | 默认 | 说明 |
|
||||
| ---------------------- | -------- | :----: | ------------------------------ |
|
||||
| `success` | Boolean | `true` | 评论获取成功 |
|
||||
| `data.comments` | Array | - | 页面评论数据 |
|
||||
| `data.total` | Number | - | 页面评论总数(包括所有子评论) |
|
||||
| `data.total_parents` | Number | - | 页面评论中父级评论总数 |
|
||||
| `data.page` | Object | - | 页面数据 |
|
||||
| `data.page.id` | Number | - | 页面 ID |
|
||||
| `data.page.admin_only` | Boolean | - | 页面是否仅管理员可评论 |
|
||||
| `data.page.key` | String | - | 页面唯一标识符 |
|
||||
| `data.page.url` | String | - | 页面链接 |
|
||||
| `data.page.title` | String | - | 页面标题 |
|
||||
| `data.page.site_name` | String | - | 页面所在站点名称 |
|
||||
| `data.page.vote_up` | Number | - | 页面赞同数 |
|
||||
| `data.page.vote_down` | Number | - | 页面反对数 |
|
||||
| `data.unread` | Array | - | 未读的评论提醒数据 |
|
||||
| `data.unread_count` | Number | - | 未读的评论提醒数据总数 |
|
||||
| `data.api_version` | Object | - | 目标 Artalk 后端版本数据 |
|
||||
|
||||
::: tip
|
||||
|
||||
- `data.comments` 数组中元素结构参考 [评论新增接口](#评论新增) `/api/add` 响应的 `data.comment`
|
||||
- `data.unread` 数组中元素结构如下
|
||||
| 字段名 | 数据类型 | 默认 | 说明 |
|
||||
| ----- | ------- | :-: | --- |
|
||||
| `id` | Number | - | 评论提醒 ID |
|
||||
| `user_id` | Number | - | 评论提醒目标用户 ID |
|
||||
| `comment_id` | Number | - | 评论提醒来源评论 ID |
|
||||
| `is_read` | Boolean | - | 评论提醒是否已读 |
|
||||
| `is_emailed` | Boolean | - | 评论提醒是否已发送邮件 |
|
||||
| `read_link` | String | - | 评论提醒已读地址 |
|
||||
- `data.api_version` 对象结构参考 [Artalk 版本接口](#artalk-版本) `/api/version` 响应
|
||||
|
||||
:::
|
||||
|
||||
### 用户获取
|
||||
|
||||
**POST** `/api/user-get`
|
||||
|
||||
**参数**
|
||||
|
||||
| 字段名 | 数据类型 | 必需 | 说明 |
|
||||
| ------- | -------- | :--: | ---------------- |
|
||||
| `name` | String | 否 | 待获取用户的昵称 |
|
||||
| `email` | String | 否 | 待获取用户的邮箱 |
|
||||
|
||||
**响应**
|
||||
|
||||
| 字段名 | 数据类型 | 默认 | 说明 |
|
||||
| ----------------------- | -------- | :----: | ---------------------------------- |
|
||||
| `success` | Boolean | `true` | 用户获取成功 |
|
||||
| `data.user` | Object | `null` | 获取的用户数据,若用户不存在返回空 |
|
||||
| `data.user.id` | Number | - | 用户 ID |
|
||||
| `data.user.name` | String | - | 用户昵称 |
|
||||
| `data.user.email` | String | - | 用户邮箱 |
|
||||
| `data.user.link` | String | - | 用户链接 |
|
||||
| `data.user.badge_name` | String | - | 用户徽章文字 |
|
||||
| `data.user.badge_color` | String | - | 用户徽章颜色 |
|
||||
| `data.user.is_admin` | Boolean | - | 用户是否管理员 |
|
||||
| `data.is_login` | Boolean | - | 用户是否登录 |
|
||||
| `data.unread` | Array | `[]` | 用户未读的评论提醒数据 |
|
||||
| `data.unread_count` | Number | `0` | 用户未读的评论提醒数据总数 |
|
||||
|
||||
::: tip
|
||||
|
||||
- `data.unread` 数组中元素结构参考 [评论获取接口](#评论获取) `/api/get` **TIP**
|
||||
|
||||
:::
|
||||
|
||||
### 用户登录
|
||||
|
||||
**GET/POST** `/api/login`
|
||||
|
||||
**参数**
|
||||
|
||||
| 字段名 | 数据类型 | 必需 | 说明 |
|
||||
| ---------- | -------- | :--: | ---------------------------------- |
|
||||
| `name` | String | 是 | 待登录用户的昵称 |
|
||||
| `email` | String | 是 | 待登录用户的邮箱 |
|
||||
| `password` | String | 是 | 待登录用户的密码(管理员身份验证) |
|
||||
|
||||
**响应**
|
||||
|
||||
| 字段名 | 数据类型 | 默认 | 说明 |
|
||||
| ------------ | -------- | :----: | ---------------- |
|
||||
| `success` | Boolean | `true` | 用户登录成功 |
|
||||
| `data.token` | String | - | 登录的用户 Token |
|
||||
|
||||
### 提醒已读
|
||||
|
||||
**POST** `/api/mark-read`
|
||||
|
||||
**参数**
|
||||
|
||||
| 字段名 | 数据类型 | 必需 | 说明 |
|
||||
| ------------ | -------- | :--: | -------------------- |
|
||||
| `notify_key` | String | 否 | 评论提醒唯一标识符 |
|
||||
| `name` | String | 否 | 评论提醒用户的昵称 |
|
||||
| `email` | String | 否 | 评论提醒用户的邮箱 |
|
||||
| `all_read` | Boolean | 否 | 评论提醒是否已读 |
|
||||
| `site_name` | String | 否 | 评论提醒所在站点名称 |
|
||||
|
||||
**响应**
|
||||
|
||||
| 字段名 | 数据类型 | 默认 | 说明 |
|
||||
| --------- | -------- | :----: | ------------ |
|
||||
| `success` | Boolean | `true` | 提醒已读成功 |
|
||||
|
||||
### 评论投票
|
||||
|
||||
**POST** `/api/vote`
|
||||
|
||||
**参数**
|
||||
|
||||
| 字段名 | 数据类型 | 必需 | 说明 |
|
||||
| ----------- | -------- | :--: | ----------------------------------------------------------------------------------------------------------- |
|
||||
| `target_id` | Number | 是 | 待投票评论 ID |
|
||||
| `type` | String | 否 | 待投票类型,前缀 `comment_`、`page_` 依次代表对评论投票、对页面投票,后缀 `_up`、`_down` 依次代表赞同、反对 |
|
||||
| `name` | String | 否 | 投票用户昵称 |
|
||||
| `email` | String | 否 | 投票用户邮箱 |
|
||||
| `site_name` | String | 否 | 投票目标评论或页面所在站点名称 |
|
||||
|
||||
**响应**
|
||||
|
||||
| 字段名 | 数据类型 | 默认 | 说明 |
|
||||
| --------------- | -------- | :----: | ------------------------------ |
|
||||
| `success` | Boolean | `true` | 评论投票成功 |
|
||||
| `data.vote_num` | Number | - | 目标评论或页面指定类型投票数据 |
|
||||
|
||||
### Artalk 版本
|
||||
|
||||
::: warning
|
||||
|
||||
此接口较特殊,返回结果不包含 `success`、`msg`、`data` 字段,直接返回包含指定字段的 JSON。
|
||||
|
||||
:::
|
||||
|
||||
**GET/POST** `/api/version`
|
||||
|
||||
**参数** 无
|
||||
|
||||
**响应**
|
||||
|
||||
| 字段名 | 数据类型 | 默认 | 说明 |
|
||||
| ---------------- | -------- | :------: | ------------------------------- |
|
||||
| `app` | String | `artalk` | Artalk 后端程序名 |
|
||||
| `version` | String | - | Artalk 后端程序版本号 |
|
||||
| `commit_hash` | String | - | Artalk 后端程序 Git 提交哈希值 |
|
||||
| `fe_min_version` | String | - | Artalk 后端程序所需前端最低版本 |
|
||||
|
||||
## Captcha API
|
||||
|
||||
### 验证码刷新
|
||||
|
||||
**GET** `/api/captcha/refresh`
|
||||
|
||||
**参数** 无
|
||||
|
||||
**响应**
|
||||
|
||||
| 字段名 | 数据类型 | 默认 | 说明 |
|
||||
| --------------- | -------- | :----: | ------------------------------------ |
|
||||
| `success` | Boolean | `true` | 验证码刷新成功 |
|
||||
| `data.img_data` | String | - | 根据 IP 获取的验证码图片 Base64 编码 |
|
||||
|
||||
### 验证码检验
|
||||
|
||||
**GET** `/api/captcha/check`
|
||||
|
||||
**参数**
|
||||
|
||||
| 字段名 | 数据类型 | 必需 | 说明 |
|
||||
| ------- | -------- | :--: | ------ |
|
||||
| `value` | String | 是 | 验证码 |
|
||||
|
||||
**响应**
|
||||
|
||||
| 字段名 | 数据类型 | 默认 | 说明 |
|
||||
| --------------- | -------- | :----: | -------------------------------------------------------- |
|
||||
| `success` | Boolean | `true` | 验证码检验成功 |
|
||||
| `data.img_data` | String | - | 新的验证码图片 Base64 编码,仅验证码检验出错时存在此字段 |
|
||||
|
||||
## Admin API
|
||||
|
||||
### 评论编辑
|
||||
|
||||
**POST** `/api/admin/comment-edit`
|
||||
|
||||
**参数**
|
||||
|
||||
| 字段名 | 数据类型 | 必需 | 说明 |
|
||||
| -------------- | -------- | :--: | ------------------------------------------------- |
|
||||
| `id` | Number | 是 | 待编辑评论 ID,**不可修改** |
|
||||
| `site_name` | String | 否 | 待编辑评论所在站点名称,**不可修改** |
|
||||
| `content` | String | 否 | 待编辑评论正文 |
|
||||
| `page_key` | String | 否 | 待编辑评论所在页面唯一标识符 |
|
||||
| `nick` | String | 否 | 待编辑评论所属用户昵称 |
|
||||
| `email` | String | 否 | 待编辑评论所属用户邮箱 |
|
||||
| `link` | String | 否 | 待编辑评论所属用户链接 |
|
||||
| `rid` | String | 否 | 待编辑评论为回复评论时传入父评论 `ID`,否则为 `0` |
|
||||
| `ua` | String | 否 | 待编辑评论所属用户 User-Agent |
|
||||
| `ip` | String | 否 | 待编辑评论所属用户 IP |
|
||||
| `is_collapsed` | Boolean | 否 | 待编辑评论是否折叠 |
|
||||
| `is_pending` | Boolean | 否 | 待编辑评论是否待审 |
|
||||
|
||||
**响应**
|
||||
|
||||
| 字段名 | 数据类型 | 默认 | 说明 |
|
||||
| -------------- | -------- | :----: | ---------------- |
|
||||
| `success` | Boolean | `true` | 评论编辑成功 |
|
||||
| `data.comment` | Object | - | 编辑评论的新数据 |
|
||||
|
||||
::: tip
|
||||
|
||||
- `data.comment` 对象结构参考 [评论新增接口](#评论新增) `/api/add` 响应的 `data.comment`
|
||||
|
||||
:::
|
||||
|
||||
### 评论删除
|
||||
|
||||
::: danger
|
||||
|
||||
此接口不推荐外部调用。
|
||||
|
||||
调用此接口前请务必核实评论 ID,一旦调用成功将删除该评论下所有相关数据。
|
||||
|
||||
:::
|
||||
|
||||
**POST** `/api/admin/comment-del`
|
||||
|
||||
**参数**
|
||||
|
||||
| 字段名 | 数据类型 | 必需 | 说明 |
|
||||
| ----------- | -------- | :--: | ---------------------- |
|
||||
| `id` | Number | 是 | 待删除评论 ID |
|
||||
| `site_name` | String | 是 | 待删除评论所在站点名称 |
|
||||
|
||||
**响应**
|
||||
|
||||
| 字段名 | 数据类型 | 默认 | 说明 |
|
||||
| --------- | -------- | :----: | ------------ |
|
||||
| `success` | Boolean | `true` | 评论删除成功 |
|
||||
|
||||
### 页面获取
|
||||
|
||||
**POST** `/api/admin/page-get`
|
||||
|
||||
**参数**
|
||||
|
||||
| 字段名 | 数据类型 | 必需 | 说明 |
|
||||
| ----------- | -------- | :--: | ---------------------- |
|
||||
| `site_name` | String | 是 | 待获取页面目标站点名称 |
|
||||
|
||||
**响应**
|
||||
|
||||
| 字段名 | 数据类型 | 默认 | 说明 |
|
||||
| ------------ | -------- | :----: | ------------------ |
|
||||
| `success` | Boolean | `true` | 页面获取成功 |
|
||||
| `data.pages` | Array | - | 目标站点的页面数据 |
|
||||
| `data.sites` | Array | - | 所有站点数据 |
|
||||
|
||||
::: tip
|
||||
|
||||
- `data.pages` 数组中元素结构参考 [评论获取接口](#评论获取) `/api/get` 响应的 `data.page`
|
||||
- `data.sites` 数组中元素结构参考 [站点编辑接口](#站点编辑) `/api/admin/site-edit` 响应的 `data.site`
|
||||
|
||||
:::
|
||||
|
||||
### 页面编辑
|
||||
|
||||
**POST** `/api/admin/page-edit`
|
||||
|
||||
**参数**
|
||||
|
||||
| 字段名 | 数据类型 | 必需 | 说明 |
|
||||
| ------------ | -------- | :--: | ------------------------------------ |
|
||||
| `id` | Number | 是 | 待编辑页面 ID,**不可修改** |
|
||||
| `site_name` | String | 否 | 待编辑页面所在站点名称,**不可修改** |
|
||||
| `key` | String | 否 | 待编辑页面唯一标识符 |
|
||||
| `title` | String | 否 | 待编辑页面标题 |
|
||||
| `admin_only` | Boolean | 否 | 待编辑页面是否仅管理员可评论 |
|
||||
|
||||
**响应**
|
||||
|
||||
| 字段名 | 数据类型 | 默认 | 说明 |
|
||||
| ----------- | -------- | :----: | ---------------- |
|
||||
| `success` | Boolean | `true` | 页面编辑成功 |
|
||||
| `data.page` | Object | - | 编辑页面的新数据 |
|
||||
|
||||
::: tip
|
||||
|
||||
- `data.page` 对象结构参考 [评论获取接口](#评论获取) `/api/get` 响应的 `data.page`
|
||||
|
||||
:::
|
||||
|
||||
### 页面删除
|
||||
|
||||
::: danger
|
||||
|
||||
此接口不推荐外部调用。
|
||||
|
||||
调用此接口前请务必核实页面 ID,一旦调用成功将删除该页面下所有相关数据。
|
||||
|
||||
:::
|
||||
|
||||
**POST** `/api/admin/page-del`
|
||||
|
||||
**参数**
|
||||
|
||||
| 字段名 | 数据类型 | 必需 | 说明 |
|
||||
| ----------- | -------- | :--: | ---------------------- |
|
||||
| `key` | String | 是 | 待删除页面唯一标识符 |
|
||||
| `site_name` | String | 是 | 待删除页面所在站点名称 |
|
||||
|
||||
**响应**
|
||||
|
||||
| 字段名 | 数据类型 | 默认 | 说明 |
|
||||
| --------- | -------- | :----: | ------------ |
|
||||
| `success` | Boolean | `true` | 页面删除成功 |
|
||||
|
||||
### 页面更新
|
||||
|
||||
**POST** `/api/admin/page-fetch`
|
||||
|
||||
**参数**
|
||||
|
||||
| 字段名 | 数据类型 | 必需 | 说明 |
|
||||
| ------ | -------- | :--: | ------------- |
|
||||
| `id` | Number | 是 | 待更新页面 ID |
|
||||
|
||||
**响应**
|
||||
|
||||
| 字段名 | 数据类型 | 默认 | 说明 |
|
||||
| ----------- | -------- | :----: | ---------------- |
|
||||
| `success` | Boolean | `true` | 页面更新成功 |
|
||||
| `data.page` | Object | - | 更新页面的新数据 |
|
||||
|
||||
::: tip
|
||||
|
||||
- `data.page` 对象结构参考 [评论获取接口](#评论获取) `/api/get` 响应的 `data.page`
|
||||
|
||||
:::
|
||||
|
||||
### 站点获取
|
||||
|
||||
**POST** `/api/admin/site-get`
|
||||
|
||||
**参数** 无
|
||||
|
||||
**响应**
|
||||
|
||||
| 字段名 | 数据类型 | 默认 | 说明 |
|
||||
| ------------ | -------- | :----: | ------------ |
|
||||
| `success` | Boolean | `true` | 站点获取成功 |
|
||||
| `data.sites` | Array | - | 所有站点数据 |
|
||||
|
||||
::: tip
|
||||
|
||||
- `data.sites` 数组中元素结构参考 [站点编辑接口](#站点编辑) `/api/admin/site-edit` 响应的 `data.site`
|
||||
|
||||
:::
|
||||
|
||||
### 站点新增
|
||||
|
||||
**POST** `/api/admin/site-add`
|
||||
|
||||
**参数**
|
||||
|
||||
| 字段名 | 数据类型 | 必需 | 说明 |
|
||||
| ------ | -------- | :--: | ------------------------------------------------- |
|
||||
| `name` | String | 是 | 待新增站点名称 |
|
||||
| `urls` | String | 是 | 待新增站点链接,如有多个需以英文半角符号 `,` 分割 |
|
||||
|
||||
**响应**
|
||||
|
||||
| 字段名 | 数据类型 | 默认 | 说明 |
|
||||
| --------------------- | -------- | :----: | ----------------------------------------------- |
|
||||
| `success` | Boolean | `true` | 站点新增成功 |
|
||||
| `data.site` | Object | - | 新增站点的数据 |
|
||||
| `data.site.id` | Number | - | 站点 ID |
|
||||
| `data.site.name` | String | - | 站点名称 |
|
||||
| `data.site.urls` | Array | - | 站点链接数组,元素为单个站点链接字符串 |
|
||||
| `data.site.urls_raw` | String | - | 站点链接数组合并字符串,以英文半角符号 `,` 分割 |
|
||||
| `data.site.first_url` | String | - | 站点第一链接 |
|
||||
|
||||
### 站点编辑
|
||||
|
||||
**POST** `/api/admin/site-edit`
|
||||
|
||||
**参数**
|
||||
|
||||
| 字段名 | 数据类型 | 必需 | 说明 |
|
||||
| ------ | -------- | :--: | ---------------- |
|
||||
| `id` | Number | 是 | 待编辑站点 ID |
|
||||
| `name` | String | 是 | 待编辑站点新名称 |
|
||||
| `urls` | String | 是 | 待编辑站点新链接 |
|
||||
|
||||
**响应**
|
||||
|
||||
| 字段名 | 数据类型 | 默认 | 说明 |
|
||||
| --------------------- | -------- | :----: | ----------------------------------------------- |
|
||||
| `success` | Boolean | `true` | 站点编辑成功 |
|
||||
| `data.site` | Object | - | 编辑站点的新数据 |
|
||||
| `data.site.id` | Number | - | 站点 ID |
|
||||
| `data.site.name` | String | - | 站点名称 |
|
||||
| `data.site.urls` | Array | - | 站点链接数组,元素为单个站点链接字符串 |
|
||||
| `data.site.urls_raw` | String | - | 站点链接数组合并字符串,以英文半角符号 `,` 分割 |
|
||||
| `data.site.first_url` | String | - | 站点第一链接 |
|
||||
|
||||
### 站点删除
|
||||
|
||||
::: danger
|
||||
|
||||
此接口不推荐外部调用。
|
||||
|
||||
调用此接口前请务必核实站点 ID,一旦调用成功将删除该站点下所有相关数据。
|
||||
|
||||
:::
|
||||
|
||||
**POST** `/api/admin/site-del`
|
||||
|
||||
**参数**
|
||||
|
||||
| 字段名 | 数据类型 | 必需 | 说明 |
|
||||
| ------------- | -------- | :--: | ------------------------------------------------------------------- |
|
||||
| `id` | Number | 是 | 待删除站点 ID |
|
||||
| `del_content` | Boolean | 否 | 是否删除站点所有数据,为 `false` 时仅删除站点配置而保留站点评论数据 |
|
||||
|
||||
**响应**
|
||||
|
||||
| 字段名 | 数据类型 | 默认 | 说明 |
|
||||
| --------- | -------- | :----: | ------------ |
|
||||
| `success` | Boolean | `true` | 站点删除成功 |
|
||||
|
||||
### 设置获取
|
||||
|
||||
**POST** `/api/admin/setting-get`
|
||||
|
||||
**参数**
|
||||
|
||||
| 字段名 | 数据类型 | 必需 | 说明 |
|
||||
| ------ | -------- | :--: | -------------- |
|
||||
| `id` | Number | 是 | 管理员用户 ID |
|
||||
| `name` | String | 否 | 管理员用户昵称 |
|
||||
| `url` | String | 否 | 管理员用户链接 |
|
||||
|
||||
**响应**
|
||||
|
||||
| 字段名 | 数据类型 | 默认 | 说明 |
|
||||
| --------- | -------- | :----: | ------------ |
|
||||
| `success` | Boolean | `true` | 设置获取成功 |
|
||||
|
||||
### 设置保存
|
||||
|
||||
**POST** `/api/admin/setting-save`
|
||||
|
||||
**参数**
|
||||
|
||||
| 字段名 | 数据类型 | 必需 | 说明 |
|
||||
| ------ | -------- | :--: | -------------- |
|
||||
| `id` | Number | 是 | 管理员用户 ID |
|
||||
| `name` | String | 否 | 管理员用户昵称 |
|
||||
| `url` | String | 否 | 管理员用户链接 |
|
||||
|
||||
**响应**
|
||||
|
||||
| 字段名 | 数据类型 | 默认 | 说明 |
|
||||
| --------- | -------- | :----: | ------------ |
|
||||
| `success` | Boolean | `true` | 设置保存成功 |
|
||||
|
||||
### 邮件发送
|
||||
|
||||
::: warning
|
||||
|
||||
页面收到评论、评论收到回复等满足需要发送邮件提醒条件时,后端将自动向目标地址发送邮件提醒,不需手动处理邮件发送。请勿滥用此接口。
|
||||
|
||||
:::
|
||||
|
||||
**POST** `/api/admin/send-mail`
|
||||
|
||||
**参数**
|
||||
|
||||
| 字段名 | 数据类型 | 必需 | 说明 |
|
||||
| --------- | -------- | :--: | ---------- |
|
||||
| `subject` | String | 是 | 邮件主题 |
|
||||
| `body` | String | 是 | 邮件正文 |
|
||||
| `to_addr` | String | 是 | 邮件收件人 |
|
||||
|
||||
**响应**
|
||||
|
||||
| 字段名 | 数据类型 | 默认 | 说明 |
|
||||
| --------- | -------- | :----: | ------------ |
|
||||
| `success` | Boolean | `true` | 邮件发送成功 |
|
||||
|
||||
### Artrans 接口
|
||||
|
||||
::: warning
|
||||
|
||||
此接口暂不稳定,请避免使用!
|
||||
|
||||
:::
|
||||
|
||||
**POST** `/api/admin/artransfer`
|
||||
|
||||
**参数**
|
||||
|
||||
| 字段名 | 数据类型 | 必需 | 说明 |
|
||||
| --------- | -------- | :--: | ------------ |
|
||||
| `type` | String | 是 | 导入数据类型 |
|
||||
| `payload` | String | 否 | 导入数据 |
|
||||
|
||||
**响应** 无
|
||||
@@ -1,3 +0,0 @@
|
||||
# HTTP API
|
||||
|
||||
参考:<a href="/http-api.html" target="_blank">Artalk API 文档 (OpenAPI)</a>
|
||||
@@ -1,27 +0,0 @@
|
||||
# 开发说明
|
||||
|
||||
## 参考资源
|
||||
|
||||
📖 [开发者贡献指南 (CONTRIBUTING.md)](https://github.com/ArtalkJS/Artalk/blob/master/CONTRIBUTING.md)
|
||||
|
||||
🔬 <a href="/http-api.html" target="_blank">HTTP API 文档 (OpenAPI)</a>
|
||||
|
||||
🔖 [贡献者契约行为准则 (CODE_OF_CONDUCT.md)](https://github.com/ArtalkJS/Artalk/blob/master/CODE_OF_CONDUCT.md)
|
||||
|
||||
## 配置文档
|
||||
|
||||
- [后端配置](../guide/backend/config.md)
|
||||
- [前端配置](../guide/frontend/config.md)
|
||||
|
||||
## 补充说明
|
||||
|
||||
由于 Artalk 正处于开发阶段,使用此文档中 `API`、`Event` 前请务必检查时效性。
|
||||
|
||||
- `API` 部分参考源码
|
||||
- [@ArtalkJS/Artalk - src/api/](https://github.com/ArtalkJS/Artalk/tree/master/ui/artalk/src/api)
|
||||
- [@ArtalkJS/Artalk - server/server.go](https://github.com/ArtalkJS/Artalk/blob/master/server/server.go)
|
||||
- `UI` 及 `Event` 部分参考源码
|
||||
- [@ArtalkJS/Artalk - src/artalk.ts](https://github.com/ArtalkJS/Artalk/blob/master/ui/artalk/src/artalk.ts)
|
||||
- [@ArtalkJS/Artalk - src/types/event.ts](https://github.com/ArtalkJS/Artalk/blob/master/ui/artalk/src/types/event.ts)
|
||||
|
||||
通过 Artalk 提供的 `API` 和 `Event`,你可以实现很多高级功能,比如编写评论管理机器人、评论提醒推送插件等。Artalk 并不为此提供技术指导,但鼓励你参考此处的文档自行定制。
|
||||
@@ -0,0 +1,18 @@
|
||||
# Compatibility
|
||||
|
||||
## Browser Compatibility
|
||||
|
||||
The Artalk client production environment code is built using esbuild with a target of `es2015`. However, please note that the build process only handles syntax transpilation to comply with the `es2015` standard and does not address API compatibility (no polyfills are included).
|
||||
|
||||
If your project needs to support older browsers and devices, consider including global polyfills in your bundled application. We recommend using the [Polyfill.io](https://polyfill.io/) service, which dynamically returns the necessary polyfills based on the user's User-Agent. Alternatively, you can manually add polyfills using [core-js](https://github.com/zloirock/core-js) or [Babel](https://babeljs.io/).
|
||||
|
||||
Here is a list of modern features used by Artalk:
|
||||
|
||||
- [All ECMAScript 2015 (ES6) features](https://compat-table.github.io/compat-table/es6/)
|
||||
- [Fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)
|
||||
- [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController)
|
||||
- [Intersection Observer](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API)
|
||||
|
||||
## Server Compatibility
|
||||
|
||||
The Artalk server program is developed in Golang and supports operating systems such as Linux, Windows, and macOS. The current Golang version for Artalk can be found in the [go.mod](https://github.com/ArtalkJS/Artalk/blob/master/go.mod#L3). For the minimum operating system requirements for Golang, refer to the [Go Wiki](https://go.dev/wiki/MinimumRequirements).
|
||||
@@ -0,0 +1,3 @@
|
||||
<script setup lang="ts">
|
||||
if (!import.meta.env.SSR) window.location = 'https://github.com/ArtalkJS/Artalk/blob/master/CONTRIBUTING.md';
|
||||
</script>
|
||||
@@ -0,0 +1,50 @@
|
||||
# Frontend Event
|
||||
|
||||
## Basic Events
|
||||
|
||||
| Event Name | Description |
|
||||
| ------------------- | ------------------------------- |
|
||||
| `created` | After initialization |
|
||||
| `mounted` | After data loading |
|
||||
| `updated` | After data update |
|
||||
| `unmounted` | After destruction |
|
||||
| `list-fetch` | During comment list request |
|
||||
| `list-fetched` | After comment list request |
|
||||
| `list-load` | Before comment loading |
|
||||
| `list-loaded` | After comment loading |
|
||||
| `list-failed` | When comment loading fails |
|
||||
| `list-goto-first` | When comment list resets |
|
||||
| `list-reach-bottom` | When comment list reaches bottom|
|
||||
| `comment-inserted` | After comment insertion |
|
||||
| `comment-updated` | After comment update |
|
||||
| `comment-deleted` | After comment deletion |
|
||||
| `comment-rendered` | After comment node rendering |
|
||||
| `notifies-updated` | When unread messages change |
|
||||
| `list-goto` | During comment jump |
|
||||
| `page-loaded` | After page data update |
|
||||
| `editor-submit` | When editor submits |
|
||||
| `editor-submitted` | After editor submission |
|
||||
| `user-changed` | When local user data changes |
|
||||
| `sidebar-show` | When sidebar is shown |
|
||||
| `sidebar-hide` | When sidebar is hidden |
|
||||
|
||||
Event declaration code: [@ArtalkJS/Artalk - src/types/event.ts](https://github.com/ArtalkJS/Artalk/blob/master/ui/artalk/src/types/event.ts)
|
||||
|
||||
## Adding Event Listeners
|
||||
|
||||
```js
|
||||
artalk.on('list-loaded', () => {
|
||||
alert('Comments have been loaded')
|
||||
})
|
||||
```
|
||||
|
||||
## Removing Event Listeners
|
||||
|
||||
```js
|
||||
let foo = function () {
|
||||
/* do something */
|
||||
}
|
||||
|
||||
artalk.on('list-loaded', foo)
|
||||
artalk.off('list-loaded', foo)
|
||||
```
|
||||
@@ -0,0 +1,199 @@
|
||||
# Frontend API
|
||||
|
||||
Here are some of the main Artalk client APIs for operating the Artalk instance in frontend code.
|
||||
|
||||
For more detailed API information, you can browse the [TypeDoc documentation](https://artalk.js.org/typedoc/).
|
||||
|
||||
## Install Dependencies
|
||||
|
||||
Use your preferred package manager to install Artalk:
|
||||
|
||||
```bash
|
||||
pnpm add artalk
|
||||
# or using npm
|
||||
npm install artalk
|
||||
```
|
||||
|
||||
## Create Instance `init`
|
||||
|
||||
Calling this function creates and returns an instantiated Artalk object for subsequent operations.
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
```js
|
||||
import Artalk from 'artalk'
|
||||
|
||||
const artalk = Artalk.init({
|
||||
el: '#Comments',
|
||||
pageKey: '/post/1',
|
||||
pageTitle: 'About Introducing Artalk',
|
||||
server: 'http://your_domain:8080',
|
||||
site: 'Artalk Blog',
|
||||
})
|
||||
```
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
Calling this function will asynchronously send requests to the backend to:
|
||||
|
||||
1. Fetch frontend configuration
|
||||
2. Fetch comment list
|
||||
|
||||
The `mounted` event is triggered after configuration and plugins are loaded.
|
||||
|
||||
The `list-loaded` event is triggered after the comment list is loaded.
|
||||
|
||||
> Note: The frontend UI configuration may be overridden. See: [UI Configuration](../guide/frontend/config.md).
|
||||
|
||||
## Release Resources `destroy`
|
||||
|
||||
Destroy the Artalk instance to release resources.
|
||||
|
||||
```js
|
||||
const artalk = Artalk.init({ ... })
|
||||
|
||||
artalk.destroy()
|
||||
```
|
||||
|
||||
Calling this function triggers the `unmounted` event. Releasing resources involves deleting DOM nodes mounted by Artalk and removing Artalk event listeners. After this function completes, the Artalk instance will no longer be usable.
|
||||
|
||||
In frameworks like Vue/React, make sure to call this function when the component is destroyed to prevent memory leaks:
|
||||
|
||||
```ts
|
||||
import { onUnmounted } from 'vue'
|
||||
|
||||
onUnmounted(() => {
|
||||
artalk.destroy()
|
||||
})
|
||||
```
|
||||
|
||||
## Update Configuration `update`
|
||||
|
||||
Modify the current Artalk configuration.
|
||||
|
||||
```js
|
||||
const artalk = Artalk.init({ ... })
|
||||
|
||||
artalk.update({
|
||||
// new configuration...
|
||||
})
|
||||
```
|
||||
|
||||
Calling this function will trigger the `updated` event.
|
||||
|
||||
Updating the configuration will not automatically refresh the comment list. You can continue to call the `artalk.reload()` function as needed.
|
||||
|
||||
Note that this function is a member method of an instantiated object, not a global function.
|
||||
|
||||
Frontend configuration reference: [Frontend Configuration](../guide/frontend/config.md)
|
||||
|
||||
## Reload `reload`
|
||||
|
||||
Reload the Artalk comment list data from the backend.
|
||||
|
||||
```js
|
||||
const artalk = Artalk.init({ ... })
|
||||
|
||||
artalk.reload()
|
||||
```
|
||||
|
||||
The `list-load` event is triggered before loading the list, and the `list-loaded` event is triggered after the loading is complete.
|
||||
|
||||
## Listen to Events `on`
|
||||
|
||||
Add Artalk event listeners.
|
||||
|
||||
```js
|
||||
const artalk = Artalk.init({ ... })
|
||||
|
||||
artalk.on('list-loaded', (comments) => {
|
||||
// 'comments' contains all comments on the current page (not just the comments fetched in this request)
|
||||
console.log('Comments have been loaded: ', comments)
|
||||
})
|
||||
```
|
||||
|
||||
Refer to: [Frontend Event](./event.md).
|
||||
|
||||
## Remove Event Listeners `off`
|
||||
|
||||
Remove Artalk event listeners.
|
||||
|
||||
```js
|
||||
const artalk = Artalk.init({ ... })
|
||||
|
||||
const handler = () => {
|
||||
alert('Comments have been loaded')
|
||||
}
|
||||
|
||||
artalk.on('list-loaded', handler)
|
||||
artalk.off('list-loaded', handler)
|
||||
```
|
||||
|
||||
## Trigger Events `trigger`
|
||||
|
||||
Trigger Artalk events.
|
||||
|
||||
```js
|
||||
const artalk = Artalk.init({ ... })
|
||||
|
||||
artalk.trigger('list-loaded', [...])
|
||||
```
|
||||
|
||||
## Load Plugins `use`
|
||||
|
||||
Load plugins using the `Artalk.use` function. Note that this function is a global function, not a method of an instantiated object. Plugins loaded through this function will be effective for all Artalk instances.
|
||||
|
||||
```js
|
||||
import Artalk from 'artalk'
|
||||
|
||||
Artalk.use((ctx) => {
|
||||
ctx.editor.setContent("Hello World")
|
||||
})
|
||||
|
||||
const artalk = Artalk.init({ ... })
|
||||
```
|
||||
|
||||
See: [Plugin Development](./plugin.md)
|
||||
|
||||
## Dark Mode `setDarkMode`
|
||||
|
||||
Toggle dark mode, which can be called in conjunction with the blog theme, such as when a user clicks a dark mode toggle button.
|
||||
|
||||
```js
|
||||
const artalk = Artalk.init({ ... })
|
||||
|
||||
artalk.setDarkMode(true)
|
||||
```
|
||||
|
||||
You can also set the initial dark mode by passing the `darkMode` parameter when calling `Artalk.init`.
|
||||
|
||||
```js
|
||||
const artalk = Artalk.init({
|
||||
// ...other configurations
|
||||
darkMode: 'auto',
|
||||
})
|
||||
```
|
||||
|
||||
## View Count Widget `loadCountWidget`
|
||||
|
||||
See: [Page View Statistics](../guide/frontend/pv.md)
|
||||
|
||||
## Get Configuration `getConf`
|
||||
|
||||
Get the current Artalk configuration.
|
||||
|
||||
```js
|
||||
const artalk = Artalk.init({ ... })
|
||||
|
||||
const conf = artalk.getConf()
|
||||
```
|
||||
|
||||
## Get Mounted Element `getEl`
|
||||
|
||||
Get the DOM element that Artalk is currently mounted to.
|
||||
|
||||
```js
|
||||
const artalk = Artalk.init({ ... })
|
||||
|
||||
const el = artalk.getEl()
|
||||
```
|
||||
@@ -0,0 +1,175 @@
|
||||
# Import to Blog
|
||||
|
||||
## General Method
|
||||
|
||||
<<< @/code/quick-start/cdn.html
|
||||
|
||||
### When to execute `Artalk.init({})`?
|
||||
|
||||
- You can include JS and CSS resources anywhere, but ensure the JS is included before executing `Artalk.init({})`.
|
||||
- Ensure `<div id="artalk"></div>` is present in the page before executing `Artalk.init({ el: '#artalk' })`.
|
||||
|
||||
## Hugo
|
||||
|
||||
Create the template file `/themes/YOUR_THEME/layouts/partials/comment/artalk.html`:
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
```html
|
||||
<link href="/lib/artalk/Artalk.css" rel="stylesheet" />
|
||||
<script src="/lib/artalk/Artalk.js"></script>
|
||||
|
||||
<!-- Artalk -->
|
||||
<div id="Comments"></div>
|
||||
|
||||
<script>
|
||||
Artalk.init({
|
||||
el: '#Comments',
|
||||
pageKey: '{{ .Permalink }}',
|
||||
pageTitle: '{{ .Title }}',
|
||||
server: '{{ $.Site.Params.artalk.server }}',
|
||||
site: '{{ $.Site.Params.artalk.site }}',
|
||||
// ...your additional configuration
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
Add the following to the appropriate place in your article template `/themes/YOUR_THEME/layouts/_default/single.html`:
|
||||
|
||||
```html
|
||||
<div class="article-comments">{{- partial "comment/artalk" . -}}</div>
|
||||
```
|
||||
|
||||
Modify the Hugo configuration file:
|
||||
|
||||
::: code-group
|
||||
|
||||
```toml [config.toml]
|
||||
[params.artalk]
|
||||
server = 'https://artalk.example.org'
|
||||
site = 'Your Site Name'
|
||||
```
|
||||
|
||||
```yaml [config.yaml]
|
||||
params:
|
||||
artalk:
|
||||
server: 'https://artalk.example.org'
|
||||
site: 'Your Site Name'
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Hexo
|
||||
|
||||
Create `/themes/YOUR_THEME/layout/comment/artalk.ejs`:
|
||||
|
||||
```html
|
||||
<link href="/lib/artalk/Artalk.css" rel="stylesheet" />
|
||||
<script src="/lib/artalk/Artalk.js"></script>
|
||||
|
||||
<div id="Comments"></div>
|
||||
|
||||
<script>
|
||||
var artalk = Artalk.init({
|
||||
el: '#Comments',
|
||||
pageKey: '<%= page.permalink %>',
|
||||
pageTitle: '<%= page.title %>',
|
||||
server: '<%= theme.comment.artalk.server %>',
|
||||
site: '<%= theme.comment.artalk.site %>',
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
Modify the article template file, for example `/themes/YOUR_THEME/layout/post.ejs`:
|
||||
|
||||
```html
|
||||
<div class="article-comments"><%- partial('comment/artalk') %></div>
|
||||
```
|
||||
|
||||
Edit the theme configuration `/themes/YOUR_THEME/_config.yml`:
|
||||
|
||||
```yaml
|
||||
comment:
|
||||
artalk:
|
||||
site: 'Your Site Name'
|
||||
server: 'https://artalk.example.org'
|
||||
```
|
||||
|
||||
::: tip
|
||||
|
||||
For NexT theme, you can install the [Artalk plugin for Hexo NexT theme](https://github.com/leirock/hexo-next-artalk)
|
||||
|
||||
:::
|
||||
|
||||
## VitePress
|
||||
|
||||
Refer to: [docs/.vitepress](https://github.com/ArtalkJS/Artalk/tree/master/docs/docs/.vitepress)
|
||||
|
||||
- Modify `config.ts` to include CSS resources
|
||||
- Create `theme/Artalk.vue` component
|
||||
- Register the component in `theme/index.ts`
|
||||
- Use the component in `theme/Layout.vue`
|
||||
|
||||
Sample code: [docs/.vitepress/theme/Artalk.vue](https://github.com/ArtalkJS/Artalk/blob/master/docs/docs/.vitepress/theme/Artalk.vue)
|
||||
|
||||
## VuePress
|
||||
|
||||
(To be supplemented)
|
||||
|
||||
::: tip
|
||||
|
||||
Refer to: [Import to Frameworks](./import-framework.md)
|
||||
|
||||
:::
|
||||
|
||||
## Typecho
|
||||
|
||||
Modify the theme article template file, for example, `post.php`:
|
||||
|
||||
```php
|
||||
<!-- CSS -->
|
||||
<link href="/lib/artalk/Artalk.css" rel="stylesheet">
|
||||
|
||||
<!-- JS -->
|
||||
<script src="/lib/artalk/Artalk.js"></script>
|
||||
|
||||
<!-- Artalk -->
|
||||
<div id="Comments"></div>
|
||||
<script>
|
||||
Artalk.init({
|
||||
el: '#Comments',
|
||||
pageKey: '<?php $this->permalink() ?>',
|
||||
pageTitle: '<?php $this->title() ?>',
|
||||
server: '<Server Address>',
|
||||
site: '<?php $this->options->title() ?>',
|
||||
// ...your additional configuration
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
## WordPress
|
||||
|
||||
Modify the theme article template file, for example, `single.php`:
|
||||
|
||||
```php
|
||||
<!-- CSS -->
|
||||
<link href="/lib/artalk/Artalk.css" rel="stylesheet">
|
||||
|
||||
<!-- JS -->
|
||||
<script src="/lib/artalk/Artalk.js"></script>
|
||||
|
||||
<!-- Artalk -->
|
||||
<div id="Comments"></div>
|
||||
<script>
|
||||
Artalk.init({
|
||||
el: '#Comments',
|
||||
pageKey: '<?= addslashes(get_permalink()) ?>',
|
||||
pageTitle: '<?= addslashes(get_the_title()) ?>',
|
||||
server: '<Server Address>',
|
||||
site: '<?= addslashes(get_bloginfo('name')) ?>',
|
||||
// ...your additional configuration
|
||||
})
|
||||
</script>
|
||||
```
|
||||
@@ -0,0 +1,230 @@
|
||||
# Import to Frameworks
|
||||
|
||||
## Install Artalk
|
||||
|
||||
Install Artalk using a package manager. It is recommended to use [pnpm](https://pnpm.io/zh/).
|
||||
|
||||
```bash
|
||||
pnpm add artalk
|
||||
```
|
||||
|
||||
## Vue
|
||||
|
||||
::: code-group
|
||||
|
||||
```vue [Vue 3]
|
||||
<script lang="ts" setup>
|
||||
import Artalk from 'artalk'
|
||||
import { onMounted, onBeforeUnmount, ref } from 'vue'
|
||||
|
||||
import 'artalk/Artalk.css'
|
||||
|
||||
const el = ref<HTMLElement>()
|
||||
|
||||
let artalk: Artalk
|
||||
|
||||
onMounted(() => {
|
||||
artalk = Artalk.init({
|
||||
el: el.value,
|
||||
pageKey: location.pathname,
|
||||
pageTitle: `${document.title}`,
|
||||
server: 'http://localhost:8080',
|
||||
site: 'Artalk Blog',
|
||||
// ...
|
||||
})
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
artalk.destroy()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="el"></div>
|
||||
</template>
|
||||
```
|
||||
|
||||
```vue [Vue 3 + Vue Router]
|
||||
<script lang="ts" setup>
|
||||
import Artalk from 'artalk'
|
||||
import { onMounted, onBeforeUnmount, ref, watch, nextTick } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import 'artalk/Artalk.css'
|
||||
|
||||
const el = ref<HTMLElement>()
|
||||
const router = useRouter()
|
||||
|
||||
let artalk: Artalk
|
||||
|
||||
onMounted(() => {
|
||||
artalk = Artalk.init({
|
||||
el: el.value,
|
||||
pageKey: router.currentRoute.value.path,
|
||||
pageTitle: document.title,
|
||||
server: 'http://localhost:8080',
|
||||
site: 'Artalk Blog',
|
||||
// ...
|
||||
})
|
||||
})
|
||||
|
||||
watch(
|
||||
() => router.currentRoute.value.path,
|
||||
(path) => {
|
||||
nextTick(() => {
|
||||
artalk.update({
|
||||
pageKey: path,
|
||||
pageTitle: document.title,
|
||||
})
|
||||
artalk.reload()
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
artalk.destroy()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="el"></div>
|
||||
</template>
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## React
|
||||
|
||||
::: code-group
|
||||
|
||||
```tsx [React Hooks]
|
||||
import { useCallback, useRef } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import 'artalk/Artalk.css'
|
||||
import Artalk from 'artalk'
|
||||
|
||||
const ArtalkComment = () => {
|
||||
const { pathname } = useLocation()
|
||||
const artalk = useRef<Artalk | null>(null)
|
||||
|
||||
const handleContainerInit = useCallback(
|
||||
(node: HTMLDivElement | null) => {
|
||||
if (!node) {
|
||||
return
|
||||
}
|
||||
if (artalk.current) {
|
||||
artalk.current.destroy()
|
||||
artalk.current = null
|
||||
}
|
||||
artalk.current = Artalk.init({
|
||||
el: node,
|
||||
pageKey: pathname,
|
||||
pageTitle: document.title,
|
||||
server: 'http://localhost:8080',
|
||||
site: 'Artalk Blog',
|
||||
// ...
|
||||
})
|
||||
},
|
||||
[pathname],
|
||||
)
|
||||
|
||||
return <div ref={handleContainerInit}></div>
|
||||
}
|
||||
|
||||
export default ArtalkComment
|
||||
```
|
||||
|
||||
```jsx [React Class]
|
||||
import React, { createRef } from 'react'
|
||||
import 'artalk/Artalk.css'
|
||||
import Artalk from 'artalk'
|
||||
|
||||
export default class ArtalkComponent extends React.Component {
|
||||
el = createRef()
|
||||
artalk = null
|
||||
|
||||
componentDidMount() {
|
||||
this.artalk = Artalk.init({
|
||||
el: this.el.current,
|
||||
pageKey: location.pathname,
|
||||
pageTitle: document.title,
|
||||
server: 'http://localhost:8080',
|
||||
site: 'Artalk Blog',
|
||||
// ...
|
||||
})
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.artalk?.destroy()
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div ref={this.el} />
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## SolidJS
|
||||
|
||||
```tsx
|
||||
import { onCleanup, onMount } from 'solid-js'
|
||||
import Artalk from 'artalk'
|
||||
import 'artalk/Artalk.css'
|
||||
|
||||
const ArtalkComment = () => {
|
||||
let el: HTMLDivElement
|
||||
let artalk: Artalk
|
||||
|
||||
onMount(() => {
|
||||
artalk = Artalk.init({
|
||||
el: el,
|
||||
pageKey: location.pathname,
|
||||
pageTitle: document.title,
|
||||
server: 'http://localhost:8080',
|
||||
site: 'Artalk Blog',
|
||||
// ...
|
||||
})
|
||||
|
||||
onCleanup(() => {
|
||||
artalk.destroy()
|
||||
})
|
||||
})
|
||||
|
||||
return <div ref={el} />
|
||||
}
|
||||
```
|
||||
|
||||
## Svelte
|
||||
|
||||
```html
|
||||
<script>
|
||||
import Artalk from 'artalk'
|
||||
import { onMount, onDestroy } from 'svelte'
|
||||
|
||||
import 'artalk/Artalk.css'
|
||||
|
||||
let el
|
||||
let artalk
|
||||
|
||||
onMount(() => {
|
||||
artalk = Artalk.init({
|
||||
el: el,
|
||||
pageKey: location.pathname,
|
||||
pageTitle: document.title,
|
||||
server: 'http://localhost:8080',
|
||||
site: 'Artalk Blog',
|
||||
// ...
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
if (artalk) {
|
||||
artalk.destroy()
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<div bind:this="{el}"></div>
|
||||
```
|
||||
@@ -0,0 +1,40 @@
|
||||
# Development Instructions
|
||||
|
||||
Welcome to the official Artalk development documentation, where you will find guidelines for building Artalk plugins and themes. Should you need to set up the Artalk development environment, please refer to the [Developer Contribution Guide](./contributing.md).
|
||||
|
||||
Thank you for your interest and support of Artalk. We deeply understand that the success of an open-source project relies on the contributions of every developer. Whether it is code, documentation, testing, or feedback, your participation is the driving force behind the project's continuous advancement. We sincerely invite you to join us in perfecting and enhancing Artalk.
|
||||
|
||||
## Index
|
||||
|
||||
- [Developer Contribution Guide](./contributing.md) ⭐️
|
||||
|
||||
**Frontend Interface**
|
||||
|
||||
- [Embed in Blog](./import-blog.md)
|
||||
- [Embed in Framework](./import-framework.md)
|
||||
- [Frontend API](./fe-api.md)
|
||||
- [Frontend Events](./event.md)
|
||||
- [Frontend Types](https://artalk.js.org/typedoc/)
|
||||
- [Frontend Configuration](../guide/frontend/config.md)
|
||||
|
||||
**Plugin Development**
|
||||
|
||||
- [Plugin Development](./plugin.md)
|
||||
- [Plugin Development Vite Integration](https://github.com/ArtalkJS/Artalk/blob/master/ui/plugin-kit/README.md)
|
||||
|
||||
**Backend Program**
|
||||
|
||||
- <a href="/http-api.html" target="_blank">HTTP API Documentation</a>
|
||||
- [Environment Variables](../guide/env.md)
|
||||
- [Configuration Files](../guide/backend/config.md)
|
||||
|
||||
**Other Content**
|
||||
|
||||
- [Compatibility](../develop/compatibility.md)
|
||||
- [Changelog](https://github.com/ArtalkJS/Artalk/blob/master/CHANGELOG.md)
|
||||
- [i18n Multi-language Contribution Guide](../guide/frontend/i18n.md#贡献翻译)
|
||||
- [Contributor Covenant Code of Conduct](https://github.com/ArtalkJS/Artalk/blob/master/CODE_OF_CONDUCT.md)
|
||||
|
||||
::: tip
|
||||
Artalk is under active maintenance. Please be mindful of API version compatibility. Upgrades may include breaking changes, refer to the [Changelog](https://github.com/ArtalkJS/Artalk/blob/master/CHANGELOG.md).
|
||||
:::
|
||||
@@ -0,0 +1,130 @@
|
||||
# Plugin Development
|
||||
|
||||
## Developing with Vite
|
||||
|
||||
We provide an integrated Vite plugin, enabling you to effortlessly build Artalk plugins with out-of-the-box Vite configuration.
|
||||
|
||||
Reference Documentation: [@artalk/plugin-kit](https://github.com/ArtalkJS/Artalk/blob/master/ui/plugin-kit/README.md).
|
||||
|
||||
Additionally, we offer a template repository that you can fork to develop your plugins: [artalk-plugin-sample](https://github.com/ArtalkJS/artalk-plugin-sample).
|
||||
|
||||
Utilizing Vite in combination with the frontend ecosystem, you can develop Artalk plugins using any framework you prefer, such as Vue, React, Svelte, or SolidJS.
|
||||
|
||||
## `Artalk.use`
|
||||
|
||||
Extend Artalk's functionality using `Artalk.use`.
|
||||
|
||||
```js
|
||||
import Artalk from 'artalk'
|
||||
|
||||
Artalk.use((ctx) => {
|
||||
ctx.on('list-loaded', () => {
|
||||
console.log('Comment list loaded')
|
||||
ctx.getCommentNodes().forEach((node) => {
|
||||
node.getEl().style.background = 'red'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Artalk.use((ctx) => {
|
||||
let el = null
|
||||
|
||||
ctx.on('mounted', () => {
|
||||
el = document.createElement('div')
|
||||
document.body.appendChild(el)
|
||||
console.log('Artalk instance mounted')
|
||||
})
|
||||
|
||||
ctx.on('unmounted', () => {
|
||||
el.remove()
|
||||
console.log('Artalk instance unmounted')
|
||||
})
|
||||
})
|
||||
|
||||
const artalk = Artalk.init({ ... })
|
||||
```
|
||||
|
||||
Please note:
|
||||
|
||||
- `Artalk.use` must be called before `Artalk.init` to take effect.
|
||||
- Do not rely on the order of plugin loading; instead, listen for events to execute plugin logic.
|
||||
- The `created` event is triggered once all plugins are loaded.
|
||||
- The `mounted` event is triggered when the instance and configuration are fully loaded.
|
||||
- The `unmounted` event is triggered when the Artalk instance is destroyed.
|
||||
- The `updated` event is triggered when the configuration changes.
|
||||
|
||||
Within the use function, you can access Artalk's Context object via `ctx`.
|
||||
|
||||
### Configurable Plugin Options
|
||||
|
||||
In TypeScript, you can define a plugin with options using `ArtalkPlugin<T>`.
|
||||
|
||||
```ts
|
||||
export interface DemoPluginOptions {
|
||||
foo?: string
|
||||
}
|
||||
|
||||
export const ArtalkDemoPlugin: ArtalkPlugin<DemoPluginOptions> = (ctx, options = {}) => {
|
||||
console.log(options.foo)
|
||||
}
|
||||
```
|
||||
|
||||
Pass options when calling `Artalk.use`:
|
||||
|
||||
```ts
|
||||
import { ArtalkDemoPlugin } from 'artalk-plugin-demo'
|
||||
|
||||
Artalk.use(ArtalkDemoPlugin, { foo: 'bar' })
|
||||
```
|
||||
|
||||
## Context API
|
||||
|
||||
The Context object contains Artalk's contextual information.
|
||||
|
||||
| Member | Description |
|
||||
| -------------------- | ----------------------- |
|
||||
| `ctx.getEl` | Get container element |
|
||||
| `ctx.getConf` | Get configuration |
|
||||
| `ctx.updateConf` | Update configuration |
|
||||
| `ctx.watchConf` | Watch configuration |
|
||||
| `ctx.setDarkMode` | Set dark mode |
|
||||
| `ctx.getApi` | Get API client object |
|
||||
| -- | -- |
|
||||
| `ctx.fetch` | Fetch comment data |
|
||||
| `ctx.reload` | Reload comment list |
|
||||
| `ctx.getComments` | Get all comment data |
|
||||
| `ctx.getCommentNodes`| Get all comment nodes |
|
||||
| -- | -- |
|
||||
| `ctx.on` | Add event listener |
|
||||
| `ctx.off` | Remove event listener |
|
||||
| `ctx.trigger` | Trigger event |
|
||||
| -- | -- |
|
||||
| `ctx.get` | Get dependency |
|
||||
| `ctx.inject` | Inject dependency |
|
||||
|
||||
::: warning
|
||||
The Context API is currently unstable and subject to change. Please refer to the CHANGELOG when upgrading.
|
||||
:::
|
||||
|
||||
Reference: [@artalk/src/types/context.ts](https://github.com/ArtalkJS/Artalk/blob/master/ui/artalk/src/types/context.ts)
|
||||
|
||||
Artalk includes comprehensive TypeScript type definitions, allowing you to explore the API through editor autocompletion.
|
||||
|
||||
## Sample Plugins
|
||||
|
||||
Below is a list of externally maintained Artalk plugins:
|
||||
|
||||
| Plugin | Description |
|
||||
| -------------------------------------------------------------------------------------------- | ----------------------- |
|
||||
| [artalk-plugin-sample](https://github.com/ArtalkJS/artalk-plugin-sample) | Sample Plugin |
|
||||
| [@artalk/plugin-katex](https://github.com/ArtalkJS/Artalk/tree/master/ui/plugin-katex) | LaTeX Formula Plugin |
|
||||
| [@artalk/plugin-auth](https://github.com/ArtalkJS/Artalk/tree/master/ui/plugin-auth) | Auth Plugin (SolidJS) |
|
||||
| [@artalk/plugin-lightbox](https://github.com/ArtalkJS/Artalk/tree/master/ui/plugin-lightbox) | Basic Image Lightbox |
|
||||
|
||||
Additionally, many plugins are implemented within Artalk itself, which you can reference for developing your own plugins:
|
||||
|
||||
[@ArtalkJS/Artalk - src/plugins](https://github.com/ArtalkJS/Artalk/tree/master/ui/artalk/src/plugins)
|
||||
|
||||
## Backend Plugin Development
|
||||
|
||||
【TODO】
|
||||
@@ -0,0 +1,3 @@
|
||||
# About Us
|
||||
|
||||
【TODO】
|
||||
@@ -0,0 +1,403 @@
|
||||
# Multi-channel Notifications
|
||||
|
||||
Artalk supports sending administrator notifications through its multi-channel notification feature in various ways.
|
||||
|
||||
Supported platforms include **Telegram**, **Feishu**, **DingTalk**, **Bark**, **Slack**, and **LINE**, with the ability to enable multiple methods simultaneously.
|
||||
|
||||
You can modify these configurations in the settings interface of the [Dashboard](../frontend/sidebar.md#Settings) or via the [configuration file](../backend/config.md#multi-channel-notifications-admin-notify) or [environment variables](../env.md#multi-channel-notifications).
|
||||
|
||||
## Configuration File
|
||||
|
||||
The complete `admin_notify` configuration is as follows:
|
||||
|
||||
::: details Click to reveal
|
||||
|
||||
```yaml
|
||||
# Multi-channel Notifications
|
||||
admin_notify:
|
||||
# Notification Template
|
||||
notify_tpl: default
|
||||
noise_mode: false
|
||||
# Email notification for administrators
|
||||
email:
|
||||
enabled: true # Disable email notifications for administrators when using other push methods
|
||||
mail_subject: '[{{site_name}}] Your article "{{page_title}}" has a new reply'
|
||||
mail_tpl: ''
|
||||
# Telegram
|
||||
telegram:
|
||||
enabled: false
|
||||
api_token: ''
|
||||
receivers:
|
||||
- 7777777
|
||||
# Feishu
|
||||
lark:
|
||||
enabled: false
|
||||
webhook_url: ''
|
||||
# DingTalk
|
||||
ding_talk:
|
||||
enabled: false
|
||||
token: ''
|
||||
secret: ''
|
||||
# Bark
|
||||
bark:
|
||||
enabled: false
|
||||
server: http://day.app/xxxxxxx/
|
||||
# Slack
|
||||
slack:
|
||||
enabled: false
|
||||
oauth_token: ''
|
||||
receivers:
|
||||
- CHANNEL_ID
|
||||
# LINE
|
||||
line:
|
||||
enabled: false
|
||||
channel_secret: ''
|
||||
channel_access_token: ''
|
||||
receivers:
|
||||
- USER_ID_1
|
||||
- GROUP_ID_1
|
||||
# WebHook
|
||||
webhook:
|
||||
enabled: false
|
||||
url: ''
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Email Notifications
|
||||
|
||||
Send message notifications to administrators via email.
|
||||
|
||||
```yaml
|
||||
admin_notify:
|
||||
enabled: true # Disable email notifications for administrators when using other push methods
|
||||
mail_subject: '[{{site_name}}] Your article "{{page_title}}" has a new reply'
|
||||
mail_tpl: ''
|
||||
```
|
||||
|
||||
You can disable email notifications when using other push methods.
|
||||
|
||||
Before doing this, you need to configure the global email sending functionality: [refer here](./email.md).
|
||||
|
||||
### Administrator Email Template
|
||||
|
||||
- The `mail_subject` configuration item is the email subject sent to the administrator.
|
||||
- The `mail_tpl` configuration item specifies the email template for the administrator (fill in the email template file path).
|
||||
|
||||
(If this item is empty, it will inherit the `email.mail_tpl` configuration item).
|
||||
|
||||
## Telegram
|
||||
|
||||
```yaml
|
||||
admin_notify:
|
||||
# Telegram
|
||||
telegram:
|
||||
enabled: true
|
||||
api_token: ''
|
||||
receivers:
|
||||
- 7777777
|
||||
```
|
||||
|
||||
- `api_token`: The API token for the TG Bot.
|
||||
- `receivers`: The numeric IDs of the message recipients, multiple recipients can be set.
|
||||
|
||||
### Creating a TG Bot
|
||||
|
||||
Search for `@BotFather`, reply with `/newbot`, and follow the prompts to create a new TG bot.
|
||||
|
||||

|
||||
|
||||
The red text is the `api_token` you need to fill in Artalk's configuration.
|
||||
|
||||
Fill in the `receivers` field with the numeric IDs of the accounts that need to receive messages. You can use `@RawDataBot` to get these IDs, as shown:
|
||||
|
||||

|
||||
|
||||
For more details, refer to: [Bots: An introduction for developers - Telegram](https://core.telegram.org/bots)
|
||||
|
||||
::: tip
|
||||
|
||||
Given the complex network environment, if you need to use a proxy, configure the environment variable before starting Artalk, for example:
|
||||
|
||||
```sh
|
||||
export https_proxy=http://127.0.0.1:7890
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Feishu
|
||||
|
||||
```yaml
|
||||
admin_notify:
|
||||
# Feishu
|
||||
lark:
|
||||
enabled: true
|
||||
webhook_url: ''
|
||||
```
|
||||
|
||||
- `webhook_url`: The WebHook address obtained when creating the group bot.
|
||||
|
||||
### Creating a Group Bot
|
||||
|
||||
Click the plus sign at the top to create a new group:
|
||||
|
||||

|
||||
|
||||
Find "Group Settings" on the right, select "Group Bot", click "Add Bot", choose "Custom Bot", and follow the prompts to create it.
|
||||
|
||||
<img src="/images/notify/lark-2.png" width="700px">
|
||||
|
||||
Copy the WebHook address as shown above and modify Artalk's `webhook_url` configuration.
|
||||
|
||||
<img src="/images/notify/lark-3.png" width="400px">
|
||||
|
||||
For more details, refer to: [Feishu Help Center Documentation](https://www.feishu.cn/hc/zh-CN/articles/360024984973)
|
||||
|
||||
## DingTalk
|
||||
|
||||
```yaml
|
||||
admin_notify:
|
||||
# DingTalk
|
||||
ding_talk:
|
||||
enabled: true
|
||||
token: ''
|
||||
secret: ''
|
||||
```
|
||||
|
||||
Refer to: [DingTalk Open Documentation](https://open.dingtalk.com/document/robots/custom-robot-access)
|
||||
|
||||
## Bark
|
||||
|
||||
```yaml
|
||||
admin_notify:
|
||||
# Bark
|
||||
bark:
|
||||
enabled: true
|
||||
server: http://day.app/xxxxxxx/
|
||||
```
|
||||
|
||||
[Bark](https://github.com/Finb/Bark) is an open-source iOS app that [supports self-hosting](https://github.com/Finb/bark-server), allowing you to easily push messages to your iOS device.
|
||||
|
||||
You can search and download it from the App Store and get the `server` configuration item to fill in Artalk:
|
||||
|
||||
<img src="/images/notify/bark.png" width="700px">
|
||||
|
||||
## Slack
|
||||
|
||||
```yaml
|
||||
admin_notify:
|
||||
# Slack
|
||||
slack:
|
||||
enabled: true
|
||||
oauth_token: ''
|
||||
receivers:
|
||||
- CHANNEL_ID
|
||||
```
|
||||
|
||||
## LINE
|
||||
|
||||
```yaml
|
||||
admin_notify:
|
||||
# LINE
|
||||
line:
|
||||
enabled: true
|
||||
channel_secret: ''
|
||||
channel_access_token: ''
|
||||
receivers:
|
||||
- USER_ID_1
|
||||
- GROUP_ID_1
|
||||
```
|
||||
|
||||
## Notification Template
|
||||
|
||||
```yaml
|
||||
admin_notify:
|
||||
notify_tpl: default
|
||||
```
|
||||
|
||||
The `admin_notify.notify_tpl` configuration item can be set to a custom notification template file path. The default notification template is:
|
||||
|
||||
```
|
||||
@{{reply_nick}}:
|
||||
|
||||
{{reply_content}}
|
||||
|
||||
{{link_to_reply}}
|
||||
```
|
||||
|
||||
The available variables are the same as in the email template, refer to: [Email Template](./email.md#邮件模板)
|
||||
|
||||
## Notify Pending Comments `notify_pending`
|
||||
|
||||
```yaml
|
||||
admin_notify:
|
||||
notify_pending: false
|
||||
```
|
||||
|
||||
`notify_pending` is turned off by default. When set to `false`, pending comments will not send notifications. You can view all pending comments in the Dashboard.
|
||||
|
||||
## Noise Mode `noise_mode`
|
||||
|
||||
```yaml
|
||||
admin_notify:
|
||||
noise_mode: false
|
||||
```
|
||||
|
||||
`noise_mode` is turned off by default. When set to `false`, only messages directed to the administrator will send notifications. For example, communication between "User A" and "User B" will not notify the administrator.
|
||||
|
||||
Note: When `moderator.pending_default` is set to `true`, noise_mode is always enabled.
|
||||
|
||||
## WebHook Callback
|
||||
|
||||
When WebHook is enabled, a new comment will send a **POST** request with `application/json` type Body data to the specified WebHook address.
|
||||
|
||||
You can write your own server-side code to handle requests from Artalk.
|
||||
|
||||
**Artalk Configuration File**
|
||||
|
||||
```yaml
|
||||
admin_notify:
|
||||
webhook:
|
||||
enabled: true
|
||||
url: http://localhost:8080/
|
||||
```
|
||||
|
||||
**Body Data Content**
|
||||
|
||||
`application/json` type
|
||||
|
||||
| Key | Description | Type | Remarks |
|
||||
| ---------------- | ----------------- | ------ | ----------------------------------------- |
|
||||
| `notify_subject` | Notification Title | String | Corresponds to the admin_notify.notify_subject configuration item |
|
||||
| `notify_body` | Notification Content | String | Rendered according to the admin_notify.notify_tpl template |
|
||||
| `comment` | Comment Content | Object | New comment data object |
|
||||
| `parent_comment` | Target of Comment Reply | Object | If it's a root comment, the value is null |
|
||||
|
||||
**Body Data Sample**
|
||||
|
||||
```js
|
||||
{
|
||||
"notify_subject": "",
|
||||
"notify_body": "@TestUser:\n\nTestContent\n\nhttps://127.0.0.1/index.html?atk_comment=1057",
|
||||
"comment": {
|
||||
"id": 1057,
|
||||
"content": "TestContent",
|
||||
"user_id": 226,
|
||||
"nick": "TestUser",
|
||||
"email_encrypted": "654236c1e78i4c09a17c4869c9d43910",
|
||||
"link": "https://qwqaq.com",
|
||||
"ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 12_4_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36",
|
||||
"date": "2022-05-23 17:00:23",
|
||||
"is_collapsed": false,
|
||||
"is_pending": false,
|
||||
"is_pinned": false,
|
||||
"is_allow_reply": false,
|
||||
"rid": 0,
|
||||
"badge_name": "",
|
||||
"badge_color": "",
|
||||
"visible": true,
|
||||
"vote_up": 0,
|
||||
"vote_down": 0,
|
||||
"page_key": "/index.html",
|
||||
"page_url": "https://127.0.0.1/index.html",
|
||||
"site_name": "ArtalkDocs"
|
||||
},
|
||||
"parent_comment": null
|
||||
}
|
||||
```
|
||||
|
||||
**Node.js Express Handling Example**
|
||||
|
||||
```js
|
||||
const express = require('express')
|
||||
|
||||
const app = express()
|
||||
|
||||
app.use(express.json()) // Use JSON middleware
|
||||
|
||||
app.post('/', (request, response) => {
|
||||
console.log(request.body)
|
||||
|
||||
const notifySubject = request.body.notify_subject
|
||||
const notifyBody = request.body.notify_body
|
||||
console.log(notifySubject, notifyBody)
|
||||
|
||||
response.send(request.body)
|
||||
})
|
||||
|
||||
app.listen(8080)
|
||||
```
|
||||
|
||||
**Node.js HTTP Handling Example**
|
||||
|
||||
```js
|
||||
const http = require('http')
|
||||
|
||||
const requestListener = (req, res) => {
|
||||
// receive json request
|
||||
let body = ''
|
||||
req.on('data', (data) => {
|
||||
body += data
|
||||
})
|
||||
req.on('end', () => {
|
||||
let json = ''
|
||||
try {
|
||||
json = JSON.parse(body)
|
||||
} catch {}
|
||||
|
||||
// do something with json
|
||||
console.log(json)
|
||||
res.end()
|
||||
})
|
||||
|
||||
res.writeHead(200)
|
||||
res.end('Hello, World!')
|
||||
}
|
||||
|
||||
const server = http.createServer(requestListener)
|
||||
server.listen(8080)
|
||||
```
|
||||
|
||||
**PHP Laravel Handling Example**
|
||||
|
||||
```php
|
||||
Route::get('/', function (Request $request) {
|
||||
$data = $request->json()->all();
|
||||
$notify_subject = $data["notify_subject"];
|
||||
$notify_body = $data["notify_body"];
|
||||
});
|
||||
```
|
||||
|
||||
**Golang net/http Handling Example**
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type ArtalkNotify struct {
|
||||
NotifySubject string `json:"notify_subject"`
|
||||
NotifyBody string `json:"notify_body"`
|
||||
Comment interface{} `json:"comment"`
|
||||
ParentComment interface{} `json:"parent_comment"`
|
||||
}
|
||||
|
||||
func webhookHandler(rw http.ResponseWriter, req *http.Request) {
|
||||
decoder := json.NewDecoder(req.Body)
|
||||
var notify ArtalkNotify
|
||||
err := decoder.Decode(¬ify)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
log.Println(notify.NotifyBody)
|
||||
}
|
||||
|
||||
func main() {
|
||||
http.HandleFunc("/webhook", webhookHandler)
|
||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,159 @@
|
||||
# Captcha
|
||||
|
||||
Artalk comes with a built-in image captcha feature, allowing you to configure operation frequency limits that activate the captcha when exceeded.
|
||||
|
||||
Additionally, you can integrate various verification service providers to gain diversified verification functionality.
|
||||
|
||||
Currently, Artalk supports: Turnstile, reCAPTCHA, hCaptcha, and Geetest.
|
||||
|
||||
You can modify these settings in the [Dashboard](../frontend/sidebar.md#Settings) or via the [configuration file](./config.md#captcha) or [environment variables](../env.md#captcha).
|
||||
|
||||
## Configuration File
|
||||
|
||||
The full `captcha` configuration is as follows:
|
||||
|
||||
```yaml
|
||||
# Captcha
|
||||
captcha:
|
||||
enabled: true # Master switch
|
||||
always: false # Always require captcha
|
||||
captcha_type: image # Type of captcha
|
||||
action_limit: 3 # Number of actions required to activate captcha
|
||||
action_reset: 60 # Timeout to reset action counter (unit: s, set to -1 to never reset)
|
||||
# Turnstile
|
||||
# (https://www.cloudflare.com/products/turnstile/)
|
||||
turnstile:
|
||||
site_key: ''
|
||||
secret_key: ''
|
||||
# reCAPTCHA
|
||||
# (https://www.google.com/recaptcha/about/)
|
||||
recaptcha:
|
||||
site_key: ''
|
||||
secret_key: ''
|
||||
# hCaptcha (https://www.hcaptcha.com/)
|
||||
hcaptcha:
|
||||
site_key: ''
|
||||
secret_key: ''
|
||||
# Geetest (https://www.geetest.com)
|
||||
geetest:
|
||||
captcha_id: ''
|
||||
captcha_key: ''
|
||||
```
|
||||
|
||||
- **always**: When set to `true`, captcha is always required.
|
||||
- **captcha_type**: Type of captcha, options include: `image`, `turnstile`, `recaptcha`, `hcaptcha`, `geetest`.
|
||||
- **action_limit**: The number of actions required to activate the captcha.
|
||||
- **action_reset**: When the time exceeds this value, the action counter resets. Unit is seconds; set to `-1` to never reset.
|
||||
|
||||
Note: When `always` is enabled, `action_limit` and `action_reset` configurations are ignored.
|
||||
|
||||
## Configuration Examples
|
||||
|
||||
### Example 1
|
||||
|
||||
Within a 60-second time frame, if the number of actions exceeds 3, captcha will be required:
|
||||
|
||||
```yaml
|
||||
captcha:
|
||||
action_limit: 3
|
||||
action_reset: 60
|
||||
```
|
||||
|
||||
The counter will automatically reset after 60 seconds, allowing for 3 more actions without requiring a captcha.
|
||||
|
||||
### Example 2
|
||||
|
||||
Regardless of the time frame, if the number of actions from an IP address exceeds 5, captcha will be required:
|
||||
|
||||
```yaml
|
||||
captcha:
|
||||
action_limit: 5
|
||||
action_reset: -1
|
||||
```
|
||||
|
||||
### Example 3
|
||||
|
||||
Always require captcha, regardless of the number of actions:
|
||||
|
||||
```yaml
|
||||
captcha:
|
||||
always: true
|
||||
```
|
||||
|
||||
## Definition of Actions
|
||||
|
||||
Each "comment, vote, image upload, password verification" by an IP address counts as an "action."
|
||||
|
||||
## Turnstile
|
||||
|
||||
[Turnstile](https://www.cloudflare.com/zh-cn/products/turnstile/) is a verification service from Cloudflare. You can obtain the `site_key` and `secret_key` from the CF dashboard, then fill in these keys in the Artalk settings and change `captcha_type` to `turnstile`.
|
||||
|
||||
Illustrations:
|
||||
|
||||
<img src="/images/captcha/cf-turnstile-1.png" width="400px">
|
||||
|
||||
<img src="/images/captcha/cf-turnstile-2.png" width="400px">
|
||||
|
||||
Corresponding configuration file:
|
||||
|
||||
```yaml
|
||||
captcha:
|
||||
# Omit other configurations...
|
||||
captcha_type: turnstile
|
||||
turnstile:
|
||||
site_key: ''
|
||||
secret_key: ''
|
||||
```
|
||||
|
||||
## reCAPTCHA
|
||||
|
||||
[reCAPTCHA](https://developers.google.com/recaptcha) is a verification service from Google, and Artalk supports reCAPTCHA v3. You can obtain the `site_key` and `secret_key` from the Google developer dashboard, then fill in these keys in the Artalk settings and change `captcha_type` to `recaptcha`.
|
||||
|
||||
Corresponding configuration file:
|
||||
|
||||
```yaml
|
||||
captcha:
|
||||
# Omit other configurations...
|
||||
captcha_type: recaptcha
|
||||
recaptcha:
|
||||
site_key: ''
|
||||
secret_key: ''
|
||||
```
|
||||
|
||||
Note: Accessing Google APIs from within China may require configuring a system proxy.
|
||||
|
||||
Google provides test keys for reCAPTCHA: [see here](https://developers.google.com/recaptcha/docs/faq?hl=en#id-like-to-run-automated-tests-with-recaptcha.-what-should-i-do).
|
||||
|
||||
## hCaptcha
|
||||
|
||||
[hCaptcha](https://www.hcaptcha.com/) is a verification service. You can obtain the `site_key` and `secret_key` from its official website, then fill in these keys in the Artalk settings and change `captcha_type` to `hcaptcha`.
|
||||
|
||||
Corresponding configuration file:
|
||||
|
||||
```yaml
|
||||
captcha:
|
||||
# Omit other configurations...
|
||||
captcha_type: hcaptcha
|
||||
hcaptcha:
|
||||
site_key: ''
|
||||
secret_key: ''
|
||||
```
|
||||
|
||||
hCaptcha provides test keys: [see here](https://docs.hcaptcha.com/#integration-testing-test-keys).
|
||||
|
||||
## Geetest
|
||||
|
||||
Artalk supports integrating [Geetest](https://www.geetest.com/adaptive-captcha) for advanced behavior verification.
|
||||
|
||||
First, register an account on the Geetest website to obtain the `captcha_id` and `captcha_key`, then modify the configuration in the Artalk settings and change `captcha_type` to `geetest`.
|
||||
|
||||
Corresponding configuration file:
|
||||
|
||||
```yaml
|
||||
captcha:
|
||||
# Omit other configurations...
|
||||
captcha_type: geetest
|
||||
geetest:
|
||||
captcha_id: ''
|
||||
captcha_key: ''
|
||||
```
|
||||
@@ -0,0 +1,370 @@
|
||||
# Configuration File
|
||||
|
||||
## Easy Configuration
|
||||
|
||||
It is recommended to modify the configuration through the graphical interface in the sidebar "[Dashboard](../frontend/sidebar.md)" without manually editing the configuration file.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
You can configure via environment variables, refer to the documentation: [Environment Variables](../env.md).
|
||||
|
||||
## Specifying the Configuration File Path
|
||||
|
||||
By default, Artalk uses `artalk.yml` in the working directory as the configuration file. You can specify a specific file using the `-c` parameter:
|
||||
|
||||
```bash
|
||||
artalk -c ./conf.yml
|
||||
```
|
||||
|
||||
## Obtaining a Template Configuration File
|
||||
|
||||
You can refer to a "complete configuration file": [artalk.example.zh-CN.yml](https://github.com/ArtalkJS/Artalk/blob/master/conf/artalk.example.zh-CN.yml)
|
||||
|
||||
### Generate Configuration File Using the `gen` Command
|
||||
|
||||
Artalk provides the `gen` command, allowing you to quickly generate a new configuration file:
|
||||
|
||||
```bash
|
||||
artalk gen conf -lang zh-CN ./artalk.yml
|
||||
```
|
||||
|
||||
### Download Configuration File via Command Line
|
||||
|
||||
```bash
|
||||
wget -O artalk.yml https://raw.githubusercontent.com/ArtalkJS/Artalk/master/conf/artalk.example.zh-CN.yml
|
||||
```
|
||||
|
||||
## Encryption Key `app_key`
|
||||
|
||||
Before starting Artalk, you need to configure an `app_key` for securely encrypting site content:
|
||||
|
||||
```yaml
|
||||
app_key: <any random characters>
|
||||
```
|
||||
|
||||
## Language `locale`
|
||||
|
||||
Set the language for Artalk. Follows the Unicode BCP 47 standard, with the default being "zh-CN" (Simplified Chinese).
|
||||
|
||||
```yaml
|
||||
locale: zh-CN
|
||||
```
|
||||
|
||||
For details, refer to: [Multilingual](../frontend/i18n.md)
|
||||
|
||||
## Database `db`
|
||||
|
||||
Artalk supports connecting to various databases, including SQLite, MySQL, PostgreSQL, and SQL Server. The configurations are as follows:
|
||||
|
||||
#### SQLite
|
||||
|
||||
SQLite is a lightweight database that stores data in a single file, requiring no additional running programs. It is especially suitable for small sites, such as personal blogs.
|
||||
|
||||
```yaml
|
||||
db:
|
||||
type: sqlite
|
||||
file: ./data/artalk.db
|
||||
```
|
||||
|
||||
#### MySQL / PostgreSQL / SQL Server
|
||||
|
||||
Modify `type` to your database type:
|
||||
|
||||
```yaml
|
||||
db:
|
||||
type: mysql # sqlite, mysql, pgsql, mssql
|
||||
name: artalk # Database name
|
||||
host: localhost # Address
|
||||
port: 3306 # Port
|
||||
user: root # Username
|
||||
password: '' # Password
|
||||
charset: utf8mb4 # Charset
|
||||
table_prefix: '' # Table prefix (e.g., "atk_")
|
||||
ssl: false # Enable SSL
|
||||
prepare_stmt: true # Prepared statements
|
||||
```
|
||||
|
||||
The data tables will be automatically created when Artalk starts, no additional operation is required.
|
||||
|
||||
#### Database Connection String (DSN)
|
||||
|
||||
If needed, you can manually configure `db.dsn` to specify the database connection string, for example:
|
||||
|
||||
```yaml
|
||||
db:
|
||||
type: mysql
|
||||
dsn: mysql://myuser:mypassword@localhost:3306/mydatabase?tls=skip-verify
|
||||
```
|
||||
|
||||
For more details, refer to: [@go-sql-driver/mysql:README.md](https://github.com/go-sql-driver/mysql)
|
||||
|
||||
## Server `http`
|
||||
|
||||
```yaml
|
||||
http:
|
||||
# Request body size limit (unit: MB)
|
||||
body_limit: 100
|
||||
# Proxy header name (when using CDN, fill in `X-Forwarded-For` to get the user's real IP)
|
||||
proxy_header: ""
|
||||
```
|
||||
|
||||
## Admin Users `admin_users`
|
||||
|
||||
You need to configure administrator accounts to manage site content through the "[Dashboard](../frontend/sidebar.md)".
|
||||
|
||||
Artalk supports multiple sites, allowing you to create multiple administrator accounts and assign sites to them, enabling your friends to share the same backend program.
|
||||
|
||||
For details, refer to: [Admin Users × Multi-site](./multi-site.md)
|
||||
|
||||
## Trusted Domains `trusted_domains`
|
||||
|
||||
```yaml
|
||||
trusted_domains:
|
||||
- https://frontend.domainA.com
|
||||
- https://frontend.domainB.com
|
||||
```
|
||||
|
||||
Only the domains in the list are allowed to access the backend API, restricting cross-domain requests from external domains.
|
||||
|
||||
::: tip
|
||||
|
||||
You need to add the URL address of the "frontend page" to the trusted domains list.
|
||||
|
||||
If it's not the default 80/443 port, include the port number, e.g., `https://example.com:8080`.
|
||||
|
||||
:::
|
||||
|
||||
In the sidebar [Dashboard](../frontend/sidebar.md#控制中心), under the "Site" tab - select the site and "Modify URL", fill in the site URL to achieve the same setting effect; to add multiple URLs, separate them with commas `,`. After modification, manually restart Artalk.
|
||||
|
||||
The default site address `ATK_SITE_URL` configuration item will also be automatically added to the trusted domains list.
|
||||
|
||||
Additionally, you can configure trusted domains via the environment variable `ATK_TRUSTED_DOMAINS` when starting, for example:
|
||||
|
||||
```bash
|
||||
ATK_TRUSTED_DOMAINS="https://a.com https://b.org" artalk server
|
||||
```
|
||||
|
||||
Note that environment variables will override the settings in the configuration file.
|
||||
|
||||
Technical details: `trusted_domains` configuration controls cross-origin requests by setting the HTTP response header `Access-Control-Allow-Origin` (refer to: [W3C Cross-Origin Resource Sharing](https://fetch.spec.whatwg.org/#http-cors-protocol) / [OWASP Security Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html)).
|
||||
|
||||
## Default Site `site_default`
|
||||
|
||||
If you think you won't use the multi-site feature of Artalk, you can directly set this item to your site name, for example:
|
||||
|
||||
```yaml
|
||||
site_default: Artalk Official Website
|
||||
```
|
||||
|
||||
Then use this site name in the frontend:
|
||||
|
||||
```js
|
||||
Artalk.init({ site: 'Artalk Official Website' })
|
||||
```
|
||||
|
||||
This way, you don't need to manually create the site in the sidebar [Dashboard](../frontend/sidebar.md#控制中心).
|
||||
|
||||
## Frontend Configuration `frontend`
|
||||
|
||||
In Artalk's configuration file `artalk.yml`, you can configure the `frontend` field to control the frontend interface, for example:
|
||||
|
||||
```yaml
|
||||
frontend:
|
||||
placeholder: Type content...
|
||||
noComment: "Silence is golden"
|
||||
sendBtn: Send Comment
|
||||
emoticons: 'https://raw.githubusercontent.com/ArtalkJS/Emoticons/master/grps/default.json'
|
||||
# ----- Omitted -------
|
||||
# Keep the same as the frontend configuration item name
|
||||
```
|
||||
|
||||
A complete backend `frontend` field configuration file can be referred to: [artalk.example.zh-CN.yml](https://github.com/ArtalkJS/Artalk/blob/master/conf/artalk.example.zh-CN.yml)
|
||||
|
||||
## Email Notifications `email`
|
||||
|
||||
Configure email notifications to notify the target user of replies via email. You can customize the sender name, title, template, etc.
|
||||
|
||||
For details, refer to: [Backend · Email Notifications](./email.md)
|
||||
|
||||
## Multi-channel Notifications `admin_notify`
|
||||
|
||||
You can configure various message sending methods, such as Feishu, Telegram, etc., to notify administrators when new comments are received.
|
||||
|
||||
For details, refer to: [Backend · Multi-channel Notifications](./admin_notify.md)
|
||||
|
||||
## Comment Moderation `moderator`
|
||||
|
||||
Configure comment moderation to automatically intercept spam comments.
|
||||
|
||||
For details, refer to: [Backend · Comment Moderation](./moderator.md)
|
||||
|
||||
## Captcha `captcha`
|
||||
|
||||
Supports image and slide captchas to limit request frequency.
|
||||
|
||||
For details, refer to: [Backend · Captcha](./captcha.md)
|
||||
|
||||
## Cache `cache`
|
||||
|
||||
To save memory resources, caching is disabled by default. If you have high performance requirements for your site, enable it manually. You can also connect to external cache servers, supporting Redis and Memcache.
|
||||
|
||||
```yaml
|
||||
cache:
|
||||
enabled: true # Enable cache (default is off)
|
||||
type: builtin # Supports redis, memcache, builtin (built-in cache)
|
||||
expires: 30 # Cache expiration time (unit: minutes)
|
||||
warm_up: false # Warm up cache on program startup
|
||||
server: '' # Connect to cache server (e.g., "localhost:6379")
|
||||
```
|
||||
|
||||
- **warm_up**: Cache warm-up feature. Set to `true`, it will immediately cache all database content when Artalk starts. If you have a large number of comments, this may extend the startup time.
|
||||
- **type**: Cache type, defaults to `builtin`. Options: `redis`, `memcache`, `builtin`.
|
||||
|
||||
Note: If you modify the database content outside the Artalk program, you need to refresh the Artalk cache to update it.
|
||||
|
||||
---
|
||||
|
||||
Redis authentication and database configuration:
|
||||
|
||||
```yaml
|
||||
# Cache
|
||||
cache:
|
||||
# Omit other configurations...
|
||||
redis:
|
||||
network: tcp # Connection method (tcp or unix)
|
||||
username: '' # Username
|
||||
password: '' # Password
|
||||
db: 0 # Use database 0
|
||||
```
|
||||
|
||||
<!-- Technical details: [Artalk Cache Mechanism Sequence Diagram.png](/images/artalk/artalk-cache.png) -->
|
||||
<!--  -->
|
||||
|
||||
## Listening Address `host`
|
||||
|
||||
The default HTTP port for Artalk is 23366. You can specify it in the configuration file:
|
||||
|
||||
```yaml
|
||||
host: '0.0.0.0'
|
||||
port: 23366
|
||||
```
|
||||
|
||||
Configuring the `host` listening address to `0.0.0.0` will expose the Artalk service to the entire network.
|
||||
|
||||
If you want Artalk to be accessible only locally, configure `host` to `127.0.0.1`.
|
||||
|
||||
When starting Artalk via the command line, you can specify the address and port using the `--host` and `--port` parameters, for example:
|
||||
|
||||
```bash
|
||||
artalk server --host 127.0.0.1 --port 8080
|
||||
```
|
||||
|
||||
## Encrypted Transmission `ssl`
|
||||
|
||||
```yaml
|
||||
ssl:
|
||||
enabled: true
|
||||
cert_path: ''
|
||||
key_path: ''
|
||||
```
|
||||
|
||||
You can configure this item to upgrade HTTP to HTTPS, encrypting data transmission via SSL.
|
||||
|
||||
- `cert_path`: Path to the SSL certificate public key file.
|
||||
- `key_path`: Path to the SSL certificate private key file.
|
||||
|
||||
You can also directly reverse proxy the Artalk local server and then enable HTTPS in, for example, Nginx.
|
||||
|
||||
## Time Zone Configuration `timezone`
|
||||
|
||||
```yaml
|
||||
timezone: Asia/Shanghai
|
||||
```
|
||||
|
||||
Fill in the time zone where you are located, corresponding to the IANA database time zone name, refer to: [Wikipedia](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) / [RFC-6557](https://www.rfc-editor.org/rfc/rfc6557.html).
|
||||
|
||||
`timezone` controls the timezone used by the Artalk application. It may differ from the host system or container `TZ` setting. The same configuration can be supplied through `ATK_TIMEZONE`:
|
||||
|
||||
```bash
|
||||
ATK_TIMEZONE=America/New_York artalk server
|
||||
```
|
||||
|
||||
::: warning Restart required
|
||||
The application timezone is a process-level fixed value and is set only when the Artalk process initializes. It cannot be changed dynamically at runtime. After changing it in the configuration file, environment, or Dashboard, fully restart the Artalk process or container for the new value to take effect.
|
||||
:::
|
||||
|
||||
```
|
||||
UTC+08:00 Asia/Shanghai
|
||||
UTC+09:00 Asia/Tokyo
|
||||
UTC-07:00 America/Los_Angeles
|
||||
UTC-04:00 America/New_York
|
||||
```
|
||||
|
||||
## Login Timeout `login_timeout`
|
||||
|
||||
This value sets the validity period of the administrator account login JWT token, in seconds.
|
||||
|
||||
For example, valid for 3 days:
|
||||
|
||||
```yaml
|
||||
login_timeout: 259200
|
||||
```
|
||||
|
||||
## Log Configuration `log`
|
||||
|
||||
When logging is enabled, system errors and other information will be recorded in the specified file.
|
||||
|
||||
```yaml
|
||||
log:
|
||||
enabled: true # Master switch
|
||||
filename: ./data/artalk.log # Log file path
|
||||
```
|
||||
|
||||
Logs are in JSON format, which can be viewed using the `tail` command combined with the `jq` tool in Linux:
|
||||
|
||||
```bash
|
||||
tail -f ./data/artalk.log | jq
|
||||
```
|
||||
|
||||
If you are a Grafana user, you can use Loki and Promtail to collect and query logs.
|
||||
|
||||
## Debug Mode `debug`
|
||||
|
||||
Set `debug` to `true` to enable debug mode.
|
||||
|
||||
```yaml
|
||||
debug: true
|
||||
```
|
||||
|
||||
## Working Directory `-w` Parameter
|
||||
|
||||
If the working directory is not specified, Artalk will use the "directory where the program is started" as the working directory.
|
||||
|
||||
```bash
|
||||
pwd # Display the current directory path
|
||||
```
|
||||
|
||||
Use the `-w` parameter to specify the working directory, which is usually an "absolute path", for example:
|
||||
|
||||
```bash
|
||||
artalk -w /root/artalk -c ./conf.yml
|
||||
```
|
||||
|
||||
Note: The relative path of `-c` will be based on the path of `-w`, and Artalk will read `/root/artalk/conf.yml` as the configuration file.
|
||||
|
||||
Additionally, the "relative path" used in the "configuration file" will also be based on the "working directory".
|
||||
|
||||
For example, if the configuration in `conf.yml` is:
|
||||
|
||||
```yaml
|
||||
test_file: ./data/artalk.log
|
||||
```
|
||||
|
||||
It will read `/root/artalk/data/artalk.log`.
|
||||
|
||||
::: tip
|
||||
|
||||
Configuration file related code: [/internal/config/config.go](https://github.com/ArtalkJS/Artalk/blob/master/internal/config/config.go)
|
||||
|
||||
Go to: [Frontend Configuration](../frontend/config.md)
|
||||
:::
|
||||
@@ -0,0 +1,144 @@
|
||||
# Daemon Process
|
||||
|
||||
## Systemd
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- The Artalk binary file, which can be downloaded from [GitHub Release](https://github.com/ArtalkJS/Artalk/releases)
|
||||
- `systemctl --version` 232 or later
|
||||
- `sudo` administrator privileges
|
||||
|
||||
Move Artalk to `$PATH`, for example:
|
||||
|
||||
```bash
|
||||
sudo mv artalk /usr/bin/
|
||||
```
|
||||
|
||||
Test if it works:
|
||||
|
||||
```bash
|
||||
artalk version
|
||||
```
|
||||
|
||||
Create a user group named `artalk`:
|
||||
|
||||
```bash
|
||||
sudo groupadd --system artalk
|
||||
```
|
||||
|
||||
Create a user named `artalk` with a writable home directory:
|
||||
|
||||
```bash
|
||||
sudo useradd --system \
|
||||
--gid artalk \
|
||||
--create-home \
|
||||
--home-dir /var/lib/artalk \
|
||||
--shell /usr/sbin/nologin \
|
||||
--comment "Artalk server" \
|
||||
artalk
|
||||
```
|
||||
|
||||
If you have the Artalk configuration file, ensure that the newly created `artalk` user has read permissions.
|
||||
|
||||
Create the service file:
|
||||
|
||||
```bash
|
||||
sudo vim /etc/systemd/system/artalk.service
|
||||
```
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=Artalk
|
||||
Documentation=https://artalk.js.org
|
||||
After=network.target network-online.target
|
||||
Requires=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=artalk
|
||||
Group=artalk
|
||||
ExecStart=/usr/bin/artalk server -w /var/lib/artalk -c /etc/artalk/artalk.yml
|
||||
ExecReload=/bin/kill -s HUP $MAINPID
|
||||
ExecStop=/bin/kill -s QUIT $MAINPID
|
||||
TimeoutStopSec=5s
|
||||
LimitNOFILE=1048576
|
||||
LimitNPROC=512
|
||||
PrivateTmp=true
|
||||
ProtectSystem=full
|
||||
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
Carefully check `ExecStart` and `ExecReload`. Ensure that the binary file location and program startup parameters are correct.
|
||||
|
||||
For example, change the path of the `-c` parameter to specify the configuration file and the `-w` parameter to change the working directory.
|
||||
|
||||
Note that all relative paths in the configuration file are based on the working directory. For example, the `./data/` folder in the configuration file, if the startup parameter is `-w /var/lib/artalk`, will read from the `/var/lib/artalk/data/` directory. Ensure the files in this directory are readable and writable by the created `artalk` account.
|
||||
|
||||
After saving the service file, you can set the service to start automatically:
|
||||
|
||||
```bash
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now artalk
|
||||
```
|
||||
|
||||
Verify that the service is running correctly:
|
||||
|
||||
```bash
|
||||
systemctl status artalk
|
||||
```
|
||||
|
||||
Some common commands:
|
||||
|
||||
- Start the service: `systemctl start artalk`
|
||||
- Stop the service: `systemctl stop artalk`
|
||||
- Check the status: `systemctl status artalk`
|
||||
- View logs: `journalctl -u artalk --no-pager | less +G`
|
||||
|
||||
## Tmux
|
||||
|
||||
tmux will create a persistent command-line session that remains in the background after SSH or tty disconnection.
|
||||
|
||||
Note: tmux sessions will be cleared after the server shuts down or restarts, and you will need to manually rerun the program.
|
||||
|
||||
1. Create a session `tmux new -s artalk`
|
||||
2. Run the program `./artalk server`
|
||||
|
||||
Reconnect to the session: `tmux attach -t artalk`
|
||||
|
||||
View all sessions: `tmux ls`
|
||||
|
||||
## Supervisor
|
||||
|
||||
Taking Baota Panel as an example: Open the "Software Store," search for and install the "Supervisor Manager":
|
||||
|
||||

|
||||
|
||||
After installation, open the plugin and click "Add Daemon":
|
||||
|
||||

|
||||
|
||||
> - Startup User: `root` or others
|
||||
> - Working Directory: Click the icon on the right and select the Artalk directory
|
||||
> - Startup Command: `./artalk server`
|
||||
|
||||
## Docker
|
||||
|
||||
Update the Docker container's [Restart Policy](https://docs.docker.com/config/containers/start-containers-automatically/#use-a-restart-policy) to achieve the effect of process daemonization.
|
||||
|
||||
```bash
|
||||
docker update --restart=unless-stopped artalk
|
||||
```
|
||||
|
||||
## Docker Compose
|
||||
|
||||
Add the `restart: unless-stopped` policy to the service in the `docker-compose.yml` file:
|
||||
|
||||
```diff
|
||||
version: '3'
|
||||
services:
|
||||
artalk:
|
||||
+ restart: unless-stopped
|
||||
```
|
||||
@@ -0,0 +1,101 @@
|
||||
# Docker
|
||||
|
||||
Artalk provides Docker images for the backend program to accelerate the deployment process and offer a good deployment experience.
|
||||
|
||||
The [Docker Hub](https://hub.docker.com/r/artalk/artalk-go) image versions are kept in sync with the [Releases](https://github.com/ArtalkJS/Artalk/releases) in the code repository.
|
||||
|
||||
## Pulling the Image
|
||||
|
||||
```bash
|
||||
docker pull artalk/artalk-go
|
||||
```
|
||||
|
||||
## Creating the Container
|
||||
|
||||
::: tip
|
||||
|
||||
It is recommended to use Docker Compose. Refer to the steps on the [Program Deployment](../deploy) page.
|
||||
|
||||
:::
|
||||
|
||||
## Generating the Configuration File
|
||||
|
||||
```bash
|
||||
docker run -it -v $(pwd)/data:/data --rm artalk/artalk-go gen config data/artalk.yml
|
||||
```
|
||||
|
||||
## Restart
|
||||
|
||||
After modifying the configuration file, you need to restart to apply the changes.
|
||||
|
||||
```bash
|
||||
# Docker Compose
|
||||
docker-compose restart
|
||||
|
||||
# Docker
|
||||
docker restart artalk
|
||||
```
|
||||
|
||||
## Stop
|
||||
|
||||
```bash
|
||||
# Docker Compose
|
||||
docker-compose stop
|
||||
|
||||
# Docker
|
||||
docker stop artalk
|
||||
```
|
||||
|
||||
## Upgrade
|
||||
|
||||
Delete the existing container, pull the latest image, and then recreate the container.
|
||||
|
||||
### Docker Compose
|
||||
|
||||
```bash
|
||||
docker-compose down
|
||||
docker-compose pull
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### Docker
|
||||
|
||||
```bash
|
||||
docker stop artalk
|
||||
docker rm artalk
|
||||
docker pull artalk/artalk-go
|
||||
```
|
||||
|
||||
::: tip
|
||||
For breaking changes, please refer to the update notes in the version changelog: [CHANGELOG.md](https://github.com/ArtalkJS/Artalk/blob/master/CHANGELOG.md)
|
||||
:::
|
||||
|
||||
## Testing Version
|
||||
|
||||
The nightly image is a testing version that is updated daily and automatically built from the latest code in the repository.
|
||||
|
||||
```bash
|
||||
docker pull artalk/artalk-go:nightly
|
||||
```
|
||||
|
||||
## Historical Versions
|
||||
|
||||
Images are automatically built and released with the repository tags. You can pull images of different versions.
|
||||
|
||||
```bash
|
||||
docker pull artalk/artalk-go:<version>
|
||||
```
|
||||
|
||||
## Entering the Container
|
||||
|
||||
```bash
|
||||
# Docker Compose
|
||||
docker-compose exec artalk bash
|
||||
|
||||
# Docker
|
||||
docker exec -it artalk bash
|
||||
```
|
||||
|
||||
## Multi-Platform Compatibility
|
||||
|
||||
The Docker image currently only provides builds for x86 and arm64 architectures. For more platform architectures, please download the binary build and deploy it using the [Binary Deployment](../deploy.md#binary) method.
|
||||
@@ -0,0 +1,325 @@
|
||||
# Email Notifications
|
||||
|
||||
Artalk supports sending email notifications to users through various methods such as SMTP protocol, Alibaba Cloud Email Service, and system Sendmail command invocation.
|
||||
|
||||
You can modify this configuration in the settings interface of the [Dashboard](../frontend/sidebar.md#settings), or configure it through [configuration files](./config.md#email-notifications-email) or [environment variables](../env.md#email-notifications).
|
||||
|
||||
## Configuration File
|
||||
|
||||
The complete `email` configuration is as follows:
|
||||
|
||||
```yaml
|
||||
# Email Notifications
|
||||
email:
|
||||
enabled: false # Master Switch
|
||||
send_type: smtp # Sending Method [smtp, ali_dm, sendmail]
|
||||
send_name: '{{reply_nick}}' # Sender's Nickname
|
||||
send_addr: example@qq.com # Sender's Address
|
||||
mail_subject: '[{{site_name}}] You have received a reply from @{{reply_nick}}'
|
||||
mail_tpl: default # Email Template File
|
||||
smtp:
|
||||
host: smtp.qq.com
|
||||
port: 587
|
||||
username: example@qq.com
|
||||
password: ''
|
||||
ali_dm: # https://help.aliyun.com/document_detail/29444.html
|
||||
access_key_id: '' # Access Key ID issued by Alibaba Cloud
|
||||
access_key_secret: '' # Secret key for encryption
|
||||
account_name: example@example.com # Sending address configured in the management console
|
||||
```
|
||||
|
||||
### Choosing a Sending Method
|
||||
|
||||
The configuration item `enabled` enables email notifications, and `send_type` is used to select the sending method. Options are: `smtp`, `ali_dm`, `sendmail`.
|
||||
|
||||
```yaml
|
||||
email:
|
||||
enabled: true
|
||||
send_type: smtp # Sending Method
|
||||
# Other configurations omitted...
|
||||
smtp:
|
||||
# SMTP Configuration...
|
||||
ali_dm:
|
||||
# Alibaba Cloud Push Configuration...
|
||||
```
|
||||
|
||||
### SMTP Configuration
|
||||
|
||||
```yaml
|
||||
# Email Notifications
|
||||
email:
|
||||
enabled: true
|
||||
send_type: smtp # Selecting smtp
|
||||
smtp:
|
||||
host: smtp.qq.com
|
||||
port: 587
|
||||
username: example@qq.com
|
||||
password: ''
|
||||
```
|
||||
|
||||
### Alibaba Cloud Push Configuration
|
||||
|
||||
```yaml
|
||||
email:
|
||||
enabled: true
|
||||
send_type: ali_dm # Selecting ali_dm
|
||||
ali_dm:
|
||||
access_key_id: '' # Access Key ID issued by Alibaba Cloud
|
||||
access_key_secret: '' # Secret key for encryption
|
||||
account_name: example@example.com # Sending address configured in the management console
|
||||
```
|
||||
|
||||
Refer to: [Alibaba Cloud Official Documentation](https://help.aliyun.com/document_detail/29444.html)
|
||||
|
||||
## Comment Replies
|
||||
|
||||
The email will include a comment reply button, linking to the given PageKey on the frontend. If your `pageKey` configuration item is a "relative path" of the page, you need to set a URL for your site in the "[Dashboard](../frontend/sidebar.md#dashboard)" - "Site":
|
||||
|
||||
<img src="/images/sidebar/site_url.png" width="400px">
|
||||
|
||||
## Email Templates
|
||||
|
||||
### Template Variables
|
||||
|
||||
You can use template variables in `mail_subject` and `mail_subject_to_admin` as well as in the email template file:
|
||||
|
||||
```yaml
|
||||
email:
|
||||
# Other configurations omitted...
|
||||
mail_subject: '[{{site_name}}] You have received a reply from @{{reply_nick}}'
|
||||
mail_subject_to_admin: '[{{site_name}}] Your article "{{page_title}}" has a new reply'
|
||||
mail_tpl: default # Email Template File
|
||||
```
|
||||
|
||||
Variables follow the "Mustache" syntax, outputting a variable with `double braces` + `variable name`:
|
||||
|
||||
**Basic Content Variables**
|
||||
|
||||
```yaml
|
||||
{{content}} # Comment content
|
||||
{{link_to_reply}} # Reply link
|
||||
{{nick}} # Commenter's nickname
|
||||
{{page_title}} # Page title
|
||||
{{page_url}} # Page PageKey (URL)
|
||||
{{reply_content}} # Content of the reply
|
||||
{{reply_nick}} # Nickname of the reply target
|
||||
{{site_name}} # Site name
|
||||
{{site_url}} # Site URL
|
||||
```
|
||||
|
||||
::: details View Other Variables
|
||||
|
||||
```yaml
|
||||
# Comment Creator
|
||||
{{comment.badge_color}}
|
||||
{{comment.badge_name}}
|
||||
{{comment.content}}
|
||||
{{comment.content_raw}}
|
||||
{{comment.date}}
|
||||
{{comment.datetime}}
|
||||
{{comment.email}}
|
||||
{{comment.email_encrypted}}
|
||||
{{comment.id}}
|
||||
{{comment.is_allow_reply}}
|
||||
{{comment.is_collapsed}}
|
||||
{{comment.is_pending}}
|
||||
{{comment.link}}
|
||||
{{comment.nick}}
|
||||
{{comment.page.admin_only}}
|
||||
{{comment.page.id}}
|
||||
{{comment.page.key}}
|
||||
{{comment.page.site_name}}
|
||||
{{comment.page.title}}
|
||||
{{comment.page.url}}
|
||||
{{comment.page.vote_down}}
|
||||
{{comment.page.vote_up}}
|
||||
{{comment.page_key}}
|
||||
{{comment.page_title}}
|
||||
{{comment.rid}}
|
||||
{{comment.site.first_url}}
|
||||
{{comment.site.id}}
|
||||
{{comment.site.name}}
|
||||
{{comment.site.urls.0}}
|
||||
{{comment.site.urls_raw}}
|
||||
{{comment.site_name}}
|
||||
{{comment.time}}
|
||||
{{comment.ua}}
|
||||
{{comment.visible}}
|
||||
{{comment.vote_down}}
|
||||
{{comment.vote_up}}
|
||||
|
||||
# Parent Comment (Comment that the creator is replying to)
|
||||
{{parent_comment.badge_color}}
|
||||
{{parent_comment.badge_name}}
|
||||
{{parent_comment.content}}
|
||||
{{parent_comment.content_raw}}
|
||||
{{parent_comment.date}}
|
||||
{{parent_comment.datetime}}
|
||||
{{parent_comment.email}}
|
||||
{{parent_comment.email_encrypted}}
|
||||
{{parent_comment.id}}
|
||||
{{parent_comment.is_allow_reply}}
|
||||
{{parent_comment.is_collapsed}}
|
||||
{{parent_comment.is_pending}}
|
||||
{{parent_comment.link}}
|
||||
{{parent_comment.nick}}
|
||||
{{parent_comment.page.admin_only}}
|
||||
{{parent_comment.page.id}}
|
||||
{{parent_comment.page.key}}
|
||||
{{parent_comment.page.site_name}}
|
||||
{{parent_comment.page.title}}
|
||||
{{parent_comment.page.url}}
|
||||
{{parent_comment.page.vote_down}}
|
||||
{{parent_comment.page.vote_up}}
|
||||
{{parent_comment.page_key}}
|
||||
{{parent_comment.page_title}}
|
||||
{{parent_comment.rid}}
|
||||
{{parent_comment.site.first_url}}
|
||||
{{parent_comment.site.id}}
|
||||
{{parent_comment.site.name}}
|
||||
{{parent_comment.site.urls}}
|
||||
{{parent_comment.site.urls_raw}}
|
||||
{{parent_comment.site_name}}
|
||||
{{parent_comment.time}}
|
||||
{{parent_comment.ua}}
|
||||
{{parent_comment.visible}}
|
||||
{{parent_comment.vote_down}}
|
||||
{{parent_comment.vote_up}}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
::: details View Data Sample
|
||||
|
||||
```json
|
||||
{
|
||||
"content": "<p>Test content</p>\n",
|
||||
"link_to_reply": "https://artalk.js.org/?atk_comment=8100&atk_notify_key=44a9b2f08312565fba47c716df9d177f",
|
||||
"nick": "Username",
|
||||
"page_title": "Artalk",
|
||||
"page_url": "https://artalk.js.org/",
|
||||
|
||||
"reply_content": "<p>Replier's content</p>\n",
|
||||
"reply_nick": "Replier",
|
||||
"site_name": "ArtalkDemo",
|
||||
"site_url": "http://localhost:3000/",
|
||||
|
||||
"comment.badge_color": "",
|
||||
"comment.badge_name": "",
|
||||
"comment.content": "<p>Replier's content</p>\n",
|
||||
"comment.content_raw": "Replier's content",
|
||||
"comment.date": "2021-11-22",
|
||||
"comment.datetime": "2021-11-22 22:22:42",
|
||||
"comment.email": "replyer@example.com",
|
||||
"comment.email_encrypted": "249898bd50e0febc5799485cf10b345a",
|
||||
"comment.id": 8100,
|
||||
"comment.is_allow_reply": true,
|
||||
"comment.is_collapsed": false,
|
||||
"comment.is_pending": false,
|
||||
"comment.link": "",
|
||||
"comment.nick": "Replier",
|
||||
"comment.page.admin_only": false,
|
||||
"comment.page.id": 75,
|
||||
"comment.page.key": "https://artalk.js.org/",
|
||||
"comment.page.site_name": "ArtalkDemo",
|
||||
"comment.page.title": "Artalk",
|
||||
"comment.page.url": "https://artalk.js.org/",
|
||||
"comment.page.vote_down": 0,
|
||||
"comment.page.vote_up": 0,
|
||||
"comment.page_key": "https://artalk.js.org/",
|
||||
"comment.page_title": "Artalk",
|
||||
"comment.rid": 8099,
|
||||
"comment.site.first_url": "http://localhost:3000/",
|
||||
"comment.site.id": 2,
|
||||
"comment.site.name": "ArtalkDemo",
|
||||
"comment.site.urls.0": "http://localhost:3000/",
|
||||
"comment.site.urls_raw": "http://localhost:3000/",
|
||||
"comment.site_name": "ArtalkDemo",
|
||||
"comment.time": "22:22:42",
|
||||
"comment.ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1",
|
||||
"comment.visible": false,
|
||||
"comment.vote_down": 0,
|
||||
"comment.vote_up": 0,
|
||||
|
||||
"parent_comment.badge_color": "",
|
||||
"parent_comment.badge_name": "",
|
||||
"parent_comment.content": "<p>Test content</p>\n",
|
||||
"parent_comment.content_raw": "Test content",
|
||||
"parent_comment.date": "2021-11-22",
|
||||
"parent_comment.datetime": "2021-11-22 22:21:17",
|
||||
"parent_comment.email": "test@example.com",
|
||||
"parent_comment.email_encrypted": "55502f40dc8b7c769880b10874abc9d0",
|
||||
"parent_comment.id": 8099,
|
||||
"parent_comment.is_allow_reply": true,
|
||||
"parent_comment.is_collapsed": false,
|
||||
"parent_comment.is_pending": false,
|
||||
"parent_comment.link": "https://qwqaq.com",
|
||||
"parent_comment.nick": "Username",
|
||||
"parent_comment.page.admin_only": false,
|
||||
"parent_comment.page.id": 75,
|
||||
"parent_comment.page.key": "https://artalk.js.org/",
|
||||
"parent_comment.page.site_name": "ArtalkDemo",
|
||||
"parent_comment.page.title": "Artalk",
|
||||
"parent_comment.page.url": "https://artalk.js.org/",
|
||||
"parent_comment.page.vote_down": 0,
|
||||
"parent_comment.page.vote_up": 0,
|
||||
"parent_comment.page_key": "https://artalk.js.org/",
|
||||
"parent_comment.page_title": "Artalk",
|
||||
"parent_comment.rid": 0,
|
||||
"parent_comment.site.first_url": "http://localhost:3000/",
|
||||
"parent_comment.site.id": 2,
|
||||
"parent_comment.site.name": "ArtalkDemo",
|
||||
"parent_comment.site.urls.0": "http://localhost:3000/",
|
||||
"parent_comment.site.urls_raw": "http://localhost:3000/",
|
||||
"parent_comment.site_name": "ArtalkDemo",
|
||||
"parent_comment.time": "22:21:17",
|
||||
"parent_comment.ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36",
|
||||
"parent_comment.visible": false,
|
||||
"parent_comment.vote_down": 0,
|
||||
"parent_comment.vote_up": 0
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Custom Templates
|
||||
|
||||
You can set `mail_tpl` to a "specific file path" to use an external custom email template.
|
||||
|
||||
For example, set `mail_tpl` to `"/root/Artalk/data/mail_tpl/your_email_template.html"`
|
||||
|
||||
```yaml
|
||||
email:
|
||||
mail_tpl: /root/Artalk/data/mail_tpl/your_email_template.html
|
||||
# Other configurations omitted...
|
||||
```
|
||||
|
||||
Then, there should be a file at this path:
|
||||
|
||||
```html
|
||||
<div>
|
||||
<p>Hi, {{nick}}:</p>
|
||||
<p>You have received a reply in “{{page_title}}”:</p>
|
||||
<div>
|
||||
<div>@{{reply_nick}}:</div>
|
||||
<div>{{reply_content}}</div>
|
||||
</div>
|
||||
<p><a href="{{link_to_reply}}" target="_blank">Reply to the message »</a></p>
|
||||
</div>
|
||||
```
|
||||
|
||||
Artalk includes many preset email templates, such as `mail_tpl: "default"`, which uses: [@ArtalkJS/Artalk:/internal/template/email_tpl/default.html](https://github.com/ArtalkJS/Artalk/blob/master/internal/template/email_tpl/default.html)
|
||||
|
||||
## Emails to Administrators
|
||||
|
||||
Email notifications target both administrators and regular users. You can set different subjects for emails sent to administrators via the following configuration:
|
||||
|
||||
```yaml
|
||||
admin_notify:
|
||||
enabled: true
|
||||
mail_subject: '[{{site_name}}] Your article "{{page_title}}" has a new reply'
|
||||
```
|
||||
|
||||
Note: The old `email.mail_subject_to_admin` configuration item has been deprecated. Please use the above instead.
|
||||
|
||||
Artalk supports various methods to send notifications to administrators, not limited to emails. Refer to: [Diverse Push Notifications](./admin_notify.md#email-notifications).
|
||||
@@ -0,0 +1,114 @@
|
||||
# Image Upload
|
||||
|
||||
Artalk provides an image upload feature with options to limit image size, upload frequency, and more. You can also integrate with UpGit to upload images to image hosting services.
|
||||
|
||||
You can modify this configuration in the settings interface of the [Dashboard](../frontend/sidebar.md#settings), or configure it through [configuration files](./config.md#image-upload-img_upload) or [environment variables](../env.md#image-upload).
|
||||
|
||||
## Configuration File
|
||||
|
||||
The complete `img_upload` configuration is as follows:
|
||||
|
||||
```yaml
|
||||
# Image Upload
|
||||
img_upload:
|
||||
enabled: true # Master Switch
|
||||
path: ./data/artalk-img/ # Image storage path
|
||||
max_size: 5 # Image size limit (Unit: MB)
|
||||
public_path: null # Specify the base URL for image links (default is "/static/images/")
|
||||
# Use UpGit to upload images to GitHub or image hosting services
|
||||
upgit:
|
||||
enabled: false # Enable UpGit
|
||||
exec: upgit -c <upgit configuration file path> -t /artalk-img
|
||||
del_local: true # Delete local images after uploading
|
||||
```
|
||||
|
||||
## Using UpGit to Upload to Image Hosting Services
|
||||
|
||||
[UpGit](https://github.com/pluveto/upgit) supports uploading images to various image hosting services or code repositories such as GitHub, Gitee, Tencent Cloud COS, Qiniu Cloud, UpYun, SM.MS, and more.
|
||||
|
||||
First, download UpGit and complete the configuration for your target image hosting service according to the [README.md](https://github.com/pluveto/upgit).
|
||||
|
||||
Then, add UpGit to the system's environment variables by adding the following to `~/.bashrc`:
|
||||
|
||||
```bash
|
||||
export PATH=$PATH:/path/to/upgit
|
||||
```
|
||||
|
||||
(Or move it directly to `/usr/bin`)
|
||||
|
||||
Finally, fill in the UpGit startup parameters in Artalk's `img_upload.upgit` field:
|
||||
|
||||
```yaml
|
||||
upgit:
|
||||
enabled: true # Enable UpGit
|
||||
exec: upgit -c <upgit configuration file path> -t /artalk-img
|
||||
del_local: true # Delete local images after uploading
|
||||
```
|
||||
|
||||
::: warning Update Notice
|
||||
Starting from version `v2.8.4`, to enhance security, Artalk no longer allows specifying the UpGit executable file path. Please add it to the system's environment variables. :)
|
||||
:::
|
||||
|
||||
### Mounting UpGit with Docker
|
||||
|
||||
If you are deploying Artalk with Docker, you can mount the UpGit executable to the container:
|
||||
|
||||
```bash
|
||||
docker run -d --name artalk -v /path/to/upgit:/usr/bin/upgit -v /path/to/artalk:/app/data -p 8080:23366 artalk
|
||||
```
|
||||
|
||||
## Upload Frequency Limit
|
||||
|
||||
The frequency limit follows the `captcha` configuration. When the limit is exceeded, a captcha will be prompted.
|
||||
|
||||
Refer to: [Backend · Captcha](./captcha.md)
|
||||
|
||||
## Path
|
||||
|
||||
`img_upload.path` is the "local storage directory" path for uploaded image files. This directory will be mapped by Artalk to be accessible at:
|
||||
|
||||
```
|
||||
http://<backend address>/static/images/
|
||||
```
|
||||
|
||||
## Public Path
|
||||
|
||||
The default value for `img.public_path` is: `/static/images/`
|
||||
|
||||
When this item is a "relative path", for example: `/static/images/`, the HTML tag for the uploaded image on the frontend will be:
|
||||
|
||||
```html
|
||||
<img src="http://<backend address>/static/images/1.png" />
|
||||
```
|
||||
|
||||
Note: The `<backend address>` is configured in the frontend `conf.server`.
|
||||
|
||||
When this item is a "complete URL path", for example: `https://cdn.github.com/img/`, the image tag will be:
|
||||
|
||||
```html
|
||||
<img src="https://cdn.github.com/img/1.png" />
|
||||
```
|
||||
|
||||
Tip: This configuration can be used in scenarios such as load balancing.
|
||||
|
||||
## Custom Upload API on the Frontend
|
||||
|
||||
The frontend provides the `imgUploader` configuration option, allowing you to customize the API for image upload requests, for example:
|
||||
|
||||
```js
|
||||
Artalk.init({
|
||||
imgUploader: async (file) => {
|
||||
const form = new FormData()
|
||||
form.set('file', file)
|
||||
|
||||
const imgUrl = await fetch('https://api.example.org/upload', {
|
||||
method: 'POST',
|
||||
body: form,
|
||||
})
|
||||
|
||||
return imgUrl
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Refer to: [Frontend Configuration Documentation](../frontend/config.md#imguploader)
|
||||
@@ -0,0 +1,119 @@
|
||||
# Comment Moderation
|
||||
|
||||
Artalk supports multiple comment filtering methods to intercept spam comments. It offers integration with online APIs like Akismet, Tencent Cloud Content Safety, Alibaba Cloud Content Safety, and local offline keyword libraries. You can further enhance comment moderation by combining it with the [captcha](./captcha.md) feature, making it hard for spam comments to get through.
|
||||
|
||||
You can modify this configuration in the settings interface of the [Dashboard](../frontend/sidebar.md#settings), or configure it through [configuration files](./config.md#comment-moderation-moderator) or [environment variables](../env.md#comment-moderation).
|
||||
|
||||
## Configuration File
|
||||
|
||||
The complete `moderator` configuration is as follows:
|
||||
|
||||
```yaml
|
||||
# Comment Moderation
|
||||
moderator:
|
||||
pending_default: false # New comments default to "pending review" status
|
||||
api_fail_block: false # Intercept comments even if the spam detection API request fails
|
||||
# akismet.com anti-spam
|
||||
akismet_key: ''
|
||||
# Tencent Cloud Text Content Safety (tms)
|
||||
tencent: # https://cloud.tencent.com/document/product/1124/64508
|
||||
enabled: false
|
||||
secret_id: ''
|
||||
secret_key: ''
|
||||
region: ap-guangzhou
|
||||
# Alibaba Cloud Content Safety
|
||||
aliyun: # https://help.aliyun.com/document_detail/28417.html
|
||||
enabled: false
|
||||
access_key_id: ''
|
||||
access_key_secret: ''
|
||||
region: cn-shanghai
|
||||
# Keyword library filtering
|
||||
keywords:
|
||||
enabled: false
|
||||
pending: false # Set to pending review if matched
|
||||
files: # Support multiple keyword files
|
||||
- './data/keyword_1.txt'
|
||||
file_sep: "\n" # Keyword file content separator
|
||||
replace_to: 'x' # Replacement character
|
||||
```
|
||||
|
||||
## Default Pending Mode
|
||||
|
||||
Enable new comments to default to "pending review" status:
|
||||
|
||||
```yaml
|
||||
moderator:
|
||||
pending_default: true
|
||||
```
|
||||
|
||||
## Akismet
|
||||
|
||||
[Akismet](https://akismet.com/) is a globally recognized anti-spam API provided by WordPress, particularly effective against English spam comments. Akismet offers a free Personal version suitable for personal blog sites.
|
||||
|
||||

|
||||
|
||||
You can easily apply for an `akismet_key` on the [Akismet official website](https://akismet.com/) and fill it in the configuration file to enable Akismet spam filtering.
|
||||
|
||||

|
||||
|
||||
```yaml
|
||||
moderator:
|
||||
akismet_key: your_key
|
||||
```
|
||||
|
||||
## Tencent Cloud Text Content Safety
|
||||
|
||||
Refer to: [Tencent Cloud Documentation](https://cloud.tencent.com/document/product/1124/64508)
|
||||
|
||||
After enabling "Text Content Safety", add a Secret with permissions in "Access Management" - "API Key Management", and fill in the configuration:
|
||||
|
||||
```yaml
|
||||
moderator:
|
||||
tencent:
|
||||
enabled: true
|
||||
secret_id: ''
|
||||
secret_key: ''
|
||||
region: ap-guangzhou
|
||||
```
|
||||
|
||||
## Alibaba Cloud Content Safety
|
||||
|
||||
Refer to: [Alibaba Cloud Documentation](https://help.aliyun.com/document_detail/28417.html)
|
||||
|
||||
After enabling "Alibaba Cloud Content Safety", create an Access Key in the Alibaba Cloud backend and fill in the configuration:
|
||||
|
||||
```yaml
|
||||
moderator:
|
||||
aliyun:
|
||||
enabled: true
|
||||
access_key_id: ''
|
||||
access_key_secret: ''
|
||||
region: cn-shanghai
|
||||
```
|
||||
|
||||
## Keyword Library Filtering
|
||||
|
||||
If you prefer not to rely on remote APIs, you can configure and import keyword files locally to let Artalk detect spam comments based on keywords:
|
||||
|
||||
```yaml
|
||||
moderator:
|
||||
keywords:
|
||||
enabled: true
|
||||
pending: false # Set to pending review if matched
|
||||
files: # Support multiple keyword files
|
||||
- ./data/keyword_1.txt
|
||||
file_sep: "\n" # Keyword file content separator
|
||||
replace_to: 'x' # Replacement character
|
||||
```
|
||||
|
||||
- **pending**: Whether to set the comment to pending review status upon successful match.
|
||||
- **files**: Keyword files. Multiple files are allowed, and Artalk will merge the keyword libraries upon startup.
|
||||
- **file_sep**: Keyword file content separator. For example, if each line in the file contains a keyword, set this item to `\n`.
|
||||
The value cannot be empty. The dashboard displays line feeds, carriage returns, and tabs as `\n`, `\r`, and `\t`. Enter `\\n` when the separator must be the literal characters `\n`.
|
||||
- **replace_to**: Replacement character. For example, if this item is set to `x`, you can set `pending` to `false`, allowing the comment to pass review automatically, but the matched keywords will be replaced with `x`, resulting in `fxxk` or `xxxx`.
|
||||
|
||||
Note: It is recommended not to use `*` asterisk as `replace_to` because it conflicts with the Markdown bold syntax.
|
||||
|
||||
## Using Captcha
|
||||
|
||||
You can enable Artalk's captcha feature, supporting image and slider captchas, [refer here](./captcha.md).
|
||||
@@ -0,0 +1,86 @@
|
||||
# Admins and Multi-Site Support
|
||||
|
||||
Artalk supports multi-site management, allowing administrators to create and manage multiple sites and their respective comments and pages from a centralized interface.
|
||||
|
||||
## Creating an Administrator
|
||||
|
||||
Run the following command and follow the prompts to quickly create an administrator:
|
||||
|
||||
```sh
|
||||
./artalk admin
|
||||
```
|
||||
|
||||
If you are using Docker, you can run the following command:
|
||||
|
||||
```sh
|
||||
docker exec -it artalk artalk admin
|
||||
```
|
||||
|
||||
After creating the administrator account, you can log into the control panel to create additional accounts through the graphical user interface on the user management page, eliminating the need to manually edit configuration files.
|
||||
|
||||
## Creating and Managing Sites
|
||||
|
||||
You can create and manage multiple sites and switch between them quickly in the "Dashboard" accessible from the sidebar.
|
||||
|
||||
## Admin Configuration
|
||||
|
||||
You can set up multiple administrator accounts. When the input field matches an administrator's username and email, a password verification prompt will appear. Only administrators can access the "Dashboard" and manage comments from the frontend.
|
||||
|
||||
(Optional) Add administrators via the configuration file:
|
||||
|
||||
```yaml
|
||||
admin_users:
|
||||
- name: admin
|
||||
email: admin@example.com
|
||||
password: (bcrypt)$2y$10$ti4vZYIrxVN8rLcYXVgXCO.GJND0dyI49r7IoF3xqIx8bBRmIBZRm
|
||||
badge_name: Administrator
|
||||
badge_color: '#0083FF'
|
||||
- name: admin2
|
||||
email: admin2@example.com
|
||||
password: (bcrypt)$2y$10$ti4vZYIrxVN8rLcYXVgXCO.GJND0dyI49r7IoF3xqIx8bBRmIBZRm
|
||||
badge_name: Junior Admin
|
||||
badge_color: '#0083FF'
|
||||
```
|
||||
|
||||
Explanation of each configuration item:
|
||||
|
||||
- **name** & **email**: Username and email, **case-insensitive**.
|
||||
- **password**: User password.
|
||||
|
||||
Supports bcrypt and md5 encryption. For example, you can specify: `"(md5)50c21190c6e4e5418c6a90d2b5031119"`.
|
||||
|
||||
**Using the more secure bcrypt encryption algorithm is recommended**. In a Linux environment, you can use the [htpasswd command](https://httpd.apache.org/docs/2.4/programs/htpasswd.html) to generate the encrypted password:
|
||||
|
||||
```bash
|
||||
unset HISTFILE # Temporarily disable history to prevent the password from appearing in the history
|
||||
htpasswd -bnBC 10 "" "your_password" | tr -d ':'
|
||||
```
|
||||
|
||||
Then configure it as: `"(bcrypt)$2y$10$ti4vZYIrxVN8rLcY..."`, starting with `(bcrypt)`.
|
||||
|
||||
Command explanation reference: [Compute bcrypt hash from command line](https://unix.stackexchange.com/questions/307994/compute-bcrypt-hash-from-command-line#answer-419855)
|
||||
|
||||
- **badge_name**: The title badge text displayed for the user.
|
||||
- **badge_color**: The background color of the title badge displayed for the user.
|
||||
|
||||
::: tip
|
||||
|
||||
You can also configure administrator accounts through environment variables. Refer to: [Environment Variable Configuration](../env.md)
|
||||
|
||||
:::
|
||||
|
||||
### Controlling Admin Email Notifications
|
||||
|
||||
By default, when there are new comments on a page, emails are sent to all administrators. However, you can configure `receive_email` to forcefully disable email notifications for specific administrators.
|
||||
|
||||
This is useful if you have multiple email addresses configured but do not want certain addresses to receive comment notifications.
|
||||
|
||||
- **receive_email**: When set to `false`, the system will not send email notifications to that user.
|
||||
|
||||
Note: Even if email notifications are disabled, the administrator will still receive @AT replies from others. The system will not send email notifications when the user comments on a page (creates a root comment).
|
||||
|
||||
```yaml
|
||||
admin_users:
|
||||
- name: admin
|
||||
receive_email: false # ← Forcefully disable email notifications
|
||||
```
|
||||
@@ -0,0 +1,53 @@
|
||||
# Resolve Relative Path
|
||||
|
||||
Artalk supports parsing relative paths, allowing you to configure it on the frontend as follows:
|
||||
|
||||
```js
|
||||
Artalk.init({
|
||||
site: 'Example Site', // Your site name
|
||||
pageKey: '/relative-path/xx.html', // Using relative path
|
||||
})
|
||||
```
|
||||
|
||||
Using relative paths is recommended because it facilitates future "site migration" needs.
|
||||
|
||||
Next, in the sidebar, navigate to "Dashboard" - "Site", find "Example Site", and modify the site URL.
|
||||
|
||||

|
||||
|
||||
Afterward, all relative paths will be "based on this site URL". For example:
|
||||
|
||||
```bash
|
||||
"/relative-path/xx.html"
|
||||
↓ resolves to
|
||||
"https://set-example-site-url.xxx/relative-path/xx.html"
|
||||
```
|
||||
|
||||
## Uses of Resolved URLs
|
||||
|
||||
The site URL + page relative path will be used for:
|
||||
|
||||
- **Email notifications** containing links to reply to comments
|
||||
- **Sidebar** quick jumps to specific comments
|
||||
- **Dashboard** page management to open pages
|
||||
- **Fetching page titles** and other information
|
||||
|
||||
## Configuring Multiple Site URLs
|
||||
|
||||
You might need to configure multiple URLs for a site to allow referer and cross-origin requests.
|
||||
|
||||
In "Dashboard" - "Site", you can modify the site URL to support adding multiple URLs by separating each URL with a comma `,`.
|
||||
|
||||
**When a site has multiple URLs**, the "relative path" will be based on the "first" URL in the list.
|
||||
|
||||
## Using Absolute Paths
|
||||
|
||||
As opposed to using relative paths, you can use absolute paths. For example, configure the frontend like this:
|
||||
|
||||
```js
|
||||
Artalk.init({
|
||||
pageKey: 'https://your_domain.com/relative-path/xx.html', // Using absolute path
|
||||
})
|
||||
```
|
||||
|
||||
In this case, the backend will not resolve this address. The absolute path specified in `pageKey` will be directly used for locating the page in emails, sidebar, etc.
|
||||
@@ -0,0 +1,123 @@
|
||||
# Reverse Proxy
|
||||
|
||||
## Caddy
|
||||
|
||||
**/etc/caddy/Caddyfile**
|
||||
|
||||
```nginx
|
||||
artalk.your_domain.com {
|
||||
tls /etc/caddy/ssl/cert.pem /etc/caddy/ssl/cert.key
|
||||
|
||||
reverse_proxy http://localhost:23366 {
|
||||
header_up X-Forwarded-For {header.X-Forwarded-For}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Reload the Caddy configuration:
|
||||
|
||||
```sh
|
||||
sudo systemctl reload caddy
|
||||
```
|
||||
|
||||
## Nginx
|
||||
|
||||
Assumptions:
|
||||
|
||||
- The domain you want to bind is: `artalk.your_domain.com`
|
||||
- Artalk local address: `http://localhost:23366`
|
||||
|
||||
For Ubuntu 20.04:
|
||||
|
||||
Create the site configuration file:
|
||||
|
||||
```bash
|
||||
sudo vim /etc/nginx/sites-available/artalk.your_domain.com
|
||||
```
|
||||
|
||||
Edit the reverse proxy configuration file:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
|
||||
server_name artalk.your_domain.com;
|
||||
|
||||
location / {
|
||||
proxy_redirect off;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_pass http://localhost:23366/;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Create a symbolic link to enable the site:
|
||||
|
||||
```bash
|
||||
sudo ln -s /etc/nginx/sites-available/artalk.your_domain.com /etc/nginx/sites-enabled/
|
||||
```
|
||||
|
||||
Check if the configuration file is valid:
|
||||
|
||||
```bash
|
||||
sudo nginx -t
|
||||
```
|
||||
|
||||
If the configuration is correct, restart Nginx:
|
||||
|
||||
```bash
|
||||
sudo systemctl restart nginx
|
||||
```
|
||||
|
||||
Configure the frontend:
|
||||
|
||||
```js
|
||||
Artalk.init({ server: 'http://artalk.your_domain.com' })
|
||||
```
|
||||
|
||||
::: tip
|
||||
You can also add a layer of CDN and SSL.
|
||||
|
||||
Pay attention to the configuration file permissions and the accessibility of the reverse proxy target URL.
|
||||
|
||||
Especially if Artalk is running inside a Docker container, make sure the IP and port can be accessed by Nginx.
|
||||
:::
|
||||
|
||||
## Apache
|
||||
|
||||
You need to enable the reverse proxy module `mod_proxy.c`
|
||||
|
||||
```apache
|
||||
<VirtualHost *:80>
|
||||
ServerName your_domain.xxx
|
||||
ServerAlias
|
||||
|
||||
RewriteEngine On
|
||||
RewriteCond %{QUERY_STRING} transport=polling [NC]
|
||||
RewriteRule /(.*) http://localhost:23366/$1 [P]
|
||||
|
||||
<IfModule mod_proxy.c>
|
||||
ProxyRequests Off
|
||||
SSLProxyEngine on
|
||||
ProxyPass / http://localhost:23366/
|
||||
ProxyPassReverse / http://localhost:23366/
|
||||
</IfModule>
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
## BaoTa Panel (宝塔面板)
|
||||
|
||||
First, create a site (e.g., `artalk.your_domain.com`), then click the site "Settings":
|
||||
|
||||

|
||||
|
||||
Open the "Reverse Proxy" tab, click "Add Reverse Proxy", fill in the "Target URL" with `http://localhost:port` (where the port corresponds to the Artalk port), and fill in the "Send Domain" with `$host`, as shown in the figure:
|
||||
|
||||

|
||||
|
||||
## Getting the Accurate IP Address
|
||||
|
||||
When using a reverse proxy server, you need to configure the proxy headers to get the user's accurate IP address. Refer to the [IP Region](../frontend/ip-region.md#获取准确的-ip-地址) documentation for more details.
|
||||
@@ -0,0 +1,28 @@
|
||||
# Program Upgrade
|
||||
|
||||
## Update Changes
|
||||
|
||||
For significant changes, such as updates that require modifications to existing configurations, refer to the release notes and migration instructions:
|
||||
|
||||
- [v2.10.0 Release Notes and Migration Guide](../releases/v2.10.0.md)
|
||||
- [Complete CHANGELOG.md](https://github.com/ArtalkJS/Artalk/blob/master/CHANGELOG.md)
|
||||
|
||||
## One-Click Upgrade via Command Line
|
||||
|
||||
Execute `./artalk upgrade`
|
||||
|
||||
This operation will automatically download and upgrade the program from GitHub Release. Ensure to stop Artalk before executing this command.
|
||||
|
||||
::: tip
|
||||
Execute `./artalk upgrade -f` with the `-f` parameter to force an update.
|
||||
:::
|
||||
|
||||
## Docker Upgrade
|
||||
|
||||
Refer to: [Docker · Upgrade](./docker.md#升级)
|
||||
|
||||
## Manual Method
|
||||
|
||||
Go to [GitHub Release](https://github.com/ArtalkJS/Artalk/releases) to manually download the latest build.
|
||||
|
||||
Replace the old version files with the new ones.
|
||||
@@ -0,0 +1,12 @@
|
||||
# 🚀 Case Showcase
|
||||
|
||||
### Open Source Projects
|
||||
|
||||
- [Wiki.js](https://github.com/requarks/wiki/): A modern and powerful wiki app built on Node.js
|
||||
- [vuepress-theme-hope](https://github.com/vuepress-theme-hope/vuepress-theme-hope): A vuepress theme with tons of features
|
||||
- [hexo-theme-stellar](https://github.com/xaoxuu/hexo-theme-stellar): Elegant and powerful theme for Hexo
|
||||
- [FixIt Theme | Hugo](https://github.com/Lruihao/FixIt): A clean, elegant but advanced blog theme for Hugo
|
||||
- [hexo-theme-volantis](https://github.com/volantis-x/hexo-theme-volantis): A Wonderful Theme for Hexo
|
||||
- [hexo-theme-cards](https://github.com/ChrAlpha/hexo-theme-cards): Another Simple & Swift theme for Hexo
|
||||
- [hexo-theme-butterfly](https://github.com/jerryc127/hexo-theme-butterfly): A Hexo Theme: Butterfly
|
||||
- [hexo-theme-solitude](https://github.com/valor-x/hexo-theme-solitude): A Hexo Theme: Solitude
|
||||
@@ -0,0 +1,322 @@
|
||||
# 📦 Getting Started
|
||||
|
||||
This guide will help you deploy Artalk on your server. Once deployed, you can integrate the Artalk client into your website or blog to enable comment functionality.
|
||||
|
||||
## Docker
|
||||
|
||||
Here is a simple guide for deploying the Artalk **Server** and **Client**.
|
||||
|
||||
### Launch Server
|
||||
|
||||
It is recommended to use Docker for deployment. Pre-install the [Docker Engine](https://docs.docker.com/engine/install/) and create a working directory, then run the command to launch a container in the background:
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name artalk \
|
||||
-p 8080:23366 \
|
||||
-v $(pwd)/data:/data \
|
||||
-e "TZ=America/New_York" \
|
||||
-e "ATK_LOCALE=en" \
|
||||
-e "ATK_SITE_DEFAULT=Artalk Blog" \
|
||||
-e "ATK_SITE_URL=https://example.com" \
|
||||
artalk/artalk-go
|
||||
```
|
||||
|
||||
(Tip: We also provide a [Docker Compose](#docker-compose) configuration file).
|
||||
|
||||
Run the command to create an admin account:
|
||||
|
||||
```bash
|
||||
docker exec -it artalk artalk admin
|
||||
```
|
||||
|
||||
Enter `http://artalk.example.com:8080` in your browser to access the Artalk Dashboard.
|
||||
|
||||
### Integrate Client
|
||||
|
||||
Import the embedded client JS and CSS resources from the Artalk server to your webpage and initialize the Artalk client:
|
||||
|
||||
```html
|
||||
<!-- CSS -->
|
||||
<link href="http://artalk.example.com:8080/dist/Artalk.css" rel="stylesheet" />
|
||||
|
||||
<!-- JS -->
|
||||
<script src="http://artalk.example.com:8080/dist/Artalk.js"></script>
|
||||
|
||||
<!-- Artalk -->
|
||||
<div id="Comments"></div>
|
||||
<script>
|
||||
Artalk.init({
|
||||
el: '#Comments', // Selector of the bound element
|
||||
pageKey: '/post/1', // Permalink
|
||||
pageTitle: 'About Introducing Artalk', // Page title (leave blank to auto-fetch)
|
||||
server: 'http://artalk.example.com:8080', // Artalk server address
|
||||
site: 'Artalk Blog', // Your site name
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
Enter the admin username and email in the comment box, and the "Dashboard" button will appear at the bottom right of the comment box.
|
||||
|
||||
In the Dashboard, you can [configure the comment system](./backend/config.md) to your liking or [migrate comments to Artalk](./transfer.md).
|
||||
|
||||
🥳 You have successfully deployed Artalk!
|
||||
|
||||
## Binary
|
||||
|
||||
1. Download the program package from [GitHub Release](https://github.com/ArtalkJS/Artalk/releases)
|
||||
2. Extract it with `tar -zxvf artalk_version_system_architecture.tar.gz`
|
||||
3. Run `./artalk server`
|
||||
4. Configure and initialize the client on your webpage:
|
||||
|
||||
```js
|
||||
Artalk.init({ server: 'http://artalk.example.com:23366' })
|
||||
```
|
||||
|
||||
Advanced operations:
|
||||
|
||||
- [Daemon Process (Systemd, Supervisor)](./backend/daemon.md)
|
||||
- [Reverse Proxy (Caddy, Nginx, Apache)](./backend/reverse-proxy.md)
|
||||
- [Self-compile (Build from the latest code)](https://github.com/ArtalkJS/Artalk/blob/master/CONTRIBUTING.md)
|
||||
|
||||
## Go Install
|
||||
|
||||
If you have the Golang toolchain installed, you can run the following commands to compile and install the latest version of Artalk:
|
||||
|
||||
```bash
|
||||
go install github.com/artalkjs/artalk/v2@latest
|
||||
```
|
||||
|
||||
Then run the server:
|
||||
|
||||
```bash
|
||||
artalk server
|
||||
```
|
||||
|
||||
The client integration steps are [here](#integrate-client).
|
||||
|
||||
## Linux Distributions
|
||||
|
||||
**Fedora (Copr)**:
|
||||
|
||||
```bash
|
||||
dnf install 'dnf-command(copr)'
|
||||
dnf copr enable @artalk/artalk
|
||||
dnf install artalk
|
||||
```
|
||||
|
||||
**Arch Linux (AUR)**:
|
||||
|
||||
```bash
|
||||
paru -S artalk
|
||||
```
|
||||
|
||||
**NixOS**:
|
||||
|
||||
```bash
|
||||
nix-env -iA nixpkgs.artalk
|
||||
```
|
||||
|
||||
**Termux**:
|
||||
|
||||
```bash
|
||||
pkg install artalk
|
||||
```
|
||||
|
||||
[](https://repology.org/project/artalk/versions)
|
||||
|
||||
[](https://copr.fedorainfracloud.org/coprs/g/artalk/artalk/)
|
||||
|
||||
## Docker Compose
|
||||
|
||||
Create a working directory and edit the `docker-compose.yml` file:
|
||||
|
||||
```yaml
|
||||
version: '3.8'
|
||||
services:
|
||||
artalk:
|
||||
container_name: artalk
|
||||
image: artalk/artalk-go
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 8080:23366
|
||||
volumes:
|
||||
- ./data:/data
|
||||
environment:
|
||||
- TZ=America/New_York
|
||||
- ATK_LOCALE=en
|
||||
- ATK_SITE_DEFAULT=Artalk Blog
|
||||
- ATK_SITE_URL=https://your_domain
|
||||
```
|
||||
|
||||
Start the container to launch Artalk server:
|
||||
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
The client integration steps are [here](#integrate-client).
|
||||
|
||||
::: details Common Compose Commands
|
||||
|
||||
```bash
|
||||
docker-compose restart # Restart the container
|
||||
docker-compose stop # Stop the container
|
||||
docker-compose down # Remove the container
|
||||
docker-compose pull # Update the image
|
||||
docker-compose exec artalk bash # Enter the container
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
More information: [Docker](./backend/docker.md) / [Environment Variables](./env.md)
|
||||
|
||||
## Client
|
||||
|
||||
If you have a frontend or Node.js web project, this following guide will help you integrate the Artalk **Client** into your web project.
|
||||
|
||||
### Client Installation
|
||||
|
||||
Install via npm:
|
||||
|
||||
::: code-group
|
||||
|
||||
```sh [npm]
|
||||
npm install artalk
|
||||
```
|
||||
|
||||
```sh [yarn]
|
||||
yarn add artalk
|
||||
```
|
||||
|
||||
```sh [pnpm]
|
||||
pnpm add artalk
|
||||
```
|
||||
|
||||
```sh [bun]
|
||||
bun add artalk
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
(or see [CDN Resources](#client-cdn-resources)).
|
||||
|
||||
### Client Usage
|
||||
|
||||
Import Artalk in your web project:
|
||||
|
||||
```js
|
||||
import 'artalk/Artalk.css'
|
||||
import Artalk from 'artalk'
|
||||
|
||||
const artalk = Artalk.init({
|
||||
el: document.querySelector('#Comments'), // DOM element where you want to mount
|
||||
pageKey: '/post/1', // Permalink
|
||||
pageTitle: 'About Introducing Artalk', // Page title
|
||||
server: 'http://artalk.example.com:8080', // Artalk server address
|
||||
site: 'Artalk Blog', // Site name
|
||||
})
|
||||
```
|
||||
|
||||
For more references:
|
||||
|
||||
- [Import to Blog Documentation](../develop/import-blog.md)
|
||||
- [Import to Frameworks Documentation](../develop/import-framework.md)
|
||||
- [Client API Reference](../develop/fe-api.md)
|
||||
- [UI (Client) Configuration](./frontend/config.md)
|
||||
|
||||
### Client CDN Resources
|
||||
|
||||
To use Artalk in the browser via CDN, for modern browsers (which support [ES modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules)), you can use [esm.sh](https://esm.sh) or [esm.run](https://esm.run):
|
||||
|
||||
::: code-group
|
||||
|
||||
```html [ES Module]
|
||||
<body>
|
||||
<div id="Comments"></div>
|
||||
|
||||
<script type="module">
|
||||
import Artalk from 'https://esm.sh/artalk@:ArtalkVersion:'
|
||||
|
||||
Artalk.init({
|
||||
el: '#Comments',
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
```
|
||||
|
||||
```html [Legacy]
|
||||
<body>
|
||||
<div id="Comments"></div>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/artalk/:ArtalkVersion:/Artalk.js"></script>
|
||||
<script>
|
||||
Artalk.init({
|
||||
el: '#Comments',
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Don't forget to include the CSS file:
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/artalk/:ArtalkVersion:/Artalk.css">
|
||||
```
|
||||
|
||||
::: tip Latest Version of Artalk
|
||||
|
||||
The latest Artalk client version is: `:ArtalkVersion:`
|
||||
|
||||
To upgrade the client, replace the numeric part of the version number in the URL.
|
||||
:::
|
||||
|
||||
Note: The Artalk server had embedded the client resources. Ensure version compatibility when using public CDN resources.
|
||||
|
||||
::: details Alternative CDNs
|
||||
|
||||
**CDNJS**
|
||||
|
||||
```
|
||||
https://cdnjs.cloudflare.com/ajax/libs/artalk/:ArtalkVersion:/Artalk.js
|
||||
```
|
||||
|
||||
```
|
||||
https://cdnjs.cloudflare.com/ajax/libs/artalk/:ArtalkVersion:/Artalk.css
|
||||
```
|
||||
|
||||
**JSDELIVR**
|
||||
|
||||
```
|
||||
https://cdn.jsdelivr.net/npm/artalk@:ArtalkVersion:/dist/Artalk.js
|
||||
```
|
||||
|
||||
```
|
||||
https://cdn.jsdelivr.net/npm/artalk@:ArtalkVersion:/dist/Artalk.css
|
||||
```
|
||||
|
||||
**UNPKG**
|
||||
|
||||
```
|
||||
https://unpkg.com/artalk@:ArtalkVersion:/dist/Artalk.js
|
||||
```
|
||||
|
||||
```
|
||||
https://unpkg.com/artalk@:ArtalkVersion:/dist/Artalk.css
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Data Migration
|
||||
|
||||
Import data from other comment systems: [Data Migration](./transfer.md).
|
||||
|
||||
## ArtalkLite
|
||||
|
||||
ArtalkLite is a lightweight version of the Artalk client. See: [ArtalkLite](./frontend/artalk-lite.md).
|
||||
|
||||
## Development Environment
|
||||
|
||||
Please refer to: [Developer Guide](https://github.com/ArtalkJS/Artalk/blob/master/CONTRIBUTING.md).
|
||||
@@ -0,0 +1,355 @@
|
||||
# Environment Variables
|
||||
|
||||
Artalk supports configuration changes via environment variables, simplifying containerized cloud deployments such as Docker.
|
||||
|
||||
Environment variable names start with `ATK_`, in all uppercase, corresponding to each node in the [configuration file](./backend/config.md).
|
||||
|
||||
Environment variables take precedence over configuration files. Verify if the configuration has taken effect using the following command:
|
||||
|
||||
```bash
|
||||
artalk config
|
||||
```
|
||||
|
||||
To deploy Artalk using Docker Compose, add environment variables in the `compose.yml` file, for instance:
|
||||
|
||||
```yaml
|
||||
version: '3.8'
|
||||
services:
|
||||
artalk:
|
||||
container_name: artalk
|
||||
image: artalk/artalk-go
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 8080:23366
|
||||
volumes:
|
||||
- ./data:/data
|
||||
environment:
|
||||
- TZ=America/New_York
|
||||
- ATK_TIMEZONE=America/New_York
|
||||
- ATK_LOCALE=en
|
||||
- ATK_SITE_DEFAULT=Artalk's Blog
|
||||
- ATK_SITE_URL=https://example.com
|
||||
- ATK_TRUSTED_DOMAINS=https://dev.example.com https://localhost:8080
|
||||
- ATK_ADMIN_USERS_0_NAME=admin
|
||||
- ATK_ADMIN_USERS_0_EMAIL=admin@example.org
|
||||
- ATK_ADMIN_USERS_0_PASSWORD=(bcrypt)$2y$10$ti4vZYIrxVN8rLcYXVgXCO.GJND0dyI49r7IoF3xqIx8bBRmIBZRm
|
||||
- ATK_ADMIN_USERS_0_BADGE_NAME=Administrator
|
||||
- ATK_ADMIN_USERS_0_BADGE_COLOR=#0083FF
|
||||
```
|
||||
|
||||
`TZ` controls the container or host process environment, while `ATK_TIMEZONE` controls the Artalk application timezone; the two values may differ. The Artalk timezone is applied only during process initialization, so fully restart the Artalk process or container after changing `ATK_TIMEZONE`.
|
||||
|
||||
When the variable is an array, set array values using space-separated strings or numerical indices, for example:
|
||||
|
||||
```
|
||||
ATK_TRUSTED_DOMAINS="https://a.com https://b.com"
|
||||
ATK_TRUSTED_DOMAINS_0="https://a.com"
|
||||
```
|
||||
|
||||
<style>
|
||||
.env-table table {
|
||||
table-layout: fixed;
|
||||
word-break: break-word;
|
||||
|
||||
th:first-of-type, td:first-of-type {
|
||||
width: 30%;
|
||||
}
|
||||
th:nth-of-type(2), td:nth-of-type(2) {
|
||||
width: 20%;
|
||||
}
|
||||
th:nth-of-type(3), td:nth-of-type(3) {
|
||||
width: 20%;
|
||||
}
|
||||
th:nth-of-type(4), td:nth-of-type(4) {
|
||||
width: 30%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="env-table">
|
||||
<!-- env-variables -->
|
||||
|
||||
## General
|
||||
|
||||
| Env | Default | Desc | Path |
|
||||
| --- | --- | --- | --- |
|
||||
| **ATK_APP_KEY** | `""` | App Key (for generation of JWT) | app_key (App Key) |
|
||||
| **ATK_DEBUG** | `false` | Debug mode | debug (Debug mode) |
|
||||
| **ATK_HOST** | `"0.0.0.0"` | Listen host | host (Listen host) |
|
||||
| **ATK_LOCALE** | `"en"` | Language (follow Unicode BCP 47) (可选:`["en", "zh-CN", "zh-TW", "ja", "fr", "ko", "ru", "tr"]`) | locale (Language) |
|
||||
| **ATK_LOGIN_TIMEOUT** | `259200` | Login timeout (in seconds) | login_timeout (Login timeout) |
|
||||
| **ATK_PORT** | `23366` | Listen port | port (Listen port) |
|
||||
| **ATK_SITE_DEFAULT** | `"Default Site"` | Default site name (create when app is first launched) | site_default (Default site name) |
|
||||
| **ATK_SITE_URL** | `""` | Default site url | site_url (Default site url) |
|
||||
| **ATK_TIMEZONE** | `"Asia/Shanghai"` | Timezone (follow IANA Time Zone Database) | timezone (Timezone) |
|
||||
| **ATK_TRUSTED_DOMAINS** | `[]` | Trusted domains | trusted_domains (Trusted domains) |
|
||||
|
||||
|
||||
## Multi-Push
|
||||
|
||||
| Env | Default | Desc | Path |
|
||||
| --- | --- | --- | --- |
|
||||
| **ATK_ADMIN_NOTIFY_BARK_ENABLED** | `false` | 启用 | admin_notify.bark.enabled (Multi-Push > Bark > Enabled) |
|
||||
| **ATK_ADMIN_NOTIFY_BARK_SERVER** | `"http://day.app/xxxxxxx/"` | Server | admin_notify.bark.server (Multi-Push > Bark > Server) |
|
||||
| **ATK_ADMIN_NOTIFY_DING_TALK_ENABLED** | `false` | 启用 | admin_notify.ding_talk.enabled (Multi-Push > DingTalk > Enabled) |
|
||||
| **ATK_ADMIN_NOTIFY_DING_TALK_SECRET** | `""` | Secret | admin_notify.ding_talk.secret (Multi-Push > DingTalk > Secret) |
|
||||
| **ATK_ADMIN_NOTIFY_DING_TALK_TOKEN** | `""` | Token | admin_notify.ding_talk.token (Multi-Push > DingTalk > Token) |
|
||||
| **ATK_ADMIN_NOTIFY_EMAIL_ENABLED** | `true` | Enable (can be disabled when using other push methods) | admin_notify.email.enabled (Multi-Push > Notify admin > Enable) |
|
||||
| **ATK_ADMIN_NOTIFY_EMAIL_MAIL_SUBJECT** | `"[{{site_name}}] Post \"{{page_title}}\" has new a comment"` | Email subject (email subject sent to admin) | admin_notify.email.mail_subject (Multi-Push > Notify admin > Email subject) |
|
||||
| **ATK_ADMIN_NOTIFY_EMAIL_MAIL_TPL** | `""` | Admin email template file (set to file path to use custom template) | admin_notify.email.mail_tpl (Multi-Push > Notify admin > Admin email template file) |
|
||||
| **ATK_ADMIN_NOTIFY_LARK_ENABLED** | `false` | 启用 | admin_notify.lark.enabled (Multi-Push > Lark > Enabled) |
|
||||
| **ATK_ADMIN_NOTIFY_LARK_MSG_TYPE** | `"text"` | Message type (可选:`["text", "card"]`) | admin_notify.lark.msg_type (Multi-Push > Lark > Message type) |
|
||||
| **ATK_ADMIN_NOTIFY_LARK_WEBHOOK_URL** | `""` | WebhookUrl | admin_notify.lark.webhook_url (Multi-Push > Lark > WebhookUrl) |
|
||||
| **ATK_ADMIN_NOTIFY_LINE_CHANNEL_ACCESS_TOKEN** | `""` | ChannelAccessToken | admin_notify.line.channel_access_token (Multi-Push > LINE > ChannelAccessToken) |
|
||||
| **ATK_ADMIN_NOTIFY_LINE_CHANNEL_SECRET** | `""` | ChannelSecret | admin_notify.line.channel_secret (Multi-Push > LINE > ChannelSecret) |
|
||||
| **ATK_ADMIN_NOTIFY_LINE_ENABLED** | `false` | 启用 | admin_notify.line.enabled (Multi-Push > LINE > Enabled) |
|
||||
| **ATK_ADMIN_NOTIFY_LINE_RECEIVERS** | `[USER_ID_1 GROUP_ID_1]` | Receivers | admin_notify.line.receivers (Multi-Push > LINE > Receivers) |
|
||||
| **ATK_ADMIN_NOTIFY_NOISE_MODE** | `false` | Noise mode | admin_notify.noise_mode (Multi-Push > Noise mode) |
|
||||
| **ATK_ADMIN_NOTIFY_NOTIFY_PENDING** | `false` | Pending comment still send notification (notifications are still sent when comments are intercepted) | admin_notify.notify_pending (Multi-Push > Pending comment still send notification) |
|
||||
| **ATK_ADMIN_NOTIFY_NOTIFY_TPL** | `"default"` | Notification template (set to file path to use custom template) | admin_notify.notify_tpl (Multi-Push > Notification template) |
|
||||
| **ATK_ADMIN_NOTIFY_SLACK_ENABLED** | `false` | 启用 | admin_notify.slack.enabled (Multi-Push > Slack > Enabled) |
|
||||
| **ATK_ADMIN_NOTIFY_SLACK_OAUTH_TOKEN** | `""` | OauthToken | admin_notify.slack.oauth_token (Multi-Push > Slack > OauthToken) |
|
||||
| **ATK_ADMIN_NOTIFY_SLACK_RECEIVERS** | `[CHANNEL_ID]` | Receivers | admin_notify.slack.receivers (Multi-Push > Slack > Receivers) |
|
||||
| **ATK_ADMIN_NOTIFY_TELEGRAM_API_TOKEN** | `""` | ApiToken | admin_notify.telegram.api_token (Multi-Push > Telegram > ApiToken) |
|
||||
| **ATK_ADMIN_NOTIFY_TELEGRAM_ENABLED** | `false` | 启用 | admin_notify.telegram.enabled (Multi-Push > Telegram > Enabled) |
|
||||
| **ATK_ADMIN_NOTIFY_TELEGRAM_RECEIVERS** | `[7777777]` | Receivers | admin_notify.telegram.receivers (Multi-Push > Telegram > Receivers) |
|
||||
| **ATK_ADMIN_NOTIFY_WEBHOOK_ENABLED** | `false` | 启用 | admin_notify.webhook.enabled (Multi-Push > WebHook > Enabled) |
|
||||
| **ATK_ADMIN_NOTIFY_WEBHOOK_URL** | `""` | Url | admin_notify.webhook.url (Multi-Push > WebHook > Url) |
|
||||
|
||||
|
||||
## Social Login
|
||||
|
||||
| Env | Default | Desc | Path |
|
||||
| --- | --- | --- | --- |
|
||||
| **ATK_AUTH_ANONYMOUS** | `false` | Allow anonymous commenting (Allow skipping verification, only fill in an anonymous nickname and email) | auth.anonymous (Social Login > Allow anonymous commenting) |
|
||||
| **ATK_AUTH_APPLE_CLIENT_ID** | `""` | ClientId | auth.apple.client_id (Social Login > Apple > ClientId) |
|
||||
| **ATK_AUTH_APPLE_CLIENT_SECRET** | `""` | ClientSecret | auth.apple.client_secret (Social Login > Apple > ClientSecret) |
|
||||
| **ATK_AUTH_APPLE_ENABLED** | `false` | 启用 | auth.apple.enabled (Social Login > Apple > Enabled) |
|
||||
| **ATK_AUTH_AUTH0_CLIENT_ID** | `""` | ClientId | auth.auth0.client_id (Social Login > Auth0 > ClientId) |
|
||||
| **ATK_AUTH_AUTH0_CLIENT_SECRET** | `""` | ClientSecret | auth.auth0.client_secret (Social Login > Auth0 > ClientSecret) |
|
||||
| **ATK_AUTH_AUTH0_DOMAIN** | `""` | Domain | auth.auth0.domain (Social Login > Auth0 > Domain) |
|
||||
| **ATK_AUTH_AUTH0_ENABLED** | `false` | 启用 | auth.auth0.enabled (Social Login > Auth0 > Enabled) |
|
||||
| **ATK_AUTH_CALLBACK** | `"http://localhost:23366/api/v2/auth/{provider}/callback"` | Callback URL (https://example.com/api/v2/auth/{provider}/callback) | auth.callback (Social Login > Callback URL) |
|
||||
| **ATK_AUTH_DISCORD_CLIENT_ID** | `""` | ClientId | auth.discord.client_id (Social Login > Discord > ClientId) |
|
||||
| **ATK_AUTH_DISCORD_CLIENT_SECRET** | `""` | ClientSecret | auth.discord.client_secret (Social Login > Discord > ClientSecret) |
|
||||
| **ATK_AUTH_DISCORD_ENABLED** | `false` | 启用 | auth.discord.enabled (Social Login > Discord > Enabled) |
|
||||
| **ATK_AUTH_EMAIL_ENABLED** | `true` | Enable email password login | auth.email.enabled (Social Login > Email > Enable email password login) |
|
||||
| **ATK_AUTH_EMAIL_VERIFY_SUBJECT** | `"Your Code - {{code}}"` | Verification email subject | auth.email.verify_subject (Social Login > Email > Verification email subject) |
|
||||
| **ATK_AUTH_EMAIL_VERIFY_TPL** | `"default"` | Verification email template (set to file path to use custom template) | auth.email.verify_tpl (Social Login > Email > Verification email template) |
|
||||
| **ATK_AUTH_ENABLED** | `false` | Enable Social Login | auth.enabled (Social Login > Enable Social Login) |
|
||||
| **ATK_AUTH_FACEBOOK_CLIENT_ID** | `""` | ClientId | auth.facebook.client_id (Social Login > Facebook > ClientId) |
|
||||
| **ATK_AUTH_FACEBOOK_CLIENT_SECRET** | `""` | ClientSecret | auth.facebook.client_secret (Social Login > Facebook > ClientSecret) |
|
||||
| **ATK_AUTH_FACEBOOK_ENABLED** | `false` | 启用 | auth.facebook.enabled (Social Login > Facebook > Enabled) |
|
||||
| **ATK_AUTH_GITEA_CLIENT_ID** | `""` | ClientId | auth.gitea.client_id (Social Login > Gitea > ClientId) |
|
||||
| **ATK_AUTH_GITEA_CLIENT_SECRET** | `""` | ClientSecret | auth.gitea.client_secret (Social Login > Gitea > ClientSecret) |
|
||||
| **ATK_AUTH_GITEA_ENABLED** | `false` | 启用 | auth.gitea.enabled (Social Login > Gitea > Enabled) |
|
||||
| **ATK_AUTH_GITHUB_CLIENT_ID** | `""` | ClientId | auth.github.client_id (Social Login > GitHub > ClientId) |
|
||||
| **ATK_AUTH_GITHUB_CLIENT_SECRET** | `""` | ClientSecret | auth.github.client_secret (Social Login > GitHub > ClientSecret) |
|
||||
| **ATK_AUTH_GITHUB_ENABLED** | `false` | 启用 | auth.github.enabled (Social Login > GitHub > Enabled) |
|
||||
| **ATK_AUTH_GITLAB_CLIENT_ID** | `""` | ClientId | auth.gitlab.client_id (Social Login > GitLab > ClientId) |
|
||||
| **ATK_AUTH_GITLAB_CLIENT_SECRET** | `""` | ClientSecret | auth.gitlab.client_secret (Social Login > GitLab > ClientSecret) |
|
||||
| **ATK_AUTH_GITLAB_ENABLED** | `false` | 启用 | auth.gitlab.enabled (Social Login > GitLab > Enabled) |
|
||||
| **ATK_AUTH_GOOGLE_CLIENT_ID** | `""` | ClientId | auth.google.client_id (Social Login > Google > ClientId) |
|
||||
| **ATK_AUTH_GOOGLE_CLIENT_SECRET** | `""` | ClientSecret | auth.google.client_secret (Social Login > Google > ClientSecret) |
|
||||
| **ATK_AUTH_GOOGLE_ENABLED** | `false` | 启用 | auth.google.enabled (Social Login > Google > Enabled) |
|
||||
| **ATK_AUTH_LINE_CLIENT_ID** | `""` | ClientId | auth.line.client_id (Social Login > Line > ClientId) |
|
||||
| **ATK_AUTH_LINE_CLIENT_SECRET** | `""` | ClientSecret | auth.line.client_secret (Social Login > Line > ClientSecret) |
|
||||
| **ATK_AUTH_LINE_ENABLED** | `false` | 启用 | auth.line.enabled (Social Login > Line > Enabled) |
|
||||
| **ATK_AUTH_MASTODON_CLIENT_ID** | `""` | ClientId | auth.mastodon.client_id (Social Login > Mastodon > ClientId) |
|
||||
| **ATK_AUTH_MASTODON_CLIENT_SECRET** | `""` | ClientSecret | auth.mastodon.client_secret (Social Login > Mastodon > ClientSecret) |
|
||||
| **ATK_AUTH_MASTODON_ENABLED** | `false` | 启用 | auth.mastodon.enabled (Social Login > Mastodon > Enabled) |
|
||||
| **ATK_AUTH_MICROSOFT_CLIENT_ID** | `""` | ClientId | auth.microsoft.client_id (Social Login > Microsoft > ClientId) |
|
||||
| **ATK_AUTH_MICROSOFT_CLIENT_SECRET** | `""` | ClientSecret | auth.microsoft.client_secret (Social Login > Microsoft > ClientSecret) |
|
||||
| **ATK_AUTH_MICROSOFT_ENABLED** | `false` | 启用 | auth.microsoft.enabled (Social Login > Microsoft > Enabled) |
|
||||
| **ATK_AUTH_PATREON_CLIENT_ID** | `""` | ClientId | auth.patreon.client_id (Social Login > Patreon > ClientId) |
|
||||
| **ATK_AUTH_PATREON_CLIENT_SECRET** | `""` | ClientSecret | auth.patreon.client_secret (Social Login > Patreon > ClientSecret) |
|
||||
| **ATK_AUTH_PATREON_ENABLED** | `false` | 启用 | auth.patreon.enabled (Social Login > Patreon > Enabled) |
|
||||
| **ATK_AUTH_SLACK_CLIENT_ID** | `""` | ClientId | auth.slack.client_id (Social Login > Slack > ClientId) |
|
||||
| **ATK_AUTH_SLACK_CLIENT_SECRET** | `""` | ClientSecret | auth.slack.client_secret (Social Login > Slack > ClientSecret) |
|
||||
| **ATK_AUTH_SLACK_ENABLED** | `false` | 启用 | auth.slack.enabled (Social Login > Slack > Enabled) |
|
||||
| **ATK_AUTH_SSO_ENABLED** | `false` | Enable SSO token exchange | auth.sso.enabled (Social Login > SSO > Enable SSO token exchange) |
|
||||
| **ATK_AUTH_SSO_ISSUER** | `""` | OIDC issuer URL (e.g. "tenant.auth0.com" or "https://tenant.auth0.com") | auth.sso.issuer (Social Login > SSO > OIDC issuer URL) |
|
||||
| **ATK_AUTH_STEAM_API_KEY** | `""` | ApiKey | auth.steam.api_key (Social Login > Steam > ApiKey) |
|
||||
| **ATK_AUTH_STEAM_ENABLED** | `false` | 启用 | auth.steam.enabled (Social Login > Steam > Enabled) |
|
||||
| **ATK_AUTH_TIKTOK_CLIENT_ID** | `""` | ClientId | auth.tiktok.client_id (Social Login > Tiktok > ClientId) |
|
||||
| **ATK_AUTH_TIKTOK_CLIENT_SECRET** | `""` | ClientSecret | auth.tiktok.client_secret (Social Login > Tiktok > ClientSecret) |
|
||||
| **ATK_AUTH_TIKTOK_ENABLED** | `false` | 启用 | auth.tiktok.enabled (Social Login > Tiktok > Enabled) |
|
||||
| **ATK_AUTH_TWITTER_CLIENT_ID** | `""` | ClientId | auth.twitter.client_id (Social Login > Twitter > ClientId) |
|
||||
| **ATK_AUTH_TWITTER_CLIENT_SECRET** | `""` | ClientSecret | auth.twitter.client_secret (Social Login > Twitter > ClientSecret) |
|
||||
| **ATK_AUTH_TWITTER_ENABLED** | `false` | 启用 | auth.twitter.enabled (Social Login > Twitter > Enabled) |
|
||||
| **ATK_AUTH_WECHAT_CLIENT_ID** | `""` | ClientId | auth.wechat.client_id (Social Login > WeChat > ClientId) |
|
||||
| **ATK_AUTH_WECHAT_CLIENT_SECRET** | `""` | ClientSecret | auth.wechat.client_secret (Social Login > WeChat > ClientSecret) |
|
||||
| **ATK_AUTH_WECHAT_ENABLED** | `false` | 启用 | auth.wechat.enabled (Social Login > WeChat > Enabled) |
|
||||
|
||||
|
||||
## Cache
|
||||
|
||||
| Env | Default | Desc | Path |
|
||||
| --- | --- | --- | --- |
|
||||
| **ATK_CACHE_ENABLED** | `false` | Enable cache | cache.enabled (Cache > Enable cache) |
|
||||
| **ATK_CACHE_EXPIRES** | `30` | Cache expiration time (in minutes) | cache.expires (Cache > Cache expiration time) |
|
||||
| **ATK_CACHE_REDIS_DB** | `0` | Redis database number (e.g. 0) | cache.redis.db (Cache > Redis config > Redis database number) |
|
||||
| **ATK_CACHE_REDIS_NETWORK** | `"tcp"` | Connection type (可选:`["tcp", "unix"]`) | cache.redis.network (Cache > Redis config > Connection type) |
|
||||
| **ATK_CACHE_REDIS_PASSWORD** | `""` | Redis password | cache.redis.password (Cache > Redis config > Redis password) |
|
||||
| **ATK_CACHE_REDIS_USERNAME** | `""` | Redis username | cache.redis.username (Cache > Redis config > Redis username) |
|
||||
| **ATK_CACHE_SERVER** | `""` | Cache server address (e.g. "localhost:6379") | cache.server (Cache > Cache server address) |
|
||||
| **ATK_CACHE_TYPE** | `"builtin"` | Cache type (可选:`["redis", "memcache", "builtin"]`) | cache.type (Cache > Cache type) |
|
||||
| **ATK_CACHE_WARM_UP** | `false` | Cache warm up (warm up cache when program starts) | cache.warm_up (Cache > Cache warm up) |
|
||||
|
||||
|
||||
## Captcha
|
||||
|
||||
| Env | Default | Desc | Path |
|
||||
| --- | --- | --- | --- |
|
||||
| **ATK_CAPTCHA_ACTION_LIMIT** | `3` | Action limit (the number of actions required to activate captcha) | captcha.action_limit (Captcha > Action limit) |
|
||||
| **ATK_CAPTCHA_ACTION_RESET** | `60` | Reset Timeout (timeout to reset action counter. unit: s, set to -1 to disable) | captcha.action_reset (Captcha > Reset Timeout) |
|
||||
| **ATK_CAPTCHA_ALWAYS** | `false` | Captcha is required always | captcha.always (Captcha > Captcha is required always) |
|
||||
| **ATK_CAPTCHA_CAPTCHA_TYPE** | `"image"` | Captcha type (可选:`["image", "turnstile", "recaptcha", "hcaptcha", "geetest"]`) | captcha.captcha_type (Captcha > Captcha type) |
|
||||
| **ATK_CAPTCHA_ENABLED** | `true` | Enable captcha | captcha.enabled (Captcha > Enable captcha) |
|
||||
| **ATK_CAPTCHA_GEETEST_CAPTCHA_ID** | `""` | CaptchaId | captcha.geetest.captcha_id (Captcha > Geetest > CaptchaId) |
|
||||
| **ATK_CAPTCHA_GEETEST_CAPTCHA_KEY** | `""` | CaptchaKey | captcha.geetest.captcha_key (Captcha > Geetest > CaptchaKey) |
|
||||
| **ATK_CAPTCHA_HCAPTCHA_SECRET_KEY** | `""` | SecretKey | captcha.hcaptcha.secret_key (Captcha > hCaptcha > SecretKey) |
|
||||
| **ATK_CAPTCHA_HCAPTCHA_SITE_KEY** | `""` | SiteKey | captcha.hcaptcha.site_key (Captcha > hCaptcha > SiteKey) |
|
||||
| **ATK_CAPTCHA_RECAPTCHA_SECRET_KEY** | `""` | SecretKey | captcha.recaptcha.secret_key (Captcha > reCaptcha > SecretKey) |
|
||||
| **ATK_CAPTCHA_RECAPTCHA_SITE_KEY** | `""` | SiteKey | captcha.recaptcha.site_key (Captcha > reCaptcha > SiteKey) |
|
||||
| **ATK_CAPTCHA_TURNSTILE_SECRET_KEY** | `""` | SecretKey | captcha.turnstile.secret_key (Captcha > Turnstile > SecretKey) |
|
||||
| **ATK_CAPTCHA_TURNSTILE_SITE_KEY** | `""` | SiteKey | captcha.turnstile.site_key (Captcha > Turnstile > SiteKey) |
|
||||
|
||||
|
||||
## Database
|
||||
|
||||
| Env | Default | Desc | Path |
|
||||
| --- | --- | --- | --- |
|
||||
| **ATK_DB_CHARSET** | `"utf8mb4"` | Database charset | db.charset (Database > Database charset) |
|
||||
| **ATK_DB_FILE** | `"./data/artalk.db"` | Database file (only for SQLite) | db.file (Database > Database file) |
|
||||
| **ATK_DB_HOST** | `"localhost"` | Host address | db.host (Database > Host address) |
|
||||
| **ATK_DB_NAME** | `"artalk"` | Database name | db.name (Database > Database name) |
|
||||
| **ATK_DB_PASSWORD** | `""` | Database password | db.password (Database > Database password) |
|
||||
| **ATK_DB_PORT** | `3306` | Host port | db.port (Database > Host port) |
|
||||
| **ATK_DB_PREPARE_STMT** | `true` | Prepared Statement | db.prepare_stmt (Database > Prepared Statement) |
|
||||
| **ATK_DB_SSL** | `false` | Enable SSL mode | db.ssl (Database > Enable SSL mode) |
|
||||
| **ATK_DB_TABLE_PREFIX** | `""` | Table prefix (e.g. "atk_") | db.table_prefix (Database > Table prefix) |
|
||||
| **ATK_DB_TYPE** | `"sqlite"` | Database type (可选:`["sqlite", "mysql", "pgsql", "mssql"]`) | db.type (Database > Database type) |
|
||||
| **ATK_DB_USER** | `"root"` | Database user | db.user (Database > Database user) |
|
||||
|
||||
|
||||
## Email
|
||||
|
||||
| Env | Default | Desc | Path |
|
||||
| --- | --- | --- | --- |
|
||||
| **ATK_EMAIL_ALI_DM_ACCESS_KEY_ID** | `""` | AccessKeyId | email.ali_dm.access_key_id (Email > Aliyun mail push > AccessKeyId) |
|
||||
| **ATK_EMAIL_ALI_DM_ACCESS_KEY_SECRET** | `""` | AccessKeySecret | email.ali_dm.access_key_secret (Email > Aliyun mail push > AccessKeySecret) |
|
||||
| **ATK_EMAIL_ALI_DM_ACCOUNT_NAME** | `"noreply@example.com"` | AccountName | email.ali_dm.account_name (Email > Aliyun mail push > AccountName) |
|
||||
| **ATK_EMAIL_ENABLED** | `false` | Enable email notification | email.enabled (Email > Enable email notification) |
|
||||
| **ATK_EMAIL_MAIL_SUBJECT** | `"[{{site_name}}] You got a reply from @{{reply_nick}}"` | Email subject | email.mail_subject (Email > Email subject) |
|
||||
| **ATK_EMAIL_MAIL_TPL** | `"default"` | Email template file (set to file path to use custom template) | email.mail_tpl (Email > Email template file) |
|
||||
| **ATK_EMAIL_SEND_ADDR** | `"noreply@example.com"` | Email address of sender | email.send_addr (Email > Email address of sender) |
|
||||
| **ATK_EMAIL_SEND_NAME** | `"{{reply_nick}}"` | Nick name of sender | email.send_name (Email > Nick name of sender) |
|
||||
| **ATK_EMAIL_SEND_TYPE** | `"smtp"` | Send method (可选:`["smtp", "ali_dm", "sendmail"]`) | email.send_type (Email > Send method) |
|
||||
| **ATK_EMAIL_SMTP_HOST** | `"smtp.qq.com"` | Email address of sender | email.smtp.host (Email > SMTP send > Email address of sender) |
|
||||
| **ATK_EMAIL_SMTP_PASSWORD** | `""` | Password | email.smtp.password (Email > SMTP send > Password) |
|
||||
| **ATK_EMAIL_SMTP_PORT** | `587` | Email port | email.smtp.port (Email > SMTP send > Email port) |
|
||||
| **ATK_EMAIL_SMTP_USERNAME** | `"example@qq.com"` | Email address of sender | email.smtp.username (Email > SMTP send > Email address of sender) |
|
||||
|
||||
|
||||
## UI Settings
|
||||
|
||||
| Env | Default | Desc | Path |
|
||||
| --- | --- | --- | --- |
|
||||
| **ATK_FRONTEND_DARKMODE** | `"inherit"` | Dark mode (可选:`["inherit", "auto"]`) | frontend.darkMode (UI Settings > Dark mode) |
|
||||
| **ATK_FRONTEND_EDITORTRAVEL** | `true` | Movable comment box | frontend.editorTravel (UI Settings > Movable comment box) |
|
||||
| **ATK_FRONTEND_EMOTICONS** | `"https://cdn.jsdelivr.net/gh/ArtalkJS/Emoticons/grps/default.json"` | Emoticons | frontend.emoticons (UI Settings > Emoticons) |
|
||||
| **ATK_FRONTEND_FLATMODE** | `"auto"` | Flatten mode (可选:`["auto", "true", "false"]`) | frontend.flatMode (UI Settings > Flatten mode) |
|
||||
| **ATK_FRONTEND_GRAVATAR_MIRROR** | `"https://www.gravatar.com/avatar/"` | API URL | frontend.gravatar.mirror (UI Settings > Gravatar > API URL) |
|
||||
| **ATK_FRONTEND_GRAVATAR_PARAMS** | `"sha256=1&d=mp&s=240"` | API parameters | frontend.gravatar.params (UI Settings > Gravatar > API parameters) |
|
||||
| **ATK_FRONTEND_HEIGHTLIMIT_CHILDREN** | `400` | Sub-comment area height limit (unit: px) | frontend.heightLimit.children (UI Settings > Content height limit > Sub-comment area height limit) |
|
||||
| **ATK_FRONTEND_HEIGHTLIMIT_CONTENT** | `300` | Comment content height limit (unit: px) | frontend.heightLimit.content (UI Settings > Content height limit > Comment content height limit) |
|
||||
| **ATK_FRONTEND_HEIGHTLIMIT_SCROLLABLE** | `false` | Scrollable (scrollable height limit area) | frontend.heightLimit.scrollable (UI Settings > Content height limit > Scrollable) |
|
||||
| **ATK_FRONTEND_IMGLAZYLOAD** | `false` | Image lazy load (可选:`["false", "native", "data-src"]`) | frontend.imgLazyLoad (UI Settings > Image lazy load) |
|
||||
| **ATK_FRONTEND_LISTSORT** | `true` | Comment sorting | frontend.listSort (UI Settings > Comment sorting) |
|
||||
| **ATK_FRONTEND_NESTMAX** | `2` | Maximum nesting level | frontend.nestMax (UI Settings > Maximum nesting level) |
|
||||
| **ATK_FRONTEND_NESTSORT** | `"DATE_ASC"` | Nesting comment sorting rules (可选:`["DATE_ASC", "DATE_DESC", "VOTE_UP_DESC"]`) | frontend.nestSort (UI Settings > Nesting comment sorting rules) |
|
||||
| **ATK_FRONTEND_NOCOMMENT** | `""` | Text to display when there is | frontend.noComment (UI Settings > Text to display when there is) |
|
||||
| **ATK_FRONTEND_PAGINATION_AUTOLOAD** | `true` | Scroll loading | frontend.pagination.autoLoad (UI Settings > Comment pagination > Scroll loading) |
|
||||
| **ATK_FRONTEND_PAGINATION_PAGESIZE** | `20` | Number of comments per page | frontend.pagination.pageSize (UI Settings > Comment pagination > Number of comments per page) |
|
||||
| **ATK_FRONTEND_PAGINATION_READMORE** | `true` | Load more mode (disabled to use pagination bar) | frontend.pagination.readMore (UI Settings > Comment pagination > Load more mode) |
|
||||
| **ATK_FRONTEND_PLACEHOLDER** | `""` | Comment box placeholder | frontend.placeholder (UI Settings > Comment box placeholder) |
|
||||
| **ATK_FRONTEND_PLUGINURLS** | `[]` | Plugins | frontend.pluginURLs (UI Settings > Plugins) |
|
||||
| **ATK_FRONTEND_PREVIEW** | `true` | Editor real-time preview | frontend.preview (UI Settings > Editor real-time preview) |
|
||||
| **ATK_FRONTEND_REQTIMEOUT** | `15000` | Request timeout (unit: ms) | frontend.reqTimeout (UI Settings > Request timeout) |
|
||||
| **ATK_FRONTEND_SENDBTN** | `""` | Text of the send button | frontend.sendBtn (UI Settings > Text of the send button) |
|
||||
| **ATK_FRONTEND_UABADGE** | `false` | User UA badge | frontend.uaBadge (UI Settings > User UA badge) |
|
||||
| **ATK_FRONTEND_VERSIONCHECK** | `true` | Version check | frontend.versionCheck (UI Settings > Version check) |
|
||||
| **ATK_FRONTEND_VOTE** | `true` | Vote button | frontend.vote (UI Settings > Vote button) |
|
||||
| **ATK_FRONTEND_VOTEDOWN** | `false` | Vote down button | frontend.voteDown (UI Settings > Vote down button) |
|
||||
|
||||
|
||||
## Web server
|
||||
|
||||
| Env | Default | Desc | Path |
|
||||
| --- | --- | --- | --- |
|
||||
| **ATK_HTTP_BODY_LIMIT** | `100` | Body size limit (unit: MB) | http.body_limit (Web server > Body size limit) |
|
||||
| **ATK_HTTP_PROXY_HEADER** | `""` | Proxy Header (fill `X-Forwarded-For` to get user real IP if behind a trusted reverse proxy or CDN) | http.proxy_header (Web server > Proxy Header) |
|
||||
|
||||
|
||||
## Upload
|
||||
|
||||
| Env | Default | Desc | Path |
|
||||
| --- | --- | --- | --- |
|
||||
| **ATK_IMG_UPLOAD_ENABLED** | `true` | Enable image upload | img_upload.enabled (Upload > Enable image upload) |
|
||||
| **ATK_IMG_UPLOAD_MAX_SIZE** | `5` | Image size limit (unit: MB) | img_upload.max_size (Upload > Image size limit) |
|
||||
| **ATK_IMG_UPLOAD_PATH** | `"./data/artalk-img/"` | Image storage | img_upload.path (Upload > Image storage) |
|
||||
| **ATK_IMG_UPLOAD_PUBLIC_PATH** | `<nil>` | Image link base path (default: "/static/images/") | img_upload.public_path (Upload > Image link base path) |
|
||||
| **ATK_IMG_UPLOAD_UPGIT_DEL_LOCAL** | `true` | Delete local image after upload success | img_upload.upgit.del_local (Upload > Upgit config > Delete local image after upload success) |
|
||||
| **ATK_IMG_UPLOAD_UPGIT_ENABLED** | `false` | Enable Upgit | img_upload.upgit.enabled (Upload > Upgit config > Enable Upgit) |
|
||||
| **ATK_IMG_UPLOAD_UPGIT_EXEC** | `"upgit -c UPGIT_CONF_FILE_PATH -t /artalk-img"` | Command line arguments | img_upload.upgit.exec (Upload > Upgit config > Command line arguments) |
|
||||
|
||||
|
||||
## Logging
|
||||
|
||||
| Env | Default | Desc | Path |
|
||||
| --- | --- | --- | --- |
|
||||
| **ATK_LOG_ENABLED** | `true` | Enable logging | log.enabled (Logging > Enable logging) |
|
||||
| **ATK_LOG_FILENAME** | `"./data/artalk.log"` | Log file path | log.filename (Logging > Log file path) |
|
||||
|
||||
|
||||
## Moderator
|
||||
|
||||
| Env | Default | Desc | Path |
|
||||
| --- | --- | --- | --- |
|
||||
| **ATK_MODERATOR_AKISMET_KEY** | `""` | Akismet Key (Akismet anti-spam service, https://akismet.com) | moderator.akismet_key (Moderator > Akismet Key) |
|
||||
| **ATK_MODERATOR_ALIYUN_ACCESS_KEY_ID** | `""` | AccessKeyId | moderator.aliyun.access_key_id (Moderator > Aliyun Content Security > AccessKeyId) |
|
||||
| **ATK_MODERATOR_ALIYUN_ACCESS_KEY_SECRET** | `""` | AccessKeySecret | moderator.aliyun.access_key_secret (Moderator > Aliyun Content Security > AccessKeySecret) |
|
||||
| **ATK_MODERATOR_ALIYUN_ENABLED** | `false` | 启用 | moderator.aliyun.enabled (Moderator > Aliyun Content Security > Enabled) |
|
||||
| **ATK_MODERATOR_ALIYUN_REGION** | `"cn-shanghai"` | Region | moderator.aliyun.region (Moderator > Aliyun Content Security > Region) |
|
||||
| **ATK_MODERATOR_API_FAIL_BLOCK** | `false` | Block when API request fails (set to false to let comments pass when API request fails) | moderator.api_fail_block (Moderator > Block when API request fails) |
|
||||
| **ATK_MODERATOR_KEYWORDS_ENABLED** | `false` | Enable keyword filter | moderator.keywords.enabled (Moderator > Keyword filter > Enable keyword filter) |
|
||||
| **ATK_MODERATOR_KEYWORDS_FILE_SEP** | `"\n"` | FileSep | moderator.keywords.file_sep (Moderator > Keyword filter > FileSep) |
|
||||
| **ATK_MODERATOR_KEYWORDS_FILES** | `[./data/keywords_1.txt]` | Dictionary file (support multiple dictionary files) | moderator.keywords.files (Moderator > Keyword filter > Dictionary file) |
|
||||
| **ATK_MODERATOR_KEYWORDS_PENDING** | `false` | Set to pending when match | moderator.keywords.pending (Moderator > Keyword filter > Set to pending when match) |
|
||||
| **ATK_MODERATOR_KEYWORDS_REPLACE_TO** | `"x"` | ReplaceTo | moderator.keywords.replace_to (Moderator > Keyword filter > ReplaceTo) |
|
||||
| **ATK_MODERATOR_PENDING_DEFAULT** | `false` | Default pending (new comments need to be approved by admin) | moderator.pending_default (Moderator > Default pending) |
|
||||
| **ATK_MODERATOR_TENCENT_ENABLED** | `false` | 启用 | moderator.tencent.enabled (Moderator > Tencent Cloud Content Security > Enabled) |
|
||||
| **ATK_MODERATOR_TENCENT_REGION** | `"ap-guangzhou"` | Region | moderator.tencent.region (Moderator > Tencent Cloud Content Security > Region) |
|
||||
| **ATK_MODERATOR_TENCENT_SECRET_ID** | `""` | SecretId | moderator.tencent.secret_id (Moderator > Tencent Cloud Content Security > SecretId) |
|
||||
| **ATK_MODERATOR_TENCENT_SECRET_KEY** | `""` | SecretKey | moderator.tencent.secret_key (Moderator > Tencent Cloud Content Security > SecretKey) |
|
||||
|
||||
|
||||
## SSL
|
||||
|
||||
| Env | Default | Desc | Path |
|
||||
| --- | --- | --- | --- |
|
||||
| **ATK_SSL_CERT_PATH** | `""` | Certificate file path (e.g. "/etc/letsencrypt/live/example.com/fullchain.pem") | ssl.cert_path (SSL > Certificate file path) |
|
||||
| **ATK_SSL_ENABLED** | `false` | Enable SSL | ssl.enabled (SSL > Enable SSL) |
|
||||
| **ATK_SSL_KEY_PATH** | `""` | Key file path (e.g. "/etc/letsencrypt/live/example.com/privkey.pem") | ssl.key_path (SSL > Key file path) |
|
||||
|
||||
<!-- /env-variables -->
|
||||
</div>
|
||||
|
||||
## Developers
|
||||
|
||||
Enable developer mode by setting `ATK_DEBUG=1`.
|
||||
|
||||
This document is auto-generated by the Artalk developer tool. Run `make update-conf-docs` to update this page's content based on the template configuration file ([conf/artalk.example.zh-CN.yml](https://github.com/ArtalkJS/Artalk/tree/master/conf/artalk.example.zh-CN.yml)).
|
||||
|
||||
Related code can be found in the [@ArtalkJS/Artalk:/internal/config/](https://github.com/ArtalkJS/Artalk/tree/master/internal/config/) directory.
|
||||
@@ -0,0 +1,3 @@
|
||||
# 📚 Further reading
|
||||
|
||||
【TODO】
|
||||
@@ -0,0 +1,17 @@
|
||||
# ArtalkLite Overview
|
||||
|
||||
ArtalkLite is a simplified version of Artalk, with the following differences compared to the standard version:
|
||||
|
||||
- Smaller size and more streamlined.
|
||||
- Emojis, voting, and UA badges are disabled by default.
|
||||
- Removed the dependency on marked, but supports basic Markdown syntax.
|
||||
|
||||
ArtalkLite is released alongside the standard version. You can obtain it by modifying the file name in the CDN address to ArtalkLite:
|
||||
|
||||
```
|
||||
https://unpkg.com/artalk@:ArtalkVersion:/dist/ArtalkLite.js
|
||||
```
|
||||
|
||||
```
|
||||
https://unpkg.com/artalk@:ArtalkVersion:/dist/ArtalkLite.css
|
||||
```
|
||||
@@ -0,0 +1,131 @@
|
||||
# Social Login
|
||||
|
||||
By default, Artalk allows users to post comments by simply filling in their nickname and email without email verification.
|
||||
|
||||
However, sometimes we want users to log in with their social accounts to reduce the time spent filling in information or to increase the authenticity of user information. This can be achieved by enabling social login.
|
||||
|
||||
Currently, the following social login methods are supported:
|
||||
|
||||
| Login Method | Integration Docs | Login Method | Integration Docs | Login Method | Integration Docs |
|
||||
| ------------ | ---------------- | ------------ | ---------------- | ------------ | ---------------- |
|
||||
| Google | [View](https://developers.google.com/identity/protocols/oauth2) | Microsoft | [View](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow) | Apple | [View](https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_js/configuring_your_webpage_for_sign_in_with_apple) |
|
||||
| Facebook | [View](https://developers.facebook.com/docs/facebook-login/) | Twitter | [View](https://developer.twitter.com/en/docs/basics/authentication/overview) | Discord | [View](https://discord.com/developers/docs/topics/oauth2) |
|
||||
| Slack | [View](https://api.slack.com/authentication/oauth-v2) | GitHub | [View](https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/) | TikTok | [View](https://developers.tiktok.com/doc/login) |
|
||||
| Steam | [View](https://partner.steamgames.com/doc/webapi_overview/auth) | WeChat | [View](https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html) | Line | [View](https://developers.line.biz/en/docs/line-login/integrate-line-login/) |
|
||||
| GitLab | [View](https://docs.gitlab.com/ee/api/oauth2.html) | Gitea | [View](https://docs.gitea.io/en-us/oauth2-provider/) | Mastodon | [View](https://docs.joinmastodon.org/api/authentication/) |
|
||||
| Patreon | [View](https://docs.patreon.com/#oauth) | Auth0 | [View](https://auth0.com/docs/connections/social/) | Email & Password | [View](#email-password-login) |
|
||||
|
||||
To enable social login, simply find the "Social Login" option in the [Dashboard](./sidebar.md#settings), enable it, and fill in the corresponding configuration information. Alternatively, you can configure it through the [configuration file](../backend/config.md) or [environment variables](../env.md#social-login).
|
||||
|
||||
## Email & Password Login
|
||||
|
||||

|
||||
|
||||
After enabling email and password login, the nickname and email input fields at the top of the comment box will be hidden, and the send button will change to a login button. When the user clicks the login button, a login box will pop up, allowing the user to log in with their email and password. Once logged in, the user can post comments. Comments posted by the user will display a "Email Verified" badge.
|
||||
|
||||
<img src="/images/auth/email_verified.png" width="550" alt="Email Verified Badge">
|
||||
|
||||
Users can register an account via email, and Artalk will send a verification code to their email. The verification code is valid for 10 minutes, and the frequency of sending verification codes is limited to once per minute.
|
||||
|
||||

|
||||
|
||||
You can customize the verification email template and subject. In the settings page of the Artalk Dashboard under social login, you can find options for "Email Verification Subject" and "Email Verification Template". In the configuration file, you can set them using `auth.email.verify_subject` and `auth.email.verify_tpl`:
|
||||
|
||||
```yaml
|
||||
auth:
|
||||
enabled: true
|
||||
email:
|
||||
enabled: true
|
||||
verify_subject: "Your verification code is - {{code}}"
|
||||
verify_tpl: default
|
||||
```
|
||||
|
||||
The default template is as follows:
|
||||
|
||||
```html
|
||||
Your verification code is: {{code}}. Please use it to verify your email and log in to Artalk. If you did not request this, please ignore this message.
|
||||
```
|
||||
|
||||

|
||||
|
||||
After enabling email and password login, users can still skip email verification: the login popup will show a "Skip, do not verify" button at the bottom, which, when clicked, restores the original nickname, email, and website input fields at the top of the comment box. You can change this by selecting "Allow Anonymous Comments" in the settings.
|
||||
|
||||
## Account Merging Tool
|
||||
|
||||
If multiple accounts with different usernames but the same email are detected after logging in, an account merging tool will pop up. Users can choose to keep one username, and all comments and data associated with that email will be merged into the retained account. The original accounts will be deleted, and the displayed username on comments will change to the retained username.
|
||||
|
||||

|
||||
|
||||
## Multiple Login Methods
|
||||
|
||||
Artalk supports enabling multiple login methods simultaneously, allowing users to choose any method to log in.
|
||||
|
||||

|
||||
|
||||
If only one login method is enabled, such as GitHub login, the GitHub authorization login page will pop up directly.
|
||||
|
||||

|
||||
|
||||
For integrating GitHub login, refer to the documentation: [About Creating GitHub Apps](https://docs.github.com/en/developers/apps/building-oauth-apps/creating-an-oauth-app). After obtaining the Client ID and Client Secret, fill them in the "GitHub" option in the social login settings page of the Artalk Dashboard.
|
||||
|
||||
## SSO Token Exchange
|
||||
|
||||
If your site is embedded in an application that already authenticates users through an external OIDC identity provider (Auth0, Keycloak, Okta, etc.), you can let Artalk reuse that session instead of showing its own login UI. The surrounding application exchanges the IdP access token it already holds for an Artalk session token, so users who are already signed in to the parent app can comment without an extra click or popup.
|
||||
|
||||
This feature is **disabled by default** and is opt-in only. Enable it through the [configuration file](../backend/config.md) or [environment variables](../env.md#social-login), filling in the OIDC issuer of your provider:
|
||||
|
||||
```yaml
|
||||
auth:
|
||||
enabled: true
|
||||
sso:
|
||||
enabled: true
|
||||
issuer: "https://tenant.auth0.com" # e.g. "tenant.auth0.com" or "https://tenant.auth0.com"
|
||||
```
|
||||
|
||||
Once enabled, the surrounding application posts the IdP access token to the exchange endpoint:
|
||||
|
||||
```
|
||||
POST /api/v2/sso/exchange
|
||||
Content-Type: application/json
|
||||
|
||||
{ "token": "<external IdP access token>" }
|
||||
```
|
||||
|
||||
Artalk validates the token by calling the issuer's `/userinfo` endpoint (the standard OIDC way — the IdP verifies and signs the response server-side, so no key handling is required on Artalk's side) and reads the `email` claim. Artalk only finds or creates the matching user when the response also contains `email_verified: true`, then returns the same response shape as the other login endpoints:
|
||||
|
||||
```json
|
||||
{
|
||||
"token": "<Artalk session token>",
|
||||
"user": { "name": "...", "email": "...", "is_admin": false }
|
||||
}
|
||||
```
|
||||
|
||||
The frontend writes this into `localStorage["ArtalkUser"]` before `Artalk.init()` runs, and the widget then treats the user as fully logged in (no popup, and no admin password prompt for admin users):
|
||||
|
||||
```js
|
||||
const res = await fetch('https://comments.example.com/api/v2/sso/exchange', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ token: idpAccessToken }), // the access token from your IdP
|
||||
})
|
||||
const { token, user } = await res.json()
|
||||
|
||||
// Pre-populate the Artalk session before the widget mounts
|
||||
localStorage.setItem('ArtalkUser', JSON.stringify({ ...user, token }))
|
||||
|
||||
Artalk.init({ el: '#Comments', server: 'https://comments.example.com', site: 'Blog' })
|
||||
```
|
||||
|
||||
The endpoint returns `404` when SSO is not enabled, `401` when the IdP rejects the token or the email is unverified, and `400` when the token carries no email claim. Artalk does not provide a switch to bypass email verification.
|
||||
|
||||
::: tip
|
||||
|
||||
Validation relies entirely on the issuer's `/userinfo` rejecting invalid or revoked tokens, and the user is matched by the `email` claim. Only enable this for an issuer you trust. Because an unverified email could impersonate an existing user or administrator, Artalk requires `email_verified: true`.
|
||||
|
||||
:::
|
||||
|
||||
## Plugin Development
|
||||
|
||||
The social login feature of Artalk is implemented through an independent plugin developed using Solid.js. The code can be found in [@ArtalkJS/Artalk:ui/plugin-auth](https://github.com/ArtalkJS/Artalk/tree/master/ui/plugin-auth).
|
||||
|
||||
After enabling the social login feature in the Dashboard, the plugin will be automatically loaded on the front end.
|
||||
@@ -0,0 +1,541 @@
|
||||
# UI Configuration
|
||||
|
||||
UI configuration also known as Client (Frontend) configuration, is the configuration of the Artalk interface.
|
||||
|
||||
You can modify the UI configuration in the "[Dashboard](./sidebar.md#dashboard)" without changing the frontend code. Additionally, [Configuration files](../backend/config.md#frontend-configuration-frontend) and [Environment variables](../env.md#ui-settings) are also supported.
|
||||
|
||||
The priority of UI configuration is as follows:
|
||||
|
||||
```
|
||||
Frontend Code > Environment Variables > Dashboard = Configuration File
|
||||
```
|
||||
|
||||
If you have modified the UI configuration through environment variables, configuration files, or the Dashboard, but the changes are not taking effect, please consider the issue of UI configuration priority.
|
||||
|
||||
## Configuring the Interface via `Artalk.init`
|
||||
|
||||
When initializing with `Artalk.init` on the frontend page, pass parameters to configure the interface. Below are all the available configuration options.
|
||||
|
||||
```js
|
||||
const artalk = Artalk.init({
|
||||
el: '#Comments',
|
||||
pageKey: '/post/1',
|
||||
pageTitle: 'Page Title',
|
||||
server: 'http://your_domain:8080',
|
||||
site: 'Artalk Blog',
|
||||
})
|
||||
|
||||
// Update configuration
|
||||
artalk.update({ ... })
|
||||
```
|
||||
|
||||
- Default Configuration: [defaults.ts](https://github.com/ArtalkJS/Artalk/blob/master/ui/artalk/src/defaults.ts)
|
||||
- Declaration File: [config.ts](https://github.com/ArtalkJS/Artalk/blob/master/ui/artalk/src/types/config.ts)
|
||||
|
||||
## Basic Configuration (Required)
|
||||
|
||||
### el
|
||||
|
||||
**Mount Element** (Specify the element selector to bind)
|
||||
|
||||
- Type: `String|HTMLElement`
|
||||
- Default: `undefined`
|
||||
|
||||
> Example: `#Comments` corresponds to the element `<div id="Comments"></div>`
|
||||
|
||||
### pageKey
|
||||
|
||||
**Page Address** (Relative path / full URL)
|
||||
|
||||
- Type: `String`
|
||||
- Default: `location.pathname`
|
||||
|
||||
Leave empty to automatically get the relative path of the page.
|
||||
|
||||
You can fill in the `permalink` generated by the blog system, but it is recommended to use the relative path for easier domain switching in the future.
|
||||
|
||||
Reference: [Using Relative / Absolute Paths](../backend/relative-path.md)
|
||||
|
||||
### pageTitle
|
||||
|
||||
**Page Title** (Used for management list display, email notifications, etc.)
|
||||
|
||||
- Type: `String`
|
||||
- Default: `document.title`
|
||||
|
||||
Leave empty to automatically get the value of the `<title>` tag of the page.
|
||||
|
||||
### server
|
||||
|
||||
**Backend Program API Address**
|
||||
|
||||
- Type: `String`
|
||||
- Default: `undefined`
|
||||
|
||||
Deploy the backend and ensure that the backend address is accessible from the front end.
|
||||
|
||||
> Example: http://yourdomain.xxx
|
||||
|
||||
### site
|
||||
|
||||
**Site Name**
|
||||
|
||||
- Type: `String`
|
||||
- Default: `undefined`
|
||||
|
||||
Artalk supports unified management of multiple sites, this item is used for site isolation.
|
||||
|
||||
### preferRemoteConf
|
||||
|
||||
**Prefer Remote Configuration**
|
||||
|
||||
- Type: `Boolean`
|
||||
- Default: `false` (disabled by default)
|
||||
|
||||
This config option is disabled by default, meaning Artalk will prioritize using local config. Local config refers to the settings passed through `Artalk.init` in the frontend code.
|
||||
|
||||
When enabled, Artalk will prioritize using remote config. If the remote config does not exist, it will resort to using the local config. Remote config refers to the settings returned by the backend server program, allowing for dynamic config of the Artalk client. You can modify the user interface config in the "[Dashboard](./sidebar.md#dashboard)".
|
||||
|
||||
::: warning Update Notice
|
||||
As of version v2.10.0, the `useBackendConf` configuration option has been deprecated and is always `true`. Please use `preferRemoteConf` instead.
|
||||
:::
|
||||
|
||||
## Internationalization (i18n)
|
||||
|
||||
### locale
|
||||
|
||||
**Language**
|
||||
|
||||
- Type: `String|Object|"auto"`
|
||||
- Default: `"zh-CN"`
|
||||
|
||||
Following the Unicode BCP 47 standard, the default is "zh-CN" (Simplified Chinese).
|
||||
|
||||
Feel free to submit a PR to help translate Artalk into multiple languages and contribute to the community!
|
||||
|
||||
For details, see: [Localization](./i18n.md)
|
||||
|
||||
### dateFormatter
|
||||
|
||||
**Date Formatting Function**
|
||||
|
||||
- Type: `(date: Date) => string`
|
||||
- Default: `undefined`
|
||||
|
||||
By default, it shows a few seconds ago, a few minutes ago, a few hours ago, a few days ago, and if it exceeds 7 days, it shows the specific date in the format `YYYY-MM-DD`.
|
||||
|
||||
You can also customize the date formatting function. Below is an example using [toLocaleString](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleString) and [dayjs](https://day.js.org/) to format dates.
|
||||
|
||||
```js
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
Artalk.init({
|
||||
dateFormatter: (date) => {
|
||||
// return date.toLocaleString() // '2024/8/28 17:51:31'
|
||||
return dayjs(date).fromNow("MMMM D, YYYY h:mm A") // 'August 28, 2024 5:51 PM'
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Requests
|
||||
|
||||
### reqTimeout
|
||||
|
||||
**Request Timeout**
|
||||
|
||||
- Type: `Number`
|
||||
- Default: `15000`
|
||||
|
||||
Automatically disconnects the request and reports an error if the request time exceeds this value (unit: milliseconds).
|
||||
|
||||
## Emoticons
|
||||
|
||||
### emoticons
|
||||
|
||||
**Emoticons**
|
||||
|
||||
- Type: `Object|Array|String|Boolean`
|
||||
- Default: "[https://cdn.jsdelivr.net/gh/ArtalkJS/Emoticons/grps/default.json](https://cdn.jsdelivr.net/gh/ArtalkJS/Emoticons/grps/default.json)"
|
||||
|
||||
For details: [Frontend · Emoticons](./emoticons.md)
|
||||
|
||||
Now compatible with [OwO format](https://github.com/DIYgod/OwO), supports dynamic loading by URL.
|
||||
|
||||
Set to `false` to disable the emoticon feature.
|
||||
|
||||
## Interface
|
||||
|
||||
### placeholder
|
||||
|
||||
**Comment Box Placeholder Text**
|
||||
|
||||
- Type: `String`
|
||||
- Default: `"Type something..."`
|
||||
|
||||
### noComment
|
||||
|
||||
**Text Displayed When There Are No Comments**
|
||||
|
||||
- Type: `String`
|
||||
- Default: `"「Silence is golden」"`
|
||||
|
||||
### sendBtn
|
||||
|
||||
**Send Button Text**
|
||||
|
||||
- Type: `String`
|
||||
- Default: `"Post Comment"`
|
||||
|
||||
### editorTravel
|
||||
|
||||
**Comment Box Travel**
|
||||
|
||||
- Type: `Boolean`
|
||||
- Default: `true`
|
||||
|
||||
Set to `true` to move the comment box to the position of the comment being replied to, instead of keeping it fixed.
|
||||
|
||||
### darkMode
|
||||
|
||||
**Dark Mode**
|
||||
|
||||
- Type: `Boolean|"auto"`
|
||||
- Default: `false`
|
||||
|
||||
When Artalk is instantiated, it reads this value and chooses whether to enable dark mode (can be used with the blog theme).
|
||||
|
||||
To dynamically modify darkMode:
|
||||
|
||||
```js
|
||||
artalk.setDarkMode(true)
|
||||
```
|
||||
|
||||
> Reference code: “[index.html](https://github.com/ArtalkJS/Artalk/blob/master/ui/artalk/index.html#L97-L150)”
|
||||
|
||||
Set to `"auto"`, Artalk will listen to `(prefers-color-scheme: dark)` and automatically switch to dark mode based on the user's operating system settings.
|
||||
|
||||
### flatMode
|
||||
|
||||
**Flat Mode**
|
||||
|
||||
- Type: `Boolean|"auto"`
|
||||
- Default: `"auto"`
|
||||
|
||||
By default, `"auto"` automatically enables "flat mode" on small screen devices (screen width < 768px).
|
||||
|
||||
Set to `true` to display comments in "flat mode".
|
||||
|
||||
Set to `false` to display comments in "nested mode".
|
||||
|
||||
### nestMax
|
||||
|
||||
**Maximum Nested Levels**
|
||||
|
||||
- Type: `Number`
|
||||
- Default: `2`
|
||||
|
||||
Maximum nested levels for comments in "nested mode".
|
||||
|
||||
### nestSort
|
||||
|
||||
**Sorting Rules for Nested Comments**
|
||||
|
||||
- Type: `"DATE_ASC"|"DATE_DESC"|"VOTE_UP_DESC"`
|
||||
- Default: `"DATE_ASC"`
|
||||
|
||||
By default, nested comments are sorted in ascending order by date (new comments at the end).
|
||||
|
||||
## Features
|
||||
|
||||
### pvEl
|
||||
|
||||
**Page Views (PV) Binding Element**
|
||||
|
||||
- Type: `String`
|
||||
- Default: `".artalk-pv-count"`
|
||||
|
||||
You can place an HTML tag anywhere on the page: `<span class="artalk-pv-count"></span>`
|
||||
|
||||
When Artalk finishes loading, it displays the page views.
|
||||
|
||||
This item specifies the selector of the binding element, defaulting to `.artalk-pv-count`.
|
||||
|
||||
### countEl
|
||||
|
||||
**Comment Count Binding Element**
|
||||
|
||||
- Type: `String`
|
||||
- Default: `".artalk-comment-count"`
|
||||
|
||||
You can place an HTML tag anywhere on the page: `<span class="artalk-comment-count"></span>` to display the current page's comment count.
|
||||
|
||||
::: tip
|
||||
|
||||
Both pvEl and countEl element tags can set the `data-page-key` attribute to specify the statistics of a particular page, for example: `<span class="artalk-comment-count" data-page-key="/t/1.html"></span>`
|
||||
|
||||
For details, see: [PV Statistics](./pv.md#Loading statistics for multiple pages simultaneously)
|
||||
|
||||
:::
|
||||
|
||||
### statPageKeyAttr
|
||||
|
||||
**Statistical Component PageKey Attribute Name**
|
||||
|
||||
- Type: `String`
|
||||
- Default: `"data-page-key"`
|
||||
|
||||
When querying comment counts and page views, Artalk's statistical component uses this attribute name to query the specified page, for example: `<span data-page-key="/t/1.html"></span>`.
|
||||
|
||||
To facilitate theme adaptation, you can customize the attribute name as needed, for example, replacing it with `data-path`, the HTML tag would be `<span data-path="/t/1.html"></span>`.
|
||||
|
||||
### pageVote
|
||||
|
||||
**Page Voting Buttons**
|
||||
|
||||
- Type: `Boolean | { upBtnEl: string; downBtnEl: string; upCountEl: string; downCountEl: string; activeClass: string }`
|
||||
- Default: `true`
|
||||
|
||||
Enable the page voting feature (upvote / downvote).
|
||||
|
||||
For details, see: [Page Voting](./voting.md#page-voting)
|
||||
|
||||
### vote
|
||||
|
||||
**Voting Buttons**
|
||||
|
||||
- Type: `Boolean`
|
||||
- Default: `true`
|
||||
|
||||
Enable the comment voting feature (upvote / downvote).
|
||||
|
||||
### voteDown
|
||||
|
||||
**Downvote Button**
|
||||
|
||||
- Type: `Boolean`
|
||||
- Default: `false`
|
||||
|
||||
Downvote button (hidden by default).
|
||||
|
||||
### uaBadge
|
||||
|
||||
**Display User's UserAgent Information Badge**
|
||||
|
||||
- Type: `Boolean`
|
||||
- Default: `false`
|
||||
|
||||
### listSort
|
||||
|
||||
**Comment Sorting Feature**
|
||||
|
||||
- Type: `Boolean`
|
||||
- Default: `true`
|
||||
|
||||
Hover over the "n comments" in the top left of the comment list to display a floating dropdown, allowing the comment list to be sorted and filtered according to "latest / hottest / earliest / author".
|
||||
|
||||
### imgUpload
|
||||
|
||||
**Image Upload Feature**
|
||||
|
||||
- Type: `Boolean`
|
||||
- Default: `true`
|
||||
|
||||
This configuration item automatically follows the backend. When the backend image upload feature is disabled, only administrators will see the image upload button.
|
||||
|
||||
### imgUploader
|
||||
|
||||
**Image Uploader**
|
||||
|
||||
- Type: `(file: File) => Promise<string>`
|
||||
- Default: `undefined`
|
||||
|
||||
Custom image uploader, for example:
|
||||
|
||||
```js
|
||||
Artalk.init({
|
||||
imgUploader: async (file) => {
|
||||
const form = new FormData()
|
||||
form.set('file', file)
|
||||
|
||||
const imgUrl = await fetch('https://api.example.org/upload', {
|
||||
method: 'POST',
|
||||
body: form,
|
||||
})
|
||||
|
||||
return imgUrl
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### imgLazyLoad
|
||||
|
||||
**Lazy Load Images**
|
||||
|
||||
- Type: `"native"|"data-src"|Boolean`
|
||||
- Default: `false`
|
||||
|
||||
Enable lazy loading of images in comments.
|
||||
|
||||
For details, see: [Lazy Load Images](./img-lazy-load.md)
|
||||
|
||||
### preview
|
||||
|
||||
**Editor Real-Time Preview Feature**
|
||||
|
||||
- Type: `Boolean`
|
||||
- Default: `true`
|
||||
|
||||
Display the "Preview" button in the editor.
|
||||
|
||||
## Avatars
|
||||
|
||||
```js
|
||||
gravatar: {
|
||||
mirror: '<Gravatar Mirror URL>',
|
||||
default: 'mp',
|
||||
}
|
||||
```
|
||||
|
||||
### gravatar.mirror
|
||||
|
||||
**Gravatar Mirror URL**
|
||||
|
||||
- Type: `String`
|
||||
- Default: `"https://www.gravatar.com/avatar/"`
|
||||
|
||||
If you find Gravatar avatar loading speed unsatisfactory, you can try replacing it.
|
||||
|
||||
For example:
|
||||
|
||||
> WeAvatar: https://weavatar.com/avatar/
|
||||
>
|
||||
> Cravatar: https://cravatar.cn/avatar/
|
||||
>
|
||||
> V2EX: https://cdn.v2ex.com/gravatar/
|
||||
>
|
||||
> Geekzu: https://sdn.geekzu.org/avatar/
|
||||
>
|
||||
> Loli: https://gravatar.loli.net/avatar/
|
||||
|
||||
### gravatar.params
|
||||
|
||||
**Gravatar API Parameters**
|
||||
|
||||
- Type: `String`
|
||||
- Default: `"sha256=1&d=mp&s=240"`
|
||||
|
||||
For example, you can set the default avatar (`d=mp`) and avatar size (`s=240`) through this configuration item.
|
||||
|
||||
Reference: [Gravatar API Documentation](http://cn.gravatar.org/site/implement/images/)
|
||||
|
||||
This configuration item is in HTTP Query format.
|
||||
|
||||
::: warning Update Notice
|
||||
|
||||
v2.8.7 supports generating Gravatar avatar links using the SHA256 encryption algorithm to enhance user privacy protection. Please include `sha256=1` in `params` to enable this encryption ([#874](https://github.com/ArtalkJS/Artalk/issues/874)).
|
||||
|
||||
v2.5.5 deprecated the `gravatar.default` configuration item; please use `gravatar.params` instead.
|
||||
|
||||
:::
|
||||
|
||||
### avatarURLBuilder
|
||||
|
||||
**Avatar URL Builder**
|
||||
|
||||
- Type: `(comment: CommentData) => string`
|
||||
- Default: `undefined`
|
||||
|
||||
Custom user avatar image URL generator, for example:
|
||||
|
||||
```js
|
||||
Artalk.init({
|
||||
avatarURLBuilder: (comment) => {
|
||||
return `/api/avatar?email=${comment.email_encrypted}`
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Comment Pagination
|
||||
|
||||
```js
|
||||
pagination: {
|
||||
pageSize: 20, // Number of comments per page
|
||||
readMore: true, // Load more or pagination
|
||||
autoLoad: true, // Auto-load (load more)
|
||||
}
|
||||
```
|
||||
|
||||
### pagination.readMore
|
||||
|
||||
**Load More Mode**
|
||||
|
||||
- Type: `Boolean`
|
||||
- Default: `true`
|
||||
|
||||
Set to `true` for "Load More" mode.
|
||||
|
||||
Set to `false` for "Pagination" mode.
|
||||
|
||||
### pagination.autoLoad
|
||||
|
||||
**Auto-Load on Scroll**
|
||||
|
||||
- Type: `Boolean`
|
||||
- Default: `true`
|
||||
|
||||
(Must enable "Load More" mode by setting `readMore` to `true`)
|
||||
|
||||
### pagination.pageSize
|
||||
|
||||
**Number of Comments Per Request**
|
||||
|
||||
- Type: `Number`
|
||||
- Default: `20`
|
||||
|
||||
## Content Height Limit
|
||||
|
||||
Content exceeding the set height will be hidden, and a "Read More" button will be displayed.
|
||||
|
||||
```js
|
||||
heightLimit: {
|
||||
content: 300, // Comment content height limit
|
||||
children: 400, // Child comment area height limit
|
||||
scrollable: false, // Height limit scrolling
|
||||
}
|
||||
```
|
||||
|
||||
### heightLimit.content
|
||||
|
||||
**Comment Content Height Limit**
|
||||
|
||||
- Type: `Number`
|
||||
- Default: `300`
|
||||
|
||||
> Set to 0 to disable height limit.
|
||||
|
||||
### heightLimit.children
|
||||
|
||||
**Child Comment Area Height Limit**
|
||||
|
||||
- Type: `Number`
|
||||
- Default: `400`
|
||||
|
||||
### heightLimit.scrollable
|
||||
|
||||
**Scrollable Height Limit Area**
|
||||
|
||||
- Type: `Boolean`
|
||||
- Default: `false`
|
||||
|
||||
Allow height limit area to scroll.
|
||||
|
||||
## Version Check
|
||||
|
||||
### versionCheck
|
||||
|
||||
**Version Check**
|
||||
|
||||
- Type: `Boolean`
|
||||
- Default: `true`
|
||||
|
||||
Display a warning prompt when the frontend and backend versions are incompatible.
|
||||
@@ -0,0 +1,180 @@
|
||||
# Emoticons
|
||||
|
||||
You can modify this configuration in the settings interface of the [Dashboard](./sidebar.md#设置), or through [configuration files](../backend/config.md#界面配置-frontend) and [environment variables](../env.md#界面配置).
|
||||
|
||||
In the configuration file, you can fill in the URL of the emoticon list under `frontend.emoticons`:
|
||||
|
||||
```yaml
|
||||
frontend:
|
||||
emoticons: https://raw.githubusercontent.com/ArtalkJS/Emoticons/master/grps/default.json
|
||||
```
|
||||
|
||||
## Emoticon Presets
|
||||
|
||||
The Artalk community offers many emoticon presets. You can choose a few favorite sets and easily add them to your comment system with simple configurations. Visit the repository: [@ArtalkJS/Emoticons](https://github.com/ArtalkJS/Emoticons).
|
||||
|
||||
## Supported Formats
|
||||
|
||||
### OwO Format
|
||||
|
||||
[OwO](https://github.com/DIYgod/OwO) is an open-source JS plugin developed by [@DIYgod](https://github.com/DIYgod) that allows quick insertion of emoticons into the input box.
|
||||
|
||||
Artalk's emoticon feature draws inspiration from its excellent design and natively supports and is compatible with OwO format emoticon data files, as shown below:
|
||||
|
||||
```yaml
|
||||
frontend:
|
||||
emoticons: https://raw.githubusercontent.com/DIYgod/OwO/master/demo/OwO.json
|
||||
# Directly use OwO format emoticons ↑↑
|
||||
```
|
||||
|
||||
The community also has many OwO format emoticon resources that can be used directly, for example: [@2X-ercha/Twikoo-Magic](https://github.com/2X-ercha/Twikoo-Magic).
|
||||
|
||||
### Artalk Emoticon List File Standard Format
|
||||
|
||||
In addition to supporting the OwO format, Artalk also natively supports a standard format for emoticon list files:
|
||||
|
||||
```js
|
||||
[
|
||||
{
|
||||
name: 'Emoji',
|
||||
type: 'emoticon', // Text type
|
||||
items: [
|
||||
{ key: 'Hi', val: '|´・ω・)ノ' },
|
||||
{ key: 'Happy', val: 'ヾ(≧∇≦*)ゝ' },
|
||||
//...
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Funny',
|
||||
type: 'image', // Image type
|
||||
items: [
|
||||
{
|
||||
key: 'Original Funny',
|
||||
val: '<Image URL>',
|
||||
},
|
||||
//...
|
||||
],
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
## Front-end Configuration
|
||||
|
||||
To configure emoticon lists on the front end, for example:
|
||||
|
||||
```js
|
||||
Artalk.init({
|
||||
// Default emoticon list, dynamically imported ↓↓
|
||||
emoticons:
|
||||
'https://raw.githubusercontent.com/ArtalkJS/Emoticons/master/grps/default.json',
|
||||
})
|
||||
```
|
||||
|
||||
::: tip
|
||||
|
||||
Configuration file and dashboard emoticon settings have a higher priority than front-end configuration.
|
||||
|
||||
:::
|
||||
|
||||
## Disabling Emoticons
|
||||
|
||||
You can disable the emoticon feature by setting `emoticons` to `false`:
|
||||
|
||||
```js
|
||||
Artalk.init({
|
||||
emoticons: false,
|
||||
})
|
||||
```
|
||||
|
||||
## Loading Methods
|
||||
|
||||
### Dynamic Loading
|
||||
|
||||
Set the `emoticons` attribute to the URL of the emoticon data file. When the emoticon list is opened, Artalk will dynamically import it.
|
||||
|
||||
```js
|
||||
Artalk.init({
|
||||
emoticons: '<Emoticon Data File URL>',
|
||||
})
|
||||
```
|
||||
|
||||
Remote emoticon files support both Artalk and OwO formats, and support nested and mixed loading.
|
||||
|
||||
### Static Loading
|
||||
|
||||
Compared to dynamic importing, you can statically save the emoticon list object in the JS code of the page to avoid dynamic loading:
|
||||
|
||||
```js
|
||||
Artalk.init({
|
||||
emoticons: [
|
||||
{
|
||||
name: 'Emoji',
|
||||
type: 'emoticon', // Text type
|
||||
items: [
|
||||
{ key: 'Hi', val: '|´・ω・)ノ' },
|
||||
{ key: 'Happy', val: 'ヾ(≧∇≦*)ゝ' },
|
||||
//...
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Funny',
|
||||
type: 'image', // Image type
|
||||
items: [
|
||||
{
|
||||
key: 'Original Funny',
|
||||
val: '<Image URL>',
|
||||
},
|
||||
//...
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
### Mixed Loading
|
||||
|
||||
Artalk supports a mix of **dynamic** and **static** loading, for example:
|
||||
|
||||
```js
|
||||
Artalk.init({
|
||||
emoticons: [
|
||||
// Dynamic loading
|
||||
'https://raw.githubusercontent.com/DIYgod/OwO/master/demo/OwO.json', // OwO format emoticons
|
||||
'https://raw.githubusercontent.com/qwqcode/huaji/master/huaji.json',
|
||||
// Static loading
|
||||
{
|
||||
name: 'Emoticon Name',
|
||||
type: 'emoticon', // Text type
|
||||
items: [
|
||||
{ key: 'Go Master Ball', val: '(╯°A°)╯︵○○○' },
|
||||
//...
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
### Nested Import
|
||||
|
||||
Artalk supports **nested import** of other emoticon resources within remote emoticon resources, for example:
|
||||
|
||||
```js
|
||||
Artalk.init({
|
||||
emoticons: ['https://example.org/emoticons.json'],
|
||||
})
|
||||
```
|
||||
|
||||
Data in `emoticons.json`:
|
||||
|
||||
```json
|
||||
[
|
||||
"https://example.org/other-emoticons.json",
|
||||
//...
|
||||
{
|
||||
// Artalk format, OwO format
|
||||
//...
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
This allows you to efficiently manage and load multiple emoticon sets with minimal configuration.
|
||||
Loaded 100 of 923 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user