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 |
No files matched your search
@@ -1,70 +0,0 @@
|
||||
version: 2.1
|
||||
|
||||
orbs:
|
||||
node: circleci/node@6.1.0
|
||||
go: circleci/go@1.11.0
|
||||
codecov: codecov/codecov@4.1.0
|
||||
|
||||
executors:
|
||||
base:
|
||||
docker:
|
||||
- image: cimg/base:stable
|
||||
node:
|
||||
docker:
|
||||
- image: "cimg/node:20.17.0"
|
||||
go:
|
||||
docker:
|
||||
- image: cimg/go:1.22.7
|
||||
|
||||
parameters:
|
||||
trigger-app:
|
||||
type: boolean
|
||||
default: false
|
||||
run-them-all:
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
jobs:
|
||||
## Backend
|
||||
test_app:
|
||||
executor: go
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: "Print the Go version"
|
||||
command: go version
|
||||
- go/load-cache
|
||||
- go/mod-download
|
||||
- go/save-cache
|
||||
- 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
|
||||
@@ -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
|
||||
|
||||
+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,14 +6,15 @@ on:
|
||||
version:
|
||||
required: true
|
||||
type: string
|
||||
ref:
|
||||
required: true
|
||||
type: string
|
||||
dry_run:
|
||||
required: true
|
||||
type: boolean
|
||||
|
||||
env:
|
||||
GO_VERSION: 1.22.7
|
||||
DOCKER_IMG: ghcr.io/goreleaser/goreleaser-cross
|
||||
CACHE_DIR: /tmp/cache/docker-image
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build_publish:
|
||||
@@ -21,64 +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):/repo \
|
||||
-w /repo \
|
||||
--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@v6
|
||||
- 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
-200
@@ -1,5 +1,5 @@
|
||||
name: Build Nightly
|
||||
run-name: Build Nightly
|
||||
run-name: Build Nightly ${{ inputs.dry_run && '(🧪 Dry-Run)' || '' }}
|
||||
|
||||
on:
|
||||
schedule:
|
||||
@@ -7,355 +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@v4
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
with:
|
||||
version: 9.10.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
|
||||
# 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@v6
|
||||
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@v4
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
with:
|
||||
version: 9.10.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"]
|
||||
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@v4
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
with:
|
||||
version: 9.10.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 }}
|
||||
|
||||
+178
-26
@@ -21,67 +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 for `internal/config/version.go`
|
||||
perl -i -pe 's/const Version = ".*?"/const Version = "'${VERSION}'"/g' 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`
|
||||
perl -i -pe 's/"version": ".*?"/"version": "'${VERSION#v}'"/g' ui/artalk/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,23 @@ 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@v4
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
with:
|
||||
version: 9.10.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
|
||||
|
||||
@@ -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,21 +2,38 @@ 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:
|
||||
- if: matrix.os == 'windows-latest'
|
||||
@@ -26,17 +43,19 @@ jobs:
|
||||
git config --global core.eol lf
|
||||
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
version: 9.10.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
|
||||
@@ -44,13 +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 test: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
-26
@@ -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,9 +33,6 @@ 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 |
|
||||
@@ -44,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
|
||||
@@ -59,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
|
||||
@@ -76,9 +70,6 @@ builds:
|
||||
- darwin
|
||||
goarch:
|
||||
- amd64
|
||||
env:
|
||||
- CC=o64-clang
|
||||
- CXX=o64-clang++
|
||||
binary: "{{.ProjectName}}"
|
||||
main: ./main.go
|
||||
ldflags: *common_ldflags
|
||||
@@ -89,9 +80,6 @@ builds:
|
||||
- darwin
|
||||
goarch:
|
||||
- arm64
|
||||
env:
|
||||
- CC=oa64-clang
|
||||
- CXX=oa64-clang++
|
||||
binary: "{{.ProjectName}}"
|
||||
main: ./main.go
|
||||
ldflags: *common_ldflags
|
||||
@@ -106,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
|
||||
@@ -124,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
|
||||
@@ -134,7 +116,7 @@ builds:
|
||||
|
||||
archives:
|
||||
- id: artalk
|
||||
builds:
|
||||
ids:
|
||||
- linux-amd64
|
||||
- darwin-amd64
|
||||
- windows-amd64
|
||||
@@ -143,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*
|
||||
@@ -157,7 +141,7 @@ checksum:
|
||||
name_template: "checksums.txt"
|
||||
|
||||
snapshot:
|
||||
name_template: "{{.Version}}-SNAPSHOT-{{.ShortCommit}}"
|
||||
version_template: "{{.Version}}-SNAPSHOT-{{.ShortCommit}}"
|
||||
|
||||
# changelog:
|
||||
# sort: asc
|
||||
|
||||
@@ -9,6 +9,7 @@ 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
|
||||
|
||||
Vendored
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
// Enable the ESlint flat config support
|
||||
"eslint.experimental.useFlatConfig": true,
|
||||
// Enable the ESLint flat config support (ESLint 9+ / extension v3+)
|
||||
"eslint.useFlatConfig": true,
|
||||
|
||||
// Disable the default formatter, use eslint instead
|
||||
// "prettier.enable": false,
|
||||
|
||||
@@ -1,4 +1,103 @@
|
||||
|
||||
## [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
|
||||
|
||||
+51
-12
@@ -24,9 +24,9 @@ cd Artalk
|
||||
|
||||
To develop Artalk, both frontend and backend, install the following tools:
|
||||
|
||||
- [Node.js](https://nodejs.org/en/) (>= 20.17.0)
|
||||
- [PNPM](https://pnpm.io/) (>= 9.10.0)
|
||||
- [Go](https://golang.org/) (>= 1.22)
|
||||
- [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)
|
||||
|
||||
@@ -35,30 +35,25 @@ To develop Artalk, both frontend and backend, install the following tools:
|
||||
The development workflow consists of the following steps:
|
||||
|
||||
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`.
|
||||
|
||||
2. **Access Frontend for Development**:
|
||||
|
||||
- Open your browser and go to `http://localhost:5173` to perform your development and testing.
|
||||
|
||||
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`.
|
||||
|
||||
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.
|
||||
|
||||
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`.
|
||||
@@ -102,13 +97,11 @@ Artalk is a monorepo project, meaning all the source code resides in a single re
|
||||
### 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.
|
||||
@@ -124,7 +117,7 @@ If you are using the VSCode, you can use the `F5` key (or RUN button) to start d
|
||||
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-fronted` to build the frontend, as the backend app embeds the frontend code.
|
||||
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.
|
||||
@@ -161,6 +154,52 @@ The compiled JavaScript and CSS files will be located in the `/public` directory
|
||||
|
||||
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
|
||||
@@ -178,7 +217,7 @@ Replace `TAG` with the desired tag name (e.g., `latest`).
|
||||
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_FRONTEND_BUILD=true -t artalk:latest .
|
||||
docker build --build-arg SKIP_UI_BUILD=true -t artalk:latest .
|
||||
```
|
||||
|
||||
For more details, refer to the `Dockerfile`.
|
||||
|
||||
+6
-6
@@ -1,11 +1,11 @@
|
||||
### build Artalk
|
||||
FROM golang:1.22.7-alpine3.20 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.10.0 \
|
||||
&& npm install -g pnpm@10.33.2 \
|
||||
;fi
|
||||
|
||||
RUN set -ex \
|
||||
@@ -35,12 +35,12 @@ 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.20
|
||||
FROM alpine:3.24
|
||||
|
||||
# we set the timezone `Asia/Shanghai` by default, you can be modified
|
||||
# by `docker build --build-arg="TZ=Other_Timezone ..."`
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
PKG_NAME := github.com/artalkjs/artalk/v2
|
||||
BIN_NAME := ./bin/artalk
|
||||
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 := 1
|
||||
export CGO_ENABLED := 0
|
||||
|
||||
all: install build
|
||||
|
||||
@@ -17,12 +26,15 @@ run: all
|
||||
|
||||
build:
|
||||
go build \
|
||||
-ldflags "-s -w" \
|
||||
-ldflags "$(LDFLAGS)" \
|
||||
-o $(BIN_NAME) \
|
||||
$(PKG_NAME)
|
||||
|
||||
build-frontend:
|
||||
./scripts/build-frontend.sh
|
||||
@./scripts/build-frontend.sh
|
||||
|
||||
pack-frontend:
|
||||
@./scripts/pack-frontend.sh
|
||||
|
||||
build-debug:
|
||||
@echo "Building Artalk for debugging..."
|
||||
@@ -73,7 +85,7 @@ docker-build:
|
||||
docker-push:
|
||||
./scripts/docker-build.sh --push
|
||||
|
||||
.PHONY: all install run build build-frontend build-debug dev \
|
||||
.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 \
|
||||
|
||||
+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,12 +7,13 @@
|
||||
[](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)
|
||||
|
||||
[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)
|
||||
[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 is an intuitive yet feature-rich comment system, ready for immediate deployment into any blog, website, or web application.
|
||||
|
||||
|
||||
+2
-1
@@ -7,12 +7,13 @@
|
||||
[](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)
|
||||
[官方网站](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 应用。
|
||||
|
||||
|
||||
+74
-8
@@ -4,11 +4,15 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"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"
|
||||
)
|
||||
@@ -64,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 {
|
||||
@@ -167,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)
|
||||
}
|
||||
@@ -220,6 +220,9 @@ auth:
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
domain: ""
|
||||
sso:
|
||||
enabled: false
|
||||
issuer: ""
|
||||
frontend:
|
||||
placeholder: ""
|
||||
noComment: ""
|
||||
|
||||
@@ -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: []
|
||||
@@ -10,7 +10,7 @@ app_key: ""
|
||||
# Debug mode
|
||||
debug: false
|
||||
|
||||
# Language (follow Unicode BCP 47) ["en", "zh-CN", "zh-TW", "ja", "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
|
||||
|
||||
@@ -386,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:
|
||||
@@ -401,7 +408,7 @@ 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
|
||||
|
||||
@@ -394,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:
|
||||
|
||||
@@ -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: []
|
||||
@@ -1,2 +1,3 @@
|
||||
.vitepress/dist/**
|
||||
.vitepress/cache/**
|
||||
.vitepress/.temp/**
|
||||
@@ -17,7 +17,7 @@ export const en = defineConfig({
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Core Guide',
|
||||
text: 'Basic Usage',
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: 'Sidebar', link: '/en/guide/frontend/sidebar.md' },
|
||||
@@ -32,7 +32,7 @@ export const en = defineConfig({
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Advanced Guide',
|
||||
text: 'Extensions',
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: 'Emoticons', link: '/en/guide/frontend/emoticons.md' },
|
||||
@@ -40,22 +40,23 @@ export const en = defineConfig({
|
||||
{ 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: 'Configuration Documentation',
|
||||
text: 'Configurations',
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: 'Environment Variables', link: '/en/guide/env.md' },
|
||||
{ text: 'Configuration File', link: '/en/guide/backend/config.md' },
|
||||
{ text: 'Interface Configuration', link: '/en/guide/frontend/config.md' },
|
||||
{ text: 'UI Configuration', link: '/en/guide/frontend/config.md' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Deployment Instructions',
|
||||
text: 'Deployment details',
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: 'Daemon Process', link: '/en/guide/backend/daemon.md' },
|
||||
@@ -82,7 +83,8 @@ export const en = defineConfig({
|
||||
{ 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 Event', link: '/en/develop/event.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' },
|
||||
{
|
||||
@@ -100,7 +102,7 @@ export const en = defineConfig({
|
||||
link: '/en/guide/intro',
|
||||
},
|
||||
{
|
||||
text: 'Deployment',
|
||||
text: 'Installation',
|
||||
link: '/en/guide/deploy',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { defineConfig } from 'vitepress'
|
||||
import iterator from 'markdown-it-for-inline'
|
||||
import {
|
||||
GitChangelog,
|
||||
GitChangelogMarkdownSection,
|
||||
} from '@nolebase/vitepress-plugin-git-changelog/vite'
|
||||
import * as Version from '../../code/ArtalkVersion.json'
|
||||
|
||||
export const shared = defineConfig({
|
||||
@@ -24,12 +27,18 @@ export const shared = defineConfig({
|
||||
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))
|
||||
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)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
},
|
||||
@@ -62,5 +71,28 @@ export const shared = defineConfig({
|
||||
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(),
|
||||
],
|
||||
},
|
||||
})
|
||||
@@ -40,6 +40,7 @@ export const zh = defineConfig({
|
||||
{ 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' },
|
||||
@@ -85,7 +86,8 @@ export const zh = defineConfig({
|
||||
{ text: '置入博客', link: '/zh/develop/import-blog.md' },
|
||||
{ text: '置入框架', link: '/zh/develop/import-framework.md' },
|
||||
{ text: '前端 API', link: '/zh/develop/fe-api.md' },
|
||||
{ text: '前端 Event', link: '/zh/develop/event.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' },
|
||||
{
|
||||
|
||||
@@ -10,7 +10,7 @@ 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/dist/Artalk.css'
|
||||
import 'artalk/Artalk.css'
|
||||
|
||||
const el = ref<HTMLElement | null>(null)
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
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 '@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.9.1"
|
||||
"latest": "2.10.0"
|
||||
}
|
||||
@@ -1,5 +1,9 @@
|
||||
# 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:
|
||||
|
||||
@@ -17,7 +17,7 @@ pnpm add artalk
|
||||
import Artalk from 'artalk'
|
||||
import { onMounted, onBeforeUnmount, ref } from 'vue'
|
||||
|
||||
import 'artalk/dist/Artalk.css'
|
||||
import 'artalk/Artalk.css'
|
||||
|
||||
const el = ref<HTMLElement>()
|
||||
|
||||
@@ -50,7 +50,7 @@ import Artalk from 'artalk'
|
||||
import { onMounted, onBeforeUnmount, ref, watch, nextTick } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import 'artalk/dist/Artalk.css'
|
||||
import 'artalk/Artalk.css'
|
||||
|
||||
const el = ref<HTMLElement>()
|
||||
const router = useRouter()
|
||||
@@ -98,32 +98,37 @@ onBeforeUnmount(() => {
|
||||
::: code-group
|
||||
|
||||
```tsx [React Hooks]
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import { useCallback, useRef } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import 'artalk/dist/Artalk.css'
|
||||
import 'artalk/Artalk.css'
|
||||
import Artalk from 'artalk'
|
||||
|
||||
const ArtalkComment = () => {
|
||||
const container = useRef<HTMLDivElement>(null)
|
||||
const location = useLocation()
|
||||
const artalk = useRef<Artalk>()
|
||||
const { pathname } = useLocation()
|
||||
const artalk = useRef<Artalk | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
artalk.current = Artalk.init({
|
||||
el: container.current!,
|
||||
pageKey: location.pathname,
|
||||
pageTitle: document.title,
|
||||
server: 'http://localhost:8080',
|
||||
site: 'Artalk Blog',
|
||||
// ...
|
||||
})
|
||||
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 () => {
|
||||
artalk.current?.destroy()
|
||||
}
|
||||
}, [location.pathname])
|
||||
|
||||
return <div ref={container}></div>
|
||||
return <div ref={handleContainerInit}></div>
|
||||
}
|
||||
|
||||
export default ArtalkComment
|
||||
@@ -131,7 +136,7 @@ export default ArtalkComment
|
||||
|
||||
```jsx [React Class]
|
||||
import React, { createRef } from 'react'
|
||||
import 'artalk/dist/Artalk.css'
|
||||
import 'artalk/Artalk.css'
|
||||
import Artalk from 'artalk'
|
||||
|
||||
export default class ArtalkComponent extends React.Component {
|
||||
@@ -166,7 +171,7 @@ export default class ArtalkComponent extends React.Component {
|
||||
```tsx
|
||||
import { onCleanup, onMount } from 'solid-js'
|
||||
import Artalk from 'artalk'
|
||||
import 'artalk/dist/Artalk.css'
|
||||
import 'artalk/Artalk.css'
|
||||
|
||||
const ArtalkComment = () => {
|
||||
let el: HTMLDivElement
|
||||
@@ -198,7 +203,7 @@ const ArtalkComment = () => {
|
||||
import Artalk from 'artalk'
|
||||
import { onMount, onDestroy } from 'svelte'
|
||||
|
||||
import 'artalk/dist/Artalk.css'
|
||||
import 'artalk/Artalk.css'
|
||||
|
||||
let el
|
||||
let artalk
|
||||
|
||||
@@ -12,8 +12,9 @@ Thank you for your interest and support of Artalk. We deeply understand that the
|
||||
|
||||
- [Embed in Blog](./import-blog.md)
|
||||
- [Embed in Framework](./import-framework.md)
|
||||
- [Frontend API Documentation](./fe-api.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**
|
||||
|
||||
@@ -283,6 +283,16 @@ 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
|
||||
|
||||
@@ -109,6 +109,7 @@ moderator:
|
||||
- **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.
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
## Update Changes
|
||||
|
||||
For significant changes, such as updates that require modifications to existing configurations, please refer to the version update log for relevant prompts: [CHANGELOG.md](https://github.com/ArtalkJS/Artalk/blob/master/CHANGELOG.md).
|
||||
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
|
||||
|
||||
|
||||
+116
-31
@@ -47,11 +47,11 @@ Import the embedded client JS and CSS resources from the Artalk server to your w
|
||||
<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)
|
||||
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
|
||||
site: 'Artalk Blog', // Your site name
|
||||
})
|
||||
</script>
|
||||
```
|
||||
@@ -79,7 +79,7 @@ Advanced operations:
|
||||
- [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 Module
|
||||
## Go Install
|
||||
|
||||
If you have the Golang toolchain installed, you can run the following commands to compile and install the latest version of Artalk:
|
||||
|
||||
@@ -97,6 +97,14 @@ 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
|
||||
@@ -117,6 +125,8 @@ 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:
|
||||
@@ -161,26 +171,50 @@ docker-compose exec artalk bash # Enter the container
|
||||
|
||||
More information: [Docker](./backend/docker.md) / [Environment Variables](./env.md)
|
||||
|
||||
## Client Projects (Node.js)
|
||||
## Client
|
||||
|
||||
Install Artalk via npm:
|
||||
If you have a frontend or Node.js web project, this following guide will help you integrate the Artalk **Client** into your web project.
|
||||
|
||||
```bash
|
||||
### 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/dist/Artalk.css'
|
||||
import 'artalk/Artalk.css'
|
||||
import Artalk from 'artalk'
|
||||
|
||||
const artalk = 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
|
||||
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
|
||||
})
|
||||
```
|
||||
|
||||
@@ -189,38 +223,89 @@ 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)
|
||||
- [Client Configuration](./frontend/config.md)
|
||||
- [UI (Client) Configuration](./frontend/config.md)
|
||||
|
||||
## Client CDN Resources
|
||||
### 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 current latest version number of the Artalk client is: **:ArtalkVersion:**
|
||||
The latest Artalk client version is: `:ArtalkVersion:`
|
||||
|
||||
To upgrade the client, replace the numeric part of the version number in the URL.
|
||||
:::
|
||||
|
||||
The Artalk server had embedded the client resources. Ensure version compatibility when using public CDN resources.
|
||||
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>
|
||||
```
|
||||
https://cdnjs.cloudflare.com/ajax/libs/artalk/:ArtalkVersion:/Artalk.js
|
||||
```
|
||||
|
||||
::: details View More
|
||||
|
||||
**UNPKG**
|
||||
|
||||
> <https://unpkg.com/artalk@:ArtalkVersion:/dist/Artalk.js>
|
||||
>
|
||||
> <https://unpkg.com/artalk@:ArtalkVersion:/dist/Artalk.css>
|
||||
```
|
||||
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>
|
||||
```
|
||||
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
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
|
||||
+21
-16
@@ -25,6 +25,7 @@ services:
|
||||
- ./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
|
||||
@@ -36,6 +37,8 @@ services:
|
||||
- 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:
|
||||
|
||||
```
|
||||
@@ -66,14 +69,14 @@ ATK_TRUSTED_DOMAINS_0="https://a.com"
|
||||
<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"]`) | locale (Language) |
|
||||
| **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) |
|
||||
@@ -84,7 +87,7 @@ ATK_TRUSTED_DOMAINS_0="https://a.com"
|
||||
|
||||
## 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) |
|
||||
@@ -116,7 +119,7 @@ ATK_TRUSTED_DOMAINS_0="https://a.com"
|
||||
|
||||
## 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) |
|
||||
@@ -164,6 +167,8 @@ ATK_TRUSTED_DOMAINS_0="https://a.com"
|
||||
| **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) |
|
||||
@@ -179,7 +184,7 @@ ATK_TRUSTED_DOMAINS_0="https://a.com"
|
||||
|
||||
## 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) |
|
||||
@@ -194,7 +199,7 @@ ATK_TRUSTED_DOMAINS_0="https://a.com"
|
||||
|
||||
## 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) |
|
||||
@@ -213,7 +218,7 @@ ATK_TRUSTED_DOMAINS_0="https://a.com"
|
||||
|
||||
## 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) |
|
||||
@@ -230,7 +235,7 @@ ATK_TRUSTED_DOMAINS_0="https://a.com"
|
||||
|
||||
## 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) |
|
||||
@@ -249,7 +254,7 @@ ATK_TRUSTED_DOMAINS_0="https://a.com"
|
||||
|
||||
## 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) |
|
||||
@@ -276,12 +281,12 @@ ATK_TRUSTED_DOMAINS_0="https://a.com"
|
||||
| **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` | Dislike button | frontend.voteDown (UI Settings > Dislike 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) |
|
||||
@@ -289,7 +294,7 @@ ATK_TRUSTED_DOMAINS_0="https://a.com"
|
||||
|
||||
## 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) |
|
||||
@@ -302,7 +307,7 @@ ATK_TRUSTED_DOMAINS_0="https://a.com"
|
||||
|
||||
## 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) |
|
||||
@@ -310,7 +315,7 @@ ATK_TRUSTED_DOMAINS_0="https://a.com"
|
||||
|
||||
## 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) |
|
||||
@@ -332,7 +337,7 @@ ATK_TRUSTED_DOMAINS_0="https://a.com"
|
||||
|
||||
## 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) |
|
||||
|
||||
@@ -8,6 +8,10 @@ ArtalkLite is a simplified version of Artalk, with the following differences com
|
||||
|
||||
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@2/dist/ArtalkLite.js
|
||||
>
|
||||
> https://unpkg.com/artalk@2/dist/ArtalkLite.css
|
||||
```
|
||||
https://unpkg.com/artalk@:ArtalkVersion:/dist/ArtalkLite.js
|
||||
```
|
||||
|
||||
```
|
||||
https://unpkg.com/artalk@:ArtalkVersion:/dist/ArtalkLite.css
|
||||
```
|
||||
@@ -68,6 +68,62 @@ If only one login method is enabled, such as GitHub login, the GitHub authorizat
|
||||
|
||||
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).
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
# Interface Configuration
|
||||
# UI Configuration
|
||||
|
||||
You can modify the interface configuration in the "[Dashboard](./sidebar.md)" without changing the front-end code. Additionally, configuration files and environment variables are also supported.
|
||||
UI configuration also known as Client (Frontend) configuration, is the configuration of the Artalk interface.
|
||||
|
||||
The priority of interface configuration is as follows:
|
||||
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:
|
||||
|
||||
```
|
||||
Environment Variables > Configuration File = Dashboard > Front-end Code
|
||||
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 front-end page, pass parameters to configure the interface. Below are all the available configuration options.
|
||||
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({
|
||||
@@ -81,14 +85,20 @@ Deploy the backend and ensure that the backend address is accessible from the fr
|
||||
|
||||
Artalk supports unified management of multiple sites, this item is used for site isolation.
|
||||
|
||||
### useBackendConf
|
||||
### preferRemoteConf
|
||||
|
||||
**Refer to Backend Configuration**
|
||||
**Prefer Remote Configuration**
|
||||
|
||||
- Type: `Boolean`
|
||||
- Default: `true` (enabled by default)
|
||||
- Default: `false` (disabled by default)
|
||||
|
||||
You can define the front-end configuration in the backend configuration file, so that the front-end configuration always refers to the backend.
|
||||
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)
|
||||
|
||||
@@ -278,6 +288,17 @@ When querying comment counts and page views, Artalk's statistical component uses
|
||||
|
||||
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**
|
||||
@@ -517,4 +538,4 @@ Allow height limit area to scroll.
|
||||
- Type: `Boolean`
|
||||
- Default: `true`
|
||||
|
||||
Display a warning prompt when the front-end and back-end versions are incompatible.
|
||||
Display a warning prompt when the frontend and backend versions are incompatible.
|
||||
@@ -26,11 +26,12 @@ We welcome PRs to help translate Artalk into multiple languages and contribute t
|
||||
|
||||
| Translation Content | Language File Directory | Template File |
|
||||
| ------------------- | ---------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
|
||||
| Backend Program | [/i18n/[LANG].yml](https://github.com/ArtalkJS/Artalk/tree/master/i18n) | [zh-CN.yml](https://github.com/ArtalkJS/Artalk/blob/master/i18n/zh-CN.yml) |
|
||||
| Frontend Interface | [/ui/artalk/src/i18n/[LANG].ts](https://github.com/ArtalkJS/Artalk/tree/master/ui/artalk/src/i18n) | [zh-CN.ts](https://github.com/ArtalkJS/Artalk/blob/master/ui/artalk/src/i18n/zh-CN.ts) |
|
||||
| Backend Interface | [/ui/artalk-sidebar/src/i18n/[LANG].ts](https://github.com/ArtalkJS/Artalk/tree/master/ui/artalk-sidebar/src/i18n) | [zh-CN.ts](https://github.com/ArtalkJS/Artalk/blob/master/ui/artalk-sidebar/src/i18n/zh-CN.ts) |
|
||||
| Configuration Files | [/conf/artalk.example.[LANG].yml](https://github.com/ArtalkJS/Artalk/tree/master/conf) | [artalk.example.zh-CN.yml](https://github.com/ArtalkJS/Artalk/blob/master/conf/artalk.example.zh-CN.yml) |
|
||||
| Documentation | [/docs/[LANG]/\*\*/\*.md](https://github.com/ArtalkJS/Artalk/tree/master/docs) | [/docs/zh/\*\*/\*.md](https://github.com/ArtalkJS/Artalk/tree/master/docs) |
|
||||
| Backend Program | [/i18n/[LANG].yml](https://github.com/ArtalkJS/Artalk/tree/master/i18n) | [en.yml](https://github.com/ArtalkJS/Artalk/blob/master/i18n/en.yml) |
|
||||
| Frontend Interface | [/ui/artalk/src/i18n/[LANG].ts](https://github.com/ArtalkJS/Artalk/tree/master/ui/artalk/src/i18n) | [en.ts](https://github.com/ArtalkJS/Artalk/blob/master/ui/artalk/src/i18n/en.ts) |
|
||||
| Backend Interface | [/ui/artalk-sidebar/src/i18n/[LANG].ts](https://github.com/ArtalkJS/Artalk/tree/master/ui/artalk-sidebar/src/i18n) | [i18n-en.ts](https://github.com/ArtalkJS/Artalk/blob/master/ui/artalk-sidebar/src/i18n-en.ts) |
|
||||
| Configuration Files | [/conf/artalk.example.[LANG].yml](https://github.com/ArtalkJS/Artalk/tree/master/conf) | [artalk.example.yml](https://github.com/ArtalkJS/Artalk/blob/master/conf/artalk.example.yml) |
|
||||
| Documentation | [/docs/docs/[LANG]/\*\*/\*.md](https://github.com/ArtalkJS/Artalk/tree/master/docs/docs) | [en/\*\*/\*.md](https://github.com/ArtalkJS/Artalk/tree/master/docs/docs/en) |
|
||||
| Official website home page | [/docs/landing/src/i18n/[LANG].ts](https://github.com/ArtalkJS/Artalk/tree/master/docs/landing/src/i18n) | [en.ts](https://github.com/ArtalkJS/Artalk/blob/master/docs/landing/src/i18n/en.ts) |
|
||||
|
||||
For more details, refer to: [Development Documentation](../../develop/index.md) / [CONTRIBUTING.md](https://github.com/ArtalkJS/Artalk/blob/master/CONTRIBUTING.md#translation)
|
||||
|
||||
@@ -58,7 +59,7 @@ Artalk.init({
|
||||
|
||||
### Custom Frontend Locale Content
|
||||
|
||||
You can pass an Object type to the frontend and customize the locale content according to the keys in the [template file](https://github.com/ArtalkJS/Artalk/blob/master/ui/artalk/src/i18n/zh-CN.ts).
|
||||
You can pass an Object type to the frontend and customize the locale content according to the keys in the [template file](https://github.com/ArtalkJS/Artalk/blob/master/ui/artalk/src/i18n/en.ts).
|
||||
|
||||
```js
|
||||
Artalk.init({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Image Lazy Loading
|
||||
|
||||
Artalk supports adding lazy loading functionality to images within comments to reduce page load times. This feature is disabled by default. You can find the "Image Lazy Load" configuration item (frontend.imgLazyLoad) in the Dashboard under "Settings - Interface Configuration":
|
||||
Artalk supports adding lazy loading functionality to images within comments to reduce page load times. This feature is disabled by default. You can find the "Image Lazy Load" configuration item (frontend.imgLazyLoad) in the Dashboard under "Settings - UI Configuration":
|
||||
|
||||
| Configuration Value | Description |
|
||||
| --- | --- |
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
# Voting Feature
|
||||
|
||||
Artalk supports voting on comments and pages, allowing users to click the "Up" or "Down" buttons to cast their votes. The comment list can be sorted based on the number of votes, which helps users better assess the quality of the comments.
|
||||
|
||||
## Comment Voting
|
||||
|
||||
The comment voting feature is enabled by default, allowing users to vote on comments.
|
||||
|
||||
You can find the "UI Settings" in the Dashboard to modify the "Vote Button" option to enable or disable comment voting.
|
||||
|
||||
Environment variable for the vote button:
|
||||
|
||||
```
|
||||
ATK_FRONTEND_VOTE=1
|
||||
```
|
||||
|
||||
Configuration file for the vote button:
|
||||
|
||||
```yaml
|
||||
frontend:
|
||||
vote: true
|
||||
```
|
||||
|
||||
### Down Button
|
||||
|
||||
By default, Artalk does not display the Down button. You can find the "UI Settings" in the Dashboard to modify the "Vote Down Button" option to enable it.
|
||||
|
||||
Environment variable for the Down button:
|
||||
|
||||
```
|
||||
ATK_FRONTEND_VOTE_DOWN=1
|
||||
```
|
||||
|
||||
Configuration file for the Down button:
|
||||
|
||||
```yaml
|
||||
frontend:
|
||||
voteDown: true
|
||||
```
|
||||
|
||||
## Page Voting
|
||||
|
||||
Artalk supports voting on pages. To enable page voting, you need to add elements in the page to display the voting buttons, which Artalk will automatically initialize on load:
|
||||
|
||||
```html
|
||||
<div>
|
||||
<span class="artalk-page-vote-up"></span>
|
||||
<span class="artalk-page-vote-down"></span>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Voted Status Styling
|
||||
|
||||
When users click the page voting button, the element will be given an `active` class to indicate that the user has voted. For example:
|
||||
|
||||
```html
|
||||
<span class="artalk-page-vote-up active"></span>
|
||||
```
|
||||
|
||||
You can customize the voted button style using CSS:
|
||||
|
||||
```css
|
||||
.artalk-page-vote-up.active {
|
||||
color: #0083ff;
|
||||
}
|
||||
```
|
||||
|
||||
The default added class name is `active`, but it can be changed using `pageVote.activeClass` in the client configuration:
|
||||
|
||||
```js
|
||||
Artalk.init({
|
||||
pageVote: {
|
||||
activeClass: 'active',
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Custom Element Selectors
|
||||
|
||||
By default, Artalk searches for `.artalk-page-vote-up` and `.artalk-page-vote-down` as the voting button elements.
|
||||
|
||||
You can customize the voting button selectors by modifying the `pageVote.upBtnEl` and `pageVote.downBtnEl` configuration in the client:
|
||||
|
||||
```js
|
||||
Artalk.init({
|
||||
pageVote: {
|
||||
upBtnEl: '.artalk-page-vote-up',
|
||||
downBtnEl: '.artalk-page-vote-down',
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Further Customizing Page Voting Buttons
|
||||
|
||||
If the voting buttons do not contain any child elements, Artalk will output the text "Up (n)" into the element.
|
||||
|
||||
If you want to output the vote count into a separate element, you can add a tag inside the button, for example:
|
||||
|
||||
```html
|
||||
<div class="artalk-page-vote">
|
||||
<span class="artalk-page-vote-up">
|
||||
👍 (<span class="artalk-page-vote-up-count"></span>)
|
||||
</span>
|
||||
<span class="artalk-page-vote-down">
|
||||
👎 (<span class="artalk-page-vote-down-count"></span>)
|
||||
</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
To further customize, you can replace the text with icons or add other styles.
|
||||
|
||||
The default selectors for vote counts are `.artalk-page-vote-up-count` and `.artalk-page-vote-down-count`.
|
||||
|
||||
You can modify `pageVote.upCountEl` and `pageVote.downCountEl` to customize the vote count output elements:
|
||||
|
||||
```js
|
||||
Artalk.init({
|
||||
pageVote: {
|
||||
upCountEl: '.artalk-page-vote-up-count',
|
||||
downCountEl: '.artalk-page-vote-down-count',
|
||||
},
|
||||
})
|
||||
```
|
||||
@@ -0,0 +1,314 @@
|
||||
# Artalk v2.10.0 Release Notes and Migration Guide
|
||||
|
||||
v2.10.0 is the next stable release after v2.9.1. It adds page voting, SSO token exchange, MySQL Cloud SQL TLS, and CGO-free SQLite builds, while reorganizing the UI client's lifecycle, configuration, and type system.
|
||||
|
||||
::: warning Read before upgrading
|
||||
v2.10.0 contains intentional API and behavior changes. Upgrade the server and UI client together. Themes, plugins, direct HTTP API consumers, and projects importing Artalk TypeScript types should complete the migrations below first.
|
||||
:::
|
||||
|
||||
## Upgrade checklist
|
||||
|
||||
- Back up the database, configuration files, and custom frontend assets.
|
||||
- For binary deployments, verify the working directory and every relative file path; pass `-w` and an absolute `-c` path to retain the previous behavior.
|
||||
- Upgrade source-build and packaging environments to Go 1.26.5.
|
||||
- To change the application timezone, set `timezone` or `ATK_TIMEZONE` and fully restart Artalk.
|
||||
- Upgrade the server and UI client together to avoid mixing the old and new Vote APIs.
|
||||
- Review local-versus-remote UI configuration priority; local configuration is now preferred by default.
|
||||
- Search for `useBackendConf`, `remoteConfModifier`, two-argument `ctx.inject()`, assignments to `ctx.conf` / `ctx.$root`, and `artalk.update(...).xxx()` chains.
|
||||
- Rebuild themes and plugins that import Artalk types or internal UI services.
|
||||
- Use a modern Node.js toolchain; the Artalk repository development baseline is Node.js 22.19 or later.
|
||||
|
||||
## Highlights
|
||||
|
||||
### UI and interaction
|
||||
|
||||
- Added [page voting](../frontend/voting.md#page-voting) and the ability to query the current visitor's vote state ([#983](https://github.com/ArtalkJS/Artalk/issues/983), [#998](https://github.com/ArtalkJS/Artalk/issues/998)).
|
||||
- Added `preferRemoteConf` to make local-versus-remote UI configuration precedence explicit ([#1000](https://github.com/ArtalkJS/Artalk/issues/1000), [#1011](https://github.com/ArtalkJS/Artalk/issues/1011)).
|
||||
- Introduced a dependency injection container and reorganized UI modules, lifecycle handling, and public types ([#1006](https://github.com/ArtalkJS/Artalk/issues/1006), [#1007](https://github.com/ArtalkJS/Artalk/issues/1007)).
|
||||
- Added the Artalk UI ESLint plugin and updated it to v1.0.2 ([#995](https://github.com/ArtalkJS/Artalk/issues/995), [#1010](https://github.com/ArtalkJS/Artalk/issues/1010)).
|
||||
- Extended User-Agent detection for OpenHarmony and more browsers ([#1087](https://github.com/ArtalkJS/Artalk/issues/1087)).
|
||||
- Added `ugc` to comment-link `rel` attributes ([#1120](https://github.com/ArtalkJS/Artalk/issues/1120)).
|
||||
|
||||
### Server and API
|
||||
|
||||
- Added `POST /api/v2/sso/exchange` to exchange an access token from a trusted OIDC identity provider for an Artalk session token ([#1134](https://github.com/ArtalkJS/Artalk/issues/1134)). It is disabled by default; configure only a trusted HTTPS issuer in production, and require `/userinfo` to return `email_verified: true`.
|
||||
- Unified the Vote API around a target type, target ID, and choice, and added a vote-status endpoint.
|
||||
- Improved configuration-file and data-directory discovery while preserving the located configuration before changing the data directory ([#992](https://github.com/ArtalkJS/Artalk/issues/992)).
|
||||
- Added mutual TLS configuration for MySQL on Google Cloud SQL ([#1104](https://github.com/ArtalkJS/Artalk/issues/1104)).
|
||||
|
||||
### Builds and runtime
|
||||
|
||||
- SQLite now uses a pure-Go driver. Stable builds no longer require CGO or a separate `GORELEASER_CROSS` toolchain, so standard Go tooling can produce cross-platform artifacts directly.
|
||||
- Upgraded the Go toolchain to v1.26.5 and updated related dependencies, including the maintained v5 JWT implementation used for login sessions. Existing HS256 token claims and signatures remain wire-compatible, so this server upgrade does not itself force users to sign in again.
|
||||
- Updated the official container build and runtime bases to Alpine 3.24. The Go builder remains pinned to v1.26.5, and container data-directory and configuration paths are unchanged.
|
||||
- Updated the frontend and documentation toolchain, including Vite 8.1, Marked 18.0.7, and KaTeX 0.17. The `@artalk/plugin-katex` peer range still accepts KaTeX 0.16.45 through 0.17.x, so existing themes are not forced to replace KaTeX as part of this upgrade.
|
||||
- Added scripts for fetching the plugin registry and checking versions already published to npm.
|
||||
- npm packages use modern conditional exports; legacy Node.js 10 module resolution is no longer supported.
|
||||
|
||||
### Documentation and localization
|
||||
|
||||
- Added Turkish (`tr-TR`) and a Traditional Chinese configuration template.
|
||||
- Added a Japanese README.
|
||||
- Redesigned the landing page, added translations, and improved responsive font sizing.
|
||||
- Integrated Git Changelog into the documentation site.
|
||||
|
||||
## Security and reliability
|
||||
|
||||
- The backend JWT parser now uses `github.com/golang-jwt/jwt/v5`, explicitly allows only HS256, continues to validate expiration, and has regression coverage for legacy token format, signing algorithms, and expiration behavior.
|
||||
- The Markdown HTML sanitizer now uses the maintained `DOMPurify` instead of the unpatched, ReDoS-affected `insane` parser. Artalk retains its previous tag, attribute, class, style, and URL-scheme allowlists and adds malicious-markup and ReDoS regression tests.
|
||||
- The frontend lockfile pins patched transitive versions of `fast-uri`, `brace-expansion`, `js-yaml`, `undici`, Vite, and other build and development tooling; the complete workspace dependency audit reports no known vulnerabilities.
|
||||
- Google Cloud SQL TLS now loads the server CA, client certificate, and client key together, verifies the server certificate and hostname, and requires TLS 1.2 or later. All three certificate paths must be configured together so partial TLS configuration cannot be silently ignored.
|
||||
- The Vote API rejects malformed, non-positive, missing, or unknown targets. Vote records and target counters are updated in one database transaction and roll back together on failure.
|
||||
- Plugin URLs are filtered by the server: relative and trusted URLs may be sent to the client, while untrusted absolute URLs are removed.
|
||||
- Plugin-registry downloads require HTTPS and approved hosts and have bounded redirects, response size, and request time.
|
||||
- The npm version check no longer interpolates package names into a shell command and reports network or parsing errors as failures.
|
||||
- Process timezone initialization now happens once instead of mutating global timezone state during concurrent requests.
|
||||
|
||||
## Breaking changes and migration
|
||||
|
||||
### 1. Local UI configuration is preferred by default
|
||||
|
||||
In v2.9.x, remote configuration overrode values passed to `Artalk.init()`. v2.10.0 prefers local configuration by default:
|
||||
|
||||
```text
|
||||
Artalk.init() local config > environment variables > Dashboard = configuration file
|
||||
```
|
||||
|
||||
No additional option is needed for the new default:
|
||||
|
||||
```js
|
||||
Artalk.init({
|
||||
server: 'https://comments.example.com',
|
||||
site: 'My Site',
|
||||
locale: 'en', // the local value is retained
|
||||
})
|
||||
```
|
||||
|
||||
Explicitly enable remote-first behavior when required:
|
||||
|
||||
```js
|
||||
Artalk.init({
|
||||
server: 'https://comments.example.com',
|
||||
site: 'My Site',
|
||||
preferRemoteConf: true,
|
||||
})
|
||||
```
|
||||
|
||||
`useBackendConf` is deprecated and is always treated as `true`. Setting it to `false` no longer prevents the client from reading configuration that the backend must provide; it is not a replacement for `preferRemoteConf`.
|
||||
|
||||
### 2. `remoteConfModifier` has been removed
|
||||
|
||||
`remoteConfModifier` is no longer part of the public configuration. Migrate according to its purpose:
|
||||
|
||||
- Put fixed overrides directly in `Artalk.init()`; local values win by default.
|
||||
- Use `preferRemoteConf: true` when Dashboard values should win.
|
||||
- Use `fetchCommentsOnInit` to control the initial comment request.
|
||||
- Listen for `created` and call `update()` for post-initialization changes.
|
||||
|
||||
```js
|
||||
const artalk = Artalk.init({
|
||||
server: 'https://comments.example.com',
|
||||
site: 'My Site',
|
||||
fetchCommentsOnInit: false,
|
||||
})
|
||||
|
||||
artalk.on('created', () => {
|
||||
artalk.update({ locale: 'en' })
|
||||
artalk.reload()
|
||||
})
|
||||
```
|
||||
|
||||
There is no equivalent hook for arbitrarily modifying remote configuration before plugin initialization.
|
||||
|
||||
### 3. Configuration types distinguish resolved values from inputs
|
||||
|
||||
`Config` represents a fully resolved configuration. `ConfigPartial` is the recursively optional input accepted by `Artalk.init()`, `update()`, and plugin configuration updates.
|
||||
|
||||
```ts
|
||||
import type { Config, ConfigPartial } from 'artalk'
|
||||
|
||||
function createComments(conf: ConfigPartial) {
|
||||
return Artalk.init(conf)
|
||||
}
|
||||
|
||||
function readResolvedConfig(conf: Config) {
|
||||
console.log(conf.server, conf.pagination.pageSize)
|
||||
}
|
||||
```
|
||||
|
||||
Code that used `Partial<ArtalkConfig>` for initialization should use `ConfigPartial`. `ArtalkConfig` remains importable but now represents the complete configuration.
|
||||
|
||||
`ConfigPartial` preserves functions, DOM elements, `Date`, `RegExp`, and array-element semantics instead of incorrectly expanding those values into recursively partial object fields.
|
||||
|
||||
### 4. DI registration and resolution are separate
|
||||
|
||||
The old two-argument `ctx.inject(key, value)` registration form has been removed. Register with `provide()` and resolve with `inject()`:
|
||||
|
||||
```ts
|
||||
// v2.9.x
|
||||
ctx.inject('example', createExample())
|
||||
const example = ctx.get('example')
|
||||
|
||||
// v2.10.0
|
||||
ctx.provide('example', () => createExample())
|
||||
const example = ctx.inject('example')
|
||||
```
|
||||
|
||||
`provide()` supports dependency lists and `singleton` / `transient` lifecycles. Custom TypeScript service keys should extend `Services` through declaration merging.
|
||||
|
||||
### 5. `ctx.conf` and `ctx.$root` cannot be replaced
|
||||
|
||||
Both remain readable, but assigning a new object or element throws an error with migration guidance:
|
||||
|
||||
```ts
|
||||
// Incorrect
|
||||
ctx.conf = nextConf
|
||||
ctx.$root = nextElement
|
||||
|
||||
// Correct
|
||||
ctx.updateConf(nextConf)
|
||||
const conf = ctx.getConf()
|
||||
const root = ctx.getEl()
|
||||
```
|
||||
|
||||
To change the root element, destroy the old instance and create a new instance with a different `el`.
|
||||
|
||||
### 6. `Artalk.update()` returns `void`
|
||||
|
||||
`update()` no longer returns the Artalk instance. Split chained calls:
|
||||
|
||||
```js
|
||||
// v2.9.x
|
||||
artalk.update({ pageKey: '/next' }).reload()
|
||||
|
||||
// v2.10.0
|
||||
artalk.update({ pageKey: '/next' })
|
||||
artalk.reload()
|
||||
```
|
||||
|
||||
### 7. Plugin initialization and the `created` event
|
||||
|
||||
Built-in, local, and network plugins now initialize after local and remote configuration have been merged. Network plugins no longer observe a temporary configuration from only one source.
|
||||
|
||||
`created` fires after every plugin has initialized, followed by `mounted`. Plugins that depended on the old order should perform pre-initialization work in the plugin function and use a `created` listener for work requiring the complete plugin environment.
|
||||
|
||||
### 8. Vote HTTP API and client methods
|
||||
|
||||
Old vote endpoint:
|
||||
|
||||
```http
|
||||
POST /api/v2/votes/comment_up/123
|
||||
```
|
||||
|
||||
New endpoints:
|
||||
|
||||
```http
|
||||
GET /api/v2/votes/comment/123
|
||||
POST /api/v2/votes/comment/123/up
|
||||
```
|
||||
|
||||
`target_name` must be `comment` or `page`; `choice` must be `up` or `down`.
|
||||
|
||||
The generated client methods changed accordingly:
|
||||
|
||||
```ts
|
||||
await api.votes.getVote('comment', 123)
|
||||
await api.votes.createVote('comment', 123, 'up', { name, email })
|
||||
```
|
||||
|
||||
The old endpoint and `votes.vote()` method do not have compatibility adapters.
|
||||
|
||||
### 9. SQLite now uses a pure-Go driver
|
||||
|
||||
The server SQLite driver has changed from the CGO implementation based on `mattn/go-sqlite3` to the pure-Go implementation provided by `github.com/libtnb/sqlite`. Stable builds no longer require a local C compiler, `CGO_ENABLED=1`, or the cross-compilation toolchain selected through `GORELEASER_CROSS_VERSION`.
|
||||
|
||||
The SQLite on-disk format has not changed. Existing databases configured through `db.file` can be used directly, so standard deployments do not require a database configuration change. Back up the database before upgrading as usual; Artalk will run its normal schema migrations on the first v2.10.0 startup.
|
||||
|
||||
Projects that build Artalk from source or embed it as a Go module should note the following:
|
||||
|
||||
- C compiler, CGO, and Goreleaser Cross configuration used only for SQLite can be removed.
|
||||
- Do not rely on `mattn/go-sqlite3`-specific build tags, DSN parameters, C APIs, or dynamic extension-loading behavior.
|
||||
- Re-run migration, concurrent read/write, and backup/restore tests when using custom SQLite compile options, extensions, or native backup integrations.
|
||||
|
||||
This change does not affect deployments using MySQL, PostgreSQL, or MSSQL.
|
||||
|
||||
### 10. Default configuration and data directories follow XDG rules
|
||||
|
||||
Without `--workdir` / `-w`, Artalk no longer always uses the invocation directory as its working directory. v2.10.0 uses the following discovery order:
|
||||
|
||||
- Configuration files: the invocation directory, its `data` subdirectory, `$XDG_CONFIG_HOME/artalk`, then `/etc/artalk`.
|
||||
- Data directory: keep the previous working directory when `./data` exists; otherwise use `$XDG_DATA_HOME/artalk`, then `/var/lib/artalk`, or create `$XDG_DATA_HOME/artalk` if neither exists.
|
||||
|
||||
The configuration file is located before an implicit data-directory change. Relative database, log, and upload paths in that configuration—as well as relative file arguments passed to commands such as `import`, `export`, and `gen`—are still resolved from the final working directory.
|
||||
|
||||
Existing deployments with a `./data` directory keep the previous behavior. The official Docker image also passes an explicit working directory and configuration path, so it is not affected.
|
||||
|
||||
To preserve the previous path semantics for a binary deployment, specify both paths explicitly:
|
||||
|
||||
```bash
|
||||
artalk -w /srv/artalk -c /srv/artalk/artalk.yml server
|
||||
```
|
||||
|
||||
Before upgrading, verify every relative path including `db.file`, `log.filename`, `img_upload.path`, IP-region databases, and keyword files to avoid creating an empty database or files in the newly selected data directory.
|
||||
|
||||
### 11. Source builds require Go 1.26.5
|
||||
|
||||
The minimum version in `go.mod` has increased from Go 1.22.7 to Go 1.26.5. This affects `go install`, source builds, distribution packaging, and downstream projects that include Artalk in a Go build.
|
||||
|
||||
Users of official binaries and Docker images do not need to install Go. Source-build environments must use Go 1.26.5. CI systems that disable automatic Go Toolchain downloads or operate offline must install the matching toolchain in advance.
|
||||
|
||||
### 12. The application timezone is fixed at process initialization
|
||||
|
||||
The `timezone` configuration and `ATK_TIMEZONE` environment variable remain available for an Artalk application timezone that is independent of the host system or container `TZ` setting:
|
||||
|
||||
```yaml
|
||||
timezone: America/New_York
|
||||
```
|
||||
|
||||
```bash
|
||||
ATK_TIMEZONE=America/New_York artalk server
|
||||
```
|
||||
|
||||
Timezone is process-global state. v2.10.0 sets it once during Artalk process initialization and does not support changing it dynamically at runtime. After changing it in the configuration file, environment, or Dashboard, fully restart the Artalk process or container; using Dashboard “Apply settings” alone is not sufficient to switch timezone.
|
||||
|
||||
### 13. `marked` 18 and modern package entry points
|
||||
|
||||
The default Artalk build continues to include Markdown support. ArtalkLite does not bundle `marked`; downstream consumers must provide a compatible `marked@^18.0.3`.
|
||||
|
||||
Artalk's use of `Marked`, `Renderer`, `parse()`, `setOptions()`, `use()`, and tokenizer extensions does not rely on v18-only APIs. Upstream parsing details changed between v14 and v18, however, so projects with custom renderers, tokenizers, or output snapshots should rerun their Markdown tests.
|
||||
|
||||
Node.js 10 and toolchains that rely on legacy package resolution are no longer supported. Artalk repository development and builds use Node.js 22.19 or later.
|
||||
|
||||
## Retained compatibility entry points
|
||||
|
||||
The following entry points remain in v2.10.0 to give themes and third-party plugins time to migrate, but are marked `deprecated`:
|
||||
|
||||
| Previous entry point | Preferred entry point |
|
||||
| --- | --- |
|
||||
| `ContextApi` | `Context` |
|
||||
| `DataManagerApi` | `DataManager` |
|
||||
| `EditorApi` | `Editor` |
|
||||
| `ctx.data` | `ctx.getData()` or `ctx.inject('data')` |
|
||||
| `ctx.user` | `ctx.getUser()` or `ctx.inject('user')` |
|
||||
| `ctx.editor` | `ctx.inject('editor')` |
|
||||
| `ctx.list` | `ctx.inject('list')` |
|
||||
| `ctx.layerManager` | `ctx.inject('layers')` |
|
||||
| `ctx.checkerLauncher` | `ctx.inject('checkers')` |
|
||||
| `ctx.sidebarLayer` | `ctx.inject('sidebar')` |
|
||||
| `ctx.editorPlugs` | `ctx.inject('editorPlugs')` |
|
||||
| `editor.$el` | `editor.getEl()` |
|
||||
| `editor.getPlugs()` | `editor.getPlugins()` |
|
||||
| `editor.setReply()` | `editor.setReplyComment()` |
|
||||
|
||||
These aliases are transitional. New code should use the preferred entry points directly. See [TypeDoc](https://artalk.js.org/typedoc/) for the complete public type surface.
|
||||
|
||||
## Other changes
|
||||
|
||||
- The comment reply button now uses a component implementation.
|
||||
- Sidebar and Auth UI have been updated for the new client types.
|
||||
- Added npm-published-version checking and plugin-registry generation workflows.
|
||||
- Pinned the Swagger TypeScript client generator and updated its command so development environments do not receive incompatible versions through `npx`.
|
||||
- Updated the GitHub Actions runtimes used for artifacts, caches, Docker, CodeQL, and Codecov while preserving workflow inputs and release-artifact layout.
|
||||
- Updated frontend, Go, and documentation dependencies and removed obsolete test projects and CI configuration.
|
||||
|
||||
See the complete commit comparison at [`v2.9.1...v2.10.0`](https://github.com/ArtalkJS/Artalk/compare/v2.9.1...v2.10.0).
|
||||
@@ -136,13 +136,16 @@ Go to the [Disqus backend](https://disqus.com/admin), find "Moderation - Export"
|
||||
|
||||
### Commento
|
||||
|
||||
You can export the data file in JSON format from the Commento backend, then use the [conversion tool](#conversion-tool) for conversion.
|
||||
Go to the Commento backend, click on "General - Export" in sequence, export the data file in JSON format, and then use the [conversion tool](#conversion-tool) for conversion.
|
||||
|
||||
[Image, to be supplemented...]
|
||||

|
||||
|
||||
### Twikoo
|
||||
[Twikoo](https://twikoo.js.org/) can obtain the JSON file through the "Export" section in the administrative panel where it is located, and then convert it using the [conversion tool](#conversion-tool).
|
||||
|
||||
[Twikoo](https://twikoo.js.org/) is a comment system developed based on Tencent Cloud. Go to the [Tencent Cloud backend](https://console.cloud.tencent.com/tcb) to export the comment data file in JSON format, then use the [conversion tool](#conversion-tool) for conversion.
|
||||

|
||||
|
||||
In addition, for Twikoo tencent cloud deployment, you can travel to [Tencent Cloud Background](https://console.cloud.tencent.com/tcb) export comment JSON format data.
|
||||
|
||||
<img src="/images/transfer/tencent-tcb.png" style="max-width: 480px;">
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
@@ -15,15 +15,13 @@
|
||||
"serve:docs": "vitepress serve"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@artalk/plugin-katex": "workspace:^",
|
||||
"@artalk/plugin-lightbox": "workspace:^",
|
||||
"@nolebase/vitepress-plugin-git-changelog": "^2.18.2",
|
||||
"artalk": "workspace:^",
|
||||
"markdown-it-for-inline": "^2.0.1",
|
||||
"vitepress": "1.3.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@artalk/plugin-katex": "^0.2.3",
|
||||
"@artalk/plugin-lightbox": "^0.2.3",
|
||||
"katex": "^0.16.11",
|
||||
"lightgallery": "^2.7.2",
|
||||
"vue": "^3.5.6"
|
||||
"katex": "^0.17.0",
|
||||
"lightgallery": "^2.9.0",
|
||||
"vitepress": "1.6.4",
|
||||
"vue": "^3.5.40"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,17 @@
|
||||
/guide/frontend/import-blog.html /develop/import-blog.html 301
|
||||
/guide/frontend/import-framework.html /develop/import-framework.html 301
|
||||
/guide/frontend/install.html /guide/deploy.html 301
|
||||
/guide/backend/data.html /guide/transfer.html 301
|
||||
/guide/backend/build.html /develop/contributing.html 301
|
||||
/guide/backend/fe-control.html /guide/frontend/config.html 301
|
||||
/guide/backend/install.html /guide/deploy.html 301
|
||||
/develop/api.html /http-api.html 301
|
||||
/develop/plugs.md /develop/plugin.html 301
|
||||
|
||||
# Language redirect
|
||||
/guide/* /zh/guide/:splat 302 Language=zh
|
||||
/guide/* /en/guide/:splat 302 Language=en
|
||||
/guide/* /en/guide/:splat 302
|
||||
|
||||
/develop/* /zh/develop/:splat 302 Language=zh
|
||||
/develop/* /en/develop/:splat 302 Language=en
|
||||
/develop/* /en/develop/:splat 302
|
||||
@@ -1,8 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"composite": true,
|
||||
"types": ["vite/client"],
|
||||
"resolveJsonModule": true
|
||||
"resolveJsonModule": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler"
|
||||
},
|
||||
"include": ["**/.vitepress/**/*"]
|
||||
"include": ["**/.vitepress/**/*.ts", "**/.vitepress/**/*.mts", "**/.vitepress/**/*.vue"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -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 | - | 评论者邮箱 SHA256 加密值 |
|
||||
| `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>
|
||||
@@ -12,7 +12,7 @@
|
||||
git clone https://github.com/ArtalkJS/Artalk.git
|
||||
```
|
||||
|
||||
建议您先 fork 仓库,然后克隆 fork 后的仓库。
|
||||
建议你先 fork 仓库,然后克隆 fork 后的仓库。
|
||||
|
||||
导航到目录:
|
||||
|
||||
@@ -24,9 +24,9 @@ cd Artalk
|
||||
|
||||
为了开发 Artalk 的前端和后端,请安装以下工具:
|
||||
|
||||
- [Node.js](https://nodejs.org/en/) (>= 20.17.0)
|
||||
- [PNPM](https://pnpm.io/) (>= 9.10.0)
|
||||
- [Go](https://golang.org/) (>= 1.22)
|
||||
- [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)(可选)
|
||||
- [Docker Compose](https://docs.docker.com/compose/) (>= 1.29.0)(可选)
|
||||
|
||||
@@ -53,13 +53,13 @@ cd Artalk
|
||||
|
||||
4. **构建前端代码**:
|
||||
|
||||
- 当您对前端代码进行更改后,使用 `pnpm build:all` 构建完整的前端程序。
|
||||
- 当你对前端代码进行更改后,使用 `pnpm build:all` 构建完整的前端程序。
|
||||
- JavaScript 和 CSS 代码将在 `ui/artalk/dist` 目录中找到。
|
||||
- 前端代码由 GitHub Actions 自动部署到 NPM。
|
||||
|
||||
5. **构建后端代码**:
|
||||
|
||||
- 当您对后端代码进行更改后,注意后端程序会嵌入前端代码。
|
||||
- 当你对后端代码进行更改后,注意后端程序会嵌入前端代码。
|
||||
- 在构建后端之前,运行 `make build-frontend`(它运行 `scripts/build-frontend.sh`)。这将把嵌入的前端主程序和侧边栏前端程序放入 `/public` 目录。
|
||||
- 然后,使用 `make build` 构建后端程序。
|
||||
|
||||
@@ -69,7 +69,7 @@ cd Artalk
|
||||
请参考 [Artalk 插件开发指南](https://artalk.js.org/develop/plugin.html)。
|
||||
|
||||
- **Makefile**:
|
||||
您可以探索 `Makefile` 中的过程和命令。
|
||||
你可以探索 `Makefile` 中的过程和命令。
|
||||
|
||||
- **GitHub 上的自动化 CI**:
|
||||
GitHub 上的自动化 CI 流水线位于 `.github/workflows` 目录中,负责构建前端、后端和 Docker 镜像,发布 Docker 镜像到 Docker Hub,处理 npm 包发布,并创建 GitHub Releases,同时测试前端和后端。
|
||||
@@ -116,7 +116,7 @@ Artalk 是一个 monorepo 项目,这意味着所有源代码都在一个仓库
|
||||
|
||||
`make dev` 命令会带有调试符号构建后端,这方便使用 GDB 进行调试。
|
||||
|
||||
如果您使用 VSCode,可以使用 `F5` 键(或运行按钮)直接启动调试后端程序。
|
||||
如果你使用 VSCode,可以使用 `F5` 键(或运行按钮)直接启动调试后端程序。
|
||||
|
||||
### 构建后端
|
||||
|
||||
@@ -161,6 +161,52 @@ make build-frontend
|
||||
|
||||
更多详细信息,请参考 `scripts/build-frontend.sh` 脚本。
|
||||
|
||||
### 发布前端
|
||||
|
||||
核心的 Artalk 客户端代码通过 GitHub Actions 自动部署到 NPM。部署过程在 `.github/workflows/build-ui.yml` 文件中定义。
|
||||
|
||||
如果你在 monorepo 中编写 Artalk 插件,它不会被自动部署。你需要手动发布插件。要发布插件,请首先确保在 `package.json` 文件中更新版本号。然后,运行以下命令:
|
||||
|
||||
```sh
|
||||
pnpm publish --access public
|
||||
```
|
||||
|
||||
#### 检查已发布版本
|
||||
|
||||
`pnpm check:publish` 脚本旨在验证项目中的所有包是否在 npm 上发布了最新版本。它会自动跳过私有包,并允许使用 `-F` 选项进行筛选以检查特定包。
|
||||
|
||||
检查所有包:
|
||||
|
||||
```sh
|
||||
pnpm check:publish
|
||||
```
|
||||
|
||||
这将启动检查存储库中所有公共包的过程,以确保它们在 npm 上是最新的。
|
||||
|
||||
### 开发和调试 `@artalk/plugin-kit` 和 `eslint-plugin-artalk`
|
||||
|
||||
这个 monorepo 默认会从 NPM 安装最新版本的 `@artalk/plugin-kit` 和 `eslint-plugin-artalk` 发布包。要开发和调试这些包,请在仓库的根目录下运行以下命令:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
执行这些命令后,`eslint-plugin-artalk` 和 `@artalk/plugin-kit` 包将链接到本地开发环境中的 monorepo 工作空间。你可以在 `ui/eslint-plugin-artalk` 和 `ui/plugin-kit` 目录中修改源代码,这些更改会自动反映在 monorepo 工作空间中。如果你希望取消链接这些包,请运行以下命令:
|
||||
|
||||
```sh
|
||||
pnpm unlink eslint-plugin-artalk
|
||||
pnpm unlink @artalk/plugin-kit
|
||||
|
||||
pnpm uninstall --global eslint-plugin-artalk
|
||||
pnpm uninstall --global @artalk/plugin-kit
|
||||
```
|
||||
|
||||
更多详情请参考 [pnpm link 文档](https://pnpm.io/cli/link)。
|
||||
|
||||
## Docker 开发
|
||||
|
||||
### 构建 Docker 镜像
|
||||
@@ -175,10 +221,10 @@ docker build -t artalk:TAG .
|
||||
|
||||
### 跳过前端构建
|
||||
|
||||
如果您已经在 Docker 容器外部构建了前端,可以跳过容器内的前端构建过程以加快构建速度。使用以下命令:
|
||||
如果你已经在 Docker 容器外部构建了前端,可以跳过容器内的前端构建过程以加快构建速度。使用以下命令:
|
||||
|
||||
```sh
|
||||
docker build --build-arg SKIP_FRONTEND_BUILD=true -t artalk:latest .
|
||||
docker build --build-arg SKIP_UI_BUILD=true -t artalk:latest .
|
||||
```
|
||||
|
||||
更多详细信息,请参考 `Dockerfile`。
|
||||
@@ -227,7 +273,7 @@ docker build --build-arg SKIP_FRONTEND_BUILD=true -t artalk:latest .
|
||||
|
||||
在 `internal/config/config.go` 文件中,有一个名为 `Config` 的结构体定义,该结构体用于将 yml 文件解析为 Go 结构体。该结构体包含更精确的类型定义。如果需要添加、修改或删除配置项,必须同时修改 `/conf` 目录中的配置文件模板和 `Config` 结构体。
|
||||
|
||||
当您修改配置文件后,请运行以下命令以更新配置数据缓存:
|
||||
当你修改配置文件后,请运行以下命令以更新配置数据缓存:
|
||||
|
||||
```sh
|
||||
make update-conf
|
||||
@@ -281,18 +327,18 @@ Swagger 定义位于后端代码的 `/server` 目录中。修改 Swagger 定义
|
||||
|
||||
## 翻译 (i18n)
|
||||
|
||||
如果您编写了新功能或进行了修复/重构,请使用以下命令通过解析源代码中的 `i18n.T` 函数调用来增量生成翻译文件:
|
||||
如果你编写了新功能或进行了修复/重构,请使用以下命令通过解析源代码中的 `i18n.T` 函数调用来增量生成翻译文件:
|
||||
|
||||
```sh
|
||||
make update-i18n
|
||||
```
|
||||
|
||||
如果您不是程序员,但希望帮助改进翻译,可以直接编辑 `/i18n` 目录中的翻译文件,然后提交 pull 请求。
|
||||
如果你不是程序员,但希望帮助改进翻译,可以直接编辑 `/i18n` 目录中的翻译文件,然后提交 pull 请求。
|
||||
|
||||
## 结尾
|
||||
|
||||
感谢您耐心阅读本指南,并感谢您对 Artalk 的兴趣和支持。我们深知,一个开源项目的成功依赖于每一位开发者的贡献。无论是通过代码、文档、测试还是反馈,您的参与是推动项目持续进步的动力。我们诚挚邀请您加入我们,共同改进和增强 Artalk。
|
||||
感谢你耐心阅读本指南,并感谢你对 Artalk 的兴趣和支持。我们深知,一个开源项目的成功依赖于每一位开发者的贡献。无论是通过代码、文档、测试还是反馈,你的参与是推动项目持续进步的动力。我们诚挚邀请你加入我们,共同改进和增强 Artalk。
|
||||
|
||||
通过合作,我们可以创建一个更加健壮和多功能的平台,造福整个社区。您的独特见解和专业知识是无价的,只有共同努力,我们才能实现显著的进步。无论您是经验丰富的开发者,还是刚刚起步的新手,我们的项目中总有您可以贡献的一席之地。
|
||||
通过合作,我们可以创建一个更加健壮和多功能的平台,造福整个社区。你的独特见解和专业知识是无价的,只有共同努力,我们才能实现显著的进步。无论你是经验丰富的开发者,还是刚刚起步的新手,我们的项目中总有你可以贡献的一席之地。
|
||||
|
||||
如果您有任何问题或需要帮助,请随时在我们的 GitHub 页面上的 issue 部分提问:<https://github.com/ArtalkJS/Artalk/issues>。再次感谢您的支持,我们期待与您合作。
|
||||
如果你有任何问题或需要帮助,请随时在我们的 GitHub 页面上的 issue 部分提问:<https://github.com/ArtalkJS/Artalk/issues>。再次感谢你的支持,我们期待与你合作。
|
||||
@@ -1,5 +1,9 @@
|
||||
# Frontend API
|
||||
|
||||
以下列出了一些主要的 Artalk 客户端 API,用于在前端代码中操作 Artalk 实例。
|
||||
|
||||
更多详细 API 信息可以通过 [TypeDoc 文档](https://artalk.js.org/typedoc/) 浏览。
|
||||
|
||||
## 安装依赖
|
||||
|
||||
用你喜欢的包管理器安装 Artalk:
|
||||
|
||||
@@ -17,7 +17,7 @@ pnpm add artalk
|
||||
import Artalk from 'artalk'
|
||||
import { onMounted, onBeforeUnmount, ref } from 'vue'
|
||||
|
||||
import 'artalk/dist/Artalk.css'
|
||||
import 'artalk/Artalk.css'
|
||||
|
||||
const el = ref<HTMLElement>()
|
||||
|
||||
@@ -50,7 +50,7 @@ import Artalk from 'artalk'
|
||||
import { onMounted, onBeforeUnmount, ref, watch, nextTick } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import 'artalk/dist/Artalk.css'
|
||||
import 'artalk/Artalk.css'
|
||||
|
||||
const el = ref<HTMLElement>()
|
||||
const router = useRouter()
|
||||
@@ -98,32 +98,37 @@ onBeforeUnmount(() => {
|
||||
::: code-group
|
||||
|
||||
```tsx [React Hooks]
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import { useCallback, useRef } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import 'artalk/dist/Artalk.css'
|
||||
import 'artalk/Artalk.css'
|
||||
import Artalk from 'artalk'
|
||||
|
||||
const ArtalkComment = () => {
|
||||
const container = useRef<HTMLDivElement>(null)
|
||||
const location = useLocation()
|
||||
const artalk = useRef<Artalk>()
|
||||
const { pathname } = useLocation()
|
||||
const artalk = useRef<Artalk | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
artalk.current = Artalk.init({
|
||||
el: container.current!,
|
||||
pageKey: location.pathname,
|
||||
pageTitle: document.title,
|
||||
server: 'http://localhost:8080',
|
||||
site: 'Artalk 的博客',
|
||||
// ...
|
||||
})
|
||||
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 的博客',
|
||||
// ...
|
||||
})
|
||||
},
|
||||
[pathname],
|
||||
)
|
||||
|
||||
return () => {
|
||||
artalk.current?.destroy()
|
||||
}
|
||||
}, [container, location.pathname])
|
||||
|
||||
return <div ref={container}></div>
|
||||
return <div ref={handleContainerInit}></div>
|
||||
}
|
||||
|
||||
export default ArtalkComment
|
||||
@@ -133,7 +138,7 @@ export default ArtalkComment
|
||||
|
||||
```jsx [React Class]
|
||||
import React, { createRef } from 'react'
|
||||
import 'artalk/dist/Artalk.css'
|
||||
import 'artalk/Artalk.css'
|
||||
import Artalk from 'artalk'
|
||||
|
||||
export default class Artalk extends React.Component {
|
||||
@@ -168,7 +173,7 @@ export default class Artalk extends React.Component {
|
||||
```tsx
|
||||
import { onCleanup, onMount } from 'solid-js'
|
||||
import Artalk from 'artalk'
|
||||
import 'artalk/dist/Artalk.css'
|
||||
import 'artalk/Artalk.css'
|
||||
|
||||
const ArtalkComment = () => {
|
||||
let el: HTMLDivElement
|
||||
@@ -200,7 +205,7 @@ const ArtalkComment = () => {
|
||||
import Artalk from 'artalk'
|
||||
import { onMount, onDestroy } from 'svelte'
|
||||
|
||||
import 'artalk/dist/Artalk.css'
|
||||
import 'artalk/Artalk.css'
|
||||
|
||||
let el
|
||||
let artalk
|
||||
|
||||
@@ -12,8 +12,9 @@
|
||||
|
||||
- [置入博客](./import-blog.md)
|
||||
- [置入框架](./import-framework.md)
|
||||
- [前端 API 文档](./fe-api.md)
|
||||
- [前端 Event](./event.md)
|
||||
- [前端 API](./fe-api.md)
|
||||
- [前端 Events](./event.md)
|
||||
- [前端 Types](https://artalk.js.org/typedoc/)
|
||||
- [前端配置](../guide/frontend/config.md)
|
||||
|
||||
**插件开发**
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
# 插件开发
|
||||
|
||||
本页面内容已移动至 <https://artalk.js.org/develop/plugin.html>。
|
||||
|
||||
<script setup lang="ts">
|
||||
if (!import.meta.env.SSR) window.location = 'https://artalk.js.org/develop/plugin.html';
|
||||
</script>
|
||||
@@ -0,0 +1,347 @@
|
||||
# 2026-05 依赖升级说明
|
||||
|
||||
本文档记录了本次大规模依赖升级所涉及的全部变更,涵盖前端工具链、ESLint 生态、TypeScript 编译配置、构建工具迁移及相关文档与 CI 的同步更新。
|
||||
|
||||
---
|
||||
|
||||
## 一、前端依赖升级
|
||||
|
||||
### 核心工具链
|
||||
|
||||
| 包 | 旧版本 | 新版本 |
|
||||
|----|--------|--------|
|
||||
| `typescript` | 5.6.3 | 6.0.3 |
|
||||
| `vite` | 5.4.9 | 8.0.10 |
|
||||
| `vitest` | ^2.1.3 | ^4.1.5 |
|
||||
| `rollup` | 4.24.0 | 4.60.3 |
|
||||
| `sass` | 1.80.3 | 1.99.0 |
|
||||
| `terser` | 5.36.0 | 5.46.2 |
|
||||
| `postcss` | 8.4.47 | 8.5.14 |
|
||||
| `autoprefixer` | 10.4.20 | 10.5.0 |
|
||||
| `tsx` | ^4.19.1 | ^4.21.0 |
|
||||
| `cross-env` | ^7.0.3 | ^10.1.0 |
|
||||
| `prettier` | 3.3.3 | 3.8.3 |
|
||||
|
||||
### ESLint 生态
|
||||
|
||||
| 包 | 旧版本 | 新版本 |
|
||||
|----|--------|--------|
|
||||
| `eslint` | ^9.13.0 | ^10.3.0 |
|
||||
| `typescript-eslint` | ^8.10.0 | ^8.59.1 |
|
||||
| `@typescript-eslint/eslint-plugin` | 8.10.0 | 8.59.1 |
|
||||
| `eslint-plugin-vue` | ^9.29.0 | ^10.9.0 |
|
||||
| `eslint-plugin-import-x` | ^4.3.1 | ^4.16.2 |
|
||||
| `eslint-plugin-compat` | ^6.0.1 | ^7.0.2 |
|
||||
| `eslint-plugin-react-hooks` | 5.1.0-beta | 7.1.1 |
|
||||
| `eslint-config-prettier` | 9.1.0 | 10.1.8 |
|
||||
| `eslint-import-resolver-typescript` | 3.6.3 | 4.4.4 |
|
||||
| `vue-eslint-parser` | ^9.4.3 | ^10.4.0 |
|
||||
| `globals` | ^15.11.0 | ^17.6.0 |
|
||||
|
||||
### Vite 插件
|
||||
|
||||
| 包 | 旧版本 | 新版本 |
|
||||
|----|--------|--------|
|
||||
| `vite-plugin-dts` | 4.2.4 | 5.0.0 |
|
||||
| `vite-plugin-checker` | 0.8.0 | 0.13.0 |
|
||||
|
||||
### UI 子包
|
||||
|
||||
| 包 | 子包 | 旧版本 | 新版本 |
|
||||
|----|------|--------|--------|
|
||||
| `marked` | `ui/artalk` (dep + peer) | ^14.1.3 | ^18.0.3 |
|
||||
| `vue` | artalk-sidebar, docs | ^3.5.12 | ^3.5.33 |
|
||||
| `vue-router` | artalk-sidebar | ^4.4.5 | ^5.0.6 |
|
||||
| `vue-i18n` | artalk-sidebar | ^10.0.4 | ^11.4.0 |
|
||||
| `pinia` | artalk-sidebar | ^2.2.4 | ^3.0.4 |
|
||||
| `vue-tsc` | artalk-sidebar | ^2.1.6 | ^3.2.8 |
|
||||
| `@vitejs/plugin-vue` | artalk-sidebar | ^5.1.4 | ^6.0.6 |
|
||||
| `unplugin-auto-import` | artalk-sidebar | ^0.18.3 | ^21.0.0 |
|
||||
| `unplugin-vue-components` | artalk-sidebar | ^0.27.4 | ^32.0.0 |
|
||||
| `react` / `react-dom` | docs/landing | ^18.3.1 | ^19.2.5 |
|
||||
| `@vitejs/plugin-react-swc` | docs/landing | ^3.7.1 | ^4.3.0 |
|
||||
| `react-i18next` | docs/landing | ^15.0.3 | ^17.0.6 |
|
||||
| `i18next` | docs/landing | ^23.16.0 | ^26.0.8 |
|
||||
| `katex` | plugin-katex | ^0.16.11 | ^0.16.45 |
|
||||
| `lightgallery` | plugin-lightbox | ^2.7.2 | ^2.9.0 |
|
||||
| `vitepress` | docs/docs | 1.4.1 | 1.6.4 |
|
||||
| `@redocly/cli` | docs/swagger | 1.25.7 | 2.30.3 |
|
||||
| `solid-js` | plugin-auth | ^1.9.2 | ^1.9.12 |
|
||||
|
||||
---
|
||||
|
||||
## 二、包管理器与运行时要求更新
|
||||
|
||||
**`package.json` 根配置:**
|
||||
|
||||
```jsonc
|
||||
// 旧
|
||||
"packageManager": "pnpm@9.10.0"
|
||||
|
||||
// 新
|
||||
"packageManager": "pnpm@10.33.2",
|
||||
"engines": {
|
||||
"node": ">=22.19.0",
|
||||
"pnpm": ">=10.33.2"
|
||||
}
|
||||
```
|
||||
|
||||
新增 `pnpm.peerDependencyRules.allowedVersions` 以抑制 peer dep 版本警告:
|
||||
|
||||
```json
|
||||
"pnpm": {
|
||||
"peerDependencyRules": {
|
||||
"allowedVersions": {
|
||||
"eslint-plugin-import>eslint": "10",
|
||||
"eslint-plugin-react>eslint": "10",
|
||||
"artalk>marked": "18"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
新增 `prepare` 脚本,`pnpm install` 后自动构建本地 workspace 包:
|
||||
|
||||
```json
|
||||
"prepare": "pnpm build:plugin-kit && pnpm build:eslint-plugin"
|
||||
```
|
||||
|
||||
本地 workspace 引用方式也由 npm 发布版改为 workspace 直引:
|
||||
|
||||
```jsonc
|
||||
// 旧
|
||||
"@artalk/plugin-kit": "^1.0.7",
|
||||
"eslint-plugin-artalk": "^1.0.2",
|
||||
|
||||
// 新
|
||||
"@artalk/plugin-kit": "workspace:*",
|
||||
"eslint-plugin-artalk": "workspace:*",
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、构建工具迁移:tsup → tsdown
|
||||
|
||||
`eslint-plugin-artalk` 和 `@artalk/plugin-kit` 从 `tsup`(基于 esbuild)迁移到 `tsdown`(基于 rolldown)。
|
||||
|
||||
### 迁移对比
|
||||
|
||||
| 项目 | tsup | tsdown |
|
||||
|------|------|--------|
|
||||
| 构建器内核 | esbuild | rolldown |
|
||||
| `?raw` 文件插件 | `esbuild-plugin-raw`,API:`setup/onResolve/onLoad` | 内联 rolldown 插件,API:`resolveId/load` |
|
||||
| externals | `external: [...]` | `deps.neverBundle: [...]` |
|
||||
| 产物扩展名 | `.js` / `.d.ts` | `.mjs`/`.cjs` / `.d.mts`/`.d.cts` |
|
||||
| 插件类型 | `esbuild.Plugin` | `TsdownPlugin`(从 `tsdown` 直接导入) |
|
||||
|
||||
### 产物入口更新
|
||||
|
||||
由于产物扩展名变化,`package.json` 的 `main`/`types`/`exports` 字段同步更新:
|
||||
|
||||
```jsonc
|
||||
// 旧
|
||||
"main": "dist/main.js",
|
||||
"types": "dist/main.d.ts",
|
||||
"exports": {
|
||||
"require": { "types": "./dist/main.d.cjs" },
|
||||
"default": { "types": "./dist/main.d.ts", "default": "./dist/main.js" }
|
||||
}
|
||||
|
||||
// 新
|
||||
"main": "dist/main.cjs",
|
||||
"types": "dist/main.d.cts",
|
||||
"exports": {
|
||||
"require": { "types": "./dist/main.d.cts", "default": "./dist/main.cjs" },
|
||||
"default": { "types": "./dist/main.d.mts", "default": "./dist/main.mjs" }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、eslint-plugin-artalk 重构
|
||||
|
||||
### 问题背景
|
||||
|
||||
`eslint@10` 内置 `@eslint/core@1.2.1`,而 `typescript-eslint@8` 依赖 `@eslint/core@0.7.0`,两者的 `RuleContext` API 不兼容,导致 `TSESLint.RuleModule` 无法直接赋值给 `eslint` 的 `Rule.RuleModule`。
|
||||
|
||||
### 解决方案
|
||||
|
||||
完全弃用 `ESLintUtils.RuleCreator`,改为直接使用 `eslint` 原生的 `Rule.RuleModule` 类型构造规则,无需类型桥接或强制转换。
|
||||
|
||||
**`src/helper.ts`(重写):**
|
||||
|
||||
```ts
|
||||
import type { Rule } from 'eslint'
|
||||
|
||||
export function createRule(def: {
|
||||
name: string
|
||||
meta: Rule.RuleMetaData
|
||||
defaultOptions?: unknown[]
|
||||
create: Rule.RuleModule['create']
|
||||
}): Rule.RuleModule {
|
||||
return {
|
||||
meta: { ...def.meta, docs: { ...def.meta.docs, url: `${docsBaseUrl}${def.name}` } },
|
||||
create: def.create,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**`src/main.ts`(重写):**
|
||||
|
||||
改为 default-only 导出,避免 rolldown MIXED_EXPORTS 警告:
|
||||
|
||||
```ts
|
||||
import type { ESLint } from 'eslint'
|
||||
|
||||
const plugin: ESLint.Plugin & { configs: typeof configs } = {
|
||||
rules: { 'artalk-plugin': artalkPlugin },
|
||||
configs,
|
||||
}
|
||||
|
||||
export default plugin
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、TypeScript 编译配置重构
|
||||
|
||||
### `tsconfig.base.json`
|
||||
|
||||
- 新增 `"composite": true`:支持项目引用(Project References),使 ESLint `projectService` 能正确解析各子包
|
||||
- 移除 `"noEmit": true`(与 `composite` 不兼容)
|
||||
- 移除 `"experimentalDecorators": true`(TypeScript 5+ 已默认支持)
|
||||
- 移除 `"allowSyntheticDefaultImports": true`(`esModuleInterop: true` 已隐含)
|
||||
|
||||
### 新增根级 `tsconfig.json`
|
||||
|
||||
新增仅供工具链(ESLint、IDE)使用的根 tsconfig,通过 `references` 聚合所有子包:
|
||||
|
||||
```json
|
||||
{
|
||||
"extends": "./tsconfig.base.json",
|
||||
"compilerOptions": { "noEmit": true, "allowJs": true },
|
||||
"files": ["eslint.config.mjs", "vitest.workspace.ts", ...],
|
||||
"references": [
|
||||
{ "path": "./ui/artalk" },
|
||||
{ "path": "./ui/artalk-sidebar" },
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 各子包 `tsconfig.json` 变更
|
||||
|
||||
需要 composite 支持但由 vite 构建(不 emit)的包(`artalk-sidebar`、`docs/landing`):
|
||||
|
||||
```jsonc
|
||||
// 不能用 noEmit,改为
|
||||
"emitDeclarationOnly": true,
|
||||
"declarationDir": ".tsbuildinfo-dts"
|
||||
```
|
||||
|
||||
并将 `.tsbuildinfo-dts/` 加入 `.gitignore`。
|
||||
|
||||
`ui/artalk/tsconfig.json` 新增:
|
||||
|
||||
```json
|
||||
"include": ["./src", "./types", "./tests", "./scripts", "package.json"],
|
||||
"files": ["./package.json"]
|
||||
```
|
||||
|
||||
原因:`composite: true` 下所有 import 的文件必须被 tsconfig 显式涵盖;`import { version } from '~/package.json'` 的 JSON 文件不会被目录 glob 匹配,需手动列出。同时 `vite-plugin-dts` 检查 `include` 而 `tsc` 检查 `files`,因此两处均需声明。
|
||||
|
||||
`ui/artalk/vite.config.ts` 为 `vite-plugin-dts` 添加:
|
||||
|
||||
```ts
|
||||
compilerOptions: { composite: false }
|
||||
```
|
||||
|
||||
避免 dts 插件在生成声明文件时触发 composite 文件列表检查(TS6307)。
|
||||
|
||||
---
|
||||
|
||||
## 六、ESLint 配置重构(`eslint.config.mjs`)
|
||||
|
||||
| 变更 | 旧 | 新 |
|
||||
|------|----|----|
|
||||
| 配置入口 | `eslintTs.config()` | `defineConfig()` from `'eslint/config'` |
|
||||
| typescript-eslint 配置展开 | `...eslintTs.configs.recommended` | `eslintTs.configs.recommended`(无需展开) |
|
||||
| vue 配置展开 | `...pluginVue.configs['flat/recommended']` | `pluginVue.configs['flat/recommended']` |
|
||||
| `@typescript-eslint` 插件 | 手动注册 `pluginTS` | 由 `typescript-eslint` 内部管理,移除手动注册 |
|
||||
| TypeScript 项目解析 | `project: tsProjects`(数组 glob) | `projectService: true`(语言服务模式) |
|
||||
| import-x resolver | `project: tsProjects` | `project: './tsconfig.json'`(单一根 tsconfig) |
|
||||
| React 版本 | `version: '18.3'` | `version: '19.2'` |
|
||||
| typed linting | `recommended` | `recommendedTypeChecked` |
|
||||
|
||||
---
|
||||
|
||||
## 七、依赖分类修复
|
||||
|
||||
### `ui/plugin-katex`
|
||||
|
||||
- `katex`:从 `dependencies` 移至 `devDependencies`(vite 配置中已 external,不打包进产物,应由用户提供)
|
||||
- `@types/katex`:从 `dependencies` + `peerDependencies` 完全移除(katex >= 0.16 已自带 `types/katex.d.ts`,`@types/katex` 为旧版遗留包)
|
||||
|
||||
### `ui/eslint-plugin-artalk`
|
||||
|
||||
- 新增 `eslint: "^10.3.0"` 到 `devDependencies`(peerDep 已声明但开发依赖缺失)
|
||||
|
||||
### `ui/plugin-kit`
|
||||
|
||||
- 新增 `vite: "^8.0.10"` 到 `devDependencies`(源码中 `import type { ViteDevServer, Plugin } from 'vite'`,peerDep 已声明但开发依赖缺失)
|
||||
|
||||
### `ui/plugin-auth`、`ui/plugin-katex`、`ui/plugin-lightbox`
|
||||
|
||||
- 新增 `"@artalk/plugin-kit": "workspace:^"` 到各自 `devDependencies`(这些插件基于 plugin-kit 开发,之前遗漏了显式声明)
|
||||
|
||||
### 根 `package.json`
|
||||
|
||||
- `@microsoft/api-extractor` 保留在根级(由 `vite-plugin-dts` 的 `bundleTypes: true` 调用,属于根级工具链依赖)
|
||||
|
||||
---
|
||||
|
||||
## 八、vitest 配置迁移
|
||||
|
||||
`vitest.workspace.ts` 从 `defineWorkspace` API 迁移到 `defineConfig` + `test.projects`(vitest v4 新 API):
|
||||
|
||||
```ts
|
||||
// 旧
|
||||
import { defineWorkspace } from 'vitest/config'
|
||||
export default defineWorkspace(['./ui/artalk/vitest.config.ts'])
|
||||
|
||||
// 新
|
||||
import { defineConfig } from 'vitest/config'
|
||||
export default defineConfig({
|
||||
test: { projects: ['./ui/artalk/vitest.config.ts'] },
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 九、GitHub Actions CI 更新
|
||||
|
||||
所有使用 Node.js 和 pnpm 的工作流同步更新版本:
|
||||
|
||||
| 文件 | Node.js | pnpm |
|
||||
|------|---------|------|
|
||||
| `test-frontend.yml` | `[18, 20]` → `[22]` | `9.10.0` → `10.33.2` |
|
||||
| `build-ui.yml` | `20.x` → `22.x` | `9.10.0` → `10.33.2` |
|
||||
| `test-docs.yml` | `20.x` → `22.x` | `9.10.0` → `10.33.2` |
|
||||
| `docs-cn.yml` | `20` → `22` | `9.10.0` → `10.33.2` |
|
||||
| `build-nightly.yml` | `20.x` → `22.x` | `9.10.0` → `10.33.2` |
|
||||
| `release.yml` | `20.x` → `22.x` | — |
|
||||
|
||||
---
|
||||
|
||||
## 十、文档更新
|
||||
|
||||
- `CONTRIBUTING.md`(英文):Node.js 要求 `>=20.17.0` → `>=22.19.0`,pnpm `>=9.10.0` → `>=10.33.2`,修复 `make build-fronted` 拼写错误为 `make build-frontend`
|
||||
- `docs/docs/zh/develop/contributing.md`(中文):同步上述版本要求更新
|
||||
- `docs/swagger/redocly.yaml`:更新 Redocly 配置适配 CLI v2 的新格式
|
||||
- `docs/swagger/package.json`:`swagger:serve` 命令 `npx @redocly/cli preview-docs` → `redocly preview`
|
||||
|
||||
---
|
||||
|
||||
## 十一、Go 后端依赖更新
|
||||
|
||||
`go.mod` / `go.sum` 同步更新了若干后端 Go 模块依赖(具体版本见 `go.mod` diff)。
|
||||
@@ -1,7 +0,0 @@
|
||||
# 后端构建
|
||||
|
||||
本页面内容已移动至 <https://artalk.js.org/develop/contributing.html>。
|
||||
|
||||
<script setup lang="ts">
|
||||
if (!import.meta.env.SSR) window.location = 'https://artalk.js.org/develop/contributing.html';
|
||||
</script>
|
||||
@@ -283,6 +283,16 @@ timezone: Asia/Shanghai
|
||||
|
||||
该值填写你所在地时区,对应 IANA 数据库时区名,参考:[Wikipedia](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) / [RFC-6557](https://www.rfc-editor.org/rfc/rfc6557.html)。
|
||||
|
||||
`timezone` 设置的是 Artalk 应用使用的时区,可以与宿主系统或容器的 `TZ` 时区不同。也可以使用环境变量 `ATK_TIMEZONE` 设置相同配置:
|
||||
|
||||
```bash
|
||||
ATK_TIMEZONE=Asia/Shanghai artalk server
|
||||
```
|
||||
|
||||
::: warning 修改后需要重启
|
||||
应用时区是进程级固定值,只在 Artalk 进程初始化时设置。运行期间不支持动态切换;修改配置文件、环境变量或控制中心中的时区后,必须完整重启 Artalk 进程或容器才能生效。
|
||||
:::
|
||||
|
||||
```
|
||||
UTC+08:00 Asia/Shanghai
|
||||
UTC+09:00 Asia/Tokyo
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
# 数据
|
||||
|
||||
本页面内容已移动至 <https://artalk.js.org/guide/transfer.html>。
|
||||
|
||||
<script setup lang="ts">
|
||||
if (!import.meta.env.SSR) window.location = 'https://artalk.js.org/guide/transfer.html';
|
||||
</script>
|
||||
@@ -1,7 +0,0 @@
|
||||
# 如何配置界面
|
||||
|
||||
本页面内容已移动至 <https://artalk.js.org/guide/frontend/config.html>。
|
||||
|
||||
<script setup lang="ts">
|
||||
if (!import.meta.env.SSR) window.location = 'https://artalk.js.org/guide/frontend/config.html';
|
||||
</script>
|
||||
@@ -1,7 +0,0 @@
|
||||
# 后端部署
|
||||
|
||||
本页面内容已移动至 <https://artalk.js.org/guide/deploy.html>。
|
||||
|
||||
<script setup lang="ts">
|
||||
if (!import.meta.env.SSR) window.location = 'https://artalk.js.org/guide/deploy.html';
|
||||
</script>
|
||||
@@ -109,6 +109,7 @@ moderator:
|
||||
- **pending**:当成功匹配时,是否将评论设为待审核状态。
|
||||
- **files**:词库文件。允许多个文件,Artalk 启动时会合并词库。
|
||||
- **file_sep**:词库文件内容分割符。例如:文件中每行一个词语,该项配置 `\n`。
|
||||
该配置不能为空。控制中心会将换行、回车和制表符分别显示为 `\n`、`\r`、`\t`;若分隔符需要是字面量 `\n`,请输入 `\\n`。
|
||||
- **replace_to**:替换字符。例如:该项设置为 `x`,你可以将 `pending` 设置为 `false`,评论自动过审,但匹配到的词语会被替换为 `x`,例如 `fxxk`、`xxxx`。
|
||||
|
||||
注:`replace_to` 不建议使用 `*` 星号,应为它和 Markdown 的加粗语法冲突。
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
## 更新变动
|
||||
|
||||
突破性变动,例如更新需要修改原有配置等,请注意查看版本更新日志中的提示:[CHANGELOG.md](https://github.com/ArtalkJS/Artalk/blob/master/CHANGELOG.md)
|
||||
突破性变动,例如更新需要修改原有配置等,请注意查看版本更新日志中的提示:
|
||||
|
||||
- [v2.10.0 变更日志与迁移指南](../releases/v2.10.0.md)
|
||||
- [完整 CHANGELOG.md](https://github.com/ArtalkJS/Artalk/blob/master/CHANGELOG.md)
|
||||
|
||||
## 命令行一键升级
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
- [青空之蓝](https://blog.ixk.me/)
|
||||
- [Monstx](https://blog.monsterx.cn/)
|
||||
- [QWQAQ](https://qwqaq.com/)
|
||||
- [xaoxuu](https://xaoxuu.com)
|
||||
|
||||
::: tip
|
||||
|
||||
|
||||
@@ -49,11 +49,11 @@ docker exec -it artalk artalk admin
|
||||
<div id="Comments"></div>
|
||||
<script>
|
||||
Artalk.init({
|
||||
el: '#Comments', // 绑定元素的 Selector
|
||||
pageKey: '/post/1', // 固定链接
|
||||
pageTitle: '关于引入 Artalk 的这档子事', // 页面标题 (留空自动获取)
|
||||
el: '#Comments', // 绑定元素的 Selector
|
||||
pageKey: '/post/1', // 固定链接
|
||||
pageTitle: '关于引入 Artalk 的这档子事', // 页面标题 (留空自动获取)
|
||||
server: 'http://artalk.example.com:8080', // 后端地址
|
||||
site: 'Artalk 的博客', // 你的站点名
|
||||
site: 'Artalk 的博客', // 你的站点名
|
||||
})
|
||||
</script>
|
||||
```
|
||||
@@ -82,7 +82,7 @@ Artalk.init({
|
||||
- [反向代理 (Caddy, Nginx, Apache)](./backend/reverse-proxy.md)
|
||||
- [自编译 (通过最新代码构建)](../develop/contributing.md)
|
||||
|
||||
## Go 模块
|
||||
## Go 安装
|
||||
|
||||
如果你已经安装了 Golang 工具链,可以运行以下命令来编译和安装最新版本的 Artalk:
|
||||
|
||||
@@ -100,6 +100,14 @@ artalk server
|
||||
|
||||
## Linux 发行版
|
||||
|
||||
**Fedora (Copr)**:
|
||||
|
||||
```bash
|
||||
dnf install 'dnf-command(copr)'
|
||||
dnf copr enable @artalk/artalk
|
||||
dnf install artalk
|
||||
```
|
||||
|
||||
**Arch Linux (AUR)**:
|
||||
|
||||
```bash
|
||||
@@ -120,6 +128,8 @@ pkg install artalk
|
||||
|
||||
[](https://repology.org/project/artalk/versions)
|
||||
|
||||
[](https://copr.fedorainfracloud.org/coprs/g/artalk/artalk/)
|
||||
|
||||
## Docker Compose
|
||||
|
||||
创建一个工作目录,并编辑 `docker-compose.yml` 文件:
|
||||
@@ -164,22 +174,50 @@ docker-compose exec artalk bash # 进入容器
|
||||
|
||||
更多信息:[Docker](./backend/docker.md) / [环境变量](./env.md)
|
||||
|
||||
## 前端项目 (Node.js)
|
||||
## 客户端
|
||||
|
||||
如果你有前端或 Node.js Web 项目,以下指南将帮助你将 Artalk **客户端** 集成到你的 Web 项目中。
|
||||
|
||||
### 客户端安装
|
||||
|
||||
通过 NPM 安装 Artalk:
|
||||
|
||||
```bash
|
||||
::: code-group
|
||||
|
||||
```sh [npm]
|
||||
npm install artalk
|
||||
```
|
||||
|
||||
```sh [yarn]
|
||||
yarn add artalk
|
||||
```
|
||||
|
||||
```sh [pnpm]
|
||||
pnpm add artalk
|
||||
```
|
||||
|
||||
```sh [bun]
|
||||
bun add artalk
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
(你也可以选择通过 [CDN 资源](#客户端-cdn-资源) 引入).
|
||||
|
||||
### 客户端集成
|
||||
|
||||
在你的 Web 项目中引入 Artalk:
|
||||
|
||||
```js
|
||||
import 'artalk/dist/Artalk.css'
|
||||
import 'artalk/Artalk.css'
|
||||
import Artalk from 'artalk'
|
||||
|
||||
Artalk.init({
|
||||
// ...
|
||||
el: document.querySelector('#Comments'), // 挂载的 DOM 元素
|
||||
pageKey: '/post/1', // 固定链接
|
||||
pageTitle: '关于引入 Artalk 的这档子事', // 页面标题
|
||||
server: 'http://artalk.example.com:8080', // 后端地址
|
||||
site: 'Artalk 的博客', // 站点名
|
||||
})
|
||||
```
|
||||
|
||||
@@ -190,16 +228,63 @@ Artalk.init({
|
||||
- [前端 API](../develop/fe-api.md)
|
||||
- [前端配置](./frontend/config.md)
|
||||
|
||||
## 前端 CDN 资源
|
||||
### 客户端 CDN 资源
|
||||
|
||||
要通过 CDN 在浏览器中使用 Artalk,对于现代浏览器 (支持 [ES 模块](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules)),可以使用 [esm.sh](https://esm.sh) 或 [esm.run](https://esm.run):
|
||||
|
||||
::: code-group
|
||||
|
||||
```html [ES 模块]
|
||||
<body>
|
||||
<div id="Comments"></div>
|
||||
|
||||
<script type="module">
|
||||
import Artalk from 'https://esm.sh/artalk@:ArtalkVersion:'
|
||||
|
||||
Artalk.init({
|
||||
el: '#Comments',
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
```
|
||||
|
||||
```html [传统方式]
|
||||
<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>
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
记得引入 CSS 文件:
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/artalk/:ArtalkVersion:/Artalk.css">
|
||||
```
|
||||
|
||||
::: tip Artalk 最新版本
|
||||
|
||||
当前 Artalk 前端最新版本号为: **:ArtalkVersion:**
|
||||
Artalk 客户端的最新版本为:`:ArtalkVersion:`
|
||||
|
||||
若需升级前端将 URL 中的版本号数字部分替换即可。
|
||||
:::
|
||||
|
||||
Artalk 后端程序内嵌了前端 JS、CSS 文件,使用公共 CDN 资源请注意版本兼容性。
|
||||
注:Artalk 后端程序内嵌了前端 JS、CSS 文件,使用公共 CDN 资源请注意版本兼容性。
|
||||
|
||||
::: details 其他可选的 CDN 资源
|
||||
|
||||
**SUSTech Mirrors (境内)**
|
||||
|
||||
> <https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/artalk/:ArtalkVersion:/Artalk.js>
|
||||
>
|
||||
> <https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/artalk/:ArtalkVersion:/Artalk.css>
|
||||
|
||||
**CDNJS**
|
||||
|
||||
@@ -207,13 +292,6 @@ Artalk 后端程序内嵌了前端 JS、CSS 文件,使用公共 CDN 资源请
|
||||
>
|
||||
> <https://cdnjs.cloudflare.com/ajax/libs/artalk/:ArtalkVersion:/Artalk.css>
|
||||
|
||||
::: details 查看更多
|
||||
**SUSTech Mirrors (境内)**
|
||||
|
||||
> <https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/artalk/:ArtalkVersion:/Artalk.js>
|
||||
>
|
||||
> <https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/artalk/:ArtalkVersion:/Artalk.css>
|
||||
|
||||
**UNPKG**
|
||||
|
||||
> <https://unpkg.com/artalk@:ArtalkVersion:/dist/Artalk.js>
|
||||
|
||||
@@ -25,6 +25,7 @@ services:
|
||||
- ./data:/data
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
- ATK_TIMEZONE=Asia/Shanghai
|
||||
- ATK_LOCALE=zh-CN
|
||||
- ATK_SITE_DEFAULT=Artalk 的博客
|
||||
- ATK_SITE_URL=https://example.com
|
||||
@@ -36,6 +37,8 @@ services:
|
||||
- ATK_ADMIN_USERS_0_BADGE_COLOR=#0083FF
|
||||
```
|
||||
|
||||
`TZ` 设置容器或宿主进程环境的时区,`ATK_TIMEZONE` 设置 Artalk 应用时区;两者可以不同。Artalk 应用时区只在进程初始化时生效,修改 `ATK_TIMEZONE` 后必须完整重启 Artalk 进程或容器。
|
||||
|
||||
当变量为数组,通过空格分隔的字符串或数字索引来设置数组值,例如:
|
||||
|
||||
```
|
||||
@@ -165,6 +168,8 @@ ATK_TRUSTED_DOMAINS_0="https://a.com"
|
||||
| **ATK_AUTH_SLACK_CLIENT_ID** | `""` | ClientId | auth.slack.client_id (社交登录 > Slack > ClientId) |
|
||||
| **ATK_AUTH_SLACK_CLIENT_SECRET** | `""` | ClientSecret | auth.slack.client_secret (社交登录 > Slack > ClientSecret) |
|
||||
| **ATK_AUTH_SLACK_ENABLED** | `false` | 启用 | auth.slack.enabled (社交登录 > Slack > Enabled) |
|
||||
| **ATK_AUTH_SSO_ENABLED** | `false` | 启用 SSO 令牌交换 | auth.sso.enabled (社交登录 > SSO > 启用 SSO 令牌交换) |
|
||||
| **ATK_AUTH_SSO_ISSUER** | `""` | OIDC issuer 地址 (如 "tenant.auth0.com" 或 "https://tenant.auth0.com") | auth.sso.issuer (社交登录 > SSO > OIDC issuer 地址) |
|
||||
| **ATK_AUTH_STEAM_API_KEY** | `""` | ApiKey | auth.steam.api_key (社交登录 > Steam > ApiKey) |
|
||||
| **ATK_AUTH_STEAM_ENABLED** | `false` | 启用 | auth.steam.enabled (社交登录 > Steam > Enabled) |
|
||||
| **ATK_AUTH_TIKTOK_CLIENT_ID** | `""` | ClientId | auth.tiktok.client_id (社交登录 > Tiktok > ClientId) |
|
||||
|
||||
@@ -8,6 +8,10 @@ ArtalkLite 是 Artalk 的精简版本,相较于普通版,有以下区别:
|
||||
|
||||
ArtalkLite 随普通版一起发布,你可以将 CDN 地址文件名修改为 ArtalkLite 获取:
|
||||
|
||||
> https://unpkg.com/artalk@2/dist/ArtalkLite.js
|
||||
>
|
||||
> https://unpkg.com/artalk@2/dist/ArtalkLite.css
|
||||
```
|
||||
https://unpkg.com/artalk@:ArtalkVersion:/dist/ArtalkLite.js
|
||||
```
|
||||
|
||||
```
|
||||
https://unpkg.com/artalk@:ArtalkVersion:/dist/ArtalkLite.css
|
||||
```
|
||||
@@ -69,6 +69,62 @@ Artalk 支持同时启用多种登录方式,用户可以选择任意一种方
|
||||
|
||||
接入 GitHub 登录可参考文档:[关于创建 GitHub 应用](https://docs.github.com/zh/apps/creating-github-apps/about-creating-github-apps/about-creating-github-apps),得到 Client ID 和 Client Secret 后,填写到 Artalk 控制中心的设置页面的社交登录中的「GitHub」选项中即可。
|
||||
|
||||
## SSO 令牌交换
|
||||
|
||||
如果你的站点被嵌入在一个已经通过外部 OIDC 身份提供商(Auth0、Keycloak、Okta 等)完成用户认证的应用中,可以让 Artalk 复用该登录态,而不必显示自己的登录界面。外层应用用它已经持有的 IdP 访问令牌换取一个 Artalk 会话令牌,这样已在父应用登录的用户无需额外点击或弹窗即可发表评论。
|
||||
|
||||
该功能**默认关闭**,需手动开启。可通过 [配置文件](../backend/config.md) 或 [环境变量](../env.md#社交登录) 启用,并填写你所用提供商的 OIDC issuer:
|
||||
|
||||
```yaml
|
||||
auth:
|
||||
enabled: true
|
||||
sso:
|
||||
enabled: true
|
||||
issuer: "https://tenant.auth0.com" # 例如 "tenant.auth0.com" 或 "https://tenant.auth0.com"
|
||||
```
|
||||
|
||||
启用后,外层应用将 IdP 访问令牌提交到交换接口:
|
||||
|
||||
```
|
||||
POST /api/v2/sso/exchange
|
||||
Content-Type: application/json
|
||||
|
||||
{ "token": "<外部 IdP 访问令牌>" }
|
||||
```
|
||||
|
||||
Artalk 通过调用 issuer 的 `/userinfo` 端点校验该令牌(OIDC 标准做法——由 IdP 在服务端验证并签名响应,因此 Artalk 侧无需处理密钥),读取其中的 `email` 声明。只有同时返回 `email_verified: true` 时,Artalk 才会查找或创建对应用户,并返回与其它登录接口一致的响应结构:
|
||||
|
||||
```json
|
||||
{
|
||||
"token": "<Artalk 会话令牌>",
|
||||
"user": { "name": "...", "email": "...", "is_admin": false }
|
||||
}
|
||||
```
|
||||
|
||||
前端在 `Artalk.init()` 运行前将其写入 `localStorage["ArtalkUser"]`,组件即把该用户视为已完全登录(不弹窗,管理员用户也不再要求输入管理密码):
|
||||
|
||||
```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 }), // 来自你的 IdP 的访问令牌
|
||||
})
|
||||
const { token, user } = await res.json()
|
||||
|
||||
// 在组件挂载前预填 Artalk 登录态
|
||||
localStorage.setItem('ArtalkUser', JSON.stringify({ ...user, token }))
|
||||
|
||||
Artalk.init({ el: '#Comments', server: 'https://comments.example.com', site: 'Blog' })
|
||||
```
|
||||
|
||||
当 SSO 未启用时接口返回 `404`,IdP 拒绝令牌或邮箱未验证时返回 `401`,令牌不含邮箱声明时返回 `400`。Artalk 不提供跳过邮箱验证的开关。
|
||||
|
||||
::: tip
|
||||
|
||||
校验完全依赖 issuer 的 `/userinfo` 拒绝无效或已吊销的令牌,且用户按 `email` 声明匹配。请仅对你信任的 issuer 启用此功能。未经验证的邮箱可能冒充现有用户甚至管理员,因此 Artalk 强制要求 `email_verified: true`。
|
||||
|
||||
:::
|
||||
|
||||
## 插件开发
|
||||
|
||||
Artalk 的社交登录功能是通过独立的插件实现并采用 Solid.js 开发,代码可在 [@ArtalkJS/Artalk:ui/plugin-auth](https://github.com/ArtalkJS/Artalk/tree/master/ui/plugin-auth) 找到。
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
# 界面配置
|
||||
|
||||
可在「[控制中心](./sidebar.md)」修改界面配置,而无需改动前端代码。另外也支持 [配置文件](../backend/config.md#界面配置-frontend) 和 [环境变量](../env.md#界面配置)。
|
||||
界面配置,也称为客户端配置、前端配置。可在「[控制中心](./sidebar.md#控制中心)」修改界面配置,而无需改动前端代码。另外也支持 [配置文件](../backend/config.md#界面配置-frontend) 和 [环境变量](../env.md#界面配置)。
|
||||
|
||||
界面配置的优先级如下:
|
||||
|
||||
```
|
||||
环境变量 > 配置文件 = 控制中心 > 前端代码
|
||||
前端代码 > 环境变量 > 控制中心 = 配置文件
|
||||
```
|
||||
|
||||
如果你通过环境变量、配置文件或是在控制中心修改了界面配置,遇到了配置未生效的情况,请考虑配置优先级的问题。
|
||||
|
||||
## 通过 `Artalk.init` 配置界面
|
||||
|
||||
在前端页面调用 `Artalk.init` 初始化时传入参数来配置界面,本文列出了所有可用的配置项。
|
||||
@@ -81,14 +83,20 @@ artalk.update({ ... })
|
||||
|
||||
Artalk 支持多站点统一管理,此项用于站点隔离。
|
||||
|
||||
### useBackendConf
|
||||
### preferRemoteConf
|
||||
|
||||
**引用后端的配置**
|
||||
**优先使用远程配置**
|
||||
|
||||
- 类型:`Boolean`
|
||||
- 默认值:`true`(默认启用)
|
||||
- 默认值:`false`(默认关闭)
|
||||
|
||||
可以在后端的配置文件中定义前端的配置,让前端配置始终引用后端。
|
||||
默认情况下,该配置项为关闭状态,Artalk 界面配置优先级为:优先使用本地的配置。本地配置是指在前端代码中通过 `Artalk.init` 传入的配置。
|
||||
|
||||
开启后,Artalk 会优先使用远程配置,如果远程配置不存在则使用本地配置。远程配置是指后端服务器程序返回的配置,用于动态配置 Artalk 客户端。你可以在「[控制中心](./sidebar.md#控制中心)」中修改用户界面的配置。
|
||||
|
||||
::: warning 更新注意
|
||||
从 v2.10.0 开始,`useBackendConf` 配置项已废弃并始终为 `true`,请使用 `preferRemoteConf` 替代。
|
||||
:::
|
||||
|
||||
## 国际化 (i18n)
|
||||
|
||||
@@ -278,6 +286,17 @@ Artalk 统计组件查询评论数和浏览量时,会通过该属性名来查
|
||||
|
||||
为了便于主题适配,可根据需要自定义属性名,例如将其替换为 `data-path`,则 HTML 标签为 `<span data-path="/t/1.html"></span>`。
|
||||
|
||||
### pageVote
|
||||
|
||||
**页面投票**
|
||||
|
||||
- 类型:`Boolean | { upBtnEl: string; downBtnEl: string; upCountEl: string; downCountEl: string; activeClass: string }`
|
||||
- 默认值: `true`
|
||||
|
||||
启用页面投票功能,用户可以为页面投票。
|
||||
|
||||
详情参考:[页面投票](./voting.md#页面投票)
|
||||
|
||||
### vote
|
||||
|
||||
**投票按钮**
|
||||
|
||||
@@ -30,7 +30,8 @@ locale: zh-CN
|
||||
| 前端界面 | [/ui/artalk/src/i18n/[LANG].ts](https://github.com/ArtalkJS/Artalk/tree/master/ui/artalk/src/i18n) | [zh-CN.ts](https://github.com/ArtalkJS/Artalk/blob/master/ui/artalk/src/i18n/zh-CN.ts) |
|
||||
| 后台界面 | [/ui/artalk-sidebar/src/i18n/[LANG].ts](https://github.com/ArtalkJS/Artalk/blob/master/ui/artalk-sidebar/src/i18n) | [zh-CN.ts](https://github.com/ArtalkJS/Artalk/blob/master/ui/artalk-sidebar/src/i18n/zh-CN.ts) |
|
||||
| 配置文件 | [/conf/artalk.example.[LANG].yml](https://github.com/ArtalkJS/Artalk/tree/master/conf) | [artalk.example.zh-CN.yml](https://github.com/ArtalkJS/Artalk/blob/master/conf/artalk.example.zh-CN.yml) |
|
||||
| 说明文档 | [/docs/[LANG]/\*\*/\*.md](https://github.com/ArtalkJS/Artalk/tree/master/docs) | [/docs/\*\*/\*.md](https://github.com/ArtalkJS/Artalk/tree/master/docs) |
|
||||
| 说明文档 | [/docs/docs/[LANG]/\*\*/\*.md](https://github.com/ArtalkJS/Artalk/tree/master/docs/docs) | [zh/\*\*/\*.md](https://github.com/ArtalkJS/Artalk/tree/master/docs/docs/zh) |
|
||||
| 官网首页 | [/docs/landing/src/i18n/[LANG].ts](https://github.com/ArtalkJS/Artalk/tree/master/docs/landing/src/i18n) | [zh-CN.ts](https://github.com/ArtalkJS/Artalk/blob/master/docs/landing/src/i18n/zh-CN.ts) |
|
||||
|
||||
更多参考:[开发文档](../../develop/index.md) / [CONTRIBUTING.md](https://github.com/ArtalkJS/Artalk/blob/master/CONTRIBUTING.md#translation)
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
# 置入博客
|
||||
|
||||
本页面内容已移动至 <https://artalk.js.org/develop/import-blog.html>。
|
||||
|
||||
<script setup lang="ts">
|
||||
if (!import.meta.env.SSR) window.location = 'https://artalk.js.org/develop/import-blog.html';
|
||||
</script>
|
||||
@@ -1,7 +0,0 @@
|
||||
# 置入框架
|
||||
|
||||
本页面内容已移动至 <https://artalk.js.org/develop/import-framework.html>。
|
||||
|
||||
<script setup lang="ts">
|
||||
if (!import.meta.env.SSR) window.location = 'https://artalk.js.org/develop/import-framework.html';
|
||||
</script>
|
||||
@@ -1,7 +0,0 @@
|
||||
# 前端部署
|
||||
|
||||
本页面内容已移动至 <https://artalk.js.org/guide/deploy.html>。
|
||||
|
||||
<script setup lang="ts">
|
||||
if (!import.meta.env.SSR) window.location = 'https://artalk.js.org/guide/deploy.html';
|
||||
</script>
|
||||
@@ -0,0 +1,123 @@
|
||||
# 投票功能
|
||||
|
||||
Artalk 支持对评论和页面进行投票,用户可以点击“赞同”或“反对”按钮进行投票。评论列表支持通过投票数进行排序。评论投票功能可以帮助用户更好地了解评论的质量。
|
||||
|
||||
## 评论投票
|
||||
|
||||
评论投票功能默认启用,用户可以对评论进行投票。
|
||||
|
||||
你可以在控制台设置界面找到「界面配置」,修改「投票按钮」选项来启用或禁用评论投票功能。
|
||||
|
||||
投票按钮的环境变量:
|
||||
|
||||
```
|
||||
ATK_FRONTEND_VOTE=1
|
||||
```
|
||||
|
||||
投票按钮的配置文件:
|
||||
|
||||
```yaml
|
||||
frontend:
|
||||
vote: true
|
||||
```
|
||||
|
||||
### 反对按钮
|
||||
|
||||
默认情况下,Artalk 不会显示反对按钮。你可以在控制台设置界面找到「界面配置」,修改「反对按钮」选项来启用反对按钮。
|
||||
|
||||
反对按钮的环境变量:
|
||||
|
||||
```
|
||||
ATK_FRONTEND_VOTE_DOWN=1
|
||||
```
|
||||
|
||||
反对按钮的配置文件:
|
||||
|
||||
```yaml
|
||||
frontend:
|
||||
voteDown: true
|
||||
```
|
||||
|
||||
## 页面投票
|
||||
|
||||
Artalk 支持对页面进行投票,你需要在页面中添加元素来显示页面投票按钮,Artalk 在加载时会自动初始化页面投票按钮:
|
||||
|
||||
```html
|
||||
<div>
|
||||
<span class="artalk-page-vote-up"></span>
|
||||
<span class="artalk-page-vote-down"></span>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 已投票状态样式
|
||||
|
||||
当用户点击了页面投票按钮后,元素会被添加 `active` 类名,表示用户已经投票,例如:
|
||||
|
||||
```html
|
||||
<span class="artalk-page-vote-up active"></span>
|
||||
```
|
||||
|
||||
你可以通过 CSS 样式来自定义按钮的已投票样式:
|
||||
|
||||
```css
|
||||
.artalk-page-vote-up.active {
|
||||
color: #0083ff;
|
||||
}
|
||||
```
|
||||
|
||||
默认添加的类名为 `active`,可以在客户端通过 `pageVote.activeClass` 来修改:
|
||||
|
||||
```js
|
||||
Artalk.init({
|
||||
pageVote: {
|
||||
activeClass: 'active',
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### 自定义元素选择器
|
||||
|
||||
Artalk 默认查找 `.artalk-page-vote-up` 和 `.artalk-page-vote-down` 作为投票按钮元素。
|
||||
|
||||
修改客户端的 `pageVote.upBtnEl` 和 `pageVote.downBtnEl` 配置可以自定义投票按钮选择器:
|
||||
|
||||
```js
|
||||
Artalk.init({
|
||||
pageVote: {
|
||||
upBtnEl: '.artalk-page-vote-up',
|
||||
downBtnEl: '.artalk-page-vote-down',
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### 进一步自定义页面投票按钮
|
||||
|
||||
如果投票按钮内没有子元素,Artalk 会输出文字 "赞同 (n)" 到元素中。
|
||||
|
||||
如果你想输出投票数量到单独的元素,可以在按钮中添加一个标签,例如:
|
||||
|
||||
```html
|
||||
<div class="artalk-page-vote">
|
||||
<span class="artalk-page-vote-up">
|
||||
👍 (<span class="artalk-page-vote-up-count"></span>)
|
||||
</span>
|
||||
<span class="artalk-page-vote-down">
|
||||
👎 (<span class="artalk-page-vote-down-count"></span>)
|
||||
</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
更进一步,你可以将文字修改为图标,或者添加其他样式。
|
||||
|
||||
投票数选择器默认为 `.artalk-page-vote-up-count` 和 `.artalk-page-vote-down-count`,
|
||||
|
||||
可以修改 `pageVote.upCountEl` 和 `pageVote.downCountEl` 自定义投票数输出元素:
|
||||
|
||||
```js
|
||||
Artalk.init({
|
||||
pageVote: {
|
||||
upCountEl: '.artalk-page-vote-up-count',
|
||||
downCountEl: '.artalk-page-vote-down-count',
|
||||
},
|
||||
})
|
||||
```
|
||||
@@ -0,0 +1,314 @@
|
||||
# Artalk v2.10.0 变更日志与迁移指南
|
||||
|
||||
v2.10.0 是 v2.9.1 之后的下一个正式版本。本版本新增页面投票、SSO Token Exchange、MySQL Cloud SQL TLS 和无 CGO 的 SQLite 构建,并重构了 UI 客户端的生命周期、配置与类型系统。
|
||||
|
||||
::: warning 升级前必读
|
||||
v2.10.0 包含有意保留的接口与行为变更。服务端与 UI 客户端应同步升级;主题、插件、直接调用 HTTP API 或引用 Artalk TypeScript 类型的项目应先按本文完成迁移。
|
||||
:::
|
||||
|
||||
## 升级检查清单
|
||||
|
||||
- 备份数据库、配置文件和自定义前端资源。
|
||||
- 二进制部署确认工作目录和所有相对文件路径;需要保持旧行为时显式传入 `-w` 和绝对 `-c` 路径。
|
||||
- 自行编译或打包服务端时升级到 Go 1.26.5。
|
||||
- 如需修改应用时区,设置 `timezone` 或 `ATK_TIMEZONE` 后完整重启 Artalk。
|
||||
- 同时升级服务端与 UI 客户端,避免新旧 Vote API 混用。
|
||||
- 检查本地与远程界面配置的优先级;默认改为本地配置优先。
|
||||
- 搜索 `useBackendConf`、`remoteConfModifier`、两参数 `ctx.inject()`、直接赋值 `ctx.conf` / `ctx.$root` 和 `artalk.update(...).xxx()`。
|
||||
- 重新编译使用 Artalk 类型或内部 UI 服务的主题与插件。
|
||||
- Node.js 工具链使用现代版本;Artalk 仓库的开发基线为 Node.js 22.19 及以上。
|
||||
|
||||
## 主要更新
|
||||
|
||||
### UI 与交互
|
||||
|
||||
- 新增[页面投票](../frontend/voting.md#页面投票),并可显示当前访客的已投票状态([#983](https://github.com/ArtalkJS/Artalk/issues/983)、[#998](https://github.com/ArtalkJS/Artalk/issues/998))。
|
||||
- 新增 `preferRemoteConf`,明确本地与远程界面配置的合并优先级([#1000](https://github.com/ArtalkJS/Artalk/issues/1000)、[#1011](https://github.com/ArtalkJS/Artalk/issues/1011))。
|
||||
- UI 改用依赖注入容器,并重新整理模块、生命周期和公开类型([#1006](https://github.com/ArtalkJS/Artalk/issues/1006)、[#1007](https://github.com/ArtalkJS/Artalk/issues/1007))。
|
||||
- 新增 Artalk UI ESLint 插件,并升级到 v1.0.2([#995](https://github.com/ArtalkJS/Artalk/issues/995)、[#1010](https://github.com/ArtalkJS/Artalk/issues/1010))。
|
||||
- User-Agent 识别新增 OpenHarmony 和更多浏览器支持([#1087](https://github.com/ArtalkJS/Artalk/issues/1087))。
|
||||
- 评论链接的 `rel` 属性新增 `ugc`([#1120](https://github.com/ArtalkJS/Artalk/issues/1120))。
|
||||
|
||||
### 服务端与 API
|
||||
|
||||
- 新增 `POST /api/v2/sso/exchange`,可将受信任 OIDC 身份提供方的访问令牌交换为 Artalk 会话令牌([#1134](https://github.com/ArtalkJS/Artalk/issues/1134))。该功能默认关闭;生产环境应只配置受信任的 HTTPS Issuer,且 `/userinfo` 必须返回 `email_verified: true`。
|
||||
- Vote API 现在统一使用目标类型、目标 ID 和选择,并新增投票状态查询接口。
|
||||
- 改进配置文件与数据目录查找,保留切换数据目录前的配置文件定位结果([#992](https://github.com/ArtalkJS/Artalk/issues/992))。
|
||||
- MySQL 新增 Google Cloud SQL 双向 TLS 配置支持([#1104](https://github.com/ArtalkJS/Artalk/issues/1104))。
|
||||
|
||||
### 构建与运行环境
|
||||
|
||||
- SQLite 改用纯 Go 驱动,正式构建不再依赖 CGO,也不再需要单独的 `GORELEASER_CROSS` 工具链。跨平台产物可由标准 Go 工具链直接构建。
|
||||
- Go 工具链升级到 v1.26.5,并更新相关依赖;登录会话的 JWT 实现升级到受维护的 v5 版本。已有 HS256 Token 的声明与签名格式保持兼容,升级服务端不会因此强制用户重新登录。
|
||||
- 官方容器的构建与运行基础镜像升级到 Alpine 3.24;Go 构建版本仍固定为 v1.26.5,容器内的数据目录和配置路径保持不变。
|
||||
- 更新前端与文档工具链,包括 Vite 8.1、Marked 18.0.7 和 KaTeX 0.17。`@artalk/plugin-katex` 的 peer 范围仍兼容 KaTeX 0.16.45 至 0.17.x,依赖升级不会强制现有主题同步更换 KaTeX。
|
||||
- 新增插件注册表拉取脚本和 npm 已发布版本检查脚本。
|
||||
- npm 包使用现代条件导出;不再兼容 Node.js 10 的旧模块解析行为。
|
||||
|
||||
### 文档与本地化
|
||||
|
||||
- 新增土耳其语(`tr-TR`)和繁体中文配置模板。
|
||||
- 新增日语 README。
|
||||
- 重设计项目首页,补充多语言内容并优化响应式字号。
|
||||
- 文档站集成 Git Changelog。
|
||||
|
||||
## 安全性与可靠性
|
||||
|
||||
- 后端 JWT 解析器升级到 `github.com/golang-jwt/jwt/v5`,显式限制为 HS256,继续校验过期时间,并新增旧 Token 格式、签名算法和过期行为的回归测试。
|
||||
- Markdown HTML 清理器由无修复版本且存在 ReDoS 风险的 `insane` 切换为持续维护的 `DOMPurify`,保留原有标签、属性、class、style 和 URL scheme 白名单,并增加恶意标记与 ReDoS 回归测试。
|
||||
- 前端锁文件将构建与开发工具链中的 `fast-uri`、`brace-expansion`、`js-yaml`、`undici`、Vite 等传递依赖固定到已修复版本;完整工作区依赖审计未发现已知漏洞。
|
||||
- Google Cloud SQL TLS 会同时加载服务端 CA、客户端证书与私钥,校验服务器证书和主机名,并要求 TLS 1.2 及以上。三个证书路径必须一起配置,避免部分配置被静默忽略。
|
||||
- Vote API 会拒绝非法、非正数、不存在或类型未知的投票目标。投票记录和目标计数在同一数据库事务中更新,任一步骤失败都会回滚。
|
||||
- 插件 URL 由服务端过滤:相对 URL 与受信任 URL 可下发给客户端,不受信任的绝对 URL 会被移除。
|
||||
- 插件注册表下载仅允许 HTTPS 和限定主机,并限制重定向次数、响应大小与请求时间。
|
||||
- npm 版本检查不再通过 Shell 拼接包名,并会把网络或解析错误作为失败返回。
|
||||
- 进程时区只在初始化时设置,避免并发请求修改全局时区状态。
|
||||
|
||||
## 突破性变更与迁移
|
||||
|
||||
### 1. 本地界面配置默认优先
|
||||
|
||||
v2.9.x 中远程配置会覆盖 `Artalk.init()` 传入的本地配置。v2.10.0 默认改为本地配置优先:
|
||||
|
||||
```text
|
||||
Artalk.init() 本地配置 > 环境变量 > 控制中心 = 配置文件
|
||||
```
|
||||
|
||||
默认无需新增配置:
|
||||
|
||||
```js
|
||||
Artalk.init({
|
||||
server: 'https://comments.example.com',
|
||||
site: 'My Site',
|
||||
locale: 'zh-CN', // 保留本地值
|
||||
})
|
||||
```
|
||||
|
||||
如需恢复“远程配置优先”,显式开启:
|
||||
|
||||
```js
|
||||
Artalk.init({
|
||||
server: 'https://comments.example.com',
|
||||
site: 'My Site',
|
||||
preferRemoteConf: true,
|
||||
})
|
||||
```
|
||||
|
||||
`useBackendConf` 已废弃并始终按 `true` 处理。设置为 `false` 不再阻止客户端读取后端必须提供的配置;它不能替代 `preferRemoteConf`。
|
||||
|
||||
### 2. 删除 `remoteConfModifier`
|
||||
|
||||
`remoteConfModifier` 不再属于公开配置。请根据用途迁移:
|
||||
|
||||
- 固定覆盖项直接放入 `Artalk.init()`,默认会优先于远程配置。
|
||||
- 需要控制中心优先时使用 `preferRemoteConf: true`。
|
||||
- 需要控制首次评论请求时使用 `fetchCommentsOnInit`。
|
||||
- 仅需初始化后更新时,监听 `created` 后调用 `update()`。
|
||||
|
||||
```js
|
||||
const artalk = Artalk.init({
|
||||
server: 'https://comments.example.com',
|
||||
site: 'My Site',
|
||||
fetchCommentsOnInit: false,
|
||||
})
|
||||
|
||||
artalk.on('created', () => {
|
||||
artalk.update({ locale: 'zh-CN' })
|
||||
artalk.reload()
|
||||
})
|
||||
```
|
||||
|
||||
不再提供可在插件初始化前任意修改远程配置的等价 Hook。
|
||||
|
||||
### 3. 配置类型区分完整值与输入值
|
||||
|
||||
`Config` 表示已经解析完成的完整配置,`ConfigPartial` 表示 `Artalk.init()`、`update()` 和插件配置更新所接受的递归可选输入。
|
||||
|
||||
```ts
|
||||
import type { Config, ConfigPartial } from 'artalk'
|
||||
|
||||
function createComments(conf: ConfigPartial) {
|
||||
return Artalk.init(conf)
|
||||
}
|
||||
|
||||
function readResolvedConfig(conf: Config) {
|
||||
console.log(conf.server, conf.pagination.pageSize)
|
||||
}
|
||||
```
|
||||
|
||||
过去将 `Partial<ArtalkConfig>` 用作初始化参数的代码应改用 `ConfigPartial`。`ArtalkConfig` 仍可导入,但现在表示完整配置。
|
||||
|
||||
`ConfigPartial` 会保持函数、DOM 元素、`Date`、`RegExp` 和数组元素的原始语义,不会再把这些值错误地递归拆成对象字段。
|
||||
|
||||
### 4. DI 注册与解析分离
|
||||
|
||||
旧的双参数 `ctx.inject(key, value)` 注册方式已移除。使用 `provide()` 注册,使用 `inject()` 解析:
|
||||
|
||||
```ts
|
||||
// v2.9.x
|
||||
ctx.inject('example', createExample())
|
||||
const example = ctx.get('example')
|
||||
|
||||
// v2.10.0
|
||||
ctx.provide('example', () => createExample())
|
||||
const example = ctx.inject('example')
|
||||
```
|
||||
|
||||
`provide()` 支持依赖列表与 `singleton` / `transient` 生命周期。自定义 TypeScript 服务键应通过声明合并扩展 `Services`。
|
||||
|
||||
### 5. `ctx.conf` 与 `ctx.$root` 不可替换
|
||||
|
||||
两者仍可读取,但不能整体赋值。运行时直接赋值会抛出带迁移提示的错误:
|
||||
|
||||
```ts
|
||||
// 错误
|
||||
ctx.conf = nextConf
|
||||
ctx.$root = nextElement
|
||||
|
||||
// 正确
|
||||
ctx.updateConf(nextConf)
|
||||
const conf = ctx.getConf()
|
||||
const root = ctx.getEl()
|
||||
```
|
||||
|
||||
如需更换根元素,请销毁旧实例并使用新的 `el` 创建实例。
|
||||
|
||||
### 6. `Artalk.update()` 返回 `void`
|
||||
|
||||
`update()` 不再返回 Artalk 实例。将链式调用拆开:
|
||||
|
||||
```js
|
||||
// v2.9.x
|
||||
artalk.update({ pageKey: '/next' }).reload()
|
||||
|
||||
// v2.10.0
|
||||
artalk.update({ pageKey: '/next' })
|
||||
artalk.reload()
|
||||
```
|
||||
|
||||
### 7. 插件初始化与 `created` 时点
|
||||
|
||||
内置插件、本地插件和网络插件现在都会在本地与远程配置完成合并后初始化。网络插件不会再看到仅来自某一端的临时配置。
|
||||
|
||||
`created` 会在所有插件完成初始化后触发,随后触发 `mounted`。依赖旧时序的插件应把初始化前置逻辑放在插件函数内,把需要完整插件环境的逻辑放到 `created` 监听器中。
|
||||
|
||||
### 8. Vote HTTP API 与客户端方法
|
||||
|
||||
旧的投票端点:
|
||||
|
||||
```http
|
||||
POST /api/v2/votes/comment_up/123
|
||||
```
|
||||
|
||||
新端点:
|
||||
|
||||
```http
|
||||
GET /api/v2/votes/comment/123
|
||||
POST /api/v2/votes/comment/123/up
|
||||
```
|
||||
|
||||
`target_name` 只能为 `comment` 或 `page`,`choice` 只能为 `up` 或 `down`。
|
||||
|
||||
生成的客户端方法同步调整:
|
||||
|
||||
```ts
|
||||
await api.votes.getVote('comment', 123)
|
||||
await api.votes.createVote('comment', 123, 'up', { name, email })
|
||||
```
|
||||
|
||||
旧端点和旧 `votes.vote()` 方法不保留兼容适配器。
|
||||
|
||||
### 9. SQLite 驱动改为纯 Go 实现
|
||||
|
||||
服务端 SQLite 驱动由基于 `mattn/go-sqlite3` 的 CGO 实现切换为 `github.com/libtnb/sqlite` 提供的纯 Go 实现。正式构建不再依赖本地 C 编译器、`CGO_ENABLED=1` 或 `GORELEASER_CROSS_VERSION` 对应的交叉编译工具链。
|
||||
|
||||
SQLite 磁盘文件格式没有改变,现有 `db.file` 数据库可以直接使用,普通部署无需修改数据库配置。升级前仍建议备份数据库;首次使用新版本启动时,Artalk 会照常执行数据库结构迁移。
|
||||
|
||||
自行编译或将 Artalk 作为 Go 模块嵌入的项目需要注意:
|
||||
|
||||
- 可以删除 SQLite 构建所需的 C 编译器、CGO 和 Goreleaser Cross 配置。
|
||||
- 不要再依赖 `mattn/go-sqlite3` 专属的构建标签、DSN 参数、C API 或动态扩展加载行为。
|
||||
- 如有自定义 SQLite 编译选项、扩展或原生备份工具集成,应使用 v2.10.0 重新执行迁移、并发读写和备份恢复测试。
|
||||
|
||||
此变更不影响使用 MySQL、PostgreSQL、MSSQL 的部署。
|
||||
|
||||
### 10. 默认配置和数据目录采用 XDG 规则
|
||||
|
||||
未指定 `--workdir` / `-w` 时,Artalk 不再始终把启动目录作为工作目录。v2.10.0 的默认查找顺序如下:
|
||||
|
||||
- 配置文件:启动目录、启动目录下的 `data`、`$XDG_CONFIG_HOME/artalk`、`/etc/artalk`。
|
||||
- 数据目录:如果启动目录存在 `./data` 则保持旧工作目录;否则依次使用 `$XDG_DATA_HOME/artalk`、`/var/lib/artalk`,均不存在时创建 `$XDG_DATA_HOME/artalk`。
|
||||
|
||||
配置文件会在隐式切换数据目录前完成定位,但配置中的数据库、日志、上传目录等相对路径,以及 `import`、`export`、`gen` 等命令的相对文件参数,仍然基于最终工作目录解析。
|
||||
|
||||
现有目录中已经包含 `./data` 的常规部署保持原行为。官方 Docker 镜像也显式指定了工作目录和配置文件,不受此变化影响。
|
||||
|
||||
如果二进制部署需要完全保持旧路径语义,请显式指定工作目录和配置文件:
|
||||
|
||||
```bash
|
||||
artalk -w /srv/artalk -c /srv/artalk/artalk.yml server
|
||||
```
|
||||
|
||||
升级前应检查 `db.file`、`log.filename`、`img_upload.path`、IP 数据库和关键词文件等所有相对路径,避免在新的数据目录中意外创建空数据库或文件。
|
||||
|
||||
### 11. 源码构建要求 Go 1.26.5
|
||||
|
||||
`go.mod` 的最低 Go 版本从 1.22.7 提升到 1.26.5。该变化影响 `go install`、自行编译、发行版打包和将 Artalk 纳入 Go 构建流程的下游项目。
|
||||
|
||||
正式二进制和 Docker 镜像用户无需安装 Go。源码构建环境应升级到 Go 1.26.5;禁用 Go Toolchain 自动下载或处于离线环境的 CI 必须提前安装对应工具链。
|
||||
|
||||
### 12. 应用时区在进程初始化时固定
|
||||
|
||||
`timezone` 配置和 `ATK_TIMEZONE` 环境变量继续保留,用于设置独立于宿主系统或容器 `TZ` 的 Artalk 应用时区:
|
||||
|
||||
```yaml
|
||||
timezone: Asia/Shanghai
|
||||
```
|
||||
|
||||
```bash
|
||||
ATK_TIMEZONE=Asia/Shanghai artalk server
|
||||
```
|
||||
|
||||
时区属于进程级全局状态,v2.10.0 只在 Artalk 进程初始化时设置一次,运行期间不支持动态切换。修改配置文件、环境变量或控制中心中的时区后,必须完整重启 Artalk 进程或容器;仅调用控制中心的“应用配置”不足以切换时区。
|
||||
|
||||
### 13. `marked` 18 与现代包入口
|
||||
|
||||
默认 Artalk 构建继续内置 Markdown 能力;ArtalkLite 不把 `marked` 打包进自身,需要下游提供兼容的 `marked@^18.0.3`。
|
||||
|
||||
Artalk 使用的 `Marked`、`Renderer`、`parse()`、`setOptions()`、`use()` 和 Tokenizer Extension 均不是 v18 专属 API,但 v14 到 v18 的上游解析细节仍可能影响自定义 Renderer、Tokenizer 或快照输出。使用这些扩展的项目应重新运行 Markdown 测试。
|
||||
|
||||
Node.js 10 和依赖旧式包解析的工具链不再受支持。Artalk 仓库开发与构建使用 Node.js 22.19 及以上。
|
||||
|
||||
## 保留的兼容入口
|
||||
|
||||
为了给主题和第三方插件留出迁移时间,以下入口在 v2.10.0 仍然存在,但已经标记为 `deprecated`:
|
||||
|
||||
| 旧入口 | 推荐入口 |
|
||||
| --- | --- |
|
||||
| `ContextApi` | `Context` |
|
||||
| `DataManagerApi` | `DataManager` |
|
||||
| `EditorApi` | `Editor` |
|
||||
| `ctx.data` | `ctx.getData()` 或 `ctx.inject('data')` |
|
||||
| `ctx.user` | `ctx.getUser()` 或 `ctx.inject('user')` |
|
||||
| `ctx.editor` | `ctx.inject('editor')` |
|
||||
| `ctx.list` | `ctx.inject('list')` |
|
||||
| `ctx.layerManager` | `ctx.inject('layers')` |
|
||||
| `ctx.checkerLauncher` | `ctx.inject('checkers')` |
|
||||
| `ctx.sidebarLayer` | `ctx.inject('sidebar')` |
|
||||
| `ctx.editorPlugs` | `ctx.inject('editorPlugs')` |
|
||||
| `editor.$el` | `editor.getEl()` |
|
||||
| `editor.getPlugs()` | `editor.getPlugins()` |
|
||||
| `editor.setReply()` | `editor.setReplyComment()` |
|
||||
|
||||
这些别名仅用于过渡;新代码应直接使用推荐入口。完整公开类型请查看 [TypeDoc](https://artalk.js.org/typedoc/)。
|
||||
|
||||
## 其他变更
|
||||
|
||||
- 评论回复按钮改用组件实现。
|
||||
- Sidebar 与 Auth UI 已适配新的客户端类型。
|
||||
- 新增 npm 发布版本检查与插件注册表生成流程。
|
||||
- 固定 Swagger TypeScript 客户端生成器版本并更新命令,避免不同开发环境通过 `npx` 获取不兼容版本。
|
||||
- 更新 GitHub Actions 运行时及制品、缓存、Docker、CodeQL 和 Codecov 相关 Actions;工作流输入与发布产物布局保持不变。
|
||||
- 更新前端、Go 和文档依赖,并清理旧测试项目与 CI 配置。
|
||||
|
||||
完整提交差异可查看 [`v2.9.1...v2.10.0`](https://github.com/ArtalkJS/Artalk/compare/v2.9.1...v2.10.0)。
|
||||
@@ -138,15 +138,19 @@ typecho-20220424-202246.artrans
|
||||
|
||||
### Commento
|
||||
|
||||
你可在 Commento 后台导出 JSON 格式的数据文件,然后使用[转换工具](#转换工具)进行转换。
|
||||
前往 Commento 后台,依次点击「General - Export」,导出 JSON 格式的数据文件,然后使用[转换工具](#转换工具)进行转换。
|
||||
|
||||
【图示,待补充...】
|
||||

|
||||
|
||||
### Twikoo
|
||||
|
||||
[Twikoo](https://twikoo.js.org/) 是一款基于腾讯云开发的评论系统,可前往 [腾讯云后台](https://console.cloud.tencent.com/tcb) 导出 JSON 格式的评论数据,然后使用[转换工具](#转换工具)进行转换。
|
||||
[Twikoo](https://twikoo.js.org/) 可以通过所在管理面板中的「导出」板块获取JSON 文件,然后使用[转换工具](#转换工具)进行转换。
|
||||
|
||||
<img src="/images/transfer/tencent-tcb.png" style="max-width: 480px;">
|
||||

|
||||
|
||||
除此之外,对于腾讯云一键部署的Twikoo,可前往 [腾讯云后台](https://console.cloud.tencent.com/tcb) 导出 JSON 格式的评论数据。
|
||||
|
||||

|
||||
|
||||
### Artalk v1 (PHP 旧版后端)
|
||||
|
||||
|
||||
+14
-14
@@ -14,21 +14,21 @@
|
||||
"preview": "vite preview",
|
||||
"update:readme": "npx tsx ./scripts/update-readme.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"devDependencies": {
|
||||
"@headlessui/react": "^2.2.10",
|
||||
"@types/node": "25.9.5",
|
||||
"@types/react": "^19.2.17",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react-swc": "^4.3.2",
|
||||
"i18next": "^26.3.6",
|
||||
"i18next-browser-languagedetector": "^8.2.1",
|
||||
"include-media": "^2.0.0",
|
||||
"normalize.css": "^8.0.1",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-icons": "5.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@headlessui/react": "^2.1.8",
|
||||
"@types/react": "^18.3.7",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@vitejs/plugin-react-swc": "^3.7.0",
|
||||
"i18next": "^23.15.1",
|
||||
"i18next-browser-languagedetector": "^8.0.0",
|
||||
"react-i18next": "^15.0.2",
|
||||
"usehooks-ts": "^3.1.0"
|
||||
"react": "^19.2.8",
|
||||
"react-dom": "^19.2.8",
|
||||
"react-i18next": "^17.0.10",
|
||||
"react-icons": "5.7.0",
|
||||
"usehooks-ts": "^3.1.1",
|
||||
"vanilla-lazyload": "^19.1.3"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
Loaded 100 of 481 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user