Compare commits

...
108 Commits
Author SHA1 Message Date
qwqcode d25beede91 fix(scripts): detect unpublished npm versions
Query the npm registry for the exact local version instead of comparing only with the latest dist-tag.

Report unpublished packages accurately and avoid spawning pnpm for each lookup.
2026-07-25 04:01:48 +08:00
qwqcode 16b690c8c0 fix(plugins): prepare unpublished packages for release
Declare compatible Artalk ranges and npm repository metadata for the pending plugin versions.

Correct ESM and CommonJS declaration exports, trim plugin-kit package contents, and validate package types in CI.
2026-07-25 04:01:43 +08:00
qwqcode 2036a0261e fix(ci): publish npm package with trusted OIDC
Replace the long-lived npm token with GitHub OIDC credentials, verify the release version, and retain pnpm packaging so the published tarball still includes the repository license.
2026-07-25 03:26:34 +08:00
qwqcode 66c8f6b94b fix(docker): push release images by digest
Avoid assigning temporary platform tags to digest-only outputs so Buildx can push canonical per-platform digests before the manifest merge.
2026-07-25 03:26:33 +08:00
qwqcode 1bf21404bd fix(build): bind Docker metadata to release commit
Pass the resolved release commit into Docker builds, verify every checkout against it, and derive image version metadata and sha tags from the packaged source instead of the workflow invocation commit.
2026-07-24 14:24:00 +08:00
qwqcode 9ec0f1c74a fix(ci): pin release retries to reviewed commits
Resolve automatic releases from the merged release PR commit and require an explicit immutable commit when recovering a missing tag. Reuse existing tags idempotently, restore annotated release tags, and reject duplicate release preparation or inconsistent version files.
2026-07-24 14:23:26 +08:00
qwqcode 9b5e47d602 fix(ci): create release tags under tag ruleset
Create lightweight release refs directly at the checked-out commit so the v* linear-history rule evaluates the commit instead of an annotated tag object. Resolve existing tags through the remote API, verify their exact target without overwriting them, and accept an explicit version for safe manual release retries.
2026-07-23 23:52:35 +08:00
github-actions[bot] 03a433d94a chore: release v2.10.0 (#1158)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-23 23:41:37 +08:00
qwqcode 4a9b07e687 fix(ci): preserve nightly tag during release refresh
Keep the movable nightly tag while replacing the release, verify its exact commit through the Git refs API, and pass the build SHA as the release target. This avoids the tag visibility race caused by deleting and immediately recreating the tag before --verify-tag.
2026-07-23 14:04:01 +08:00
qwqcode d00b63c0af chore(ci): fix git-chglog installation 2026-07-23 13:20:48 +08:00
qwqcode 97352b583e fix(api/vote): return not found for deleted targets 2026-07-22 22:20:28 +08:00
qwqcode 0b44989e5f fix(cache): return database results on store failure
Treat cache writes as best-effort after a successful database query. Keep database errors authoritative and cover both paths with a failing store.

Fixes #1154
2026-07-22 22:20:28 +08:00
qwqcode a5308548ba fix(config): preserve keyword file separators
Represent control characters with reversible dashboard escapes, keep custom separators intact, and reject empty separators before loading keyword files. Document and test the full YAML round trip.

Fixes #1062
2026-07-22 22:20:28 +08:00
qwqcode 9225fc32f8 fix(email): add Message-ID header
Generate a unique RFC 5322 message identifier for every SMTP and sendmail message, using the sender domain when valid and a safe fallback otherwise.

Fixes #1111
2026-07-22 22:20:27 +08:00
qwqcode 37dc6a68d7 fix(api/stats): exclude pending comments
Use an explicit boolean condition so GORM cannot omit the moderation filter. Cover public comment lists and counts with regression tests.

Fixes #1110
2026-07-22 22:20:27 +08:00
qwqcode 69613beb88 fix(docs): support React 19 useRef types 2026-07-22 22:20:27 +08:00
qwqcode 9c5bd9653f fix(ci): satisfy workflow shell checks
Protect checksum globs and quote test artifact paths for ShellCheck. Pass release versions to Perl through the environment instead of interpolating shell variables into the program text.
2026-07-22 22:20:27 +08:00
qwqcode ca3d37fc2d fix(build): propagate image version metadata
Inject the requested release version and commit hash through Go linker variables so Docker images report the version they were built for, and make release preparation update the linkable version variable.\n\nAssert the version in release image smoke tests and trim unused files from Docker build contexts while preserving both prebuilt-UI and full frontend build paths.
2026-07-22 22:20:27 +08:00
qwqcode f554e6c438 test(ci): expand release validation
Run Go tests natively on Linux AMD64, Linux ARM64, and macOS; reduce the frontend matrix while keeping Node 22, Node 24, and Windows coverage; and make type-entry validation blocking.\n\nAdd the Playwright browser UAT flow to CI, pin test tools, isolate architecture-specific caches and artifacts, and cancel superseded pull-request runs.
2026-07-22 22:20:27 +08:00
qwqcode 76066bbdc0 fix(ci): harden release supply chain
Pin every third-party action to an immutable commit, enforce least-privilege tokens, avoid persisted checkout credentials, and validate all user-controlled values before they reach scripts.\n\nKeep dry-run GoReleaser jobs tokenless, publish npm provenance, generate tags through authenticated GitHub API calls, disable shared caches for release artifacts, and continuously audit workflow syntax and security policy.
2026-07-22 22:20:27 +08:00
qwqcode 3950ef8532 refactor(ci): accelerate nightly publishing
Run AMD64 and ARM64 Docker builds on native GitHub-hosted Linux runners, reuse architecture-scoped BuildKit caches, and merge validated image digests after both builds succeed.\n\nMake manual dry runs build, smoke-test, and upload artifacts without exposing DockerHub credentials or replacing the nightly tag and release.
2026-07-22 22:20:26 +08:00
qwqcode 3f6eb935e4 refactor(ci): optimize multi-architecture releases
Build Docker images once per native AMD64 and ARM64 runner, merge immutable digests into the release manifest, and reuse a single UI artifact across architectures.\n\nKeep dry runs local by separating the source ref from the release version, avoid remote tags and package publication, scope reusable-workflow secrets, and smoke-test each image before publication.
2026-07-22 22:20:26 +08:00
qwqcode 5364d191c7 test(ui): update comment e2e flow
Use the current English development-page labels and the configured base URL. Register the POST response wait before submission to avoid missing fast responses.
2026-07-22 22:20:26 +08:00
qwqcode 5e1b04326b test(ui): isolate Playwright end-to-end specs
Point Playwright directly at tests/e2e so Vitest unit suites are not collected as browser tests. The Chromium project now discovers only the intended comment UAT spec.
2026-07-22 22:20:26 +08:00
qwqcode add379c82c fix(ui/sidebar): handle null array preferences
Treat YAML null values as empty arrays before checking for YAML node serialization. This prevents the settings page from throwing while mounting optional array preferences.
2026-07-22 22:20:26 +08:00
qwqcode d7565e5b33 fix(ui): accept scoped comment lists without page data
Site and user scoped comment responses intentionally omit the page object. Only enforce page data for page-scoped requests and skip page state updates when it is absent.

Add regression coverage for sidebar scopes and the normal page flow.
2026-07-22 22:20:26 +08:00
qwqcode d46326788b docs: document dependency and security updates
Record the backend, frontend, container, and CI dependency refresh in both v2.10.0 release guides.

Explain JWT wire compatibility, the sanitizer replacement, patched transitive pins, generator reproducibility, and unchanged release artifact layout.
2026-07-22 22:20:26 +08:00
qwqcode 729fd9880a fix(ci): harden release tag handling
Pass the event ref through the environment instead of interpolating expression data into the shell, validate the derived semantic version, and quote Git refs and environment files.

Use rev-parse for an exact tag existence check before creating and pushing the release tag.
2026-07-22 22:20:26 +08:00
qwqcode 5b6a453349 fix(ui): preserve generated list response contract
Regenerate the Swagger specification and TypeScript client with the pinned generators, including accurate optional response fields and the verified-email SSO description.

Validate comment-list page data at the API boundary and normalize it back to the public ListData contract before storing or emitting the response.
2026-07-22 22:20:25 +08:00
qwqcode b7b6700407 fix(auth): require verified email for SSO exchange
Reject OIDC userinfo responses without email_verified=true before account lookup or JWT issuance.
2026-07-22 22:20:25 +08:00
qwqcode cb2860a778 docs: add v2.10.0 release notes
BREAKING CHANGE: Beyond #997, #1007, and #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
2026-07-22 22:20:25 +08:00
qwqcode 705e220a81 test(ui): cover Marked 18 integration 2026-07-22 22:20:25 +08:00
qwqcode ef2d1e8552 test(server): verify plugin URL trust filtering 2026-07-22 22:20:25 +08:00
qwqcode 14e2d9e824 fix(ui): align plugin lifecycle with final config 2026-07-22 22:20:25 +08:00
qwqcode 9dba2ae43f fix(ui): reject direct context replacement 2026-07-22 22:20:25 +08:00
qwqcode f1784a1bd0 fix(ui): restore deprecated context services 2026-07-22 22:20:24 +08:00
qwqcode bc30c4d26d fix(ui/editor): restore deprecated aliases 2026-07-22 22:20:24 +08:00
qwqcode ad9abd643c fix(ui/types): restore deprecated public aliases 2026-07-22 22:20:24 +08:00
qwqcode 2e7a34fc9c fix(ui/types): preserve atomic config values 2026-07-22 22:20:24 +08:00
qwqcode 082e9b7fed fix(core): initialize process timezone once 2026-07-22 22:20:24 +08:00
qwqcode a95c1b9be6 fix(db): enforce Cloud SQL TLS verification 2026-07-22 22:20:24 +08:00
qwqcode dfe65ce621 fix(cmd): preserve config lookup before data chdir 2026-07-22 22:20:24 +08:00
qwqcode da6556ec3c fix(api): make vote updates transactional 2026-07-22 22:20:23 +08:00
qwqcode 9fe237660c chore(scripts): bound registry downloads 2026-07-22 22:20:23 +08:00
qwqcode eff4a0b15d test: remove races from async tests 2026-07-22 22:20:23 +08:00
qwqcode 5b0e4c866e fix(api): validate vote status targets 2026-07-22 22:20:23 +08:00
qwqcode 617de2cdf1 fix(scripts): harden npm publish check 2026-07-22 22:20:23 +08:00
qwqcode 36c47104ca test(ui): cover sanitizer attribute allowlist 2026-07-22 22:13:45 +08:00
qwqcode 8460ae387f chore(deps): bump ci deps
Update checkout, setup, cache, artifact, Docker, CodeQL, Codecov, repository-dispatch, and supporting GitHub Actions to their current major releases.

Move the builder and runtime images to Go 1.26.5 on Alpine 3.24 while preserving workflow inputs and release artifact layout.
2026-07-22 22:13:44 +08:00
qwqcode e6a5c2d4b9 fix(scripts): import Buffer explicitly
Use the node:buffer entry point instead of relying on an implicit global when the plugin registry fetch script computes response sizes.
2026-07-22 22:13:44 +08:00
qwqcode 1cd9cdb445 test(ui): isolate browser storage from Node
Disable Node's experimental Web Storage shim for Vitest and coverage runs so jsdom consistently provides window.localStorage on supported Node 22 and 24 releases.
2026-07-22 22:13:44 +08:00
qwqcode 117908e57b fix(ui): replace vulnerable HTML sanitizer
Replace the unmaintained insane parser with DOMPurify to remove its known ReDoS exposure while retaining Artalk's tag, attribute, class, style, and URL scheme allowlists.

Add malicious markup, URL filtering, allowlist compatibility, and former ReDoS payload tests.
2026-07-22 22:13:44 +08:00
qwqcode b47eed5303 chore(deps): bump frontend deps
Update the UI, sidebar, plugins, documentation, lint, test, and build toolchains, including Vite 8.1, Marked 18.0.7, TypeScript 6, and current framework packages.

Pin patched transitive dependencies, move pnpm policy to the workspace configuration, approve the esbuild install script, and refresh generated router metadata.
2026-07-22 22:13:44 +08:00
qwqcode f7a2aeac9b refactor(auth): migrate JWT handling to v5
Use RegisteredClaims and ParseWithClaims, restrict accepted tokens to HS256, and keep existing claim names and signatures wire-compatible.

Add regression coverage for generated and legacy tokens, expiry validation, and algorithm rejection.
2026-07-22 22:13:44 +08:00
qwqcode 5ff42483aa chore(deps): bump backend deps
Update the Go dependency graph, including x/crypto, x/image, x/net, Slack, database drivers, Fiber integrations, and supporting modules.

Bump the CGO-free SQLite driver to v1.2.1 and prepare golang-jwt/jwt v5 for the dedicated authentication migration.
2026-07-22 22:13:44 +08:00
qwqcode 8de7157a91 feat(go): upgrade golang to v1.26.5 2026-07-22 22:13:43 +08:00
qwqcode 747b184fa4 build: remove cgo from SQLite releases 2026-07-22 22:13:43 +08:00
Ikko Eltociear Ashimine f875fc4023 docs: add Japanese README (#1145) 2026-06-24 17:30:17 +01:00
Henrik Pettersenandqwqcode cf06808170 feat(auth): add SSO token exchange endpoint (#1134)
* feat(auth): add SSO token exchange endpoint

Adds POST /api/v2/sso/exchange — accepts an access token from an
external OIDC IdP (verified by calling the IdP's /userinfo) and issues
an Artalk session JWT for the user identified by the email claim.

Use case: a surrounding application that already runs OIDC (e.g. an
Auth0 SPA) can drive Artalk sign-in without showing Artalk's own popup
login UI. The widget reads the resulting JWT from
localStorage["ArtalkUser"].token and treats the user as fully
authenticated for is_login/is_admin checks.

Implementation:
- One new handler at server/handler/auth_sso_exchange.go (~110 lines)
- Reuses dao.FindCreateUser + common.LoginGetUserToken; same JWT
  format as /user/access_token, so no client-side changes needed
- Validation by calling {issuer}/userinfo with Authorization: Bearer
  — OIDC-standard, no JWKS dep added
- Gated by auth.enabled && auth.sso.enabled (404 otherwise)
- Returns the same ResponseUserLogin shape as /auth/email/login

Config additions (conf/artalk.example.yml):
  auth:
    sso:
      enabled: false
      issuer: ""   # e.g. "tenant.auth0.com"

Route registered alongside the existing social-login routes in
server/server.go.

Verified by go build ./... (clean).

* chore(config): regenerate env-path cache with auth.sso entries

Required so koanf actually maps ATK_AUTH_SSO_ENABLED/ISSUER env vars
to the new auth.sso config block introduced in feat: add SSO token
exchange endpoint. Without this regen the env vars are silently
ignored and the endpoint stays in its disabled state.

* docs: add sso token exchange section for auth docs

---------

Co-authored-by: qwqcode <qwqcode@gmail.com>
2026-06-14 17:02:58 +01:00
xaoxuu 3b00a5e356 chore(docs): add xaoxuu to showcase (#1098) 2026-06-14 10:57:38 +01:00
Eser Şahinerandqwqcode 3d9c484a5d docs(i18n): add Turkish (tr-TR) language support (#1119)
* feat(i18n): add Turkish (tr-TR) language support

* chore: fix lint

---------

Co-authored-by: qwqcode <qwqcode@gmail.com>
2026-06-14 10:53:33 +01:00
Garnet dcac8cc883 chore(docs): revise French landing page translations (#1075) 2026-06-14 09:37:02 +01:00
Hsüan b39ec2a0a6 feat(db): add TLS support for MySQL with Google Cloud SQL (#1104)
* Support Google cloud SQL Mysql SSL

* fix typo
2026-05-05 21:55:54 +08:00
qwqcode 5c3df4776e feat(go): upgrade golang to v1.26.2 and some deps 2026-05-05 20:33:17 +08:00
Howie Xie 6ba19150df feat: add 'ugc' to link rel attributes (#1120) 2026-04-12 19:27:35 +08:00
Kegongteng efe62e93e2 chore(docs): Improve the transfer document (#1109) 2026-04-12 19:27:09 +08:00
她和她的猫 71a651153b chore(docs): correct docker arg name for skipping ui build (#1086) 2025-06-09 19:27:38 +08:00
ᡥᠠᡳᡤᡳᠶᠠ ᡥᠠᠯᠠ·ᠨᡝᡴᠣ 猫 b4c139bf9e fix(ui/ua): support OpenHarmony and more browsers (#1087) 2025-06-09 19:22:48 +08:00
qwqcode 3e45bb9506 chore(landing): improve landing page and docs 2025-02-02 16:26:20 +08:00
qwqcode 64a2adbe1a chore(ci): revise github dependabot for checking ui deps 2024-12-13 13:12:38 +08:00
qwqcode 99736db27f chore(style): fix bg color of login dialog in sidebar for dark mode 2024-11-25 00:00:51 +08:00
qwqcode eb3310c537 chore(ci): tidy up frontend build shell scripts 2024-11-24 23:53:20 +08:00
duli ab2c2cf153 chore(ci): include artalk_ui_nightly.tar.gz in nightly release (#1027) 2024-11-23 21:34:49 +08:00
qwqcode 71b73a323a chore(docs/landing): fix tech icons in landing page 2024-11-06 22:22:23 +08:00
qwqcode 659023988e docs(landing): redesign landing page (#1020) 2024-11-06 21:53:23 +08:00
qwqcode c2b9afa1a1 feat(plugins): add plugin registry fetch script
- Introduce `fetch-registry.mjs` to download `registry.json` from `ArtalkJS/Community` repo for the latest release
- Update `build:docs` script to include registry fetch
2024-10-22 11:23:35 +08:00
qwqcode 258dfde60e docs: integrate git changelog plugin 2024-10-19 16:32:34 +08:00
qwqcode 91ac907778 chore(ui/deps): upgrade deps for ui 2024-10-19 16:25:48 +08:00
qwqcode 73da8740ee chore(docs): add frontend typedoc 2024-10-19 14:34:01 +08:00
qwqcode 4ad0cc1366 chore(i18n): update terminology for clarity
- Change 'Frontend' to 'Client'
- Change 'Backend' to 'Server'
2024-10-19 14:09:59 +08:00
Lance.Moe 15f2fb82a3 chore(docs): corrected react usage example (#1012) 2024-10-19 13:11:29 +08:00
qwqcode bd81b7c10c feat(ui/conf): add preferRemoteConf option (#1000) (#1011)
Add `preferRemoteConf` option (disabled by default).

Local config now takes priority, addressing the issue where remote config previously overrode local settings. (Closes #1000)

Updated config merging logic in `mount.ts`.

Deprecated `useBackendConf` in favor of `preferRemoteConf`.

(The prerequisite PR is #1007)

BREAKING CHANGE: UI client now prioritizes local config in the latest version, whereas previously, the remote config would override it. (See #1011)
2024-10-18 21:46:49 +08:00
qwqcode f6c99e949b feat(ui/eslint): update eslint-plugin-artalk to v1.0.2 (#1010)
- Add new ESLint rules for ArtalkPlugin:
  - Available rules: `noCycleDeps`, `noLifeCycleEventInNestedBlocks`, `noEventInWatchConf`, `noInjectInNestedBlocks`, `noInjectOutsidePlugin`, `onePluginPerFile`. For more details, see [eslint-plugin-artalk](https://github.com/ArtalkJS/Artalk/tree/master/ui/eslint-plugin-artalk).
- Add tests for the new ESLint rules.
- Implement Tarjan's algorithm for detecting SCCs in the `noCycleDeps` rule.
- Improve code organization and maintainability.
- Disable the 'import-x/namespace' rule for better performance. (https://github.com/import-js/eslint-plugin-import/issues/2340)
2024-10-18 19:43:01 +08:00
qwqcode dcde53f8ad refactor(ui/auth): ensure compatibility with new client types (#1009)
- Bump to new Artalk client export types
- Add `AuthContext` type definition for improved clarity
- Replace `ContextApi` with `AuthContext` for reducing the scope of variables
- Bump version to 0.0.2 for release

(The prerequisite PR is #1007)
2024-10-18 17:09:15 +08:00
qwqcode d3aacf0d14 refactor(ui/sidebar): ensure compatibility with new client types (#1008)
Since the client program already prioritizes local configuration over remote configuration by default, there's no need to use the `remoteConfModifier` magic function to alter the configuration priority.

The dependency injection function has changed from `get` to `inject`, or call a more specific method from the `Context` interface.

(The prerequisite PR is #1007)
2024-10-18 16:48:41 +08:00
qwqcode 20be96f6f8 refactor(ui): improve modularity, lifecycle, and types (#1007)
- Introduce a service dependency container for better management (#1006).
  - Replace original `services` definitions with `provide` and `inject` methods.
  - Recommend calling `inject` at the beginning of `ArtalkPlugin` to explicitly inject required dependencies.
- Replace `load` with `mount` for improved lifecycle handling.
  - Track local config.
  - Fix potential language setting not working issue. (Fixes #973)
- Refine Type definitions for clarity and maintainability.
  - See [TypeDoc](https://artalk.js.org/typedoc/) for updates.
- Enhance config logic with `ConfigManager`.
  - Deprecate `ctx.conf` and `ctx.$root`; use `getConf()` and `getEl()` instead.
  - Update client default config settings. (Fixes #1001)
- Improve event handling with `EventManager` refactor.
- Update plugin and editor architecture for greater flexibility.
  - Further eliminate global variables and avoid directly passing the `Context` interface as a function parameter. Instead, use more explicit and specific types and parameters.
- Remove deprecated and redundant code for a cleaner structure.

BREAKING CHANGE: 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/).
2024-10-18 16:40:18 +08:00
qwqcode 437892cdfa feat(ui): add dependency injection container (#1006)
- Implement `createInjectionContainer` for managing service lifecycles
- Implement `provide` and `inject` for dependency injection
- Support singleton and transient service lifecycles
- Add unit tests for container functionality
2024-10-17 22:53:25 +08:00
qwqcode 5065bb4763 chore: update CONTRIBUTING.md 2024-10-11 13:31:20 +08:00
Limeandqwqcode baa63866f4 feat(ui/page_vote): add page voting feature (#983)
* feat(plugin/page_vote): add `page_vote` plugin

* feat(plugin/page_vote): update vote count text

adjust code structure
disable `page vote` plugin by default

* feat(plugin/page_vote): reuse actions btn style

* perf(plugin/page-vote): make type strong

* feat: watch `darkMode` conf

* doc(plugin/page_vote): add config doc

* feat(ui/page_vote): add page voting feature

- Introduce page voting functionality
- Update configuration to enable/disable page voting
- Retrieve html elements for voting buttons and counts
- Enhance customization options for voting elements
- Reference: #983, #998, #997

---------

Co-authored-by: qwqcode <qwqcode@gmail.com>
2024-10-08 12:38:39 +08:00
qwqcode 7c251fd067 feat(api/vote): add get vote status endpoint (#998)
A new HTTP API endpoint `GET /votes/:target_name/:target_id` is now available. (See https://artalk.js.org/http-api.html#tag/Vote/operation/GetVote)

- Implement GET endpoint for retrieving vote status
- Add tests for vote functionality

Related to: #997

This is a prerequisite PR for #983.
2024-10-06 22:31:59 +08:00
qwqcode 02a369fe5c refactor(api/vote): refactor vote http endpoint and logic (#997)
The original `POST /votes/:type/:target_id` endpoint for creating votes has been updated to `POST /votes/:target_name/:target_id/:choice`. The `target_name` can be either `comment` or `page`, the `target_id` is a numeric value, and the `choice` can be `up` or `down`. (https://artalk.js.org/http-api.html#tag/Vote/operation/CreateVote)

The HTTP response now includes two additional fields: `is_up` and `is_down`, which indicate the user's current voting state. The `up` and `down` fields remain unchanged and continue to be of numeric type, ensuring backward compatibility with existing clients.

- Change vote endpoint to include `{target_name}` and `{choice}`
- Update vote logic to handle `up` and `down` choices
- Modify request data models and API interfaces for new vote structure
- Ensure backward compatibility with existing vote response data
- Improve vote handling and response structure

BREAKING CHANGE: Vote HTTP API endpoint have been updated. (See #997)
2024-10-06 22:15:15 +08:00
qwqcode bd0db8cd57 chore(docs): revise ui config docs 2024-10-06 21:45:01 +08:00
qwqcode 206a26d0c1 feat(ui): add npm published version check script
- Introduce check:publish script to verify npm versions
- Ensure all projects have the latest versions on npm
- Skip private packages during version checks
- Allow filtering by specific project name with -F option
2024-10-05 21:13:02 +08:00
qwqcode f1ae566134 refactor(ui/conf): introduce DeepPartial type for ui config (#996)
- Replace `Partial<ArtalkConfig>` with `ArtalkConfigPartial` for better type clarity.
- Introduce `DeepPartial` type for nested partial configurations.
- Update default configuration handling to use `RequiredExcept` and `FunctionKeys`.
- Adjust related functions and interfaces to use `ArtalkConfigPartial`.
2024-10-05 20:33:06 +08:00
qwqcode 3571a18b42 refactor(ui/comment): update reply button to use component
- Replaced manual reply button creation with ActionBtn component for consistency
- Moved styling from comment.scss to main.scss for common action button styles
2024-10-05 19:36:49 +08:00
qwqcode abac3f0f36 feat(ui/eslint): add eslint plugin for artalk ui development (#995)
- Implemented `eslint-plugin-artalk` to enforce Artalk's development conventions.
- Added setup script for UI development environment.
- Created test cases for Artalk plugin rules.
- Configured TypeScript and build settings for the plugin.
- Updated documentation with installation and configuration instructions.
2024-10-05 19:19:22 +08:00
qwqcode db960aa024 chore(deps): upgrade go deps 2024-10-04 00:31:31 +08:00
qwqcode 1b5ef279bb chore(ci/tests): fix branch filters and improve caching 2024-10-04 00:31:03 +08:00
qwqcode 754223571a chore(ci/tests): switch tests from circle ci to github actions 2024-10-03 23:02:50 +08:00
qwqcode a6eba361fc chore(ci): update branch ignore patterns on circle ci 2024-10-03 21:34:24 +08:00
qwqcode 1d5811a59c feat(config): enhance config file and data dir retrieval (#992)
* feat(config): enhance config file and data dir retrieval

- Add xdg package for XDG base directory support
- Update RetrieveConfigFile to search in XDG_CONFIG_HOME and /etc
- Add RetrieveDataDir to find data directory in XDG_DATA_HOME and /var/lib
- Improve comments for better understanding of search order

* add unit tests
2024-10-03 15:04:42 +08:00
qwqcode ea9c5d9ecb chore(docs): add install guide for fedora linux distro 2024-10-01 14:47:20 +08:00
qwqcode 8707f4f327 docs(landing): add more translations 2024-09-20 21:03:56 +08:00
qwqcode b89a1866f9 docs(i18n): add zh-TW translation for config template 2024-09-20 21:03:12 +08:00
qwqcode 7e082158cd chore(docs): add go reference badge in README.md 2024-09-19 21:41:48 +08:00
qwqcode d5d458de11 docs(landing): optimize responsive font size 2024-09-19 21:17:38 +08:00
qwqcode c087400a66 chore(docs): set up redirects for old pages 2024-09-19 21:14:07 +08:00
qwqcode 34d793697c chore(docs): refine deployment documentation 2024-09-19 18:27:24 +08:00
481 changed files with 23517 additions and 10431 deletions

No files matched your search

-70
View File
@@ -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
-27
View File
@@ -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
+13
View File
@@ -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
+8 -2
View File
@@ -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
View File
@@ -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:
+62 -42
View File
@@ -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 }}
+250 -44
View File
@@ -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"
+12 -3
View File
@@ -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
View File
@@ -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"
+223 -19
View File
@@ -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 }}
+41 -12
View File
@@ -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
View File
@@ -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
+13 -4
View File
@@ -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}}"
+16 -7
View File
@@ -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
View File
@@ -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 }}"
+13 -1
View File
@@ -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 }}
+119
View File
@@ -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 }}
+18 -9
View File
@@ -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
+86
View File
@@ -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
+47 -15
View File
@@ -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
+46
View File
@@ -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
+4
View File
@@ -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
View File
@@ -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
+1
View File
@@ -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
+2 -2
View File
@@ -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,
+99
View File
@@ -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
View File
@@ -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
View File
@@ -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 ..."`
+16 -4
View File
@@ -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
View File
@@ -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
[![npm version](https://img.shields.io/npm/v/artalk.svg?style=flat-square)](https://www.npmjs.com/package/artalk)
[![npm downloads](https://img.shields.io/npm/dt/artalk.svg?style=flat-square)](https://www.npmjs.com/package/artalk)
[![Docker Pulls](https://img.shields.io/docker/pulls/artalk/artalk-go?style=flat-square)](https://hub.docker.com/r/artalk/artalk-go)
[![Go Reference](https://pkg.go.dev/badge/github.com/artalkjs/artalk/v2.svg)](https://pkg.go.dev/github.com/artalkjs/artalk/v2)
[![Go Report Card](https://goreportcard.com/badge/github.com/ArtalkJS/Artalk?style=flat-square)](https://goreportcard.com/report/github.com/ArtalkJS/Artalk)
[![CircleCI](https://img.shields.io/circleci/build/gh/ArtalkJS/Artalk?style=flat-square)](https://circleci.com/gh/ArtalkJS/Artalk/tree/master)
[![Codecov](https://img.shields.io/codecov/c/gh/ArtalkJS/Artalk?style=flat-square)](https://codecov.io/gh/ArtalkJS/Artalk)
[![npm bundle size](https://img.shields.io/bundlephobia/minzip/artalk?style=flat-square)](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://contrib.rocks/image?repo=ArtalkJS/Artalk)](https://github.com/ArtalkJS/Artalk/graphs/contributors)
## サポーター
[![Stargazers repo roster for @ArtalkJS/Artalk](https://reporoster.com/stars/ArtalkJS/Artalk)](https://github.com/ArtalkJS/Artalk/stargazers)
## Repobeats アナリティクス
![Alt](https://repobeats.axiom.co/api/embed/a9fc9191ac561bc5a8ee2cddc81e635ecaebafb6.svg 'Repobeats analytics image')
## 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>
[![Stargazers over time](https://starchart.cc/ArtalkJS/Artalk.svg)](https://starchart.cc/ArtalkJS/Artalk)
## ライセンス
[MIT](./LICENSE)
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FArtalkJS%2FArtalk.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2FArtalkJS%2FArtalk?ref=badge_shield)
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FArtalkJS%2FArtalk.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2FArtalkJS%2FArtalk?ref=badge_large)
+2 -1
View File
@@ -7,12 +7,13 @@
[![npm version](https://img.shields.io/npm/v/artalk.svg?style=flat-square)](https://www.npmjs.com/package/artalk)
[![npm downloads](https://img.shields.io/npm/dt/artalk.svg?style=flat-square)](https://www.npmjs.com/package/artalk)
[![Docker Pulls](https://img.shields.io/docker/pulls/artalk/artalk-go?style=flat-square)](https://hub.docker.com/r/artalk/artalk-go)
[![Go Reference](https://pkg.go.dev/badge/github.com/artalkjs/artalk/v2.svg)](https://pkg.go.dev/github.com/artalkjs/artalk/v2)
[![Go Report Card](https://goreportcard.com/badge/github.com/ArtalkJS/Artalk?style=flat-square)](https://goreportcard.com/report/github.com/ArtalkJS/Artalk)
[![CircleCI](https://img.shields.io/circleci/build/gh/ArtalkJS/Artalk?style=flat-square)](https://circleci.com/gh/ArtalkJS/Artalk/tree/master)
[![Codecov](https://img.shields.io/codecov/c/gh/ArtalkJS/Artalk?style=flat-square)](https://codecov.io/gh/ArtalkJS/Artalk)
[![npm bundle size](https://img.shields.io/bundlephobia/minzip/artalk?style=flat-square)](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
View File
@@ -7,12 +7,13 @@
[![npm version](https://img.shields.io/npm/v/artalk.svg?style=flat-square)](https://www.npmjs.com/package/artalk)
[![npm downloads](https://img.shields.io/npm/dt/artalk.svg?style=flat-square)](https://www.npmjs.com/package/artalk)
[![Docker Pulls](https://img.shields.io/docker/pulls/artalk/artalk-go?style=flat-square)](https://hub.docker.com/r/artalk/artalk-go)
[![Go Reference](https://pkg.go.dev/badge/github.com/artalkjs/artalk/v2.svg)](https://pkg.go.dev/github.com/artalkjs/artalk/v2)
[![Go Report Card](https://goreportcard.com/badge/github.com/ArtalkJS/Artalk?style=flat-square)](https://goreportcard.com/report/github.com/ArtalkJS/Artalk)
[![CircleCI](https://img.shields.io/circleci/build/gh/ArtalkJS/Artalk?style=flat-square)](https://circleci.com/gh/ArtalkJS/Artalk/tree/master)
[![Codecov](https://img.shields.io/codecov/c/gh/ArtalkJS/Artalk?style=flat-square)](https://codecov.io/gh/ArtalkJS/Artalk)
[![npm bundle size](https://img.shields.io/bundlephobia/minzip/artalk?style=flat-square)](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
View File
@@ -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)
}
+72
View File
@@ -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)
}
+3
View File
@@ -220,6 +220,9 @@ auth:
client_id: ""
client_secret: ""
domain: ""
sso:
enabled: false
issuer: ""
frontend:
placeholder: ""
noComment: ""
+456
View File
@@ -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: []
+9 -2
View File
@@ -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
+7
View File
@@ -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:
+464
View File
@@ -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
View File
@@ -1,2 +1,3 @@
.vitepress/dist/**
.vitepress/cache/**
.vitepress/.temp/**
+9 -7
View File
@@ -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',
},
{
+39 -7
View File
@@ -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(),
],
},
})
+3 -1
View File
@@ -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' },
{
+1 -1
View File
@@ -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)
+4
View File
@@ -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 -1
View File
@@ -1,3 +1,3 @@
{
"latest": "2.9.1"
"latest": "2.10.0"
}
+4
View File
@@ -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:
+30 -25
View File
@@ -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
+2 -1
View File
@@ -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**
+10
View File
@@ -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
+1
View File
@@ -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.
+4 -1
View File
@@ -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
View File
@@ -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
[![Packaging status](https://repology.org/badge/vertical-allrepos/artalk.svg)](https://repology.org/project/artalk/versions)
[![Copr status](https://img.shields.io/badge/dynamic/json?color=blue&label=Fedora%20Copr&query=builds.latest.source_package.version&url=https%3A%2F%2Fcopr.fedorainfracloud.org%2Fapi_3%2Fpackage%3Fownername%3D%40artalk%26projectname%3Dartalk%26packagename%3Dartalk%26with_latest_build%3DTrue)](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
View File
@@ -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) |
+7 -3
View File
@@ -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
```
+56
View File
@@ -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).
+31 -10
View File
@@ -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.
+7 -6
View File
@@ -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 -1
View File
@@ -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 |
| --- | --- |
+123
View File
@@ -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',
},
})
```
+314
View File
@@ -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).
+6 -3
View File
@@ -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...]
![](/images/transfer/commento.png)
### 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.
![](/images/transfer/twikoo-control.png)
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

+7 -9
View File
@@ -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"
}
}
+11 -1
View File
@@ -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
+6 -3
View File
@@ -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"]
}
-592
View File
@@ -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 | 否 | 导入数据 |
**响应** 无
-3
View File
@@ -1,3 +0,0 @@
# HTTP API
参考:<a href="/http-api.html" target="_blank">Artalk API 文档 (OpenAPI)</a>
+62 -16
View File
@@ -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>。再次感谢你的支持,我们期待与你合作。
+4
View File
@@ -1,5 +1,9 @@
# Frontend API
以下列出了一些主要的 Artalk 客户端 API,用于在前端代码中操作 Artalk 实例。
更多详细 API 信息可以通过 [TypeDoc 文档](https://artalk.js.org/typedoc/) 浏览。
## 安装依赖
用你喜欢的包管理器安装 Artalk:
+30 -25
View File
@@ -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
+3 -2
View File
@@ -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)
**插件开发**
-7
View File
@@ -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)。
-7
View File
@@ -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>
+10
View File
@@ -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
-7
View File
@@ -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>
-7
View File
@@ -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>
-7
View File
@@ -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>
+1
View File
@@ -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 的加粗语法冲突。
+4 -1
View File
@@ -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)
## 命令行一键升级
+1
View File
@@ -36,6 +36,7 @@
- [青空之蓝](https://blog.ixk.me/)
- [Monstx](https://blog.monsterx.cn/)
- [QWQAQ](https://qwqaq.com/)
- [xaoxuu](https://xaoxuu.com)
::: tip
+97 -19
View File
@@ -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
[![Packaging status](https://repology.org/badge/vertical-allrepos/artalk.svg)](https://repology.org/project/artalk/versions)
[![Copr status](https://img.shields.io/badge/dynamic/json?color=blue&label=Fedora%20Copr&query=builds.latest.source_package.version&url=https%3A%2F%2Fcopr.fedorainfracloud.org%2Fapi_3%2Fpackage%3Fownername%3D%40artalk%26projectname%3Dartalk%26packagename%3Dartalk%26with_latest_build%3DTrue)](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>
+5
View File
@@ -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) |
+7 -3
View File
@@ -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
```
+56
View File
@@ -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) 找到。
+25 -6
View File
@@ -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
**投票按钮**
+2 -1
View File
@@ -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>
-7
View File
@@ -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>
+123
View File
@@ -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',
},
})
```
+314
View File
@@ -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)。
+8 -4
View File
@@ -138,15 +138,19 @@ typecho-20220424-202246.artrans
### Commento
你可在 Commento 后台导出 JSON 格式的数据文件,然后使用[转换工具](#转换工具)进行转换。
前往 Commento 后台,依次点击「General - Export」,导出 JSON 格式的数据文件,然后使用[转换工具](#转换工具)进行转换。
【图示,待补充...】
![](/images/transfer/commento.png)
### 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;">
![](/images/transfer/twikoo-control.png)
除此之外,对于腾讯云一键部署的Twikoo,可前往 [腾讯云后台](https://console.cloud.tencent.com/tcb) 导出 JSON 格式的评论数据。
![](/images/transfer/tencent-tcb.png)
### Artalk v1 (PHP 旧版后端)
+14 -14
View File
@@ -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