Compare commits

..
186 Commits
Author SHA1 Message Date
Mr.Hope ba8a221833 style: fix eslint 2023-12-19 21:05:54 +08:00
Mr.Hope 3f73cd9443 style: introduce prettier 2023-12-19 20:32:04 +08:00
qwqcode 7a0a9dbd92 chore(ci): fix ci pnpm version 2023-12-19 17:07:23 +08:00
dependabot[bot] 441c4fa6a9 chore(deps/ui): bump the dev-deps group in /ui with 3 updates (#698)
Bumps the dev-deps group in /ui with 3 updates: [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin), [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) and [@vue/tsconfig](https://github.com/vuejs/tsconfig).


Updates `@typescript-eslint/eslint-plugin` from 6.14.0 to 6.15.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v6.15.0/packages/eslint-plugin)

Updates `@typescript-eslint/parser` from 6.14.0 to 6.15.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v6.15.0/packages/parser)

Updates `@vue/tsconfig` from 0.5.0 to 0.5.1
- [Commits](https://github.com/vuejs/tsconfig/compare/v0.5.0...v0.5.1)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-deps
- dependency-name: "@typescript-eslint/parser"
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-deps
- dependency-name: "@vue/tsconfig"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-deps
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-19 17:04:01 +08:00
Mr.Hope a16169f69d chore(docs): fix redocly config (#696) 2023-12-19 17:00:58 +08:00
da42676656 chore(deps): bump frontend deps (#679)
* chore: bump deps

* chore: add exports field

* chore: remove src folder

* fix: vite cjs deprecated warning

* chore(ci): fix test ci

* chore: update linter

---------

Co-authored-by: qwqcode <qwqcode@gmail.com>
Co-authored-by: qwqcode <22412567+qwqcode@users.noreply.github.com>
2023-12-19 16:54:34 +08:00
qwqcode f3a42e892f chore(ci): fix test ci 2023-12-18 23:59:18 +08:00
Mr.Hope 6a53597485 chore(docs): update README.md (#691) 2023-12-18 23:19:46 +08:00
qwqcode 60336e567d test(ci): migrate UI tests back to Github Actions (#692) 2023-12-18 23:16:16 +08:00
Mr.Hopeandqwqcode 8918c62ffd chore: update description and fix typos (#684)
* chore: update description

* chore: fix typos

* chore: tweaks

* chore: fix case

---------

Co-authored-by: qwqcode <qwqcode@gmail.com>
2023-12-18 21:21:42 +08:00
qwqcode 362fc19cd8 feat: patch locale config from zh-cn to zh-CN (#678) 2023-12-18 20:17:04 +08:00
qwqcode 1b938752ff fix(ui/dark-mode): potential memory leak issue in auto mode (#688) 2023-12-18 19:14:58 +08:00
qwqcode 835a886dc8 test(ssg): add SSG test case (#675) 2023-12-18 19:09:04 +08:00
qwqcode f390b57fe7 refactor(ui): move window references into function scope (#675) 2023-12-18 19:05:57 +08:00
qwqcode 6d21a0f204 refactor(ui/api): losing coupling between Api and User 2023-12-18 18:37:18 +08:00
qwqcode a1d0c37828 refactor(ui/user): user class no longer globally singleton 2023-12-18 18:37:18 +08:00
github-actions[bot] 763fbc417e chore: release v2.7.1 2023-12-17 17:24:29 +00:00
qwqcode 776d5e66fe docs(i18n): add translation of update notice 2023-12-18 01:22:53 +08:00
qwqcode 10c0975ce0 fix(build): generate source maps to ease debugging 2023-12-18 00:53:54 +08:00
qwqcode 7286642fc9 fix(ui): static method export issue in JS module (#669) 2023-12-18 00:46:56 +08:00
qwqcode 8db4fb3524 fix(cout-widget): loadCountWidget issue 2023-12-17 23:50:44 +08:00
qwqcode 08be04979d chore(docs): bump Artalk to v2.7.0 2023-12-17 20:33:21 +08:00
github-actions[bot] d7ea9ba43f chore: release v2.7.0 2023-12-17 11:57:30 +00:00
qwqcode bad248534c docs: revise and add more examples
BREAKING CHANGE: The following top-level functions exported by the 'artalk' npm package have been deprecated: `Artalk.update`, `Artalk.reload`, and `Artalk.destroy`. These methods now require invocation on an instance created by either `Artalk.init` or `new Artalk`. Please utilize instance-level methods instead, such as `artalkInstance.update`. For more information, refer to  [the documentation](https://artalk.js.org/guide/frontend/import-framework.html). The update was implemented to enable the concurrent creation of multiple instances, adapting to situations where Vue components are simultaneously referenced across various pages. Initially, only a singular instance was permitted to mitigate memory leak concerns. However, this proved limiting for scenarios involving the caching of multiple component instances through 'keep-alive.' To better suit intricate SPA application needs, the choice was made to permit the creation of multiple independent instances. It's crucial to remember to manually invoke the `artalk.destroy` method when releasing components to avoid memory leaks (Issue #660).
2023-12-17 19:16:33 +08:00
Zijing Zhang f56ac4eabc perf(cache): implement GC feature for simple_cache pkg (#656)
* feat: garbage collection feature

A new `GC()` method has been added to the `Cache` struct,
which performs garbage collection by deleting expired entries from the
cache.
2023-12-17 02:32:47 +08:00
qwqcode bbe2e8416c chore(docs): more description for VitePress example 2023-12-17 02:23:43 +08:00
qwqcode d44e79b1f4 docs(import): update the examples in import-framework.md (#665) 2023-12-17 01:54:21 +08:00
qwqcode e816dd686c fix(api/img-upload): public_path config enables the use of full url (#659) (#664) 2023-12-17 01:29:59 +08:00
qwqcode c9ea8f6162 fix(lifecycle): create multi-instances at the same time (#660) (#663) 2023-12-17 01:16:27 +08:00
qwqcode 87102cd3b0 refactor(ui/layer): better layer implements and independence (#662) 2023-12-17 01:10:22 +08:00
qwqcode 8b6bab828d fix(ui/dark-mode): setDarkMode cannot save to instance config (#661) 2023-12-17 01:04:30 +08:00
qwqcode 9fb658599a test(vue): add test cases for vue framework 2023-12-17 00:48:24 +08:00
qwqcode f8c33003a8 chore(docs): replace artalk to npm import in docs 2023-12-15 20:20:24 +08:00
qwqcode b97136b83f chore(docs): tidy up documentation 2023-12-13 19:09:14 +08:00
qwqcode df3e983e7f docs(conf): disable frontend.uaBadge config option by default 2023-12-13 19:06:57 +08:00
qwqcode 1b878fb3cc fix(ui): scroll to the center of the viewport issue 2023-12-13 18:57:03 +08:00
LeenHawk aa87ab94f5 docs(deploy): add deployment guide for render.com (#649)
* 添加render.com部署指南文章链接

* Update
2023-11-21 21:38:26 +08:00
qwqcode 4e9afd9761 fix(ui/editor): fix position of the comment box when replying (#643) (#648) 2023-11-20 19:21:44 +08:00
qwqcode 2b7d0ecb72 chore(ci): integrate with codecov 2023-11-19 23:35:12 +08:00
qwqcode 603c7d9439 docs: provide clearer and more detailed instructions in CONTRIBUTING.md 2023-11-05 22:48:00 +08:00
qwqcode fb93231b35 feat(ui/test): add end-to-end (e2e) testing using Playwright 2023-11-05 22:45:53 +08:00
qwqcode fed83263e2 fix(notify_pusher): panic when admin ids array is empty (#634) 2023-11-03 10:39:56 +08:00
qwqcode 9dc2d83486 chore(dev): fix .eslintignore in vscode
Add this config in `.vscode/settings.json`: `{"eslint.workingDirectories": ["./path/to/nested/front/project"]}` In this case, your `.eslintignore` file should be located in the nested path location (you have a front project in a nested folder inside the main project at root).
2023-11-01 10:59:58 +08:00
qwqcode d918caaf81 fix(ui/conf): sanitize noComment conf option for security (#624) 2023-10-31 20:11:47 +08:00
qwqcode d44212bd90 refactor(ui/marked): separate markdown related codes 2023-10-30 20:13:18 +08:00
qwqcode 37d4ca76fa refactor(ui): reduce coupling with ContextApi 2023-10-30 19:50:32 +08:00
qwqcode 8cc39f322c chore(ui/deps): downgrade eslint-import-resolver-typescript
Downgrade `eslint-import-resolver-typescript` to version 2.7.0 to resolve linting issue on Windows development environment.

https://github.com/ArtalkJS/Artalk/discussions/621#discussioncomment-7416173
2023-10-29 22:59:47 +08:00
qwqcode 2b4e80651a feat(ui): support scrollRelativeTo config option 2023-10-29 21:52:59 +08:00
qwqcode 415fa0f2e3 chore(ui/sidebar): fix sidebar dependant issues caused by refactoring 2023-10-29 21:51:54 +08:00
qwqcode 91809002d7 chore(ui): fix issues caused by refactoring 2023-10-29 19:01:16 +08:00
qwqcode dcaad1b2d5 chore(ui/lint): follow the import/no-cycle eslint rule 2023-10-29 13:12:36 +08:00
qwqcode 5e635bba68 chore(ui/list): let the service of list non-optional 2023-10-28 20:33:38 +08:00
qwqcode d136d086eb refactor(ui/stat): losing coupling of CountWidget 2023-10-28 20:07:55 +08:00
qwqcode 9fe765f874 refactor(ui/api): loose coupling between Api and Context 2023-10-28 00:02:17 +08:00
qwqcode 72317ead34 refactor(ui): separate standalone admin-only-elem checker 2023-10-27 22:50:24 +08:00
qwqcode 75c7914fb1 refactor(ui/types): better ArtalkType import and export (#620) 2023-10-27 22:24:53 +08:00
qwqcode 863b4deec0 refactor(ui/conf): loose coupling between config and list fetch 2023-10-27 17:23:05 +08:00
qwqcode 6e6fd8d587 refactor(ui/marked): loose coupling with marked func 2023-10-27 16:32:24 +08:00
qwqcode 1608911328 refactor(ui/list): separate list into standalone components (#618)
* refactor(ui/list): separate the functions of list

* fix

* move files
2023-10-27 14:28:58 +08:00
qwqcode 87a62961b6 fix(style/list): replace float to text-align in footer part (#619) 2023-10-27 00:24:11 +08:00
qwqcode e6a9748e70 refactor(ui/plugin): further divide functionality into plugins (#615)
* move stat to plugins folder

* move editor plugs to common plugins folder

* implement list-close-editor standalone plugin

* impl StateManager for Loose Coupling of Reply, Edit, and Submit Components

* rename

* remove Mover dep in editor edit state plugin

* rename editor state plugins

* refactor header plugin of editor

* separate header plugins

* add and remove event binding

* fix plugin btn ui refresh when conf updating

* rename editor plugins and remove plug classname suffix

* editor kit reload plugin when conf-loaded

* impl clear method in editor-kit class

* separate version-check from list

* separate unread from list

* make context maintain globally accessible `page` data

* rename plug to plugin

* wrap the options of List up

* separate functionalities of list

* rename
2023-10-20 17:14:53 +08:00
qwqcode 0f35532f0b refactor(editor): refactor plug kit and events (#613) 2023-10-17 20:11:47 +08:00
qwqcode d21e47f881 refactor(ui/event): refactor core event manager (#611) 2023-10-15 23:08:31 +08:00
qwqcode 8d11a239fa refactor(editor): refactor editor plugin manager (#609)
* refactor(editor): refactor editor plugin manager

* update

* modify editorTravelBack to editorResetUI
2023-10-15 21:05:30 +08:00
github-actions[bot] 8867eb296c chore: release v2.6.4 2023-10-12 06:19:33 +00:00
qwqcode a53ddc9f76 feat(go): upgrade golang to v1.21.3 and some deps 2023-10-12 14:14:07 +08:00
qwqcode 5ee9e76dcf fix(style): fix copyright text overlay on send button (#607) 2023-10-12 13:35:24 +08:00
qwqcode 3d5f77ab35 fix(email): sender not initialized correctly 2023-10-12 13:30:45 +08:00
qwqcode 25a4f48e5e fix(editor): fix duplicate event binding when update conf (#605) 2023-10-12 09:48:34 +08:00
qwqcode 2dc3bc8934 test(simple_cache): add unit test 2023-09-25 21:15:58 +08:00
qwqcode 7b1bb25d6a test(image_captcha): add unit test 2023-09-25 21:07:05 +08:00
qwqcode 9bed5be236 test(email): add unit test for queue 2023-09-25 20:54:22 +08:00
qwqcode ec0ef12b0e refactor(anti_spam): refactor anti_spam pkg and add unit test (#589)
* refactor(anti_spam): refactor anti_spam pkg and add unit test

* add more test
2023-09-21 21:48:56 +08:00
github-actions[bot] 5d2950f842 chore: release v2.6.3 2023-09-20 08:40:36 +00:00
qwqcode 25c2f30165 chore(deps): upgrade go app deps 2023-09-20 16:36:33 +08:00
qwqcode d8b49773b3 feat(config): add db.ssl to enable db ssl mode (#587)
* feat(config): add `db.ssl` to enable db ssl mode

* update artalk.example.simple.yml

* add docs

* modify conf
2023-09-20 16:19:19 +08:00
qwqcode 2c8107a4b7 chore: remove page accessible_url field in database storage 2023-09-20 15:24:46 +08:00
qwqcode 9f8aba068e perf(config): add error message for timezone setting (#586)
* perf(config): add error message for timezone setting

* update
2023-09-20 15:20:48 +08:00
qwqcode e40235b1c7 fix(notify_tpl): fix template render result in notify case 2023-09-20 14:15:54 +08:00
qwqcode e050544d21 refactor(renderer): abstract func of template renderer for multi-cases (#585)
* refactor(email_renderer): abstract function of template renderer for multi-cases

* abstract template loader

* add render test

* update docs

* chore: move pkg to template

* abstract template pkg
2023-09-20 12:10:20 +08:00
qwqcode da692649bc docs: new landing page 2023-09-17 17:42:51 +08:00
github-actions[bot] a0892fc4c0 chore: release v2.6.2 2023-09-12 09:43:50 +00:00
qwqcode c1e365fdfd chore: relicense from LGPLv3 to MIT 2023-09-12 17:02:41 +08:00
qwqcode e6e6d2aa54 perf(style): modify z-index less than 5 (#578) 2023-09-12 16:41:11 +08:00
qwqcode ac70cd4c9d fix(cmd): remove app instance creation when mini boot (#577) 2023-09-12 16:35:02 +08:00
qwqcode 600030b528 chore(script): move dev config from local to data folder 2023-09-12 16:09:37 +08:00
github-actions[bot] 4e1537b4f0 chore: release v2.6.1 2023-09-11 08:37:27 +00:00
Moraxyc 8a8aac945d docs(extras): add deploy guide for railway.app (#567) 2023-09-11 16:35:55 +08:00
Moraxycandqwqcode 89be748dee docs(config): add info about env variables (#566)
* doc(config): add info about env variables

* modify

* modify

---------

Co-authored-by: qwqcode <qwqcode@gmail.com>
2023-09-11 16:34:26 +08:00
qwqcode 6c35fe9ded fix(captcha): image captcha base64 response issue (#575) 2023-09-11 15:17:11 +08:00
qwqcode 9ea6e8d981 build: fix build-app workflow by disable cache 2023-09-01 21:59:57 +08:00
github-actions[bot] 2c89c2d633 chore: release v2.6.0 2023-09-01 13:15:41 +00:00
qwqcode 5eddd9ff05 feat(config): support load env variables as config (#564) 2023-09-01 19:34:57 +08:00
qwqcode 9fdfb2a043 fix(ui): fix plug cannot disable issue (#563) 2023-09-01 17:56:45 +08:00
qwqcode 2bdb806aaf refactor(conf): better config.NewFromFile with error handle 2023-09-01 17:02:05 +08:00
qwqcode a1b3f1a1f7 fix(cache): child comment ids store after comment updated 2023-09-01 17:02:05 +08:00
qwqcode dc333e5a9a refactor(core): better AppService func with error handle 2023-09-01 17:02:04 +08:00
qwqcode f0ecff9199 fix(config): fix ip region patch 2023-09-01 17:02:04 +08:00
qwqcode e3ccc655c5 fix(limiter): limiter log ignore options method req 2023-09-01 17:02:04 +08:00
qwqcode 04a96d4ab8 chore(build): upgrade pnpm and node 2023-09-01 17:02:03 +08:00
qwqcode 71e0fcec75 fix(limiter): fix limiter always mode 2023-09-01 17:02:03 +08:00
qwqcode 963d3cf3d9 fix(cmd): fix parse root cmd global flags 2023-09-01 17:02:03 +08:00
qwqcode 2669b0efe8 perf(cmd): support command mini boot mode 2023-09-01 17:02:03 +08:00
qwqcode a8b4eb6047 test(utils): add tests for utils 2023-09-01 17:02:02 +08:00
qwqcode cc7b2f574c test(ip_region): add tests for ip_region 2023-09-01 17:02:02 +08:00
qwqcode 150de6d7c0 test(limiter): add tests for limiter 2023-09-01 17:02:02 +08:00
qwqcode 1da3f6420e test(artransfer): add tests for artransfer 2023-09-01 17:02:02 +08:00
qwqcode 919dfa43d0 test(db): add tests for db 2023-09-01 17:02:01 +08:00
qwqcode b6805ce948 fix(db): fix dsn strings for mysql connections (#541) 2023-09-01 17:02:01 +08:00
qwqcode 137610aa82 test(i18n): add tests for i18n 2023-09-01 17:02:01 +08:00
qwqcode 0cbb5b2dd1 test(core): add tests for core 2023-09-01 17:02:00 +08:00
qwqcode 9e59fefc8a test(cache): add tests for cache and dao cache adaptor 2023-09-01 17:02:00 +08:00
qwqcode 6ead92db54 refactor(cache): cache code tidy-up 2023-09-01 17:02:00 +08:00
qwqcode 388b5b8b24 refactor(cache): refactor cache pkg 2023-09-01 17:02:00 +08:00
qwqcode e15e22ecda chore: move .chglog as a subfolder to .github 2023-09-01 17:01:59 +08:00
qwqcode 8336bb0345 chore: fix ci testing 2023-09-01 17:01:59 +08:00
qwqcode fc6c6286db refactor(dao_cache): add dao_cache pkg 2023-09-01 17:01:59 +08:00
qwqcode 3af4482db6 refactor(cache): refactor cache pkg 2023-09-01 17:01:59 +08:00
qwqcode c9f8e9b824 chore: modify pkged type to fs.FS 2023-09-01 17:01:58 +08:00
qwqcode d9c2813e50 chore: improve the naming of middleware code 2023-09-01 17:01:58 +08:00
qwqcode 84d6f9728e chore: fix test app 2023-09-01 17:01:58 +08:00
qwqcode d1b730113b refactor(cors): refactor cors middleware 2023-09-01 17:01:57 +08:00
qwqcode 731be57e74 refactor(main): refactor main.go 2023-09-01 17:01:57 +08:00
qwqcode 9f7fb9dad2 refactor(server/middleware): refactor server/middleware pkg 2023-09-01 17:01:57 +08:00
qwqcode 9f81879713 refactor(server/handler): refactor server/handler pkg 2023-09-01 17:01:57 +08:00
qwqcode ff8527f007 refactor(server/common): refactor server/common pkg 2023-09-01 17:01:56 +08:00
qwqcode 2e07d2a2ca refactor(limiter): add limiter pkg 2023-09-01 17:01:56 +08:00
qwqcode 8f5a84969b refactor(ip_region): refactor ip_region pkg 2023-09-01 17:01:56 +08:00
qwqcode 1e76a4d99f refactor(email): refactor email pkg 2023-09-01 17:01:55 +08:00
qwqcode 840621a6b1 refactor(cloud): refactor cloud pkg 2023-09-01 17:01:55 +08:00
qwqcode 24485f9615 refactor(captcha): refactor captcha pkg 2023-09-01 17:01:55 +08:00
qwqcode d4f0ae8ad7 feat(simple_cache): a simple thread-safe cache with sync.Map 2023-09-01 17:01:55 +08:00
qwqcode a06928378a refactor(cache): refactor cache pkg 2023-09-01 17:01:54 +08:00
qwqcode bf93ca78c8 refactor(artransfer): refactor artransfer pkg 2023-09-01 17:01:54 +08:00
qwqcode 75180877b1 refactor(anti_spam): refactor anti_spam pkg 2023-09-01 17:01:54 +08:00
qwqcode fb4aaf7ecb refactor(cmd): refactor cmd pkg 2023-09-01 17:01:53 +08:00
qwqcode d58355209a refactor(notify): add notify_pusher pkg 2023-09-01 17:01:53 +08:00
qwqcode 8488b7e18b refactor(i18n): refactor i18n pkg 2023-09-01 17:01:53 +08:00
qwqcode e7aced94dd feat(hook): add hook pkg 2023-09-01 17:01:53 +08:00
qwqcode 3dd93c33b3 feat(utils): add common random utils 2023-09-01 17:01:52 +08:00
qwqcode bd613a3304 refactor(config): refactor config pkg 2023-09-01 17:01:52 +08:00
qwqcode 7028dcd905 refactor(log): create common log pkg 2023-09-01 17:01:52 +08:00
qwqcode 9ef063cf98 refactor(db): refactor db pkg 2023-09-01 17:01:52 +08:00
qwqcode bf13e1cdba refactor(test): create common test pkg 2023-09-01 17:01:51 +08:00
qwqcode 79f5f53886 refactor(dao): refactor dao pkg 2023-09-01 17:01:51 +08:00
qwqcode 4c951d7e44 refactor(core): refactor core pkg 2023-09-01 17:01:51 +08:00
qwqcode b034e514a7 refactor(cache): remove cached fields in struct to save memory (#555) 2023-08-23 18:06:03 +08:00
qwqcode b88a26dcae fix(log): add missing log for web server listen func (#556) 2023-08-23 10:48:21 +08:00
qwqcode ee30ba4fc8 chore(CI): increase dependabot open-pull-requests-limit 2023-08-22 17:52:17 +08:00
qwqcode a02b89ba3e chore(CI): add dependabot updates for docs 2023-08-22 17:48:17 +08:00
dependabot[bot] 2ca3ab8274 chore(deps/ui): bump the prod-deps group in /ui with 1 update (#552)
Bumps the prod-deps group in /ui with 1 update: [katex](https://github.com/KaTeX/KaTeX).

- [Release notes](https://github.com/KaTeX/KaTeX/releases)
- [Changelog](https://github.com/KaTeX/KaTeX/blob/main/CHANGELOG.md)
- [Commits](https://github.com/KaTeX/KaTeX/compare/v0.16.4...v0.16.8)

---
updated-dependencies:
- dependency-name: katex
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-deps
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-22 17:45:36 +08:00
dependabot[bot] cbf0210131 chore(deps/ui): bump the dev-deps group in /ui with 5 updates (#553)
Bumps the dev-deps group in /ui with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `20.5.1` | `20.5.2` |
| [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) | `6.4.0` | `6.4.1` |
| [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) | `6.4.0` | `6.4.1` |
| [rollup](https://github.com/rollup/rollup) | `3.28.0` | `3.28.1` |
| [@vitejs/plugin-vue](https://github.com/vitejs/vite-plugin-vue/tree/HEAD/packages/plugin-vue) | `4.3.1` | `4.3.3` |


Updates `@types/node` from 20.5.1 to 20.5.2
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `@typescript-eslint/eslint-plugin` from 6.4.0 to 6.4.1
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v6.4.1/packages/eslint-plugin)

Updates `@typescript-eslint/parser` from 6.4.0 to 6.4.1
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v6.4.1/packages/parser)

Updates `rollup` from 3.28.0 to 3.28.1
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v3.28.0...v3.28.1)

Updates `@vitejs/plugin-vue` from 4.3.1 to 4.3.3
- [Release notes](https://github.com/vitejs/vite-plugin-vue/releases)
- [Changelog](https://github.com/vitejs/vite-plugin-vue/blob/main/packages/plugin-vue/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite-plugin-vue/commits/plugin-vue@4.3.3/packages/plugin-vue)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-deps
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-deps
- dependency-name: "@typescript-eslint/parser"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-deps
- dependency-name: rollup
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-deps
- dependency-name: "@vitejs/plugin-vue"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-deps
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-22 17:45:18 +08:00
qwqcode d8d9420616 chore(CI): dependabot use groups 2023-08-22 17:40:39 +08:00
qwqcode 891f792fe4 chore(CI): modify dependabot.yml prefix config 2023-08-22 17:07:46 +08:00
qwqcode d538e47917 chore(CI): fix dependabot.yml 2023-08-22 16:50:14 +08:00
qwqcode 6c7ce98522 refactor(CI): replace renovate with github dependabot (#546) 2023-08-22 16:49:03 +08:00
qwqcode 1e839548a4 refactor(CI): replace using docker/metadata-action for the docker push (#545) 2023-08-22 16:18:20 +08:00
qwqcode 4ea45ae375 chore(ci): upgrade deps in ci 2023-08-22 12:22:50 +08:00
qwqcode 91f89c6b7d chore(deps): update deps in docs 2023-08-22 12:11:20 +08:00
qwqcode 0ac70a5a43 chore(deps): upgrade frontend deps 2023-08-22 12:00:09 +08:00
qwqcode 9ac354cb23 chore(conf): fix default gravatar mirror url for english env in conf 2023-08-21 12:16:13 +08:00
qwqcode 40e6fcd966 feat(go): upgrade golang to v1.21.0 and some deps (#543)
* Upgrade to Golang v1.21.0 and alpine v3.18
2023-08-20 20:11:10 +08:00
WayJam So 7e476ded1c docs(extras): add deploy guide for fly.io (#520) 2023-06-05 19:08:07 +08:00
github-actions[bot] a885c0a179 chore: release v2.5.5 2023-05-11 12:10:33 +00:00
qwqcode 08c0b297e5 feat(gravatar): add gravatar.params config option (#508)
* feat(gravatar): add `gravatar.params` config option

BREAKING CHANGE: The `gravatar.default` config option has been removed. Please use `gravatar.params` instead. The default value for `gravatar.params` is now `"d=mp&s=240"`.

Signed-off-by: qwqcode <qwqcode@gmail.com>

* add docs and fix sidebar

Signed-off-by: qwqcode <qwqcode@gmail.com>

---------

Signed-off-by: qwqcode <qwqcode@gmail.com>
2023-05-10 13:42:04 +08:00
qwqcode 01ed04c791 fix(editor): prevent editor reset on comment failure (#507)
Signed-off-by: qwqcode <qwqcode@gmail.com>
2023-05-10 11:40:09 +08:00
qwqcode 0135a83b49 chore(deps): upgrade frontend deps
Signed-off-by: qwqcode <qwqcode@gmail.com>
2023-05-10 11:22:12 +08:00
qwqcode b9c177bb54 feat(go): upgrade golang to v1.20.4 and some deps (#506) 2023-05-09 11:06:03 +08:00
qwqcode 00ce23bc99 chore(deps): update some deps in docs 2023-05-09 10:56:24 +08:00
qwqcode 4853b6cc5d chore(build): bump pnpm to v8.4.0 2023-05-09 10:45:42 +08:00
qwqcode 79dea425ea feat(build): add embedded fe lite and i18n scripts (#493) (#505) 2023-05-09 10:41:28 +08:00
qwqcode c20faed4e3 fix(api): comment IP data loss after editing (#504)
Signed-off-by: qwqcode <qwqcode@gmail.com>
2023-05-07 18:55:15 +08:00
qwqcode e9acd28126 chore: update renovate.json
Signed-off-by: qwqcode <qwqcode@gmail.com>
2023-04-09 20:51:54 +08:00
github-actions[bot] b71b22f5a7 chore: release v2.5.4 2023-04-09 12:34:32 +00:00
qwqcode bc7d0e09b8 chore(deps): upgrade frontend deps
Signed-off-by: qwqcode <qwqcode@gmail.com>
2023-04-09 20:14:43 +08:00
qwqcode f682ade2c9 docs: update swagger docs and some deps
Signed-off-by: qwqcode <qwqcode@gmail.com>
2023-04-09 20:07:18 +08:00
qwqcode 2b990ff433 feat(go): upgrade golang to v1.20.3 and some deps (#485)
Signed-off-by: qwqcode <qwqcode@gmail.com>
2023-04-09 20:06:00 +08:00
qwqcode 6d21c2b074 fix(CI): upgrade and pin node.js and pnpm version (#484)
Signed-off-by: qwqcode <qwqcode@gmail.com>
2023-04-09 19:46:52 +08:00
github-actions[bot] 3840720620 chore: release v2.5.3 2023-04-06 13:08:21 +00:00
qwqcode 7dd8668b6e perf(ip_region): add db file existence check and error message (#482)
Signed-off-by: qwqcode <qwqcode@gmail.com>
2023-04-06 21:05:37 +08:00
qwqcode ac4b8b0b80 fix(api/comment_add): SIGSEGV crash in comment_add (#478) (#481)
Signed-off-by: qwqcode <qwqcode@gmail.com>
2023-04-06 20:46:27 +08:00
github-actions[bot] febe890ecb chore: release v2.5.2 2023-03-19 11:49:07 +00:00
qwqcode 1be2ad4d6e perf(style): add !important for white-space css in code block (#467)
Signed-off-by: qwqcode <qwqcode@gmail.com>
2023-03-19 19:47:15 +08:00
qwqcode 848969edbc perf(notify): shorten the length of notify key (#466)
Signed-off-by: qwqcode <qwqcode@gmail.com>
2023-03-19 19:43:20 +08:00
qwqcode f0d93ad736 fix(ui): destroy instance when calling init func twice
Signed-off-by: qwqcode <qwqcode@gmail.com>
2023-03-17 15:54:08 +08:00
qwqcode 9fb17d0483 chore(typo): fix filename typo
Signed-off-by: qwqcode <qwqcode@gmail.com>
2023-03-17 09:13:53 +08:00
611 changed files with 28866 additions and 13910 deletions

No files matched your search

+9 -35
View File
@@ -2,6 +2,7 @@ version: 2.1
orbs:
node: circleci/node@5.0.3
codecov: codecov/codecov@3.3.0
executors:
base:
@@ -9,18 +10,15 @@ executors:
- image: cimg/base:stable
node:
docker:
- image: 'cimg/node:16.15.1'
- image: 'cimg/node:18.19.0'
go:
docker:
- image: cimg/go:1.20
- image: cimg/go:1.21.3
parameters:
trigger-app:
type: boolean
default: false
trigger-ui:
type: boolean
default: false
run-them-all:
type: boolean
default: false
@@ -40,40 +38,24 @@ jobs:
go version
- restore_cache:
keys:
- go-mod-1.20-{{ checksum "go.sum" }}
- go-mod-1.21.3-{{ checksum "go.sum" }}
- run:
name: Install Dependencies
command: go mod download
- save_cache:
key: go-mod-1.20-{{ checksum "go.sum" }}
key: go-mod-1.21.3-{{ checksum "go.sum" }}
paths:
- "~/go/pkg/mod"
- run:
name: Run tests
command: |
mkdir -p /tmp/test-reports
gotestsum --junitfile /tmp/test-reports/unit-tests.xml
gotestsum --junitfile /tmp/test-reports/unit-tests.xml -- -coverprofile=/tmp/test-reports/coverage.out ./...
- store_test_results:
path: /tmp/test-reports
## Frontend
test_ui:
executor: node
steps:
- node/install
- checkout
- run:
name: Print the node version
command: node -v
- run:
name: Install pnpm
command: npm i -g pnpm@7.25.0
- run:
name: Install Dependencies
command: pnpm --dir ./ui install --frozen-lockfile
- run:
name: Build Artalk
command: pnpm --dir ./ui build:all
- codecov/upload:
flags: go
file: /tmp/test-reports/coverage.out
all_projects:
executor: base
@@ -91,14 +73,6 @@ workflows:
jobs:
- test_app
build-ui:
when:
or:
- << pipeline.parameters.trigger-ui >>
- << pipeline.parameters.run-them-all >>
jobs:
- test_ui
build-shared-other:
when: << pipeline.parameters.run-them-all >>
jobs:
+1 -2
View File
@@ -8,7 +8,7 @@ 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@0.0.1
path-filtering: circleci/path-filtering@1.0.0
workflows:
setup:
@@ -17,7 +17,6 @@ workflows:
mapping: |
(cmd|internal|server|pkg|test|conf)/.* trigger-app true
main.go|go.mod|go.sum trigger-app true
(ui)/.* trigger-ui true
base-revision: master # git branch name
config-path: .circleci/conditional_config.yml
filters:
@@ -18,4 +18,4 @@
{{ end }}
{{ end -}}
{{ end -}}
{{ end -}}
{{ end -}}
File renamed without changes.
+67
View File
@@ -0,0 +1,67 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: gomod
directory: /
schedule:
interval: monthly
commit-message:
prefix: "chore(deps/go): "
groups:
deps:
patterns:
- '*'
open-pull-requests-limit: 15
- package-ecosystem: npm
directory: /ui
schedule:
interval: monthly
commit-message:
prefix: "chore(deps/ui): "
groups:
prod-deps:
dependency-type: production
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
- package-ecosystem: docker
directory: /
schedule:
interval: monthly
commit-message:
prefix: "chore(deps/docker): "
groups:
deps:
patterns:
- '*'
open-pull-requests-limit: 15
- package-ecosystem: github-actions
directory: /.github/workflows
schedule:
interval: monthly
commit-message:
prefix: "chore(deps/ci): "
groups:
deps:
patterns:
- '*'
open-pull-requests-limit: 15
-12
View File
@@ -1,12 +0,0 @@
{
"extends": ["config:base", "schedule:weekly"],
"automerge": false,
"major": {
"automerge": false
},
"branchPrefix": "chore/",
"labels": ["dependencies"],
"gomodTidy": true,
"rangeStrategy": "bump",
"reviewers": []
}
+16 -14
View File
@@ -6,7 +6,7 @@ on:
types: [artalk-release]
env:
GO_VERSION: '1.20.2'
GO_VERSION: '1.21.3'
PKG_NAME: 'github.com/ArtalkJS/Artalk'
DOCKER_IMG: ghcr.io/goreleaser/goreleaser-cross
CACHE_DIR: /tmp/cache/docker-image
@@ -30,23 +30,25 @@ jobs:
echo "VERSION=${VERSION}" >> $GITHUB_ENV
- name: Docker Image Cache
id: docker-cache
uses: actions/cache@v3
with:
path: ${{ env.CACHE_DIR }}
key: docker-image-go-cross-${{ env.GO_VERSION }}
# 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: Pull Docker Image
if: steps.docker-cache.outputs.cache-hit != 'true'
# if: steps.docker-cache.outputs.cache-hit != 'true'
run: |
docker pull "${DOCKER_IMG}:v${GO_VERSION}"
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
# 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 git-chglog
run: |
@@ -62,7 +64,7 @@ jobs:
echo "GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}" > local/.release-env
# changelog
git-chglog ${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
+3 -9
View File
@@ -62,17 +62,12 @@ jobs:
restore-keys: |
${{ runner.os }}-buildx-
# TODO: https://github.com/docker/metadata-action/pull/248
- name: Get Docker Tags and Labels
run: |
TAGS="${DOCKER_IMG}:latest,${DOCKER_IMG}:${VERSION#v},${DOCKER_IMG}:${VERSION_SHORT},${DOCKER_IMG}:sha-${COMMIT_HASH}"
echo "DOCKER_TAGS=${TAGS}" >> $GITHUB_ENV
# https://github.com/docker/metadata-action
- name: Gen docker meta
id: meta
uses: docker/metadata-action@v4
with:
context: git
images: |
${{ env.DOCKER_IMG }}
tags: |
@@ -85,15 +80,14 @@ jobs:
# https://github.com/docker/build-push-action
- name: Build and Push
id: docker_build
uses: docker/build-push-action@v3
uses: docker/build-push-action@v4
with:
builder: ${{ steps.buildx.outputs.name }}
context: ./
file: ./Dockerfile
platforms: 'linux/amd64,linux/arm64'
push: true
# tags: ${{ steps.meta.outputs.tags }}
tags: ${{ env.DOCKER_TAGS }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache
+2 -2
View File
@@ -25,12 +25,12 @@ jobs:
- name: Use pnpm
uses: pnpm/action-setup@v2
with:
version: 7
version: 8
- name: Setup node
uses: actions/setup-node@v3
with:
node-version: 16.x
node-version: 18.x
registry-url: https://registry.npmjs.org/
cache: 'pnpm'
cache-dependency-path: './ui/pnpm-lock.yaml'
+2 -2
View File
@@ -35,12 +35,12 @@ jobs:
- name: Use pnpm
uses: pnpm/action-setup@v2
with:
version: 7
version: 8
- name: Setup node
uses: actions/setup-node@v3
with:
node-version: 16.x
node-version: 18.x
registry-url: https://registry.npmjs.org/
cache: 'pnpm'
+2 -2
View File
@@ -34,7 +34,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 16.x
node-version: 18.x
registry-url: https://registry.npmjs.org/
- name: Handle Version Number
@@ -66,7 +66,7 @@ jobs:
run: |
if [ ! $(git tag -l "${VERSION}") ]; then
# prepend changelog
changelog=$(git-chglog --next-tag "${VERSION}" "${VERSION}")
changelog=$(git-chglog --config .github/chglog/config.yml --next-tag "${VERSION}" "${VERSION}")
echo -e "${changelog}\n" | cat - CHANGELOG.md > temp && mv temp CHANGELOG.md
# modify the version of `package.json`
+47
View File
@@ -0,0 +1,47 @@
name: Test Frontend
on:
push:
paths:
- 'ui/**'
pull_request:
paths:
- 'ui/**'
jobs:
test_ui:
strategy:
matrix:
node: [18, 20]
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
defaults:
run:
working-directory: ./ui
steps:
- name: Checkout Code
uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
with:
version: 8
- name: Use Node.js ${{ matrix.node }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
cache: pnpm
cache-dependency-path: './ui/pnpm-lock.yaml'
- name: Install Dependencies
run: pnpm --dir ./ui install --frozen-lockfile
- name: Package Build Test
run: pnpm build:all
# TODO: Add unit test
# - name: Unit test
# run: pnpm test
+4
View File
@@ -48,6 +48,7 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
__debug*
# frontend
node_modules
@@ -64,3 +65,6 @@ node_modules
# always compile latest version
/public/sidebar
/public/dist
playwright-report
test-results
+2 -2
View File
@@ -8,7 +8,7 @@
"mode": "exec",
"program": "${workspaceFolder}/bin/artalk",
"preLaunchTask": "Build for debugging",
"args": ["server", "-c", "${workspaceFolder}/local/local.yml"]
"args": ["server", "-c", "${workspaceFolder}/data/artalk.yml"]
}
],
}
}
+2 -1
View File
@@ -1,4 +1,5 @@
{
"eslint.workingDirectories": ["./ui"],
"cSpell.words": [
"akismet",
"artalk",
@@ -16,4 +17,4 @@
"sqlite",
"pkged"
]
}
}
-75
View File
@@ -1,75 +0,0 @@
<p align="center">
<img src="./docs/artalk.png" alt="Artalk" width="100%">
</p>
# Artalk
[![CircleCI](https://circleci.com/gh/ArtalkJS/Artalk/tree/master.svg?style=svg)](https://circleci.com/gh/ArtalkJS/Artalk/tree/master)
[![CI to Docker Hub](https://github.com/ArtalkJS/Artalk/actions/workflows/dockerhub.yml/badge.svg)](https://github.com/ArtalkJS/Artalk/actions/workflows/dockerhub.yml) [![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)
[![Docker Pulls](https://img.shields.io/docker/pulls/artalk/artalk-go?style=flat-square)](https://hub.docker.com/r/artalk/artalk-go)
> Artalk: Golang backend of Artalk.
前往:[“**官方文档 · 后端部分**”](https://artalk.js.org/guide/backend/config.html)
---
- 高效快速
- 异步执行
- 跨平台兼容
- 轻量级部署
## Supports
- 运行环境:支持 Linux, Windows, Darwin (x64 + ARM)
- 数据存储:支持 SQLite, MySQL, PostgreSQL, SQL Server
- 邮件发送:支持 SMTP, 阿里云邮件, 调用 sendmail 发送邮件
- 高效缓存:支持 Redis, Memcache, In-Memory (BigCache)
## Build
> 部署可以使用官方提供的 Docker 镜像,见:[后端部署文档](https://artalk.js.org/guide/backend/install.html)
### 编译二进制文件
```sh
$ make all
```
编译后二进制文件将输出到 `bin/` 目录下
### Docker Compose 编译运行
```sh
# 克隆项目
$ git clone https://github.com/ArtalkJS/Artalk
$ cd Artalk
# 构建镜像
$ docker compose build
# 运行
$ docker compose up -d
```
### Docker 镜像构建
```sh
# 克隆项目
$ git clone https://github.com/ArtalkJS/Artalk
$ cd Artalk
# 构建镜像
$ make docker-build
# 发布镜像
$ make docker-push
```
## TODOs
Reference to https://github.com/ArtalkJS/Artalk#todos
## License
[![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)
+250
View File
@@ -1,4 +1,254 @@
## [v2.7.1](https://github.com/ArtalkJS/Artalk/compare/v2.7.0...v2.7.1) (2023-12-17)
### Bug Fixes
* **build:** generate source maps to ease debugging
* **cout-widget:** `loadCountWidget` issue
* **ui:** static method export issue in JS module ([#669](https://github.com/ArtalkJS/Artalk/issues/669))
### Documentation
* **i18n:** add translation of update notice
## [v2.7.0](https://github.com/ArtalkJS/Artalk/compare/v2.6.4...v2.7.0) (2023-12-17)
### Features
* **ui:** support `scrollRelativeTo` config option
* **ui/test:** add end-to-end (e2e) testing using Playwright
### Bug Fixes
* **api/img-upload:** `public_path` config enables the use of full url ([#659](https://github.com/ArtalkJS/Artalk/issues/659)) ([#664](https://github.com/ArtalkJS/Artalk/issues/664))
* **lifecycle:** create multi-instances at the same time ([#660](https://github.com/ArtalkJS/Artalk/issues/660)) ([#663](https://github.com/ArtalkJS/Artalk/issues/663))
* **notify_pusher:** panic when admin ids array is empty ([#634](https://github.com/ArtalkJS/Artalk/issues/634))
* **style/list:** replace `float` to `text-align` in footer part ([#619](https://github.com/ArtalkJS/Artalk/issues/619))
* **ui:** scroll to the center of the viewport issue
* **ui/conf:** sanitize `noComment` conf option for security ([#624](https://github.com/ArtalkJS/Artalk/issues/624))
* **ui/dark-mode:** `setDarkMode` cannot save to instance config ([#661](https://github.com/ArtalkJS/Artalk/issues/661))
* **ui/editor:** fix position of the comment box when replying ([#643](https://github.com/ArtalkJS/Artalk/issues/643)) ([#648](https://github.com/ArtalkJS/Artalk/issues/648))
### Performance Improvements
* **cache:** implement GC feature for simple_cache pkg ([#656](https://github.com/ArtalkJS/Artalk/issues/656))
### Code Refactoring
* **editor:** refactor editor plugin manager ([#609](https://github.com/ArtalkJS/Artalk/issues/609))
* **editor:** refactor plug kit and events ([#613](https://github.com/ArtalkJS/Artalk/issues/613))
* **ui:** reduce coupling with `ContextApi`
* **ui:** separate standalone admin-only-elem checker
* **ui/api:** loose coupling between `Api` and `Context`
* **ui/conf:** loose coupling between config and list fetch
* **ui/event:** refactor core event manager ([#611](https://github.com/ArtalkJS/Artalk/issues/611))
* **ui/layer:** better layer implements and independence ([#662](https://github.com/ArtalkJS/Artalk/issues/662))
* **ui/list:** separate list into standalone components ([#618](https://github.com/ArtalkJS/Artalk/issues/618))
* **ui/marked:** separate markdown related codes
* **ui/marked:** loose coupling with `marked` func
* **ui/plugin:** further divide functionality into plugins ([#615](https://github.com/ArtalkJS/Artalk/issues/615))
* **ui/stat:** losing coupling of `CountWidget`
* **ui/types:** better `ArtalkType` import and export ([#620](https://github.com/ArtalkJS/Artalk/issues/620))
### Documentation
* revise and add more examples
* provide clearer and more detailed instructions in CONTRIBUTING.md
* **conf:** disable `frontend.uaBadge` config option by default
* **deploy:** add deployment guide for render.com ([#649](https://github.com/ArtalkJS/Artalk/issues/649))
* **import:** update the examples in import-framework.md ([#665](https://github.com/ArtalkJS/Artalk/issues/665))
### BREAKING CHANGE
The following top-level functions exported by the 'artalk' npm package have been deprecated: `Artalk.update`, `Artalk.reload`, and `Artalk.destroy`. These methods now require invocation on an instance created by either `Artalk.init` or `new Artalk`. Please utilize instance-level methods instead, such as `artalkInstance.update`. For more information, refer to [the documentation](https://artalk.js.org/guide/frontend/import-framework.html). The update was implemented to enable the concurrent creation of multiple instances, adapting to situations where Vue components are simultaneously referenced across various pages. Initially, only a singular instance was permitted to mitigate memory leak concerns. However, this proved limiting for scenarios involving the caching of multiple component instances through 'keep-alive.' To better suit intricate SPA application needs, the choice was made to permit the creation of multiple independent instances. It's crucial to remember to manually invoke the `artalk.destroy` method when releasing components to avoid memory leaks (Issue [#660](https://github.com/ArtalkJS/Artalk/issues/660)).
## [v2.6.4](https://github.com/ArtalkJS/Artalk/compare/v2.6.3...v2.6.4) (2023-10-12)
### Features
* **go:** upgrade golang to v1.21.3 and some deps
### Bug Fixes
* **editor:** fix duplicate event binding when update conf ([#605](https://github.com/ArtalkJS/Artalk/issues/605))
* **email:** sender not initialized correctly
* **style:** fix copyright text overlay on send button ([#607](https://github.com/ArtalkJS/Artalk/issues/607))
### Code Refactoring
* **anti_spam:** refactor anti_spam pkg and add unit test ([#589](https://github.com/ArtalkJS/Artalk/issues/589))
## [v2.6.3](https://github.com/ArtalkJS/Artalk/compare/v2.6.2...v2.6.3) (2023-09-20)
### Features
* **config:** add `db.ssl` to enable db ssl mode ([#587](https://github.com/ArtalkJS/Artalk/issues/587))
### Bug Fixes
* **notify_tpl:** fix template render result in notify case
### Performance Improvements
* **config:** add error message for timezone setting ([#586](https://github.com/ArtalkJS/Artalk/issues/586))
### Code Refactoring
* **renderer:** abstract func of template renderer for multi-cases ([#585](https://github.com/ArtalkJS/Artalk/issues/585))
### Documentation
* new landing page
## [v2.6.2](https://github.com/ArtalkJS/Artalk/compare/v2.6.1...v2.6.2) (2023-09-12)
### Bug Fixes
* **cmd:** remove app instance creation when mini boot ([#577](https://github.com/ArtalkJS/Artalk/issues/577))
### Performance Improvements
* **style:** modify `z-index` less than 5 ([#578](https://github.com/ArtalkJS/Artalk/issues/578))
## [v2.6.1](https://github.com/ArtalkJS/Artalk/compare/v2.6.0...v2.6.1) (2023-09-11)
### Bug Fixes
* **captcha:** image captcha base64 response issue ([#575](https://github.com/ArtalkJS/Artalk/issues/575))
### Documentation
* **config:** add info about env variables ([#566](https://github.com/ArtalkJS/Artalk/issues/566))
* **extras:** add deploy guide for railway.app ([#567](https://github.com/ArtalkJS/Artalk/issues/567))
## [v2.6.0](https://github.com/ArtalkJS/Artalk/compare/v2.5.5...v2.6.0) (2023-09-01)
### Features
* **config:** support load env variables as config ([#564](https://github.com/ArtalkJS/Artalk/issues/564))
* **go:** upgrade golang to v1.21.0 and some deps ([#543](https://github.com/ArtalkJS/Artalk/issues/543))
* **hook:** add hook pkg
* **simple_cache:** a simple thread-safe cache with sync.Map
* **utils:** add common random utils
### Bug Fixes
* **cache:** child comment ids store after comment updated
* **cmd:** fix parse root cmd global flags
* **config:** fix ip region patch
* **db:** fix dsn strings for mysql connections ([#541](https://github.com/ArtalkJS/Artalk/issues/541))
* **limiter:** limiter log ignore options method req
* **limiter:** fix limiter always mode
* **log:** add missing log for web server listen func ([#556](https://github.com/ArtalkJS/Artalk/issues/556))
* **ui:** fix plug cannot disable issue ([#563](https://github.com/ArtalkJS/Artalk/issues/563))
### Performance Improvements
* **cmd:** support command mini boot mode
### Code Refactoring
* **CI:** replace using `docker/metadata-action` for the docker push ([#545](https://github.com/ArtalkJS/Artalk/issues/545))
* **CI:** replace renovate with github dependabot ([#546](https://github.com/ArtalkJS/Artalk/issues/546))
* **anti_spam:** refactor anti_spam pkg
* **artransfer:** refactor artransfer pkg
* **cache:** remove cached fields in struct to save memory ([#555](https://github.com/ArtalkJS/Artalk/issues/555))
* **cache:** cache code tidy-up
* **cache:** refactor cache pkg
* **cache:** refactor cache pkg
* **cache:** refactor cache pkg
* **captcha:** refactor captcha pkg
* **cloud:** refactor cloud pkg
* **cmd:** refactor cmd pkg
* **conf:** better config.NewFromFile with error handle
* **config:** refactor config pkg
* **core:** refactor core pkg
* **core:** better AppService func with error handle
* **cors:** refactor cors middleware
* **dao:** refactor dao pkg
* **dao_cache:** add dao_cache pkg
* **db:** refactor db pkg
* **email:** refactor email pkg
* **i18n:** refactor i18n pkg
* **ip_region:** refactor ip_region pkg
* **limiter:** add limiter pkg
* **log:** create common log pkg
* **main:** refactor main.go
* **notify:** add notify_pusher pkg
* **server/common:** refactor server/common pkg
* **server/handler:** refactor server/handler pkg
* **server/middleware:** refactor server/middleware pkg
* **test:** create common test pkg
### Documentation
* **extras:** add deploy guide for fly.io ([#520](https://github.com/ArtalkJS/Artalk/issues/520))
## [v2.5.5](https://github.com/ArtalkJS/Artalk/compare/v2.5.4...v2.5.5) (2023-05-10)
### Features
* **build:** add embedded fe lite and i18n scripts ([#493](https://github.com/ArtalkJS/Artalk/issues/493)) ([#505](https://github.com/ArtalkJS/Artalk/issues/505))
* **go:** upgrade golang to v1.20.4 and some deps ([#506](https://github.com/ArtalkJS/Artalk/issues/506))
* **gravatar:** add `gravatar.params` config option ([#508](https://github.com/ArtalkJS/Artalk/issues/508))
### Bug Fixes
* **api:** comment IP data loss after editing ([#504](https://github.com/ArtalkJS/Artalk/issues/504))
* **editor:** prevent editor reset on comment failure ([#507](https://github.com/ArtalkJS/Artalk/issues/507))
### BREAKING CHANGE
The `gravatar.default` config option has been removed. Please use `gravatar.params` instead. The default value for `gravatar.params` is now `"d=mp&s=240"`.
## [v2.5.4](https://github.com/ArtalkJS/Artalk/compare/v2.5.3...v2.5.4) (2023-04-09)
### Features
* **go:** upgrade golang to v1.20.3 and some deps ([#485](https://github.com/ArtalkJS/Artalk/issues/485))
### Bug Fixes
* **CI:** upgrade and pin node.js and pnpm version ([#484](https://github.com/ArtalkJS/Artalk/issues/484))
### Documentation
* update swagger docs and some deps
## [v2.5.3](https://github.com/ArtalkJS/Artalk/compare/v2.5.2...v2.5.3) (2023-04-06)
### Bug Fixes
* **api/comment_add:** SIGSEGV crash in comment_add ([#478](https://github.com/ArtalkJS/Artalk/issues/478)) ([#481](https://github.com/ArtalkJS/Artalk/issues/481))
### Performance Improvements
* **ip_region:** add db file existence check and error message ([#482](https://github.com/ArtalkJS/Artalk/issues/482))
## [v2.5.2](https://github.com/ArtalkJS/Artalk/compare/v2.5.1...v2.5.2) (2023-03-19)
### Bug Fixes
* **ui:** destroy instance when calling init func twice
### Performance Improvements
* **notify:** shorten the length of notify key ([#466](https://github.com/ArtalkJS/Artalk/issues/466))
* **style:** add `!important` for white-space css in code block ([#467](https://github.com/ArtalkJS/Artalk/issues/467))
## [v2.5.1](https://github.com/ArtalkJS/Artalk/compare/v2.5.0...v2.5.1) (2023-03-16)
### Features
+11 -11
View File
@@ -17,23 +17,23 @@ diverse, inclusive, and healthy community.
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall
- Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or advances of
- The use of sexualized language or imagery, and sexual attention or advances of
any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email address,
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email address,
without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
@@ -59,7 +59,7 @@ representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at artalkjs@gmail.com.
reported to the community leaders responsible for enforcement at <artalkjs@gmail.com>.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
+42 -15
View File
@@ -6,8 +6,8 @@ This guide is for developers who want to contribute to the project.
To develop Artalk, including the frontend and backend, you need to install the following tools:
- [Node.js](https://nodejs.org/en/) (>= 16.0.0)
- [Go](https://golang.org/) (>= 1.20)
- [Node.js](https://nodejs.org/en/) (>= 18.12.0)
- [Go](https://golang.org/) (>= 1.21)
- [Docker](https://www.docker.com/) (>= 20.10.0) (optional)
- [Docker Compose](https://docs.docker.com/compose/) (>= 1.29.0) (optional)
@@ -29,33 +29,52 @@ Enter the directory:
cd Artalk
```
### Build frontend and backend
### Build Backend
First, we need to install the dependencies for backend written in Go. Simply run the following command:
First, you need to install the dependencies for the backend written in Go. Simply run the `make install` command to install the dependencies.
```sh
make build-debug
```
Then, run the `make dev` to build and run `./bin/artalk`, and you can pass startup parameters to the program using `ARGS="version" make dev`.
This will build both the frontend and backend, with debugging symbols.
This will build the backend with debugging symbols. The binary file will be placed under the `./bin` directory.
- **Frontend** will be built under `./ui/packages/artalk` and copied to `./public` directory.
- **Backend** will be built under `./bin` directory.
The backend program will run by default on port `23366`. You can access it through a browser at `http://localhost:23366`. It's recommended not to change this port number for testing the backend program.
### Optional: Use one-key script to run a demo site
### Build Frontend
If you want to run a demo site, you can use the following command:
First, you need to install the dependencies for the frontend. Simply run the `cd ui && pnpm install` command to install the dependencies.
Then, run the `cd ui && pnpm dev` to build and run the frontend, and you can pass startup parameters to the program using `ARGS="--port 5173" pnpm dev`.
The frontend program will run by default on port `5173`, and you can access it in a browser at `http://localhost:5173`. The frontend testing client will, by default, request the backend on port `23366`, so it's essential to keep the backend on this port.
The frontend program is divided into the main program and a sidebar program, with the sidebar program running on a separate port, which is `23367`.
### Development Workflow
In most cases, to set up a complete development instance, you need to navigate to the Artalk Repo directory and then execute `make dev`. This will run the backend on port `23366`. Then, execute `cd ui && pnpm dev`, which will run the frontend on port `5173`. You can optionally execute `pnpm dev:sidebar` to run the sidebar frontend on port `23367`. For frontend development, you need to access `http://localhost:5173` in your browser to perform your development and testing.
When you make changes to the frontend code, you can build the complete frontend program using `cd ui && pnpm build:all`. The JavaScript and CSS code can be found in `ui/packages/artalk/dist`.
When you make changes to the backend code, running `make all` will build the complete backend program. Note that since the backend program also embeds the frontend code, the `scripts/build-frontend.sh` script will run during backend program building, which includes the embedded frontend main program and sidebar frontend program. If you are interested, you can explore the complete frontend build process in the `Makefile` code.
Additionally, there is automated CI on GitHub for building. You can find the relevant code in the `.github/workflows` directory.
### Optional: Use a One-Key Script to Run a Demo Site
When you access `http://localhost:5173` during frontend development, you will get a minimalist interface that only contains the Artalk program interface and not a real blog environment. If you want to make testing closer to a real environment, you can create a demo blog site using the following steps.
Run the following command:
```sh
./scripts/setup-example-site.sh
```
This script sets up a local example site for testing at `local` folder, with Artalk integrated into its theme.
This script sets up a local example site for testing at `data` folder, with Artalk integrated into its theme.
After running this script, run:
```sh
./bin/artalk server -c ./local/local.yml
./bin/artalk server -c ./data/artalk.yml
```
to start the artalk server.
@@ -70,6 +89,14 @@ email: "admin@test.com"
password: "admin"
```
## Testing
The backend Go program can be tested by running `make test` for unit testing. The test results will be outputted in the terminal. You can also execute `test-coverage` to check the code test coverage.
Frontend testing is conducted using Playwright for end-to-end (E2E) testing. To start the E2E testing, run `make test-frontend-e2e`.
Both frontend and backend testing are automated and will be performed during Git pull requests and as part of the build process using CircleCI or GitHub Actions.
## Project Structure
Artalk is a monorepo project, which means all the source code is in the same repository. However, the frontend and backend are separated. The frontend part is located in `./ui` directory.
@@ -80,7 +107,7 @@ Artalk is a monorepo project, which means all the source code is in the same rep
- `docs/` - The documentation site source code.
- `i18n/` - The translation files.
- `internal/` - The internal packages.
- `local/` - The local example site. This directory is ignored by git.
- `data/` - The local data. This directory is ignored by git.
- `public/` - The static files. Built frontend files will be copied here.
- `scripts/` - The scripts for development.
- `server/` - The source code for the server.
+3 -3
View File
@@ -1,12 +1,12 @@
### build Artalk
FROM golang:1.20.2-alpine3.17 as builder
FROM golang:1.21.3-alpine3.18 as builder
WORKDIR /source
# install tools
RUN set -ex \
&& apk add --no-cache make git gcc musl-dev nodejs bash npm\
&& npm install -g pnpm@7.25.0
&& npm install -g pnpm@8.12.1
# download go deps
# (cache by separating the downloading of deps)
@@ -23,7 +23,7 @@ RUN set -ex \
&& make build
### build final image
FROM alpine:3.17
FROM alpine:3.18
# we set the timezone `Asia/Shanghai` by default, you can be modified
# by `docker build --build-arg="TZ=Other_Timezone ..."`
+17 -161
View File
@@ -1,165 +1,21 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
The MIT License (MIT)
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Copyright (c) 2018-present, qwqcode and other contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+10 -2
View File
@@ -44,10 +44,15 @@ test:
test-coverage:
$(GOTEST) -cover ./...
test-coverage-html:
$(GOTEST) -v -coverprofile=coverage.out ./...
go tool cover -html=coverage.out
update-i18n:
go generate ./internal/i18n
update-swagger:
go install github.com/swaggo/swag/cmd/swag@latest
swag init -g server/server.go --output ./docs/swagger
docker-build:
@@ -56,6 +61,9 @@ docker-build:
docker-push:
./scripts/docker-build.sh --push
test-frontend-e2e:
./scripts/frontend-e2e-test.sh $(if $(REPORT), --show-report)
.PHONY: all install build build-frontend build-debug \
dev test test-coverage update-i18n \
docker-build docker-push;
dev test test-coverage test-coverage-html update-i18n \
docker-build docker-push test-frontend-e2e;
+5 -5
View File
@@ -4,12 +4,12 @@
# Artalk
[![](https://img.shields.io/npm/v/artalk.svg?style=flat-square)](https://www.npmjs.com/package/artalk)
[![](https://img.shields.io/npm/dt/artalk.svg?style=flat-square)](https://www.npmjs.com/package/artalk)
[![](https://badgen.net/bundlephobia/minzip/artalk?style=flat-square)](https://bundlephobia.com/package/artalk)
[![npm 版本](https://img.shields.io/npm/v/artalk.svg?style=flat-square)](https://www.npmjs.com/package/artalk)
[![npm 下载量](https://img.shields.io/npm/dt/artalk.svg?style=flat-square)](https://www.npmjs.com/package/artalk)
[![尺寸](https://badgen.net/bundlephobia/minzip/artalk?style=flat-square)](https://bundlephobia.com/package/artalk)
[![CircleCI](https://circleci.com/gh/ArtalkJS/Artalk/tree/master.svg?style=svg)](https://circleci.com/gh/ArtalkJS/Artalk/tree/master)
> 🌌 A Self-hosted comment system
> 🌌 A self-hosted comment system
[简体中文](./README.md) / [Documentation](https://artalk.js.org) / [Releases](https://github.com/ArtalkJS/Artalk/releases) / [Artalk](https://github.com/ArtalkJS/Artalk)
@@ -131,7 +131,7 @@ Thanks for the help and feedback provided by the community, if you have good sug
## License
[LGPL-3.0](./LICENSE)
[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)
+19 -9
View File
@@ -4,11 +4,13 @@
# Artalk
[![](https://img.shields.io/npm/v/artalk.svg?style=flat-square)](https://www.npmjs.com/package/artalk)
[![](https://img.shields.io/npm/dt/artalk.svg?style=flat-square)](https://www.npmjs.com/package/artalk)
[![](https://badgen.net/bundlephobia/minzip/artalk?style=flat-square)](https://bundlephobia.com/package/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)
[![CircleCI](https://circleci.com/gh/ArtalkJS/Artalk/tree/master.svg?style=svg)](https://circleci.com/gh/ArtalkJS/Artalk/tree/master)
[![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)
> 🌌 Golang 自托管评论系统
@@ -53,13 +55,19 @@
- 支持 Latex:提供集成 Katex 插件
- 使用 [Vite](https://github.com/vitejs/vite):属于开发者的极致体验
更多内容请查看:[“**介绍文档**”](https://artalk.js.org/guide/intro.html)
更多内容请查看:[**介绍文档**](https://artalk.js.org/guide/intro.html)
## 食用方针
## 安装
前往:[“**部署文档**”](https://artalk.js.org/guide/deploy.html)
参考:[**部署文档**](https://artalk.js.org/guide/deploy.html)
```bash
npm install artalk
```
```ts
import Artalk from 'artalk'
Artalk.init({
el: '#Comments',
site: 'Artalk 的博客',
@@ -74,6 +82,7 @@ docker run -d \
--name artalk \
-p 8080:23366 \
-v $(pwd)/data:/data \
--restart=always \
artalk/artalk-go
```
@@ -89,6 +98,7 @@ services:
artalk:
container_name: artalk
image: artalk/artalk-go
restart: always
ports:
- 8080:23366
volumes:
@@ -119,7 +129,7 @@ see [CONTRIBUTING.md](./CONTRIBUTING.md)
感谢社区提供的帮助与反馈,若有好的建议与意见欢迎前往 [ISSUES](https://github.com/ArtalkJS/Artalk/issues) 随时告知。
## TODOs
## TODOs
- [x] Golang 后端
- [x] 多数据库支持
@@ -219,7 +229,7 @@ see [CONTRIBUTING.md](./CONTRIBUTING.md)
## License
[LGPL-3.0](./LICENSE)
[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)
+44 -49
View File
@@ -2,72 +2,67 @@ package cmd
import (
"bufio"
"errors"
"fmt"
"os"
"strings"
"syscall"
"github.com/ArtalkJS/Artalk/internal/core"
"github.com/ArtalkJS/Artalk/internal/entity"
"github.com/ArtalkJS/Artalk/internal/i18n"
"github.com/ArtalkJS/Artalk/internal/query"
"github.com/ArtalkJS/Artalk/internal/log"
"github.com/ArtalkJS/Artalk/internal/utils"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"golang.org/x/term"
)
var adminCmd = &cobra.Command{
Use: "admin",
Short: "Create or edit an administrator account",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
core.LoadCore(cfgFile, workDir)
func NewAdminCommand(app *ArtalkCmd) *cobra.Command {
adminCmd := &cobra.Command{
Use: "admin",
Short: "Create or edit an administrator account",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("--------------------------------")
fmt.Println(" " + i18n.T("Create admin account"))
fmt.Println("--------------------------------")
fmt.Println("--------------------------------")
fmt.Println(" " + i18n.T("Create admin account"))
fmt.Println("--------------------------------")
username, email, password, err := credentials()
if err != nil {
logrus.Fatal(err)
}
findUser := query.FindUser(username, email)
if !findUser.IsEmpty() {
findUser.SetPasswordEncrypt(password)
if err := query.UpdateUser(&findUser); err != nil {
logrus.Fatal(err)
username, email, password, err := credentials()
if err != nil {
log.Fatal(err)
}
logrus.Info(i18n.T("{{name}} already exists", map[string]interface{}{"name": i18n.T("Account")}) +
", " + i18n.T("Password updated"))
return
}
findUser := app.Dao().FindUser(username, email)
if !findUser.IsEmpty() {
findUser.SetPasswordEncrypt(password)
if err := app.Dao().UpdateUser(&findUser); err != nil {
log.Fatal(err)
}
user := entity.User{
Name: username,
Email: email,
IsAdmin: true,
BadgeName: i18n.T("Admin"),
BadgeColor: "#0083FF",
}
user.SetPasswordEncrypt(password)
log.Info(i18n.T("{{name}} already exists", map[string]interface{}{"name": i18n.T("Account")}) +
", " + i18n.T("Password updated"))
return
}
if err := query.CreateUser(&user); err != nil {
logrus.Fatal(err)
}
user := entity.User{
Name: username,
Email: email,
IsAdmin: true,
BadgeName: i18n.T("Admin"),
BadgeColor: "#0083FF",
}
user.SetPasswordEncrypt(password)
fmt.Println("--------------------------------")
fmt.Println(" Name: " + username)
fmt.Println(" Mail: " + email)
fmt.Println("--------------------------------")
},
}
if err := app.Dao().CreateUser(&user); err != nil {
log.Fatal(err)
}
func init() {
rootCmd.AddCommand(adminCmd)
fmt.Println("--------------------------------")
fmt.Println(" Name: " + username)
fmt.Println(" Mail: " + email)
fmt.Println("--------------------------------")
},
}
return adminCmd
}
func credentials() (string, string, string, error) {
@@ -85,7 +80,7 @@ func credentials() (string, string, string, error) {
return "", "", "", err
}
if !utils.ValidateEmail(strings.TrimSpace(email)) {
return "", "", "", errors.New("invalid email format")
return "", "", "", fmt.Errorf("invalid email format")
}
fmt.Print(i18n.T("Enter {{name}}", map[string]interface{}{"name": i18n.T("Password")}) + ": ")
@@ -107,7 +102,7 @@ func credentials() (string, string, string, error) {
rePassword := strings.TrimSpace(string(byteRePassword))
if rePassword != password {
return "", "", "", errors.New("inconsistent password input")
return "", "", "", fmt.Errorf("inconsistent password input")
}
return strings.TrimSpace(username), strings.TrimSpace(email), password, nil
+223
View File
@@ -0,0 +1,223 @@
package cmd
import (
"fmt"
"os"
"os/signal"
"syscall"
"github.com/ArtalkJS/Artalk/internal/config"
"github.com/ArtalkJS/Artalk/internal/core"
"github.com/ArtalkJS/Artalk/internal/log"
"github.com/fatih/color"
"github.com/spf13/cobra"
)
var Version = config.Version + `/` + config.CommitHash
var Banner = `
________ ________ _________ ________ ___ ___ __
|\ __ \|\ __ \|\___ ___\\ __ \|\ \ |\ \|\ \
\ \ \|\ \ \ \|\ \|___ \ \_\ \ \|\ \ \ \ \ \ \/ /|_
\ \ __ \ \ _ _\ \ \ \ \ \ __ \ \ \ \ \ ___ \
\ \ \ \ \ \ \\ \| \ \ \ \ \ \ \ \ \ \____\ \ \\ \ \
\ \__\ \__\ \__\\ _\ \ \__\ \ \__\ \__\ \_______\ \__\\ \__\
\|__|\|__|\|__|\|__| \|__| \|__|\|__|\|_______|\|__| \|__|
Artalk (` + Version + `)
-> A Self-hosted Comment System.
-> https://artalk.js.org
`
type ArtalkCmd struct {
*core.App
cfgFile string
workDir string
RootCmd *cobra.Command
}
const BootModeKey = "BootMode"
const (
MODE_FULL_BOOT = "FULL_BOOT"
MODE_MINI_BOOT = "MINI_BOOT"
)
func New() *ArtalkCmd {
cmd := &ArtalkCmd{
RootCmd: &cobra.Command{
Use: "artalk",
Short: "Artalk: A self-hosted comment system",
Long: Banner,
Version: Version,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println(Banner)
fmt.Print("-------------------------------\n\n")
fmt.Println("NOTE: add `-h` flag to show help about any command.")
},
},
}
cmd.RootCmd.SetVersionTemplate("Artalk ({{printf \"%s\" .Version}})\n")
// 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)
}
}
return cmd
}
// 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 {
atk.RootCmd.PersistentFlags().StringVarP(&atk.cfgFile, "config", "c", "", "config file path (defaults are './artalk.yml')")
atk.RootCmd.PersistentFlags().StringVarP(&atk.workDir, "workdir", "w", "", "program working directory (defaults are './')")
return atk.RootCmd.ParseFlags(os.Args[1:])
}
func (atk *ArtalkCmd) addCommand(cmd *cobra.Command) {
originalPreRunFunc := cmd.PreRun
cmd.PreRun = func(cmd *cobra.Command, args []string) {
// ================
// APP Bootstrap
// ================
if cmd.Annotations[BootModeKey] != string(MODE_MINI_BOOT) {
// Load config
config, err := getConfig(atk.cfgFile)
if err != nil {
log.Fatal("Config fail: ", err)
}
// Create new instance
atk.App = core.NewApp(config)
// Bootstrap APP
if err := atk.App.Bootstrap(); err != nil {
panic(err)
}
}
if originalPreRunFunc != nil {
originalPreRunFunc(cmd, args) // extends original pre-run logic
}
}
atk.RootCmd.AddCommand(cmd)
}
func (atk *ArtalkCmd) mountCommands() {
atk.addCommand(NewServeCommand(atk))
atk.addCommand(NewAdminCommand(atk))
atk.addCommand(NewExportCommand(atk))
atk.addCommand(NewImportCommand(atk))
atk.addCommand(NewConfigCommand())
atk.addCommand(NewGenCommand())
atk.addCommand(NewUpgradeCommand())
atk.addCommand(NewVersionCommand())
}
func (atk *ArtalkCmd) Launch() error {
// ===================
// 1. Prepare Commands
// ===================
atk.mountCommands()
done := make(chan bool, 1) // shutdown signal
// listen for interrupt signal to gracefully shutdown the application
go func() {
sigch := make(chan os.Signal, 1)
signal.Notify(sigch, os.Interrupt, syscall.SIGTERM)
<-sigch
done <- true
}()
// ===================
// 2. Command Execute
// ===================
go func() {
if err := atk.RootCmd.Execute(); err != nil {
color.Red(err.Error())
}
done <- true
}()
<-done
// ===================
// 3. App Cleanups
// ===================
if atk.App != nil {
return atk.App.OnTerminate().Trigger(&core.TerminateEvent{
App: atk.App,
})
}
return 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 cfgFile == "" {
cfgFile = config.RetrieveConfigFile()
}
// Generate new config file when retrieve failed
if cfgFile == "" {
cfgFile = config.CONF_DEFAULT_FILENAMES[0]
core.Gen("config", cfgFile, false)
}
// Create new config instance and return
return config.NewFromFile(cfgFile)
}
// -------------------------------------------------------------------
// Shortcut Functions
// -------------------------------------------------------------------
func flag(cmd *cobra.Command, name string, defaultVal interface{}, usage string) {
f := cmd.PersistentFlags()
switch y := defaultVal.(type) {
case bool:
f.Bool(name, y, usage)
case int:
f.Int(name, y, usage)
case string:
f.String(name, y, usage)
}
}
func flagP(cmd *cobra.Command, name, shorthand string, defaultVal interface{}, usage string) {
f := cmd.PersistentFlags()
switch y := defaultVal.(type) {
case bool:
f.BoolP(name, shorthand, y, usage)
case int:
f.IntP(name, shorthand, y, usage)
case string:
f.StringP(name, shorthand, y, usage)
}
}
func flagV(cmd *cobra.Command, name string, defaultVal interface{}, usage string) {
flag(cmd, name, defaultVal, usage)
}
func flagPV(cmd *cobra.Command, name, shorthand string, defaultVal interface{}, usage string) {
flagP(cmd, name, shorthand, defaultVal, usage)
}
+35
View File
@@ -0,0 +1,35 @@
package cmd
import (
"encoding/json"
"fmt"
"github.com/ArtalkJS/Artalk/internal/log"
"github.com/spf13/cobra"
)
func NewConfigCommand() *cobra.Command {
configCmd := &cobra.Command{
Use: "config",
Short: "Output Config Information",
Run: func(cmd *cobra.Command, args []string) {
// Get config filename from cmd flags
filename, _ := cmd.Flags().GetString("config")
// Get new config instance
config, err := getConfig(filename)
if err != nil {
log.Fatal("Config fail: ", err)
}
// Output JSON of config
buf, _ := json.MarshalIndent(config, "", " ")
fmt.Println(string(buf))
},
Annotations: map[string]string{
BootModeKey: MODE_MINI_BOOT,
},
}
return configCmd
}
+47 -50
View File
@@ -8,68 +8,65 @@ import (
"time"
"github.com/ArtalkJS/Artalk/internal/artransfer"
"github.com/ArtalkJS/Artalk/internal/core"
"github.com/ArtalkJS/Artalk/internal/i18n"
"github.com/ArtalkJS/Artalk/internal/log"
"github.com/ArtalkJS/Artalk/internal/utils"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
var exportCmd = &cobra.Command{
Use: "export",
Aliases: []string{},
Short: "Artransfer - Export",
Long: "\n# Artransfer - Export\n\n See the documentation to learn more: https://artalk.js.org/guide/transfer.html",
Run: func(cmd *cobra.Command, args []string) {
core.LoadCore(cfgFile, workDir)
jsonStr, err := artransfer.ExportArtransString()
if err != nil {
logrus.Fatal(err)
}
if len(args) < 1 || args[0] == "" {
// write to stdout
fmt.Println(jsonStr)
} else {
filename := args[0]
// make sure is abs path
filename, err := filepath.Abs(filename)
func NewExportCommand(app *ArtalkCmd) *cobra.Command {
exportCmd := &cobra.Command{
Use: "export",
Aliases: []string{},
Short: "Artransfer export",
Long: "\n# Artransfer - Export\n\n See the documentation to learn more: https://artalk.js.org/guide/transfer.html",
Run: func(cmd *cobra.Command, args []string) {
jsonStr, err := artransfer.RunExportArtrans(app.Dao(), &artransfer.ExportParams{})
if err != nil {
logrus.Fatal(err)
log.Fatal(err)
}
// check dir
stat, err := os.Stat(filename)
if err == nil {
if stat.IsDir() {
filename = path.Join(filename, "backup-"+time.Now().Format("20060102-150405")+".artrans")
if len(args) < 1 || args[0] == "" {
// write to stdout
fmt.Println(jsonStr)
} else {
filename := args[0]
// make sure is abs path
filename, err := filepath.Abs(filename)
if err != nil {
log.Fatal(err)
}
}
// mkdir -p
if err := utils.EnsureDir(filepath.Dir(filename)); err != nil {
logrus.Fatal(err)
}
// check dir
stat, err := os.Stat(filename)
if err == nil {
if stat.IsDir() {
filename = path.Join(filename, "backup-"+time.Now().Format("20060102-150405")+".artrans")
}
}
// touch
f, err := os.Create(filename)
if err != nil {
logrus.Fatal(err)
}
// mkdir -p
if err := utils.EnsureDir(filepath.Dir(filename)); err != nil {
log.Fatal(err)
}
// >
_, err2 := f.WriteString(jsonStr)
if err2 != nil {
logrus.Fatal(err2)
}
// touch
f, err := os.Create(filename)
if err != nil {
log.Fatal(err)
}
logrus.Info(i18n.T("Export complete") + ": " + filename)
}
},
}
// >
_, err2 := f.WriteString(jsonStr)
if err2 != nil {
log.Fatal(err2)
}
func init() {
rootCmd.AddCommand(exportCmd)
log.Info(i18n.T("Export complete") + ": " + filename)
}
},
}
return exportCmd
}
+22 -29
View File
@@ -1,41 +1,34 @@
package cmd
import (
"os"
"github.com/ArtalkJS/Artalk/internal/core"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
var genCmd = &cobra.Command{
Use: "gen <TYPE> <DEST>",
Short: "A collection of several useful generators",
Long: "Generate some content\ne.g. `artalk gen config ./artalk.yml`",
Args: cobra.RangeArgs(1, 2),
Run: func(cmd *cobra.Command, args []string) {
// change working directory
if workDir != "" {
if err := os.Chdir(workDir); err != nil {
logrus.Fatal("change working directory error ", err)
func NewGenCommand() *cobra.Command {
genCmd := &cobra.Command{
Use: "gen <TYPE> <DEST>",
Short: "A collection of several useful generators",
Long: "Generate some content\ne.g. `artalk gen config ./artalk.yml`",
Args: cobra.RangeArgs(1, 2),
Run: func(cmd *cobra.Command, args []string) {
var (
specificPath string
isForce bool
)
if len(args) > 1 {
specificPath = args[1]
}
}
isForce, _ = cmd.Flags().GetBool("force")
var (
specificPath string
isForce bool
)
if len(args) > 1 {
specificPath = args[1]
}
isForce, _ = cmd.Flags().GetBool("force")
core.Gen(args[0], specificPath, isForce)
},
}
func init() {
rootCmd.AddCommand(genCmd)
core.Gen(args[0], specificPath, isForce)
},
Annotations: map[string]string{
BootModeKey: MODE_MINI_BOOT,
},
}
flagPV(genCmd, "force", "f", false, "Force overwrite an existing file")
return genCmd
}
+24 -25
View File
@@ -5,38 +5,37 @@ import (
"os"
"github.com/ArtalkJS/Artalk/internal/artransfer"
"github.com/ArtalkJS/Artalk/internal/core"
"github.com/ArtalkJS/Artalk/internal/i18n"
"github.com/sirupsen/logrus"
"github.com/ArtalkJS/Artalk/internal/log"
"github.com/spf13/cobra"
)
var importCmd = &cobra.Command{
Use: "import <FILENAME>",
Aliases: []string{},
Short: "Artransfer - Import",
Long: "\n# Artransfer - Import\n\n See the documentation to learn more: https://artalk.js.org/guide/transfer.html",
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
core.LoadCore(cfgFile, workDir) // load core
func NewImportCommand(app *ArtalkCmd) *cobra.Command {
importCmd := &cobra.Command{
Use: "import <FILENAME>",
Aliases: []string{},
Short: "Artransfer import",
Long: "\n# Artransfer - Import\n\n See the documentation to learn more: https://artalk.js.org/guide/transfer.html",
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
parcelFile := args[0]
if _, err := os.Stat(parcelFile); errors.Is(err, os.ErrNotExist) {
log.Fatal(i18n.T("{{name}} not found", map[string]interface{}{"name": i18n.T("File")}))
}
parcelFile := args[0]
if _, err := os.Stat(parcelFile); errors.Is(err, os.ErrNotExist) {
logrus.Fatal(i18n.T("{{name}} not found", map[string]interface{}{"name": i18n.T("File")}))
}
payload := args[1:]
payload = append(payload, "json_file:"+parcelFile)
payload := args[1:]
payload = append(payload, "json_file:"+parcelFile)
// import Artrans
params := artransfer.ArrToImportParams(payload)
params.Assumeyes, _ = cmd.Flags().GetBool("assumeyes")
artransfer.RunImportArtrans(app.Dao(), params)
// import Artrans
artransfer.RunImportArtrans(payload)
logrus.Info(i18n.T("Import complete"))
},
}
func init() {
rootCmd.AddCommand(importCmd)
log.Info(i18n.T("Import complete"))
},
}
flagPV(importCmd, "assumeyes", "y", false, "Automatically answer yes for all questions.")
return importCmd
}
-77
View File
@@ -1,77 +0,0 @@
package cmd
import (
"encoding/json"
"fmt"
"github.com/ArtalkJS/Artalk/internal/config"
"github.com/ArtalkJS/Artalk/internal/core"
"github.com/spf13/cobra"
)
var Version = config.Version + `/` + config.CommitHash
var Banner = `
________ ________ _________ ________ ___ ___ __
|\ __ \|\ __ \|\___ ___\\ __ \|\ \ |\ \|\ \
\ \ \|\ \ \ \|\ \|___ \ \_\ \ \|\ \ \ \ \ \ \/ /|_
\ \ __ \ \ _ _\ \ \ \ \ \ __ \ \ \ \ \ ___ \
\ \ \ \ \ \ \\ \| \ \ \ \ \ \ \ \ \ \____\ \ \\ \ \
\ \__\ \__\ \__\\ _\ \ \__\ \ \__\ \__\ \_______\ \__\\ \__\
\|__|\|__|\|__|\|__| \|__| \|__|\|__|\|_______|\|__| \|__|
Artalk (` + Version + `)
-> A Selfhosted Comment System.
-> https://artalk.js.org
`
var (
cfgFile string
workDir string
)
var rootCmd = &cobra.Command{
Use: "artalk",
Short: "Artalk: A Fast, Slight & Delightful Comment System",
Long: Banner,
Version: Version,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println(Banner)
fmt.Print("-------------------------------\n\n")
fmt.Println("NOTE: add `-h` flag to show help about any command.")
},
}
func Execute() {
cobra.CheckErr(rootCmd.Execute())
}
func init() {
rootCmd.SetVersionTemplate("Artalk ({{printf \"%s\" .Version}})\n")
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file path (defaults are './artalk.yml')")
rootCmd.PersistentFlags().StringVarP(&workDir, "workdir", "w", "", "program working directory (defaults are './')")
// Version Command
versionCmd := &cobra.Command{
Use: "version",
Short: "Output Version Information",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Artalk (" + Version + ")")
},
}
rootCmd.AddCommand(versionCmd)
// Config Command
configCmd := &cobra.Command{
Use: "config",
Short: "Output Config Information",
Run: func(cmd *cobra.Command, args []string) {
core.LoadConfOnly(cfgFile, workDir)
buf, _ := json.MarshalIndent(config.Instance, "", " ")
fmt.Println(string(buf))
},
}
rootCmd.AddCommand(configCmd)
}
+21 -72
View File
@@ -2,85 +2,34 @@ package cmd
import (
"fmt"
"io"
"os"
"os/signal"
"github.com/ArtalkJS/Artalk/internal/config"
"github.com/ArtalkJS/Artalk/internal/core"
"github.com/ArtalkJS/Artalk/internal/log"
"github.com/ArtalkJS/Artalk/server"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/logger"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
var serverCmd = &cobra.Command{
Use: "server",
Aliases: []string{"serve"},
Short: "Start the server",
Long: Banner,
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
core.LoadCore(cfgFile, workDir)
func NewServeCommand(app *ArtalkCmd) *cobra.Command {
var serverCmd = &cobra.Command{
Use: "server",
Aliases: []string{"serve"},
Short: "Start the server",
Long: Banner,
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println(Banner)
fmt.Print("-------------------------------\n\n")
fmt.Println(Banner)
fmt.Print("-------------------------------\n\n")
// create fiber app
// @see https://docs.gofiber.io/api/fiber#config
app := fiber.New(fiber.Config{
// @see https://github.com/gofiber/fiber/issues/426
// @see https://github.com/gofiber/fiber/issues/185
Immutable: true,
// TODO add config to set ProxyHeader
ProxyHeader: "X-Forwarded-For",
EnableIPValidation: true,
})
// logger
app.Use(logger.New(logger.Config{
Format: "[${status}] ${method} ${path} ${latency} ${ip} ${reqHeader:X-Request-ID} ${referer} ${ua}\n",
Output: io.Discard,
Done: func(c *fiber.Ctx, logString []byte) {
statusOK := c.Response().StatusCode() >= 200 && c.Response().StatusCode() <= 302
if !statusOK {
logrus.StandardLogger().WriterLevel(logrus.ErrorLevel).Write(logString)
} else {
logrus.StandardLogger().WriterLevel(logrus.DebugLevel).Write(logString)
}
},
}))
// init router
server.Init(app)
// graceful shutdown signal listen
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
<-c
fmt.Println("Gracefully shutting down...")
_ = app.Shutdown()
}()
// socket listen
listenAddr := fmt.Sprintf("%s:%d", config.Instance.Host, config.Instance.Port)
if config.Instance.SSL.Enabled {
app.ListenTLS(listenAddr, config.Instance.SSL.CertPath, config.Instance.SSL.KeyPath)
} else {
app.Listen(listenAddr)
}
// graceful shutdown tasks
fmt.Println("Running cleanup tasks...")
},
}
func init() {
rootCmd.AddCommand(serverCmd)
// init fiber app
_, err := server.Serve(app.App)
if err != nil {
log.Fatalln(err)
}
},
}
flagPV(serverCmd, "host", "", "0.0.0.0", "Listening IP")
flagPV(serverCmd, "port", "", 23366, "Listening port")
return serverCmd
}
+44 -44
View File
@@ -6,63 +6,63 @@ import (
"strings"
"github.com/ArtalkJS/Artalk/internal/config"
"github.com/ArtalkJS/Artalk/internal/core"
"github.com/ArtalkJS/Artalk/internal/i18n"
"github.com/ArtalkJS/Artalk/internal/log"
"github.com/blang/semver"
"github.com/rhysd/go-github-selfupdate/selfupdate"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
var upgradeCmd = &cobra.Command{
Use: "upgrade",
Aliases: []string{"update"},
Short: "Upgrade to the latest version",
Long: "Upgrade Artalk to the latest version, \n update source is GitHub Releases, \n update need to restart Artalk to take effect.",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
// loadCore()
core.LoadConfOnly(cfgFile, workDir)
func NewUpgradeCommand() *cobra.Command {
upgradeCmd := &cobra.Command{
Use: "upgrade",
Aliases: []string{"update"},
Short: "Upgrade to the latest version",
Long: "Upgrade Artalk to the latest version, \n update source is GitHub Releases, \n update need to restart Artalk to take effect.",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
log.Info(i18n.T("Checking for updates") + "...")
logrus.Info(i18n.T("Checking for updates") + "...")
latest, found, err := selfupdate.DetectLatest("ArtalkJS/Artalk")
if err != nil {
logrus.Fatal("Error occurred while detecting version: ", err)
}
ignoreVersionCheck, _ := cmd.Flags().GetBool("force")
if !ignoreVersionCheck {
v := semver.MustParse(strings.TrimPrefix(config.Version, "v"))
if !found || latest.Version.LTE(v) {
logrus.Println(i18n.T("Current version is the latest") + " (v" + v.String() + ")")
return
latest, found, err := selfupdate.DetectLatest("ArtalkJS/Artalk")
if err != nil {
log.Fatal("Error occurred while detecting version: ", err)
}
}
logrus.Info(i18n.T("New version available") + ": v" + latest.Version.String())
logrus.Info(i18n.T("Downloading") + "...")
ignoreVersionCheck, _ := cmd.Flags().GetBool("force")
if !ignoreVersionCheck {
v := semver.MustParse(strings.TrimPrefix(config.Version, "v"))
if !found || latest.Version.LTE(v) {
log.Println(i18n.T("Current version is the latest") + " (v" + v.String() + ")")
return
}
}
exe, err := os.Executable()
if err != nil {
logrus.Fatal("Could not locate executable path ", err)
}
log.Info(i18n.T("New version available") + ": v" + latest.Version.String())
log.Info(i18n.T("Downloading") + "...")
if err := selfupdate.UpdateTo(latest.AssetURL, exe); err != nil {
logrus.Fatal(i18n.T("Update failed")+" ", err)
}
exe, err := os.Executable()
if err != nil {
log.Fatal("Could not locate executable path ", err)
}
logrus.Println(i18n.T("Update complete"))
fmt.Println("\n-------------------------------\n v" +
latest.Version.String() +
" Release Note\n" +
"-------------------------------\n\n" +
latest.ReleaseNotes)
},
}
if err := selfupdate.UpdateTo(latest.AssetURL, exe); err != nil {
log.Fatal(i18n.T("Update failed")+" ", err)
}
func init() {
rootCmd.AddCommand(upgradeCmd)
log.Println(i18n.T("Update complete"))
fmt.Printf("\n"+
"-------------------------------\n"+
" v%s Release Note\n"+
"-------------------------------\n\n"+
"%s\n",
latest.Version.String(), latest.ReleaseNotes)
},
Annotations: map[string]string{
BootModeKey: MODE_MINI_BOOT,
},
}
flagPV(upgradeCmd, "force", "f", false, "Force upgrade ignore version comparison.")
return upgradeCmd
}
-39
View File
@@ -1,39 +0,0 @@
package cmd
import (
"github.com/spf13/cobra"
)
//// Shortcut Functions ////
func flag(cmd *cobra.Command, name string, defaultVal interface{}, usage string) {
f := cmd.PersistentFlags()
switch y := defaultVal.(type) {
case bool:
f.Bool(name, y, usage)
case int:
f.Int(name, y, usage)
case string:
f.String(name, y, usage)
}
}
func flagP(cmd *cobra.Command, name, shorthand string, defaultVal interface{}, usage string) {
f := cmd.PersistentFlags()
switch y := defaultVal.(type) {
case bool:
f.BoolP(name, shorthand, y, usage)
case int:
f.IntP(name, shorthand, y, usage)
case string:
f.StringP(name, shorthand, y, usage)
}
}
func flagV(cmd *cobra.Command, name string, defaultVal interface{}, usage string) {
flag(cmd, name, defaultVal, usage)
}
func flagPV(cmd *cobra.Command, name, shorthand string, defaultVal interface{}, usage string) {
flagP(cmd, name, shorthand, defaultVal, usage)
}
+22
View File
@@ -0,0 +1,22 @@
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
func NewVersionCommand() *cobra.Command {
versionCmd := &cobra.Command{
Use: "version",
Short: "Output version information",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Artalk (" + Version + ")")
},
Annotations: map[string]string{
BootModeKey: MODE_MINI_BOOT,
},
}
return versionCmd
}
+18 -15
View File
@@ -16,10 +16,12 @@ db:
user: "root"
password: ""
charset: "utf8mb4"
ssl: false
log:
enabled: true
filename: "./data/artalk.log"
cache:
enabled: false
type: "builtin"
expires: 30
warm_up: false
@@ -34,7 +36,6 @@ ssl:
enabled: false
cert_path: ""
key_path: ""
admin_users:
moderator:
pending_default: false
api_fail_block: false
@@ -52,7 +53,8 @@ moderator:
keywords:
enabled: false
pending: false
files: []
files:
- "./data/keywords_1.txt"
file_sep: "\n"
replac_to: "x"
captcha:
@@ -73,11 +75,21 @@ captcha:
geetest:
captcha_id: ""
captcha_key: ""
img_upload:
enabled: true
path: "./data/artalk-img/"
max_size: 5
public_path: null
upgit:
enabled: false
exec: "./upgit -c UPGIT_CONF_FILE_PATH -t /artalk-img"
del_local: true
email:
enabled: false
send_type: "smtp"
send_name: "{{reply_nick}}"
send_addr: "noreply@example.com"
mail_subject: "[{{site_name}}] You got a reply from @{{reply_nick}}"
mail_tpl: "default"
smtp:
host: "smtp.qq.com"
@@ -88,15 +100,6 @@ email:
access_key_id: ""
access_key_secret: ""
account_name: "noreply@example.com"
img_upload:
enabled: true
path: "./data/artalk-img/"
max_size: 5
public_path: null
upgit:
enabled: false
exec: "./upgit -c UPGIT_CONF_FILE_PATH -t /artalk-img"
del_local: true
admin_notify:
notify_tpl: "default"
noise_mode: false
@@ -139,10 +142,10 @@ frontend:
noComment: ""
sendBtn: ""
editorTravel: true
emoticons: ""
emoticons: "https://cdn.jsdelivr.net/gh/ArtalkJS/Emoticons/grps/default.json"
vote: true
voteDown: false
uaBadge: true
uaBadge: false
listSort: true
pvEl: "#ArtalkPV"
countEl: "#ArtalkCount"
@@ -151,8 +154,8 @@ frontend:
nestMax: 2
nestSort: DATE_ASC
gravatar:
mirror: "https://www.gravatar.com/"
default: "mp"
mirror: "https://www.gravatar.com/avatar/"
params: "d=mp&s=240"
pagination:
pageSize: 20
readMore: true
+9 -3
View File
@@ -45,6 +45,8 @@ db:
password: ""
# Database charset
charset: "utf8mb4"
# Enable SSL mode
ssl: false
# Logging
log:
@@ -55,6 +57,8 @@ log:
# Cache
cache:
# Enable cache
enabled: false
# Cache type ["redis", "memcache", "builtin"]
type: "builtin"
# Cache expiration time (in minutes)
@@ -280,7 +284,7 @@ frontend:
# Dislike button
voteDown: false
# User UA badge
uaBadge: true
uaBadge: false
# Comment sorting
listSort: true
# Page PV binding element
@@ -297,8 +301,10 @@ frontend:
nestSort: DATE_ASC
# Gravatar
gravatar:
mirror: "https://www.gravatar.com/"
default: "mp"
# API URL
mirror: "https://www.gravatar.com/avatar/"
# API parameters
params: "d=mp&s=240"
# Comment pagination
pagination:
# Number of comments per page
+9 -5
View File
@@ -42,6 +42,8 @@ db:
charset: "utf8mb4"
# 表前缀 (例如:"atk_")
table_prefix: ""
# 启用 SSL
ssl: false
# 日志
log:
@@ -52,6 +54,8 @@ log:
# 缓存
cache:
# 启用缓存
enabled: false
# 缓存类型 ["redis", "memcache", "builtin"]
type: "builtin"
# 缓存过期时间 (单位:分钟)
@@ -287,7 +291,7 @@ frontend:
# 反对按钮
voteDown: false
# 用户 UA 徽标
uaBadge: true
uaBadge: false
# 评论排序功能
listSort: true
# 页面 PV 绑定元素
@@ -302,12 +306,12 @@ frontend:
nestMax: 2
# 嵌套评论排序规则 ["DATE_ASC", "DATE_DESC", "VOTE_UP_DESC"]
nestSort: DATE_ASC
# 头像
# 头像 Gravatar
gravatar:
# Gravatar 镜像地址
# API 地址
mirror: "https://cravatar.cn/avatar/"
# 默认头像
default: "mp"
# API 参数
params: "d=mp&s=240"
# 评论分页
pagination:
# 每页评论数
+1
View File
@@ -1 +1,2 @@
shamefully-hoist=true
auto-install-peers=true
+13 -8
View File
@@ -50,7 +50,8 @@ export default defineConfig({
en: {
label: 'English',
lang: 'en',
link: '/en/'
link: 'https://artalk-js-org.translate.goog/guide/intro?_x_tr_sl=auto&_x_tr_tl=en-US&_x_tr_hl=en-US&_x_tr_pto=wapp'
// link: '/en/'
}
},
@@ -94,6 +95,7 @@ export default defineConfig({
{ text: '精简版本', link: '/guide/frontend/artalk-lite.md' },
{ text: '置入博客', link: '/guide/frontend/import-blog.md' },
{ text: '置入框架', link: '/guide/frontend/import-framework.md' },
{ text: '前端 API', link: '/develop/fe-api.md', },
{ text: '开发文档', link: '/develop/index.md', },
],
},
@@ -109,11 +111,11 @@ export default defineConfig({
text: "部署说明",
collapsed: true,
items: [
{ text: 'Docker', link: '/guide/backend/docker.md' },
{ text: '守护进程', link: '/guide/backend/daemon.md' },
{ text: '反向代理', link: '/guide/backend/reverse-proxy.md' },
{ text: '编译构建', link: '/guide/backend/build.md' },
{ text: '程序升级', link: '/guide/backend/update.md' },
{ text: 'Docker', link: '/guide/backend/docker.md' },
]
},
{
@@ -189,12 +191,15 @@ export default defineConfig({
{ icon: 'github', link: 'https://github.com/ArtalkJS/Artalk' }
],
algolia: {
appId: 'BH4D9OD16A',
apiKey: '37ab96e3f5a774cbbf0571b035b42adb',
indexName: 'artalk-js',
searchParameters: {
facetFilters: ['lang:zh-CN']
search: {
provider: 'algolia',
options: {
appId: '2WNJ32WVTY',
apiKey: '6c6ebc345a87b738264f19095b78c91c',
indexName: 'artalk-js',
searchParameters: {
facetFilters: ['lang:zh-CN']
}
}
},
+37 -27
View File
@@ -1,53 +1,63 @@
<template>
<div ref="artalkEl" style="margin-top: 20px;"></div>
<ClientOnly>
<div ref="el" style="margin-top: 20px;"></div>
</ClientOnly>
</template>
<script setup lang="ts">
import { watch, nextTick, ref, onMounted } from 'vue'
import { watch, nextTick, ref, onMounted, onUnmounted } from 'vue'
import { useData, useRouter } from 'vitepress'
import type Artalk from 'artalk'
const artalkEl = ref<HTMLElement | null>(null)
const el = ref<HTMLElement | null>(null)
const router = useRouter()
const page = useData().page
let artalk: Artalk
onMounted(() => {
const script = document.createElement('script')
script.src = `https://artalk.qwqaq.com/dist/Artalk.js`
document.getElementsByTagName('head')[0].appendChild(script)
script.onload = () => {
initArtalk(page.value)
}
initArtalk(getConfByPage())
})
watch(() => router.route.data.relativePath, (path) => {
watch(() => router.route.data.relativePath, () => {
nextTick(() => {
Artalk.update(getArtalkConfByPage(page.value))
Artalk.reload()
artalk.update(getConfByPage())
artalk.reload()
})
})
function getArtalkConfByPage(page: any) {
onUnmounted(() => {
artalk.destroy()
})
function initArtalk(conf: any) {
import('artalk').then(({ default: Artalk }) => {
artalk = Artalk.init({
el: el.value,
emoticons: '/assets/emoticons/default.json',
gravatar: {
mirror: 'https://cravatar.cn/avatar/'
},
...conf
})
}).then(() => {
loadExtraFuncs()
})
}
function getConfByPage() {
return {
pageKey: 'https://artalk.js.org'+location.pathname,
pageTitle: page.title,
pageTitle: page.value.title,
server: 'https://artalk.qwqaq.com',
site: 'ArtalkDocs',
}
}
function initArtalk(page: any) {
Artalk.init({
el: artalkEl.value,
emoticons: '/assets/emoticons/default.json',
gravatar: {
mirror: 'https://cravatar.cn/avatar/'
},
...getArtalkConfByPage(page)
})
function loadExtraFuncs() {
// 图片灯箱插件
Artalk.on('list-loaded', () => {
artalk.on('list-loaded', () => {
document.querySelectorAll('.atk-comment .atk-content').forEach(($content) => {
const imgEls = $content.querySelectorAll<HTMLImageElement>('img:not([atk-emoticon]):not([atk-lightbox])');
imgEls.forEach((imgEl) => {
@@ -66,7 +76,7 @@ function initArtalk(page: any) {
// 夜间模式
const darkMode = document.querySelector('html').classList.contains('dark')
Artalk.setDarkMode(darkMode)
artalk.setDarkMode(darkMode)
new MutationObserver((mList) => {
mList.forEach((m) => {
@@ -74,7 +84,7 @@ function initArtalk(page: any) {
// @ts-ignore
const darkMode = m.target.classList.contains('dark')
Artalk.setDarkMode(darkMode)
artalk.setDarkMode(darkMode)
})
}).observe(document.querySelector('html'), { attributes: true })
}
+1 -1
View File
@@ -1,3 +1,3 @@
{
"latest": "2.5.1"
"latest": "2.7.1"
}
+31 -17
View File
@@ -4,23 +4,39 @@
|事件名|描述|
|-|-|
| `list-load` | 评论加载事件 |
| `list-loaded` | 评论加载完成事件 |
| `list-inserted` | 新增评论插入事件 |
| `editor-submit` | 编辑器提交事件 |
| `editor-submitted` | 编辑器提交完成事件 |
| `user-changed` | 本地用户数据变更事件 |
| `conf-loaded` | 配置变更事件 |
| `sidebar-show` | 侧边栏显示事件 |
| `sidebar-hide` | 侧边栏隐藏事件 |
| `inited` | 初始化后 |
| `destroy` | 实例销毁时 |
| `list-fetch` | 评论列表请求时 |
| `list-fetched` | 评论列表请求后 |
| `list-load` | 评论装载前 |
| `list-loaded` | 评论装载后 |
| `list-error` | 评论加载错误时 |
| `list-goto-first` | 评论列表归位时 |
| `list-reach-bottom` | 评论列表滚动到底部时 |
| `list-reach-bottom` | 评论列表滚动到底部时 |
| `comment-inserted` | 评论插入后 |
| `comment-updated` | 评论更新后 |
| `comment-deleted` | 评论删除后 |
| `comment-rendered` | 评论节点渲染后 |
| `unreads-updated` | 未读消息变更时 |
| `list-goto` | 评论跳转时 |
| `page-loaded` | 页面数据更新后 |
| `editor-submit` | 编辑器提交时 |
| `editor-submitted` | 编辑器提交后 |
| `user-changed` | 本地用户数据变更时 |
| `conf-loaded` | 配置变更时 |
| `dark-mode-changed` | 深色模式变更时 |
| `sidebar-show` | 侧边栏显示 |
| `sidebar-hide` | 侧边栏隐藏 |
事件声明代码:[@ArtalkJS/Artalk - types/event.ts](https://github.com/ArtalkJS/Artalk/blob/master/ui/packages/artalk/types/event.ts)
## 添加事件监听
```js
Artalk.use(ctx => {
ctx.on('list-loaded', () => {
alert('评论已加载完毕')
})
artalk.on('list-loaded', () => {
alert('评论已加载完毕')
})
```
@@ -29,8 +45,6 @@ Artalk.use(ctx => {
```js
let foo = function() { /* do something */ }
Artalk.use(ctx => {
ctx.on('list-loaded', foo)
ctx.off('list-loaded', foo)
})
artalk.on('list-loaded', foo)
artalk.off('list-loaded', foo)
```
+93 -40
View File
@@ -1,11 +1,23 @@
# Frontend API
## 初始化 `Artalk.init`
## 安装依赖
创建 Artalk 实例化对象。
用你喜欢的包管理器安装 Artalk:
```bash
pnpm add artalk
# 或者使用 npm
npm install artalk
```
## 创建实例 `init`
调用该函数将创建并得到一个 Artalk 的实例化对象,可用于后续操作。
```js
Artalk.init({
import Artalk from 'artalk'
const artalk = Artalk.init({
el: '#Comments',
pageKey: '/post/1',
pageTitle: '关于引入 Artalk 的这档子事',
@@ -14,93 +26,134 @@ Artalk.init({
})
```
## 更新配置 `Artalk.update`
调用该函数会向后端发起请求:
修改 Artalk 当前配置。
1. 获取前端配置
2. 获取评论列表
```js
Artalk.update({
// 新的配置...
})
```
配置和评论加载完毕后会分别触发 `conf-loaded` 和 `list-loaded` 事件。
该函数调用后,`conf-loaded` 事件将被触发。
注:前端配置可能会被覆盖,详情见:[在后端控制前端](../guide/backend/fe-control.md)。
更新配置不会自动刷新评论列表,可按需继续执行 `Artalk.reload()` 函数。
注:前端的一些配置将会被后端的配置覆盖,详情见:[在后端控制前端](../guide/backend/fe-control.md)
## 重新加载 `Artalk.reload`
刷新 Artalk 评论列表。
```js
Artalk.reload()
```
列表加载前将触发 `list-load` 事件,加载完毕后将触发 `list-loaded` 事件。
## 释放资源 `Artalk.destroy`
## 释放资源 `destroy`
销毁 Artalk 实例,用于资源释放。
```js
Artalk.destroy()
const artalk = Artalk.init({ ... })
artalk.destroy()
```
## 事件监听 `Artalk.on`
释放资源将执行 Artalk 挂载的 DOM 节点删除,以及解除 Artalk 事件监听等操作。
在 Vue / React 等框架中,务必在组件销毁时调用该函数,否则会造成内存泄漏:
```ts
import { onUnmounted } from 'vue'
onUnmounted(() => {
artalk.destroy()
})
```
## 更新配置 `update`
修改 Artalk 当前配置。
```js
const artalk = Artalk.init({ ... })
artalk.update({
// 新的配置...
})
```
调用该函数将触发 `conf-loaded` 事件。
更新配置不会自动刷新评论列表,可按需继续执行 `artalk.reload()` 函数。
请注意,该函数是一个实例化对象中的方法,并非全局函数。
前端配置参考:[前端配置](../guide/frontend/config.md)
## 重新加载 `reload`
重新从后端加载 Artalk 评论列表数据。
```js
const artalk = Artalk.init({ ... })
artalk.reload()
```
列表加载前将触发 `list-load` 事件,加载完毕后将触发 `list-loaded` 事件。
## 监听事件 `on`
添加 Artalk 事件监听。
```js
Artalk.on('list-loaded', () => {
const artalk = Artalk.init({ ... })
artalk.on('list-loaded', () => {
alert('评论列表加载完毕')
})
```
可监听事件类型见:[前端界面事件](./event.md)
可参考:[前端 Event](./event.md)。
## 解除监听 `Artalk.off`
## 解除监听 `off`
解除 Artalk 事件监听。
```js
const artalk = Artalk.init({ ... })
const handler = () => {
alert('评论列表加载完毕')
}
Artalk.on('list-loaded', handler)
Artalk.off('list-loaded', handler)
artalk.on('list-loaded', handler)
artalk.off('list-loaded', handler)
```
## 触发事件 `Artalk.trigger`
## 触发事件 `trigger`
触发 Artalk 事件。
```js
Artalk.trigger('list-loaded')
const artalk = Artalk.init({ ... })
artalk.trigger('list-loaded')
```
## 扩展插件 `Artalk.use`
## 加载插件 `use`
该 API 用于扩展 Artalk。
通过 `Artalk.use` 函数加载插件。请注意,该函数是一个全局函数,并非实例化对象中的方法。通过该函数加载的插件将对所有 Artalk 实例生效。
```js
import Artalk from 'artalk'
Artalk.use((ctx) => {
ctx.editor.setContent("Hello World")
})
const artalk = Artalk.init({ ... })
```
详情见:[插件开发](./plugs.md)
## 夜间模式 `Artalk.setDarkMode`
## 夜间模式 `setDarkMode`
修改夜间模式,可以配合博客主题调用,例如当用户点击夜间模式切换按钮。
```js
Artalk.setDarkMode(true)
const artalk = Artalk.init({ ... })
artalk.setDarkMode(true)
```
## 浏览量组件 `Artalk.loadCountWidget`
## 浏览量组件 `loadCountWidget`
详情见:[浏览量统计](../guide/frontend/pv.md)
+1 -1
View File
@@ -23,6 +23,6 @@
* [@ArtalkJS/Artalk - server/server.go](https://github.com/ArtalkJS/Artalk/blob/master/server/server.go)
- `UI` 及 `Event` 部分参考源码
* [@ArtalkJS/Artalk - src/artalk.ts](https://github.com/ArtalkJS/Artalk/blob/master/ui/packages/artalk/src/artalk.ts)
* [@ArtalkJS/Artalk - types/event.d.ts](https://github.com/ArtalkJS/Artalk/blob/master/ui/packages/artalk/types/event.d.ts)
* [@ArtalkJS/Artalk - types/event.ts](https://github.com/ArtalkJS/Artalk/blob/master/ui/packages/artalk/types/event.ts)
通过 Artalk 提供的 `API` 和 `Event`,你可以实现很多高级功能,比如编写评论管理机器人、评论提醒推送插件等。Artalk 并不为此提供技术指导,但鼓励你参考此处的文档自行定制。
+50 -3
View File
@@ -1,17 +1,64 @@
# 插件开发
你可以使用 `Artalk.use` 来扩展 Artalk。
使用 `Artalk.use` 来扩展 Artalk。
```js
import Artalk from 'artalk'
Artalk.use((ctx) => {
ctx.editor.setContent("Hello World")
ctx.on('inited', () => {
ctx.get('editor').setContent("Hello World")
})
ctx.on('list-loaded', () => {
console.log('评论列表加载完毕')
})
})
const artalk = Artalk.init({ ... })
```
请注意:
- `Artalk.use` 必须在 `Artalk.init` 之前调用。
- 请勿依赖插件的加载顺序,请监听事件来执行插件逻辑。
- 当所有的插件加载完毕后,会触发 `inited` 事件。
在 use 函数中,你可以通过 `ctx` 来访问 Artalk 的上下文。
## Context
Context 对象包含了 Artalk 的上下文信息。
| 成员 | 说明 |
| --- | --- |
| `ctx.$root` | Artalk 容器元素 |
| `ctx.conf` | Artalk 配置 |
| `ctx.fetch` | 加载数据数据 |
| `ctx.reload` | 重载评论列表 |
| `ctx.on` | 添加事件监听 |
| `ctx.off` | 解除事件监听 |
| `ctx.trigger` | 触发事件 |
| `ctx.inject` | 依赖注入 |
| `ctx.get` | 获取依赖 |
::: warning
Context API 目前仍不稳定,开发可能会有变动,升级请关注 CHANGELOG。
:::
参考:[@artalk/types/context.d.ts](https://github.com/ArtalkJS/Artalk/blob/master/ui/packages/artalk/types/context.d.ts)
参考:[@artalk/types/context.ts](https://github.com/ArtalkJS/Artalk/blob/master/ui/packages/artalk/types/context.ts)
Artalk 拥有完整的 TypeScript 类型定义,你可以通过编辑器的自动补全来查看 API。
## 示例插件
Artalk 有很多内置的插件,你可以参考它们的源码来开发自己的插件。
[@ArtalkJS/Artalk - src/plugins](https://github.com/ArtalkJS/Artalk/tree/master/ui/packages/artalk/src/plugins)
我们还提供了一些外置的插件,同样可以参考:
| 插件 | 说明 |
| --- | --- |
| [@artalk/plugin-katex](https://github.com/ArtalkJS/Artalk/tree/master/ui/packages/plugin-katex) | LaTeX 公式插件 |
| [@artalk/plugin-lightbox](https://github.com/ArtalkJS/Artalk/tree/master/ui/packages/plugin-lightbox) | 图片灯箱插件 |
+2 -3
View File
@@ -2,11 +2,11 @@
layout: home
title: Artalk
titleTemplate: The Selfhosted Comment System
titleTemplate: A Self-hosted Comment System
hero:
name: Artalk
text: The Selfhosted Comment System
text: A Self-hosted Comment System
tagline: Elevate your online conversations with a comment system that embodies the trifecta of lightness, security, and refinement.
actions:
- theme: brand
@@ -30,4 +30,3 @@ features:
title: Flexible
details: Use Golang to interpret the excellence of multiple platforms
---
+5 -5
View File
@@ -113,7 +113,7 @@ admin_notify:
![](/images/notify/tg-2.png)
详情可参考:[“Bots: An introduction for developers - Telegram”](https://core.telegram.org/bots)
详情可参考:[Bots: An introduction for developers - Telegram](https://core.telegram.org/bots)
::: tip
@@ -151,7 +151,7 @@ admin_notify:
<img src="/images/notify/lark-3.png" width="400px">
可参考:[“飞书帮助中心文档”](https://www.feishu.cn/hc/zh-CN/articles/360024984973)
可参考:[飞书帮助中心文档](https://www.feishu.cn/hc/zh-CN/articles/360024984973)
## 钉钉
@@ -164,7 +164,7 @@ admin_notify:
secret: ""
```
可参考:[“钉钉开放文档”](https://open.dingtalk.com/document/robots/custom-robot-access)
可参考:[钉钉开放文档](https://open.dingtalk.com/document/robots/custom-robot-access)
## Bark
@@ -227,7 +227,7 @@ admin_notify:
{{link_to_reply}}
```
可用变量和邮件模板相同,可参考:[“邮件模版”](./email.md#邮件模板)
可用变量和邮件模板相同,可参考:[邮件模版](./email.md#邮件模板)
## 嘈杂模式 `noise_mode`
@@ -264,7 +264,7 @@ admin_notify:
|`notify_subject`|通知标题 |String| 对应 admin_notify.notify_subject 配置项 |
|`notify_body` |通知内容 |String| 根据 admin_notify.notify_tpl 模版渲染 |
|`comment` |评论内容 |Object| 新创建的评论数据对象 |
|`parent_comment`|评论回复的目标|Object| Root 根节点评论类型改项为 null |
|`parent_comment`|评论回复的目标|Object| 如果是根节点评论值为 null |
**Body 数据样本**
+46 -16
View File
@@ -12,6 +12,25 @@ Artalk 默认以工作目录下的 `artalk.yml` 作为配置文件,可使用
artalk -c ./conf.yml
```
## 通过环境变量配置
Artalk 读取以 `ATK_` 为前缀的环境变量,并且全部大写,子节点用单个下划线表示,配置名含下划线请用双下划线表示:
- `_` (单下划线) 转为 `.` 表示子节点
- `__` (双下划线) 转为 `_` 表示配置名的下划线
e.g.
```bash
ATK_TIMEZONE -> timezone
ATK_LOGIN__TIMEOUT -> login_timeout
ATK_SITE__DEFAULT -> site_default
ATK_DB_TYPE -> db.type
ATK_DB_TABLE__PREFIX -> db.table_prefix
ATK_CACHE_REDIS_USERNAME -> cache.redis.username
ATK_ADMIN_USERS_0_NAME -> admin_users[0].name
```
## 获取模版配置文件
可参考一份「完整的配置文件」:[artalk.example.zh-CN.yml](https://github.com/ArtalkJS/Artalk/blob/master/conf/artalk.example.zh-CN.yml)
@@ -46,7 +65,7 @@ app_key: "<任意的字符>"
locale: "zh-CN"
```
详情参考:[“多语言”](../frontend/i18n.md)
详情参考:[多语言](../frontend/i18n.md)
## 数据库 `db`
@@ -76,17 +95,30 @@ db:
password: "" # 密码
charset: "utf8mb4" # 编码格式
table_prefix: "" # 表前缀 (例如:"atk_")
ssl: false # 启用 SSL
```
数据表将在 Artalk 启动时自动完成创建,无需额外操作。
#### 数据库连接字符串 (DSN)
如有需要,你还可以手动配置 `db.dsn` 来指定数据库连接字符串,例如:
```yaml
db:
type: "mysql"
dsn: "mysql://myuser:mypassword@localhost:3306/mydatabase?tls=skip-verify"
```
更多内容参考:[@go-sql-driver/mysql:README.md](https://github.com/go-sql-driver/mysql)
## 管理员 `admin_users`
你需要配置管理员账户,这样才能通过「[控制中心](../frontend/sidebar.md)」对站点内容进行管理。
Artalk 支持多站点,你可以创建多个管理员账户,为其分配站点,让你的朋友们共用同一个后端程序。
详情参考:[“管理员 × 多站点”](/guide/backend/multi-site.md)
详情参考:[管理员 × 多站点](/guide/backend/multi-site.md)
## 可信域名 `trusted_domains`
@@ -146,38 +178,39 @@ Artalk.init({ site: "Artalk 官网" })
## 前端配置 `frontend`
增加 `frontend` 字段内容可以在后端控制前端的配置,详情可参考:[“在后端控制前端”](/guide/backend/fe-control)。
增加 `frontend` 字段内容可以在后端控制前端的配置,详情可参考:[在后端控制前端](/guide/backend/fe-control)。
## 邮件通知 `email`
配置邮件通知,让回复通过邮件的形式通知目标用户,你可以自定义邮件发送者名称、标题、模版等。
详情参考:[“后端 · 邮件通知”](/guide/backend/email.md)
详情参考:[后端 · 邮件通知](/guide/backend/email.md)
## 多元推送 `admin_notify`
你可以配置多种消息发送方式,例如飞书、Telegram 等,当收到新的评论时通知管理员。
详情参考:[“后端 · 多元推送”](/guide/backend/admin_notify.md)
详情参考:[后端 · 多元推送](/guide/backend/admin_notify.md)
## 评论审核 `moderator`
配置评论审核来自动拦截垃圾评论。
详情参考:[“后端 · 评论审核”](/guide/backend/moderator.md)
详情参考:[后端 · 评论审核](/guide/backend/moderator.md)
## 验证码 `captcha`
支持图片、滑动验证码,通过验证码对请求频率进行限制。
详情参考:[“后端 · 验证码”](/guide/backend/captcha.md)
详情参考:[后端 · 验证码](/guide/backend/captcha.md)
## 缓存配置 `cache`
## 高速缓存 `cache`
为了提高评论系统的响应速度和性能,Artalk 内置一套缓存机制,并且默认开启,无需额外配置。但如果有需要,你也可以连接外部缓存服务器,支持 Redis 和 Memcache。
为节省内存资源占用,缓存默认关闭。如果你对网站性能有较高要求,请手动开启。你还可以连接外部缓存服务器,支持 Redis 和 Memcache。
```yaml
cache:
enabled: true # 启用缓存 (默认关闭)
type: "builtin" # 支持 redis, memcache, builtin (自带缓存)
expires: 30 # 缓存过期时间 (单位:分钟)
warm_up: false # 程序启动时预热缓存
@@ -185,9 +218,7 @@ cache:
```
- **warm_up**:缓存预热功能。设置为 `true`,在 Artalk 启动时会立刻对数据库内容进行全面缓存,如果你的评论数据较多,多达上万条,启动时间可能会延长。
- **type**:缓存类型。可选:`redis`, `memcache`, `builtin`。
type 默认为 `builtin`,如遇特殊情况可将缓存关闭,将其设置为 `disabled`。
- **type**:缓存类型,默认为 `builtin`。可选:`redis`, `memcache`, `builtin`。
注:如果在 Artalk 程序外部修改数据库内容,需要刷新 Artalk 缓存才能更新。
@@ -206,9 +237,8 @@ cache:
db: 0 # 使用零号数据库
```
技术细节:[Artalk 缓存机制 时序图.png](/images/artalk/artalk-cache.png)
![](/images/artalk/artalk-cache.png)
<!-- 技术细节:[Artalk 缓存机制 时序图.png](/images/artalk/artalk-cache.png) -->
<!-- ![](/images/artalk/artalk-cache.png) -->
## 监听地址 `host`
@@ -318,5 +348,5 @@ test_file: "./data/artalk.log"
配置文件相关代码:[/internal/config/config.go](https://github.com/ArtalkJS/Artalk/blob/master/internal/config/config.go)
前往:[“前端配置”](/guide/frontend/config.md)
前往:[前端配置](/guide/frontend/config.md)
:::
+124 -55
View File
@@ -1,5 +1,129 @@
# 守护进程
## Systemd
前提条件:
- Artalk 二进制文件,可在 [GitHub Release](https://github.com/ArtalkJS/Artalk/releases) 下载
- `systemctl --version` 232 或更新的版本
- `sudo` 管理员权限
移动 Artalk 到 `$PATH`,例如:
```bash
sudo mv artalk /usr/bin/
```
测试是否有效:
```bash
artalk version
```
创建名为 artalk 的用户组:
```bash
sudo groupadd --system artalk
```
创建一个名为 artalk 的用户,并且拥有一个可写的 home 目录:
```bash
sudo useradd --system \
--gid artalk \
--create-home \
--home-dir /var/lib/artalk \
--shell /usr/sbin/nologin \
--comment "Artalk server" \
artalk
```
如果你有 Artalk 的配置文件,请确保刚刚创建的 artalk 用户有权限可以读取。
创建服务文件:
```bash
sudo vim /etc/systemd/system/artalk.service
```
```ini
[Unit]
Description=Artalk
Documentation=https://artalk.js.org
After=network.target network-online.target
Requires=network-online.target
[Service]
Type=notify
User=artalk
Group=artalk
ExecStart=/usr/bin/artalk server -w /var/lib/artalk -c /etc/artalk/artalk.yml
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s QUIT $MAINPID
TimeoutStopSec=5s
LimitNOFILE=1048576
LimitNPROC=512
PrivateTmp=true
ProtectSystem=full
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
[Install]
WantedBy=multi-user.target
```
请仔细检查 `ExecStart` 和 `ExecReload`。 确保二进制文件的位置和程序启动参数是正确的
例如:更改 `-c` 参数的路径来指定配置文件,`-w` 参数可以更改工作目录。
请注意配置文件中的所有相对路径都是基于工作目录,例如配置文件中的 `./data/` 文件夹,如果启动参数 `-w /var/lib/artalk`,将读取 `/var/lib/artalk/data/` 目录。请确保该目录中的文件对于创建的 `artalk` 账户有权限可读写。
保存服务文件后,你可以设置自动启动服务:
```bash
sudo systemctl daemon-reload
sudo systemctl enable --now artalk
```
验证服务是否正常运行:
```bash
systemctl status artalk
```
一些常用的命令:
- 启动服务:`systemctl start artalk`
- 停止服务:`systemctl stop artalk`
- 查看状态:`systemctl status artalk`
- 查看日志:`journalctl -u artalk --no-pager | less +G`
## Tmux
tmux 将创建一个持续的命令行会话,在 SSH 或 tty 断开后保持在后台。
Note: 服务器关闭或重启后,tmux 会话将被清除,需要手动重新运行程序。
1. 创建会话 `tmux new -s artalk`
2. 运行程序 `./artalk server`
恢复接入会话:`tmux attach -t artalk`
查看所有会话:`tmux ls`
## Supervisor
以宝塔面板举例:打开「软件商店」,搜索并安装「Supervisor管理器」:
![](/images/baota-supervisor/0.png)
安装后,打开插件,点击「添加守护程序」:
![](/images/baota-supervisor/1.png)
> - 启动用户:`root` 或其他
> - 运行目录:点击右侧图标,选择 Artalk 所在目录
> - 启动命令:`./artalk server`
## Docker
更新 Docker 容器的 [Restart 策略](https://docs.docker.com/config/containers/start-containers-automatically/#use-a-restart-policy) 以达到进程守护效果。
@@ -18,58 +142,3 @@ services:
artalk:
+ restart: unless-stopped
```
## tmux
tmux 将创建一个持续的命令行会话,在 SSH 或 tty 断开后保持在后台。
Note: 服务器关闭或重启后,tmux 会话将被清除,需要手动重新运行程序。
1. 创建会话 `tmux new -s artalk`
2. 运行程序 `./artalk server`
恢复接入会话:`tmux attach -t artalk`
查看所有会话:`tmux ls`
## systemd
`sudo vim /etc/systemd/system/artalk.service`
```ini
[Unit]
Description=Artalk
After=network.target remote-fs.target nss-lookup.target
[Service]
User=root
ExecStart=<Artalk 执行文件绝对路径> server -w <工作目录绝对路径> -c <配置文件相对于工作目录路径>
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s QUIT $MAINPID
Restart=on-abnormal
RestartSec=5s
[Install]
WantedBy=multi-user.target
```
- 更新 systemd 配置:`systemctl daemon-reload`
- 启动:`systemctl start artalk.service`
- 停止:`systemctl stop artalk.service`
- 状态:`systemctl status artalk.service`
Tip: 设置 `alias` 简化命令输入;Artalk 参数 `-w` 用于指定工作目录,配置文件中的所有「相对路径」会基于该目录,例如 `./data/` 文件夹。
## Supervisor
以宝塔面板举例:打开「软件商店」,搜索并安装「Supervisor管理器」:
![](/images/baota-supervisor/0.png)
安装后,打开插件,点击「添加守护程序」:
![](/images/baota-supervisor/1.png)
> - 启动用户:`root` 或其他
> - 运行目录:点击右侧图标,选择 Artalk 所在目录
> - 启动命令:`./artalk server`
+1 -1
View File
@@ -2,4 +2,4 @@
## 备份
前往:[“数据迁移 · 备份”](/guide/transfer.md)
前往:[数据迁移 · 备份](/guide/transfer.md)
+3 -3
View File
@@ -12,11 +12,11 @@ Artalk 提供后端程序的 Docker 镜像,以便加速部署流程,提供
:::tip
推荐使用 Docker Compose:[“后端部署”](/guide/backend/install) 页面已详细讲解。
推荐使用 Docker Compose,可参考 [程序部署](/guide/deploy) 页面的步骤。
:::
常规的 Docker 容器创建可参考:
Docker 容器创建:
```bash
# 为 Artalk 创建一个目录
@@ -112,4 +112,4 @@ docker exec -it artalk bash
## 多平台兼容性
Docker 镜像暂仅提供 amd_64 构建,若需要在 ARM 架构运行,请下载 [二进制编译构建](/guide/backend/install.md#普通方式)
Docker 镜像暂仅提供 x86、arm64 的镜像构建,如需更多平台架构版本,请下载 [二进制构建部署](/guide/deploy.md#普通方式部署)。
+2 -2
View File
@@ -310,7 +310,7 @@ email:
</div>
```
Artalk 内置许多预设的邮件模板,例如 `mail_tpl: "default"` 使用的就是:[/email-tpl/default.html](https://github.com/ArtalkJS/Artalk/blob/master/email-tpl/default.html)
Artalk 内置许多预设的邮件模板,例如 `mail_tpl: "default"` 使用的就是:[@ArtalkJS/Artalk:/internal/template/email_tpl/default.html](https://github.com/ArtalkJS/Artalk/blob/master/internal/template/email_tpl/default.html)
## 发向管理员的邮件
@@ -324,4 +324,4 @@ admin_notify:
注:旧版 `email.mail_subject_to_admin` 配置项已弃用,请使用以上替代。
不局限于邮件,Artalk 支持多种方式向管理员发送通知,参考:[“多元推送”](./admin_notify.md#邮件通知)。
不局限于邮件,Artalk 支持多种方式向管理员发送通知,参考:[多元推送](./admin_notify.md#邮件通知)。
+1 -1
View File
@@ -60,7 +60,7 @@ Artalk.init({
建议 `frontend.emoticons` 填入表情包的 URL,而非 Object。
参考文档:[“表情包”](/guide/frontend/emoticons)
参考文档:[表情包](/guide/frontend/emoticons)
如需传递 Object,可以将其转为 JSON 字符串格式,例如:
+1 -1
View File
@@ -41,7 +41,7 @@ img_upload:
频率限制跟随 `captcha` 验证码配置,当超出限制将弹出验证码。
可参考:[“后端 · 验证码”](/guide/backend/captcha.md)
可参考:[后端 · 验证码](/guide/backend/captcha.md)
## path
+2 -2
View File
@@ -63,7 +63,7 @@ moderator:
## 腾讯云文本内容安全
可参考:[“腾讯云文档”](https://cloud.tencent.com/document/product/1124/64508)
可参考:[腾讯云文档](https://cloud.tencent.com/document/product/1124/64508)
开通「文本内容安全」后,在「访问管理」-「API 密钥管理」新增具有权限的 Secret,然后填入配置:
@@ -78,7 +78,7 @@ moderator:
## 阿里云内容安全
可参考:[“阿里云文档”](https://help.aliyun.com/document_detail/28417.html)
可参考:[阿里云文档](https://help.aliyun.com/document_detail/28417.html)
开通「阿里云内容安全」后,阿里云后台创建 Access Key 并填入配置:
+2 -2
View File
@@ -26,7 +26,7 @@ docker exec -it artalk artalk admin
## 站点的创建和管理
你可以在侧边栏「[控制中心](../frontend/sidebar.md#控制中心)」创建多个站点,管理站点和快速切换站点,详情可参考:[“侧边栏”](/guide/frontend/sidebar.html)。
你可以在侧边栏「[控制中心](../frontend/sidebar.md#控制中心)」创建多个站点,管理站点和快速切换站点,详情可参考:[侧边栏](/guide/frontend/sidebar.html)。
## 管理员配置
@@ -66,7 +66,7 @@ admin_users:
然后配置填写:`"(bcrypt)$2y$10$ti4vZYIrxVN8rLcY..."`,以 `(bcrypt)` 开头。
命令解释参考:[“Compute bcrypt hash from command line”](https://unix.stackexchange.com/questions/307994/compute-bcrypt-hash-from-command-line#answer-419855)
命令解释参考:[Compute bcrypt hash from command line](https://unix.stackexchange.com/questions/307994/compute-bcrypt-hash-from-command-line#answer-419855)
- **badge_name**:用户显示的头衔徽标文字。
- **badge_color**:用户显示的头衔徽标背景颜色。
+20 -10
View File
@@ -1,5 +1,25 @@
# 反向代理
## Caddy
**/etc/caddy/Caddyfile**
```nginx
artalk.your_domain.com {
tls /etc/caddy/ssl/cert.pem /etc/caddy/ssl/cert.key
reverse_proxy http://localhost:23366 {
header_up X-Forwarded-For {header.X-Forwarded-For}
}
}
```
执行重载命令:
```sh
sudo systemctl reload caddy
```
## Nginx
假定:
@@ -67,16 +87,6 @@ Artalk.init({ server: "http://artalk.your_domain.com" })
尤其是运行在 Docker 容器内的 artalk,注意检查 IP 和端口是否能够被 Nginx 正常访问
:::
## Caddy
创建 Caddyfile:
```
artalk.your_domain.com {
reverse_proxy / http://localhost:23366
}
```
## Apache
需要启用反代模块 `mod_proxy.c`
+2 -2
View File
@@ -12,13 +12,13 @@
:::tip
执行 `./artalk upgrade -f` 携带参数 `-f` 来进行同版本号的补充更新。
执行 `./artalk upgrade -f` 携带参数 `-f` 执行强制更新。
:::
## Docker 升级
可参考:[“Docker · 升级”](./docker.md#升级)
可参考:[Docker · 升级](./docker.md#升级)
## 普通方式
+42 -46
View File
@@ -51,14 +51,11 @@ Artalk.init({
在控制台,你可以根据喜好配置评论系统、[将评论迁移到 Artalk](./transfer.md)。
祝贺!你已成功完成 Artalk 部署 🥳
🥳 你已成功完成 Artalk 部署!
## 普通方式部署
1. 前往 [GitHub Release](https://github.com/ArtalkJS/Artalk/releases) 下载程序压缩包
> 注:我们已将 ArtalkGo 与主仓库合并,历史旧版可在[此页面](https://github.com/ArtalkJS/ArtalkGo/releases)查看,日后新版将在[新页面](https://github.com/ArtalkJS/Artalk/releases)发布。
2. 提取压缩包:`tar -zxvf artalk_版本号_系统_架构.tar.gz`
3. 运行程序 `./artalk server`
4. 前端配置
@@ -67,25 +64,16 @@ Artalk.init({
Artalk.init({ server: "http://your_domain:23366" })
```
**其它可选操作:**
**进一步操作:**
- [“反向代理端口到 80 / 443 (Nginx, Apache)”](./backend/reverse-proxy.md)
- ["持久化运作 (tmux, systemd, supervisor)"](./backend/daemon.md)
- [守护进程 (Systemd, Supervisor)](./backend/daemon.md)
- [反向代理 (Caddy, Nginx, Apache)](./backend/reverse-proxy.md)
**附表:文件名释义表**
如果有需要,你还可以自行编译程序,参考:[后端构建](./backend/build.md)。
|文件名|操作系统|CPU 架构|
|:-|:-:|:-:|
|artalk_linux_amd64.tar.gz|Linux|x86_64|
|artalk_linux_arm64.tar.gz|Linux|ARM64|
|artalk_linux_arm7.tar.gz|Linux|ARMv7|
|artalk_windows_amd64.zip|Windows|x86_64|
|artalk_darwin_arm64.tar.gz|macOS|Apple Silicon|
|artalk_darwin_amd64.tar.gz|macOS|Intel Chip|
## Compose 部署
## Docker Compose 部署
提供 docker-compose.yaml 文件可供参考:
**compose.yaml**
```yaml
version: "3.5"
@@ -100,7 +88,7 @@ services:
- ./data:/data
```
在与配置文件相同的目录执行命令创建容器:
执行命令创建容器:
```bash
docker-compose up -d
@@ -118,11 +106,7 @@ docker-compose exec artalk bash # 进入容器
:::
> 详细可见:[“后端 · Docker”](./backend/docker.md)
## 自行编译并运行
可参考:[“后端构建”](./backend/build.md)
可参考:[Docker 部署文档](./backend/docker.md)
## CDN 资源
@@ -135,7 +119,21 @@ docker-compose exec artalk bash # 进入容器
Artalk 后端程序内嵌了前端 JS、CSS 文件,使用公共 CDN 资源请注意前后端版本的兼容性。
Artalk 静态资源通过上游 [CDNJS](https://cdnjs.com/) 分发,国内有许多镜像可供选择:
Artalk 静态资源通过上游 CDNJS 分发,以下镜像资源可供选择:
**CDNJS**
> https://cdnjs.cloudflare.com/ajax/libs/artalk/:ArtalkVersion:/Artalk.js
>
> https://cdnjs.cloudflare.com/ajax/libs/artalk/:ArtalkVersion:/Artalk.css
**Staticfile CDN (国内)**
> https://cdn.staticfile.org/artalk/:ArtalkVersion:/Artalk.js
>
> https://cdn.staticfile.org/artalk/:ArtalkVersion:/Artalk.css
::: details 查看更多
**BootCDN (国内)**
@@ -143,18 +141,11 @@ Artalk 静态资源通过上游 [CDNJS](https://cdnjs.com/) 分发,国内有
>
> https://cdn.bootcdn.net/ajax/libs/artalk/:ArtalkVersion:/Artalk.css
**75CDN (国内)**
**ElemeCDN (国内)**
> https://npm.elemecdn.com/artalk@:ArtalkVersion:/dist/Artalk.js
> https://lib.baomitu.com/artalk/:ArtalkVersion:/Artalk.js
>
> https://npm.elemecdn.com/artalk@:ArtalkVersion:/dist/Artalk.css
**CDNJS**
> https://cdnjs.cloudflare.com/ajax/libs/artalk/:ArtalkVersion:/Artalk.js
>
> https://cdnjs.cloudflare.com/ajax/libs/artalk/:ArtalkVersion:/Artalk.css
> https://lib.baomitu.com/artalk/:ArtalkVersion:/Artalk.css
**UNPKG**
@@ -168,11 +159,9 @@ Artalk 静态资源通过上游 [CDNJS](https://cdnjs.com/) 分发,国内有
>
> https://cdn.jsdelivr.net/npm/artalk@:ArtalkVersion:/dist/Artalk.css
## ArtalkLite
:::
可选择精简版 [ArtalkLite](./frontend/artalk-lite.md):体积更小、更简约。
## Node 环境
## Node 项目
```bash
pnpm add artalk
@@ -189,13 +178,20 @@ Artalk.init({
})
```
## 何时引入、何时 init?
可参考:
- 可以在任意位置引入 JS 和 CSS 资源,但需确保 JS 引入在执行 `Artalk.init({})` 前。
- 执行 `Artalk.init({ el: '#x' })` 时,需要确保 `<div id="x"></div>` 存在于页面当中。
可参考:[“前端框架引入”](./frontend/import-framework.md) / [“博客引入”](./frontend/import-blog.md)
- [置入框架文档](./frontend/import-framework.md)
- [置入博客文档](./frontend/import-blog.md)
- [前端 API](../develop/fe-api.md)
## 数据导入
从其他评论系统导入数据:[“数据迁移”](./transfer.md)
从其他评论系统导入数据:[数据迁移](./transfer.md)
## ArtalkLite
可选择精简版 [ArtalkLite](./frontend/artalk-lite.md):体积更小、更简约。
## 开发环境
可参考:[开发者指南](https://github.com/ArtalkJS/Artalk/blob/master/CONTRIBUTING.md)
+4 -1
View File
@@ -12,8 +12,11 @@ Artalk 离不开社区的支持与帮助,**是你们让 Artalk 走得更远!
- [Nuxt.js如何部署Artalk和遇到的问题 (By @子舒)](https://imhan.cn/posts/20220218/)
- [宝塔安装过程大概记录 (By @xiamuguizhi)](https://github.com/ArtalkJS/Artalk/discussions/46)
- [Vuepress配置评论插件为Artalk](https://wiki.eryajf.net/pages/b74c2b/)
- [Fly.io 部署 Artalk 评论系统](https://wayjam.me/posts/artalk-deploy-on-flyio/)
- [Railway.app 部署 Artalk 评论系统](https://blog.moraxyc.com/post/eidb8276/)
- [Render.com 部署Artalk自托管评论系统](https://www.leenhawk.com/posts/3932123804/)
更多新版读物正在撰写当中(
更多新版读物正在撰写当中...
## Artalk v1
+26 -13
View File
@@ -1,18 +1,19 @@
# 前端配置
```js
Artalk.init({ 你的配置... })
Artalk.update({ 更新配置... })
const artalk = Artalk.init({ ... })
artalk.update({ ... })
```
- 默认配置:[defaults.ts](https://github.com/ArtalkJS/Artalk/blob/master/ui/packages/artalk/src/defaults.ts)
- 声明文件:[artalk-config.d.ts](https://github.com/ArtalkJS/Artalk/blob/master/ui/packages/artalk/types/artalk-config.d.ts)
- 声明文件:[config.ts](https://github.com/ArtalkJS/Artalk/blob/master/ui/packages/artalk/types/config.ts)
## 轻松配置
推荐在侧边栏 “[控制中心](/guide/frontend/sidebar.md)” 通过图形界面修改前端的配置,而无需在代码中对界面进行设定。
注:前端的设定可能会被后端覆盖,更多内容参考:[“在后端控制前端”](/guide/backend/fe-control.md)
注:前端的设定可能会被后端覆盖,更多内容参考:[在后端控制前端](/guide/backend/fe-control.md)
## 基本配置(必填项)
@@ -36,7 +37,7 @@ Artalk.update({ 更新配置... })
可以填写由博客系统生成的 `固定链接`,但建议使用相对路径以便日后切换域名。
参考:[“关于使用相对 / 绝对路径”](/guide/backend/relative-path.md)
参考:[关于使用相对 / 绝对路径](/guide/backend/relative-path.md)
### pageTitle
@@ -84,7 +85,7 @@ Artalk 支持多站点统一管理,此项用于站点隔离。
可以在后端的配置文件中定义前端的配置,让前端配置始终跟随后端。
详情可参考:[“在后端控制前端”](/guide/backend/fe-control)
详情可参考:[在后端控制前端](/guide/backend/fe-control)
## 国际化 (i18n)
@@ -99,7 +100,7 @@ Artalk 支持多站点统一管理,此项用于站点隔离。
欢迎提交 PR 帮助翻译 Artalk 的多语言,为社区贡献一份力量!
详情参考:[“多语言”](./i18n.md)
详情参考:[多语言](./i18n.md)
## 请求
@@ -121,7 +122,7 @@ Artalk 支持多站点统一管理,此项用于站点隔离。
- 类型:`Object|Array|String|Boolean`
- 默认值:"[https://cdn.jsdelivr.net/gh/ArtalkJS/Emoticons/grps/default.json](https://cdn.jsdelivr.net/gh/ArtalkJS/Emoticons/grps/default.json)"
详细内容:[“前端 · 表情包”](/guide/frontend/emoticons.md)
详细内容:[前端 · 表情包](/guide/frontend/emoticons.md)
更新兼容 [OwO 格式](https://github.com/DIYgod/OwO),支持 URL 动态加载。
@@ -175,7 +176,7 @@ JS DELIVR 在中国大陆的 [ICP 牌照已被吊销](https://github.com/jsdeliv
代码动态修改 darkMode:
```js
Artalk.setDarkMode(true)
artalk.setDarkMode(true)
```
> 参考代码:“[index.html](https://github.com/ArtalkJS/Artalk/blob/master/ui/packages/artalk/index.html#L97-L150)”
@@ -268,7 +269,7 @@ pvEl 和 countEl 元素标签都可以设置 `data-page-key` 属性值,来指
**显示用户的 UserAgent 信息徽标**
- 类型:`Boolean`
- 默认值:`true`
- 默认值:`false`
### listSort
@@ -350,12 +351,24 @@ gravatar: {
>
> loli:https://gravatar.loli.net/avatar/
### gravatar.default
### gravatar.params
**默认头像**(URL or [Gravatar Type](http://cn.gravatar.org/site/implement/images/#default-image))
**Gravatar API 参数**
- 类型:`String`
- 默认值:`"mp"`
- 默认值:`"d=mp&s=240"`
例如,你可以通过该配置项设置默认头像 (`d=mp`) 和头像尺寸 (`s=240`)。
参考:[Gravatar API 文档](http://cn.gravatar.org/site/implement/images/)
该配置项格式为 HTTP Query。
::: warning 更新注意
v2.5.5 已废弃 `gravatar.default` 配置项,请使用 `gravatar.params` 替代。
:::
### avatarURLBuilder
+2 -2
View File
@@ -9,7 +9,7 @@ frontend:
## 表情包预设
Artalk 社区提供许多表情包预设,你能够挑选几套喜欢的表情包,仅需简单的配置,轻松添加到你的评论系统中,前往仓库:[“@ArtalkJS/Emoticons”](https://github.com/ArtalkJS/Emoticons)。
Artalk 社区提供许多表情包预设,你能够挑选几套喜欢的表情包,仅需简单的配置,轻松添加到你的评论系统中,前往仓库:[@ArtalkJS/Emoticons](https://github.com/ArtalkJS/Emoticons)。
## 格式支持
@@ -57,7 +57,7 @@ Artalk 除了支持 OwO 格式外,还内置支持一种标准的表情包列
::: warning
前端的配置将默认被后端覆盖,详情参考:[“在后端控制前端”](/guide/backend/fe-control.html)
前端的配置将默认被后端覆盖,详情参考:[在后端控制前端](/guide/backend/fe-control.html)
:::
+29 -54
View File
@@ -4,6 +4,11 @@
<<< @/code/quick-start/cdn.html
#### 什么时候执行 `Artalk.init({})`?
- 可以在任意位置引入 JS 和 CSS 资源,但需确保 JS 引入在执行 `Artalk.init({})` 前。
- 执行 `Artalk.init({ el: '#artalk' })` 前需要确保 `<div id="artalk"></div>` 在页面当中。
## Hugo
创建模板文件 `/主题目录/layouts/partials/comment/artalk.html`:
@@ -99,66 +104,36 @@ NexT 主题可以安装 [Hexo NexT 主题的 Artalk 插件](https://github.com/l
:::
## VitePress
可参考:[@ArtalkJS/Artalk:/docs/.vitepress](https://github.com/ArtalkJS/Artalk/tree/master/docs/.vitepress)
- `config.ts` 修改配置引入 CSS 资源
- `theme/Artalk.vue` 创建组件
- `theme/index.ts` 注册组件
- `theme/Layout.vue` 使用组件
注:SSG 应用需通过 `import()` 函数异步引入 Artalk,否则会导致构建失败。
```ts
import('artalk').then(({ default: Artalk }) => {
artalk = Artalk.init({
//...
})
})
```
参考文件:[.vitepress/theme/Artalk.vue](https://github.com/ArtalkJS/Artalk/blob/master/docs/.vitepress/theme/Artalk.vue)
## VuePress
以 [VuePress v2](https://github.com/vuepress/vuepress-next) 为例,继承默认主题 `@vuepress/theme-default`
(待补充)
可参考:[“/.vuepress/theme/Artalk.vue”](https://github.com/ArtalkJS/Docs/blob/eef37bca8cc0c9973bf121fdef4014dcd940f104/docs/.vuepress/theme/Artalk.vue) 创建 Artalk 评论组件。
::: tip 提示
在 `/.vuepress/theme/clientAppEnhance.ts` 文件中全局注册组件:
可参考:[置入框架](./import-framework.md)
```ts
import { defineClientAppEnhance } from '@vuepress/client'
import Artalk from './Artalk.vue'
export default defineClientAppEnhance(({ app, router, siteData }) => {
app.component('Artalk', Artalk)
})
```
主题布局 `/.vuepress/theme/Layout.vue`:
```vue
<template>
<Layout>
<template #page-bottom>
<div class="page-meta">
<!-- Artalk -->
<Artalk />
</div>
</template>
</Layout>
</template>
<script lang="ts">
import { defineComponent, nextTick } from 'vue'
import { useRouter } from 'vue-router'
import Layout from '@vuepress/theme-default/lib/client/layouts/Layout.vue'
export default defineComponent({
components: { Layout },
mounted: () => {
}
})
</script>
```
主题配置文件 `/.vuepress/theme/index.ts`:
```ts
import { path } from '@vuepress/utils'
export default ({
name: 'vuepress-theme-local',
extends: '@vuepress/theme-default',
layouts: {
Layout: path.resolve(__dirname, 'Layout.vue'),
},
clientAppEnhanceFiles: path.resolve(__dirname, 'clientAppEnhance.ts'),
})
```
:::
## Typecho
+89 -42
View File
@@ -1,6 +1,6 @@
# 置入框架
## 引入 Artalk
## 安装 Artalk
通过包管理工具引入 Artalk,推荐使用 [pnpm](https://pnpm.io/zh/)
@@ -14,49 +14,84 @@ Vue 3 + TypeScript 例:
```vue
<template>
<div class="artalk-comments"></div>
<view ref="el"></view>
</template>
<script lang="ts">
<script lang="ts" setup>
import { onMounted, onUnmounted, ref } from 'vue'
import { useRoute } from 'vue-router'
import 'artalk/dist/Artalk.css'
import { defineComponent } from 'vue'
import Artalk from 'artalk'
export default defineComponent({
mounted: () => {
const artalk = Artalk.init({
el: this.$el,
pageKey: `${location.pathname}`,
pageTitle: `${document.title}`,
server: 'http://localhost:8080',
site: 'Artalk 的博客',
// ...
})
}
const el = ref<HTMLElement>()
const route = useRoute()
let artalk: Artalk
onMounted(() => {
artalk = Artalk.init({
el: el.value,
pageKey: route.path,
pageTitle: `${document.title}`,
server: 'http://localhost:8080',
site: 'Artalk 的博客',
// ...
})
})
onUnmounted(() => {
artalk.destroy()
})
</script>
```
::: tip 提示
VuePress 可参考:[“VuePress 引入”](./import-blog.md#vuepress)
:::
## React
```jsx
import 'artalk/dist/Artalk.css'
```tsx
import React, { useEffect, useRef } from 'react';
import { useLocation } from 'react-router-dom';
import 'artalk/dist/Artalk.css';
import Artalk from 'artalk';
const ArtalkComment = () => {
const el = useRef(null);
const location = useLocation();
let artalk = null;
useEffect(() => {
artalk = new Artalk({
el: el.current,
pageKey: location.pathname,
pageTitle: document.title,
server: 'http://localhost:8080',
site: 'Artalk 的博客',
// ...
});
return () => {
if (artalk) {
artalk.destroy();
}
};
}, [location.pathname]);
return <div ref={el}></div>;
};
export default ArtalkComment;
```
```jsx
import React, { createRef } from 'react'
import 'artalk/dist/Artalk.css'
import Artalk from 'artalk'
export default class Artalk extends React.Component {
el = createRef()
artalk = null
componentDidMount () {
const artalk = Artalk.init({
componentDidMount() {
this.artalk = Artalk.init({
el: this.el.current,
pageKey: `${location.pathname}`,
pageTitle: `${document.title}`,
@@ -66,9 +101,13 @@ export default class Artalk extends React.Component {
})
}
render () {
componentWillUnmount() {
this.artalk?.destroy()
}
render() {
return (
<div ref={this.el} class="artalk-comments" />
<div ref={this.el} />
)
}
}
@@ -78,22 +117,30 @@ export default class Artalk extends React.Component {
```html
<script>
import 'artalk/dist/Artalk.css'
import Artalk from 'artalk'
import { onMount, onDestroy } from 'svelte';
import 'artalk/dist/Artalk.css';
import Artalk from 'artalk';
let comments;
let el;
let artalk;
onMount(() => {
Artalk.init({
el: comments,
pageKey: `${location.pathname}`,
pageTitle: `${document.title}`,
server: 'http://localhost:8080',
site: 'Artalk 的博客',
// ...
})
})
onMount(() => {
artalk = Artalk.init({
el: el,
pageKey: location.pathname,
pageTitle: document.title,
server: 'http://localhost:8080',
site: 'Artalk 的博客',
// ...
});
onDestroy(() => {
if (artalk) {
artalk.destroy();
}
});
});
</script>
<div bind:this={comments} class="artalk-comments"></div>
<div bind:this={el}></div>
```
+1 -23
View File
@@ -4,26 +4,4 @@ Artalk 拥有一个前端,[GitHub 代码仓库](https://github.com/ArtalkJS/Ar
## 特性
可见:[“README.md · 特性”](https://github.com/ArtalkJS/Artalk#%E7%89%B9%E6%80%A7)
## 句话概括
- 轻量级 (~30kB)
- 易上手 (防秃顶)
- TypeScript & Vanilla (纯天然无添加 / 无依赖)
<!-- ## 长篇大论
Artalk 的前端是重磅戏,它采用 TypeScript + Vite 编写,原生纯净无添加,不依赖于任何前端框架。
它体小精悍,得益于我们使用原生 DOM 操作,Artalk 的文件体积十分小巧,功能却非常完整。
您可以随意将 Artalk 置入任何位置,与 Vue, React, Svelte 项目紧密友好地结合。
开发时,我们考虑到 Artalk 的业务拓展能力,留有许多向外部暴露的 hooks,有助于完成你的预期需求。
它同时编译成几个版本,例如 UMD 版能够兼顾多种前端环境下运行。
CSS 方面,我们采用 Shadow DOM 特性对样式进行隔离处理,不污染页面样式且不被污染。
Vite 这样的下一代开发工具,为开发者创造了一个良好的编程体验,编译和热加载速度飞起。 -->
可见:[README.md · 特性](https://github.com/ArtalkJS/Artalk#%E7%89%B9%E6%80%A7)
+7 -6
View File
@@ -6,7 +6,7 @@ Artalk 内置页面浏览量统计功能,你可以在你的页面任意位置
<span id="ArtalkPV"></span>
```
当 Artalk 加载完毕时,该标签内容将修改为页面的浏览量计数。
当 Artalk 评论列表加载完毕时,该标签内容将修改为页面的浏览量计数。
Artalk 加载需要时间,所以你可以给它一个占位字符:
@@ -14,7 +14,7 @@ Artalk 加载需要时间,所以你可以给它一个占位字符:
<span id="ArtalkPV">加载中...</span>
```
你也可以绑定使用其他的元素,修改 Artalk 配置项,例如:
配置项 `pvEl` 的默认值是 `"#ArtalkPV"`,可修改将评论量加载到指定元素,例如:
```js
Artalk.init({
@@ -22,9 +22,12 @@ Artalk.init({
})
```
### 显示多个页面的浏览量
### 加载多个页面的浏览量
你能在除评论页面之外的任何页面,例如「文章列表」页,显示页面浏览量或评论数。
在非评论页,无需调用 `Artalk.init` 加载评论框 (会使页面浏览量 PV 数增加),仅调用 `loadCountWidget` 静态方法即可:
你能在除评论页面之外的任何页面,例如「文章列表」页,显示页面浏览量或评论数:
```js
Artalk.loadCountWidget({
@@ -35,8 +38,6 @@ Artalk.loadCountWidget({
});
```
在非评论页时,就无需再 Artalk.init 实例了 (否则页面浏览量 PV 数会无故增加),仅调用 `loadCountWidget` 静态方法即可。
然后你可以放置多个 `#ArtalkPV` 元素,通过属性 `data-page-key` 来指定需要查询的页面:
```html
+1 -1
View File
@@ -40,4 +40,4 @@
## 设置
登陆管理员账户可进入到 “控制中心” 的图形设置界面,轻松地对 [“配置文件”](/guide/backend/config.md) 以及 [“前端的配置”](/guide/frontend/config.md) 进行修改,而无需手动编辑复杂的配置文件。
登陆管理员账户可进入到 “控制中心” 的图形设置界面,轻松地对 [配置文件](/guide/backend/config.md) 以及 [前端的配置](/guide/frontend/config.md) 进行修改,而无需手动编辑复杂的配置文件。
+2 -2
View File
@@ -36,7 +36,7 @@
不论是 Vue、React、Svelte 的前端项目,还是 WordPress、Typecho、Hexo 等博客系统,都可以快速引入 Artalk,结合诸位的聪明才智,我们相信 Artalk 能够自如应对各种业务场景。
> 更多支持 / 计划的功能,详见:[“README.md”](https://github.com/ArtalkJS/Artalk#todos)。
> 更多支持 / 计划的功能,详见:[README.md](https://github.com/ArtalkJS/Artalk#todos)。
## 用户体验
@@ -64,7 +64,7 @@ Artalk 社区是包容开放的社区,我们欢迎不同水平的人员帮助
“More action, less talk”,Artalk 社区不欢迎无意义的争论,我们希望社区成员和谐共处、为社区发展出谋划策。在提出问题前,你应当读过《[提问的智慧](https://lug.ustc.edu.cn/wiki/doc/smart-questions/)》,这可能决定了你最终是否能得到有用的回答。在表达观点前,你应当具备基本的礼仪,比如保持平和的态度、使用得体的语言,切忌恶语相向、冷嘲热讽、不尊重他人信仰和立场等。
我们作为开源精神的推崇者以及实践者,希望我们所创造的自由软件,都应该被自由的使用,自由的研究,自由的更改和自由的分享。本项目前后端代码使用第 3 版 GNU 宽通用公共许可证([GNU Lesser General Public License V3](https://www.gnu.org/licenses/lgpl-3.0.html))开源,文档请遵循知识共享许可协议 ([Creative Commons License](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh))。
我们作为开源精神的推崇者以及实践者,希望我们所创造的自由软件,都应该被自由的使用,自由的研究,自由的更改和自由的分享。本项目主程序使用 [MIT](https://github.com/ArtalkJS/Artalk/blob/master/LICENSE) 协议开源,文档使用 [CC](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh) 许可协议。
::: tip 立即为社区贡献力量?
- 浏览开发者资料([开发文档](../develop/index.md) / [CONTRIBUTING.md](https://github.com/ArtalkJS/Artalk/blob/master/CONTRIBUTING.md))
-14
View File
@@ -2,20 +2,6 @@
## 用户身份凭证存储
RESTful API 具有无状态 (Stateless) 这一特性,尽管 Artalk 的 API 并不属于 RESTful,但保证无状态这一特性能让 API 更灵活易用,相反使用 Session 会话技术在处理多站点、跨域等操作的时候尤为繁琐,对于普通用户来说,服务器配置也成为麻烦,不当的配置会让用户的安全受到威胁。
在各种权衡考虑之下,Artalk 目前还是依然使用 localStorage 保存用户凭证数据,因为使用 localStorage 能保证 API 的无状态性,也无需考虑因使用 Cookie 而带来的各种麻烦,而且使用 Cookie 并不能换来多大的安全性提升。无论是 Cookie 还是 localStorage 都存在有一些缺陷。
Cookie 有 CSRF 安全隐患,而无状态 API 不容易有。如果使用 Cookie,就需要在任何有身份验证的地方,额外考虑对 CSRF 安全问题的防范,虽然可以使用全局 CSRF_TOKEN,但这无疑增加了 API 和客户端的复杂程度,开发也很难保证永远不会遗漏一些细节。所以,我认为使用 Cookie 既引入了一些额外的安全隐患,并且程序的开发难度也变大了,为什么要这样折磨自己呢?
还有一点是 Cookie 这种被广泛用作判断用户身份状态的技术特别容易被跟踪滥用,尽管可信的现代浏览器有很多内部的安全策略,但如果用户操作系统安装了一些恶意流氓软件,拥有较高权限,你无法保证某些软件窃取 Cookie 不是轻而易举的事 (特指某些大厂)。
那,Cookie 有啥优点?使用 Cookie 的主要优点在于能防止客户端 (浏览器) 通过 JS 对用户凭证数据的「直接」访问,阻止恶意 XSS 攻击脚本「直接」窃取数据的可能性。注意,这里用了「直接」这一个修饰词。虽然使用 Cookie (httpOnly) 不能通过 JS 读取凭证数据,但 XSS 攻击脚本依然可以在客户端执行 fetch 代码向 API 发起请求,这些请求依然是携带了 Cookie 的,是具有用户身份的 API 访问。也就是说,使用 Cookie (httpOnly) 并不能从根源上解决安全问题,不管你是 Cookie 还是 localStorage,只要网站存在 XSS 漏洞,攻击者成功注入了 XSS 脚本,都会对用户的安全造成威胁。所以使用 Cookie (httpOnly) 防 XSS 我认为是个「伪命题」。
XSS 是属于非常严重的问题了。所以无论使用何种方式存放敏感数据,XSS 防范才是重中之重。
对于我们用户来说,不应随意引入不明的 JS 脚本;不应在不了解代码意图的情况下到浏览器控制台执行 JS 代码;不应随意点击来历不明的链接;尽量使用新开的隐身模式窗口打开不明链接。
Artalk 在努力保护用户的安全。参考 [OWASP 安全备忘录](https://cheatsheetseries.owasp.org/cheatsheets/DOM_based_XSS_Prevention_Cheat_Sheet.html) 中提到的内容,Artalk 的开发对于来自用户输入的任何数据始终持以怀疑的态度,我们严格遵循以下准则防范 XSS 攻击:
- 不直接或间接地调用 `innerHTML` 输出未经处理的用户输入数据,而是使用 `innerText`。
+3 -7
View File
@@ -2,11 +2,7 @@
## 数据行囊
数据行囊(Artrans)是 Artalk 持久化数据保存规范格式。
Artran = Art + Ran (艺术 + 奔跑)
即 “奔跑的艺术”,又称~~艺术之润~~ Ran 为 Run 的过去式,代表已经润了(
数据行囊(Artrans = Art + Ran)是 Artalk 持久化数据保存规范格式。
::: details Artran 格式定义
@@ -61,7 +57,7 @@ Artran = Art + Ran (艺术 + 奔跑)
转换为 `.artrans` 格式的数据文件可以导入 Artalk:
- **前端导入**:你可在「[控制中心](./frontend/sidebar.md#控制中心)」找到「迁移」选项卡,并根据提示导入 Artrans。
- **控制中心导入**:你可在「[控制中心](./frontend/sidebar.md#控制中心)」找到「迁移」选项卡,并根据提示导入 Artrans。
- **命令行导入**:执行 `artalk import -h` 查阅帮助文档。
## 获取源数据
@@ -213,4 +209,4 @@ artalk export | gzip -9 | ssh username@remote_ip "cat > ~/backup/artrans.gz"
目前已支持将 Typecho、WordPress、Valine、Waline、Disqus、Commento、Twikoo 等类型的数据转为 Artrans,但鉴于评论系统的多样性,虽然我们已经对上述类型数据做了适配,但仍然还有许多并未兼容。如果你恰巧正在使用未被适配的评论系统,你除了等待 Artalk 官方支持之外,还可以尝试了解 Artrans 数据格式后自主编写评论数据导入导出工具。如果你觉得自己的工具写得不错,我们十分乐意将其收录在内,让我们共同创造一个能够在不同评论系统之间自由切换的工具。
前往:[“Artransfer 迁移工具代码仓库”](https://github.com/ArtalkJS/Artransfer)
前往:[Artransfer 迁移工具代码仓库](https://github.com/ArtalkJS/Artransfer)
+1 -1
View File
@@ -6,7 +6,7 @@ titleTemplate: 自托管评论系统
hero:
name: Artalk
text: The Selfhosted Comment System
text: A Self-hosted Comment System
tagline: 轻量、安全、易上手的自托管评论系统
actions:
- theme: brand
+18
View File
@@ -0,0 +1,18 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}
+24
View File
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
+27
View File
@@ -0,0 +1,27 @@
# React + TypeScript + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
## Expanding the ESLint configuration
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
- Configure the top-level `parserOptions` property like this:
```js
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
project: ['./tsconfig.json', './tsconfig.node.json'],
tsconfigRootDir: __dirname,
},
```
- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked`
- Optionally add `plugin:@typescript-eslint/stylistic-type-checked`
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list
+14
View File
@@ -0,0 +1,14 @@
<!doctype html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, target-densitydpi=device-dpi" />
<title>Artalk | 自托管评论系统</title>
<meta name="description" content="一款简洁的自托管评论系统" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
+32
View File
@@ -0,0 +1,32 @@
{
"name": "landing",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"include-media": "^2.0.0",
"normalize.css": "^8.0.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^4.11.0"
},
"devDependencies": {
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"@vitejs/plugin-react-swc": "^3.3.2",
"eslint": "^8.45.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3",
"sass": "^1.67.0",
"typescript": "^5.0.2",
"vite": "^4.4.5"
}
}
+1558
View File
File diff suppressed because it is too large. Load diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

+24
View File
@@ -0,0 +1,24 @@
* {
box-sizing: border-box;
}
body {
font: 16px "SF Pro Display", "SF Pro Icons", "Helvetica Neue", "Helvetica", "Arial", sans-serif;
overflow-x: hidden;
}
.container {
width: 100%;
margin: 0 auto;
@media (min-width: 640px) { max-width: 640px; }
@media (min-width: 720px) { max-width: 720px; }
@media (min-width: 768px) { max-width: 768px; }
@media (min-width: 1024px) { max-width: 1024px; }
@media (min-width: 1280px) { max-width: 1280px; }
@media (min-width: 1536px) { max-width: 1536px; }
}
a {
text-decoration: none;
}
+30
View File
@@ -0,0 +1,30 @@
import 'normalize.css'
import './App.scss'
import { Header } from './components/Header'
import { Slogan } from './components/Slogan'
import { Footer } from './components/Footer'
import { SlightFeature } from './components/Features/SlightFeature'
import { QuickFeature } from './components/Features/QuickFeature'
import { FullFeature } from './components/Features/FullFeature'
import { FuncFeature } from './components/Features/FuncFeature'
import { SafeFeature } from './components/Features/SafeFeature'
function App() {
return (
<>
<Header />
<Slogan />
<SlightFeature />
<QuickFeature />
<FullFeature />
<FuncFeature />
<SafeFeature />
<Footer />
</>
)
}
export default App
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

@@ -0,0 +1,6 @@
.feature-desc {
font-size: 24px;
line-height: 2;
color: #67757F;
margin: 60px 0;
}
@@ -0,0 +1,11 @@
import './FeatureDesc.scss'
export const FeatureDesc: React.FC<{
children: React.ReactNode
}> = ({ children }) => {
return (
<div className="feature-desc">
{children}
</div>
)
}
@@ -0,0 +1,8 @@
.feature-title {
font-size: 40px;
font-weight: bold;
.bold {
font-size: 1.7em;
}
}
@@ -0,0 +1,17 @@
import './FeatureTitle.scss'
import { Reveal } from './Reveal'
interface FeatureTitleProps {
text: string
color: string
}
export const FeatureTitle: React.FC<FeatureTitleProps> = (props) => {
return (
<Reveal>
<div className='feature-title' style={{color: props.color}}>
<span className='bold'>{props.text.slice(0, 1)}</span>{props.text.slice(1, props.text.length)}
</div>
</Reveal>
)
}
@@ -0,0 +1,18 @@
.feature {
min-height: 90vh;
.content {
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 0 30px;
main {
width: 40%;
}
aside {
display: flex;
}
}
}
@@ -0,0 +1,27 @@
import React, { useEffect, useRef } from 'react'
import './FeatureBase.scss'
import { useIsElementVisible } from '../../hooks/visible'
interface FeatureBaseProps extends React.ComponentProps<'div'> {
onVisibleChange?: (visible: boolean) => void
}
export const FeatureBase: React.FC<FeatureBaseProps> = (props) => {
const { className, onVisibleChange, ...otherProps } = props
const refElem = useRef<HTMLDivElement>(null)
const isSlightFeatureVisible = useIsElementVisible(refElem)
const classNames = ['feature', 'container', isSlightFeatureVisible ? 'visible' : '', ...(className?.split(' ') || [])]
useEffect(() => {
onVisibleChange && onVisibleChange(isSlightFeatureVisible)
}, [onVisibleChange, isSlightFeatureVisible])
return (
<div ref={refElem} className={classNames.join(' ')} {...otherProps}>
{props.children}
</div>
)
}
@@ -0,0 +1,84 @@
.full.feature {
.func-list {
margin: 100px 0;
.row {
display: flex;
margin-bottom: 30px;
place-items: center;
justify-items: stretch;
&:nth-child(even) {
transform: translateX(50px);
}
&:nth-child(odd) {
transform: translateX(-50px);
}
.item {
color: #000;
margin: 0 15px;
flex: 1;
font-size: 25px;
border-radius: 20px;
border: 3px solid #E1E4EC;
background: #fff;
padding: 40px 50px;
transition: transform 200ms cubic-bezier(0.750, 0.250, 0.250, 0.750);
&:hover {
transform: scale(1.05);
}
.header {
display: flex;
place-items: center;
margin-bottom: 20px;
svg {
margin-right: .5em;
}
.text {
font-weight: bold;
}
}
.body {
.desc {
color: #67757F;
display: flex;
place-items: center;
min-height: 3em;
}
}
}
}
}
.folder-icon {
margin-top: -100px;
}
.folder-icon.animate {
@keyframes fileUpAnim {
50% { transform: translateY(-10%) }
}
.file-1 {
animation: fileUpAnim 1s cubic-bezier(0.860, 0.000, 0.070, 1.000) forwards;
}
.file-2 {
animation: fileUpAnim 1s cubic-bezier(0.860, 0.000, 0.070, 1.000) forwards;
animation-delay: 500ms;
}
.file-3 {
animation: fileUpAnim 1s cubic-bezier(0.860, 0.000, 0.070, 1.000) forwards;
animation-delay: 1s;
}
}
}
@@ -0,0 +1,33 @@
import React from 'react'
import './FullFeature.scss'
import { FeatureBase } from './FeatureBase'
import { FeatureTitle } from '../FeatureTitle'
import { FeatureDesc } from '../FeatureDesc'
import { Reveal } from '../Reveal'
import { FolderIcon } from './FullFeatureIcon'
import { FullFeatureList } from './FullFeatureList'
export const FullFeature: React.FC = () => {
return (
<FeatureBase className='full'>
<FeatureTitle text='全面' color='#67757F' />
<div className="content">
<main>
<div className="desc">
<Reveal delay={200}>
<FeatureDesc>
Artalk 提供丰富的内置功能,我们尽力在简洁的同时保持功能的全面,为您带来开箱即用的体验。
</FeatureDesc>
</Reveal>
</div>
</main>
<aside>
<FolderIcon />
</aside>
</div>
<FullFeatureList />
</FeatureBase>
)
}
@@ -0,0 +1,60 @@
import React, { useEffect, useRef, useState } from 'react'
import { useIsElementVisible } from '../../hooks/visible'
export const FolderIcon: React.FC = () => {
const [animate, setAnimate] = useState(false)
const refEl = useRef<HTMLDivElement>(null)
const visible = useIsElementVisible(refEl)
useEffect(() => {
let animateTimer: number|null = null
let cleanUpTimer: number|null = null
const triggerAnimate = () => {
setAnimate(true)
cleanUpTimer && window.clearTimeout(cleanUpTimer)
cleanUpTimer = window.setTimeout(() => {
setAnimate(false)
animateTimer && window.clearTimeout(animateTimer)
animateTimer = window.setTimeout(() => {
if (visible) triggerAnimate()
}, 1000)
}, 2000)
}
if (visible) triggerAnimate()
return () => {
setAnimate(false)
cleanUpTimer && clearInterval(cleanUpTimer)
animateTimer && clearInterval(animateTimer)
}
}, [visible])
return (
<div ref={refEl} className={['folder-icon', animate ? 'animate' : ''].join(' ')}>
<svg width="320" height="320" viewBox="0 0 320 320" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M264.833 172.333C264.833 185.956 253.789 184.667 240.167 184.667H79.8333C66.2112 184.667 55.1667 185.956 55.1667 172.333L67.5 98.3334C68.2708 86.5119 78.5445 73.6667 92.1667 73.6667L227.833 73.6667C241.455 73.6667 251.211 87.2827 252.5 98.3334L264.833 172.333Z" fill="#67757F"/>
<g className='file-1'>
<path d="M221.667 129.167C221.667 135.975 216.141 141.5 209.333 141.5H110.667C103.859 141.5 98.3333 135.975 98.3333 129.167V79.8333C98.3333 73.0192 103.859 67.5 110.667 67.5H209.333C216.141 67.5 221.667 73.0192 221.667 79.8333V129.167Z" fill="#CCD6DD"/>
<rect x="134" y="49" width="70" height="44" rx="14" fill="#CCD6DD"/>
<path d="M196.5 73.5C196.5 80.308 190.975 85.8333 184.167 85.8333H153.333C146.525 85.8333 141 80.308 141 73.5V67.3333C141 60.5253 146.525 55 153.333 55H184.167C190.975 55 196.5 60.5253 196.5 67.3333V73.5Z" fill="#FCAB40"/>
</g>
<g className='file-2'>
<path d="M234 153.833C234 160.641 228.475 166.167 221.667 166.167H98.3333C91.5253 166.167 86 160.641 86 153.833V104.5C86 97.6921 91.5253 92.1667 98.3333 92.1667L221.667 92.1667C228.475 92.1667 234 97.6921 234 104.5V153.833Z" fill="#E1E8ED"/>
<rect x="110" y="73" width="70" height="44" rx="14" fill="#E1E8ED"/>
<path d="M172.5 98.5C172.5 105.308 166.975 110.833 160.167 110.833H129.333C122.525 110.833 117 105.308 117 98.5V92.3333C117 85.5253 122.525 80 129.333 80H160.167C166.975 80 172.5 85.5253 172.5 92.3333V98.5Z" fill="#5DADEC"/>
</g>
<g className='file-3'>
<path d="M246.667 178.5C246.667 185.308 241.141 190.833 234.333 190.833H86.3333C79.5253 190.833 74 185.308 74 178.5V129.167C74 122.359 79.5253 116.833 86.3333 116.833L234.333 116.833C241.141 116.833 246.667 122.359 246.667 129.167V178.5Z" fill="#F5F8FA"/>
<path d="M124.457 169V164.151C124.457 163.336 124.154 162.928 123.548 162.928C123.246 162.928 122.955 163.05 122.677 163.294C122.403 163.539 122.113 163.871 121.805 164.291V169H120.018V161.559H121.563L121.607 162.657C121.759 162.467 121.915 162.293 122.076 162.137C122.242 161.981 122.42 161.849 122.611 161.742C122.801 161.629 123.006 161.544 123.226 161.485C123.446 161.427 123.692 161.397 123.966 161.397C124.347 161.397 124.679 161.461 124.962 161.588C125.245 161.71 125.482 161.886 125.672 162.115C125.863 162.34 126.004 162.613 126.097 162.936C126.195 163.253 126.244 163.609 126.244 164.005V169H124.457ZM134.747 164.708C134.747 164.83 134.742 164.989 134.732 165.184C134.728 165.379 134.718 165.562 134.703 165.733H129.811C129.811 166.061 129.859 166.349 129.957 166.598C130.06 166.847 130.201 167.057 130.382 167.228C130.567 167.394 130.787 167.521 131.041 167.608C131.3 167.696 131.585 167.74 131.898 167.74C132.259 167.74 132.645 167.713 133.055 167.66C133.47 167.601 133.9 167.508 134.344 167.381V168.802C134.154 168.856 133.946 168.905 133.722 168.949C133.497 168.993 133.268 169.032 133.033 169.066C132.799 169.1 132.562 169.125 132.323 169.139C132.083 169.159 131.852 169.168 131.627 169.168C131.061 169.168 130.553 169.088 130.104 168.927C129.654 168.766 129.273 168.529 128.961 168.216C128.648 167.899 128.409 167.508 128.243 167.044C128.077 166.581 127.994 166.043 127.994 165.433C127.994 164.828 128.077 164.278 128.243 163.785C128.409 163.287 128.644 162.862 128.946 162.511C129.249 162.154 129.615 161.881 130.045 161.69C130.479 161.495 130.963 161.397 131.495 161.397C132.022 161.397 132.489 161.48 132.894 161.646C133.299 161.808 133.639 162.035 133.912 162.328C134.186 162.621 134.393 162.97 134.535 163.375C134.676 163.78 134.747 164.225 134.747 164.708ZM132.931 164.452C132.936 164.159 132.899 163.905 132.821 163.69C132.743 163.47 132.635 163.29 132.499 163.148C132.367 163.006 132.21 162.901 132.03 162.833C131.854 162.765 131.664 162.73 131.458 162.73C131.009 162.73 130.641 162.882 130.353 163.185C130.064 163.482 129.894 163.905 129.84 164.452H132.931ZM142.621 169H140.636L139.845 166.642L139.603 165.821L139.361 166.664L138.6 169H136.607L135.553 161.559H137.252L137.787 165.763L137.911 166.913L138.211 165.88L138.958 163.617H140.321L141.119 165.865L141.463 166.927L141.603 165.748L142.049 161.559H143.668L142.621 169Z" fill="#5DADEC"/>
<path d="M158.214 169L157.687 167.11H154.347L153.805 169H152.018L154.852 159.427H157.372L160.206 169H158.214ZM156.039 161.185L154.779 165.638H157.284L156.039 161.185ZM166.043 164.452C166.053 164.173 166.039 163.937 166 163.741C165.965 163.546 165.907 163.385 165.824 163.258C165.746 163.131 165.648 163.038 165.531 162.979C165.414 162.921 165.279 162.892 165.128 162.892C164.864 162.892 164.591 163.001 164.308 163.221C164.029 163.436 163.722 163.792 163.385 164.291V169H161.554V161.559H163.172L163.238 162.643C163.36 162.452 163.497 162.281 163.648 162.13C163.805 161.979 163.976 161.849 164.161 161.742C164.352 161.629 164.562 161.544 164.791 161.485C165.021 161.427 165.272 161.397 165.545 161.397C165.921 161.397 166.253 161.463 166.542 161.595C166.834 161.722 167.079 161.913 167.274 162.167C167.474 162.42 167.621 162.74 167.713 163.126C167.811 163.507 167.853 163.949 167.838 164.452H166.043ZM175.638 168.927C175.316 169 174.991 169.059 174.664 169.103C174.337 169.146 174.027 169.168 173.734 169.168C173.246 169.168 172.823 169.117 172.467 169.015C172.115 168.912 171.822 168.756 171.588 168.546C171.358 168.336 171.188 168.07 171.075 167.748C170.968 167.42 170.914 167.032 170.914 166.583V162.95H168.915V161.559H170.914V159.654L172.745 159.178V161.559H175.638V162.95H172.745V166.451C172.745 166.876 172.843 167.198 173.038 167.418C173.233 167.633 173.561 167.74 174.02 167.74C174.312 167.74 174.598 167.716 174.876 167.667C175.155 167.618 175.409 167.562 175.638 167.499V168.927ZM182.413 169L182.369 168.026C182.213 168.192 182.049 168.346 181.878 168.487C181.708 168.624 181.52 168.744 181.314 168.846C181.109 168.949 180.885 169.027 180.641 169.081C180.401 169.139 180.138 169.168 179.85 169.168C179.469 169.168 179.134 169.112 178.846 169C178.558 168.888 178.316 168.731 178.121 168.531C177.926 168.331 177.777 168.089 177.674 167.806C177.577 167.523 177.528 167.21 177.528 166.869C177.528 166.517 177.601 166.192 177.748 165.895C177.899 165.597 178.126 165.34 178.429 165.125C178.736 164.911 179.117 164.742 179.571 164.62C180.03 164.498 180.567 164.437 181.183 164.437H182.157V163.99C182.157 163.8 182.13 163.629 182.076 163.478C182.022 163.321 181.935 163.189 181.812 163.082C181.695 162.975 181.542 162.892 181.351 162.833C181.161 162.774 180.926 162.745 180.648 162.745C180.208 162.745 179.774 162.796 179.344 162.899C178.919 162.997 178.507 163.136 178.106 163.316V161.888C178.463 161.747 178.873 161.629 179.337 161.537C179.806 161.444 180.292 161.397 180.794 161.397C181.346 161.397 181.82 161.451 182.215 161.559C182.611 161.661 182.936 161.817 183.189 162.027C183.448 162.237 183.639 162.501 183.761 162.818C183.883 163.131 183.944 163.497 183.944 163.917V169H182.413ZM182.157 165.711H181.065C180.763 165.711 180.506 165.741 180.296 165.799C180.086 165.858 179.916 165.938 179.784 166.041C179.652 166.144 179.554 166.263 179.491 166.4C179.432 166.532 179.403 166.673 179.403 166.825C179.403 167.127 179.5 167.359 179.696 167.521C179.891 167.677 180.157 167.755 180.494 167.755C180.743 167.755 181.002 167.665 181.271 167.484C181.539 167.303 181.834 167.044 182.157 166.708V165.711ZM188.397 160.042H186.295V158.651H190.228V167.608H192.374V169H186.039V167.608H188.397V160.042ZM198.849 169L196.102 165.279V169H194.315V158.651H196.102V164.708L198.702 161.559H200.936L197.918 164.95L201.185 169H198.849Z" fill="#67757F"/>
<rect x="85" y="97" width="70" height="44" rx="14" fill="#F5F8FA"/>
<path d="M147.5 123C147.5 129.808 141.975 135.333 135.167 135.333H104.333C97.5253 135.333 92 129.808 92 123V116.833C92 110.025 97.5253 104.5 104.333 104.5H135.167C141.975 104.5 147.5 110.025 147.5 116.833V123Z" fill="#E75A70"/>
</g>
<path d="M264.833 240.167C264.833 253.789 253.789 264.833 240.167 264.833H79.8333C66.2112 264.833 55.1667 253.789 55.1667 240.167L55.1667 172.333C55.1667 158.711 66.2112 147.667 79.8333 147.667H240.167C253.789 147.667 264.833 158.711 264.833 172.333V240.167Z" fill="#9AAAB4"/>
<path d="M203.167 221.667C203.167 228.475 197.641 234 190.833 234H129.167C122.359 234 116.833 228.475 116.833 221.667V190.833C116.833 184.025 122.359 178.5 129.167 178.5H190.833C197.641 178.5 203.167 184.025 203.167 190.833V221.667Z" fill="#67757F"/>
<path d="M190.833 215.5C190.833 218.91 188.077 221.667 184.667 221.667H135.333C131.929 221.667 129.167 218.91 129.167 215.5V197C129.167 193.59 131.929 190.833 135.333 190.833H184.667C188.077 190.833 190.833 193.59 190.833 197V215.5Z" fill="#E1E8ED"/>
</svg>
</div>
)
}
@@ -0,0 +1,153 @@
import React from 'react'
import { TbLayoutSidebarRightExpandFilled, TbMailFilled, TbEyeFilled, TbTransformFilled, TbLocationFilled, TbCardsFilled, TbPhotoSearch, TbMath, TbPlug, TbLanguage, TbTerminal, TbApi } from 'react-icons/tb'
import { BiSolidNotification, BiSolidBadgeCheck } from 'react-icons/bi'
import { RiRobot2Fill, RiUpload2Fill } from 'react-icons/ri'
import { BsFillShieldLockFill } from 'react-icons/bs'
import { PiSmileyWinkBold } from 'react-icons/pi'
interface FuncItem {
icon: React.ReactNode
name: string
desc: string
link: string
}
const FuncList: FuncItem[] = [
{
icon: <TbLayoutSidebarRightExpandFilled />,
name: '侧边栏',
desc: '快速管理、直观浏览',
link: 'https://artalk.js.org/guide/frontend/sidebar.html'
},
{
icon: <TbMailFilled />,
name: '邮件通知',
desc: '多方式、邮件模板',
link: 'https://artalk.js.org/guide/backend/email.html'
},
{
icon: <BiSolidNotification />,
name: '多元推送',
desc: '支持 Telegram 等消息推送',
link: 'https://artalk.js.org/guide/backend/admin_notify.html'
},
{
icon: <RiRobot2Fill />,
name: '验证码',
desc: '多方式、频率限制',
link: 'https://artalk.js.org/guide/backend/captcha.html'
},
{
icon: <BsFillShieldLockFill />,
name: '评论审核',
desc: '内容检测、垃圾拦截',
link: 'https://artalk.js.org/guide/backend/moderator.html'
},
{
icon: <RiUpload2Fill />,
name: '图片上传',
desc: '自定义上传、支持图床',
link: 'https://artalk.js.org/guide/backend/img-upload.html'
},
{
icon: <TbCardsFilled />,
name: '多站点',
desc: '站点隔离、集中管理',
link: 'https://artalk.js.org/guide/backend/multi-site.html'
},
{
icon: <PiSmileyWinkBold />,
name: '表情包',
desc: '动态加载、兼容 OwO',
link: 'https://artalk.js.org/guide/frontend/emoticons.html'
},
{
icon: <BiSolidBadgeCheck />,
name: '管理员',
desc: '密码验证、徽章标识',
link: 'https://artalk.js.org/guide/backend/multi-site.html'
},
{
icon: <TbEyeFilled />,
name: '浏览量统计',
desc: '轻松统计网页浏览量',
link: 'https://artalk.js.org/guide/frontend/pv.html'
},
{
icon: <TbTransformFilled />,
name: '数据迁移',
desc: '自由迁移、快速备份',
link: 'https://artalk.js.org/guide/transfer.html'
},
{
icon: <TbLocationFilled />,
name: 'IP 属地',
desc: '用户 IP 属地展示',
link: 'https://artalk.js.org/guide/frontend/ip-region.html'
},
{
icon: <TbPhotoSearch />,
name: '图片灯箱',
desc: '快速集成图片灯箱',
link: 'https://artalk.js.org/guide/frontend/lightbox.html'
},
{
icon: <TbMath />,
name: 'Latex',
desc: '一键集成 Latex 公式解析',
link: 'https://artalk.js.org/guide/frontend/latex.html'
},
{
icon: <TbPlug />,
name: '自定义插件',
desc: '创造更多可能性',
link: 'https://artalk.js.org/develop/'
},
{
icon: <TbLanguage />,
name: '多语言',
desc: '支持多国语言切换',
link: 'https://artalk.js.org/guide/frontend/i18n.html'
},
{
icon: <TbTerminal />,
name: '命令行',
desc: '命令行操作管理能力',
link: 'https://artalk.js.org/guide/backend/config.html'
},
{
icon: <TbApi />,
name: 'API 文档',
desc: '提供 OpenAPI 格式文档',
link: 'https://artalk.js.org/develop/'
},
]
export const FullFeatureList: React.FC = () => {
// let func list item group by every two items
const FuncListGrouped = FuncList.reduce<FuncItem[][]>((result, current, index) => {
if (index % 3 === 0) result.push([current])
else result[result.length - 1].push(current)
return result
}, [])
return (
<div className='func-list'>
{FuncListGrouped.map((row) => (
<div className='row'>
{row.map((item) => (
<a className='item' href={item.link} target='_blank'>
<div className="header">
{item.icon}
<span className="text">{item.name}</span>
</div>
<div className="body">
<div className="desc">{item.desc}</div>
</div>
</a>
))}
</div>
))}
</div>
)
}
@@ -0,0 +1,65 @@
.func.feature {
.content .ship-icon {
margin: 0 80px;
margin-top: -70px;
}
.func-grps {
column-count: 4;
column-gap: 30px;
margin: 50px 0;
.grp {
margin-bottom: 40px;
break-inside: avoid;
.name {
display: flex;
place-items: center;
justify-content: space-between;
color: #000;
font-size: 27px;
margin-bottom: 25px;
&:hover {
svg {
animation: .5s cubic-bezier(0.25, 1, 0.5, 1) funcGrpNameArrowAnim;
opacity: 1;
}
}
@keyframes funcGrpNameArrowAnim {
from {
opacity: 0;
transform: translateX(-50%);
}
}
svg {
margin: 0 20px;
opacity: 0;
color: #67757F;
}
}
.items {
display: grid;
gap: 10px;
grid-template-columns: repeat(2, 1fr);
align-items: baseline;
.item {
font-size: 20px;
background: #F8FAFC;
border-radius: 20px;
padding: 20px 0;
text-align: center;
&:not(:last-child) {
margin-bottom: 10px;
}
}
}
}
}
}
@@ -0,0 +1,66 @@
import React from 'react'
import './FuncFeature.scss'
import { FeatureBase } from './FeatureBase'
import { FeatureTitle } from '../FeatureTitle'
import { FeatureDesc } from '../FeatureDesc'
import { Reveal } from '../Reveal'
import { FaArrowRight } from 'react-icons/fa'
const FuncGrps: {name: string, items: string[], link?: string}[] = [
{ name: '邮箱发送', items: ['SMTP', '阿里云邮件', 'sendmail'], link: 'https://artalk.js.org/guide/backend/email.html' },
{ name: '验证码', items: ['Turnstile', 'reCAPTCHA', 'hCaptcha', '极验'], link: 'https://artalk.js.org/guide/backend/captcha.html' },
{ name: '消息推送', items: ['Telegram', '飞书', '钉钉', 'Bark', 'WebHook', 'Slack', 'LINE'], link: 'https://artalk.js.org/guide/backend/admin_notify.html' },
{ name: '评论审核', items: ['Akismet', '腾讯云', '阿里云', '离线词库'], link: 'https://artalk.js.org/guide/backend/moderator.html' },
{ name: '表情包', items: ['标准格式', 'OwO 格式'], link: 'https://artalk.js.org/guide/frontend/emoticons.html' },
{ name: '图片上传', items: ['本地保存', 'UPGIT', '自定义函数'], link: 'https://artalk.js.org/guide/backend/img-upload.html' },
{ name: '富文本', items: ['Markdown', 'Latex'], link: 'https://artalk.js.org/guide/frontend/latex.html' },
{ name: '代码高亮', items: ['hanabi', 'highlight.js'] },
{ name: '用户头像', items: ['Gravatar', '自定义函数'], link: 'https://artalk.js.org/guide/frontend/config.html' },
{ name: '图片灯箱', items: ['LightGallery', 'FancyBox', 'lightbox2'], link: 'https://artalk.js.org/guide/frontend/lightbox.html' },
{ name: '数据库', items: ['SQLite', 'MySQL', 'PostgreSQL', 'SQL Server'], link: 'https://artalk.js.org/guide/backend/config.html' },
{ name: '高速缓存', items: ['内建缓存', 'Redis', 'Memcache'], link: 'https://artalk.js.org/guide/backend/config.html' },
{ name: '程序部署', items: ['二进制文件', 'Docker 镜像'], link: 'https://artalk.js.org/guide/deploy.html' },
{ name: '操作系统', items: ['Linux', 'Windows', 'macOS', 'FreeBSD'] },
{ name: '平台架构', items: ['x86', 'ARM'] },
{ name: '评论导入', items: ['Typecho', 'WordPress', 'Valine', 'Waline', 'Disqus', 'Commento', 'Twikoo', 'Artrans'], link: 'https://artalk.js.org/guide/transfer.html' },
]
export const FuncFeature: React.FC = () => {
return (
<FeatureBase className='func'>
<FeatureTitle text='可选集成' color='#292F33' />
<div className="content">
<main>
<Reveal delay={250}>
<FeatureDesc>
Artalk 提供丰富第三方接入能力。
</FeatureDesc>
</Reveal>
</main>
<aside>
<svg className="ship-icon" width="177" height="177" viewBox="0 0 177 177" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_174_446)"><path d="M78.6667 0H98.3333V24.5833H78.6667V0Z" fill="#292F33"/><path d="M78.6667 9.8335H98.3333V29.5002H78.6667V9.8335Z" fill="#D1D3D4"/><path d="M88.5 9.8335H98.3333V29.5002H88.5V9.8335Z" fill="#A7A9AC"/><path d="M88.5 93.4165H24.5833C24.5833 93.4165 31.2454 132.75 48.5521 162.25C65.8538 191.75 88.5 162.25 88.5 162.25C88.5 162.25 111.146 191.75 128.448 162.25C145.755 132.75 152.417 93.4165 152.417 93.4165H88.5Z" fill="#66757F"/><path d="M88.5 93.4165H24.5833C24.5833 93.4165 31.2454 132.75 48.5521 162.25C65.8538 191.75 88.5 162.25 88.5 162.25V93.4165Z" fill="#99AAB5"/><path d="M0 147.5H177V177H0V147.5Z" fill="#55ACEE"/><path d="M147.5 44.2502H135.454L134.264 24.5835H42.7357L41.5508 44.2502H29.5V73.7502H39.766L38.5762 93.4168H138.424L137.234 73.7502H147.5V44.2502Z" fill="#E6E7E8"/><path d="M49.1667 73.75H127.833V93.4167H49.1667V73.75Z" fill="#D1D3D4"/><path d="M39.3333 54.0835H137.667V63.9168H39.3333V54.0835Z" fill="#6D6E71"/><path d="M49.1667 34.4165H127.833V44.2498H49.1667V34.4165Z" fill="#BCBEC0"/><path d="M29.0723 113.083H147.928C148.916 109.411 149.737 106.067 150.386 103.25H26.6139C27.2629 106.067 28.084 109.411 29.0723 113.083Z" fill="#BE1931"/><path d="M88.5 113.083H147.928C148.916 109.411 149.737 106.067 150.386 103.25H88.5V113.083Z" fill="#A0041E"/><path d="M59 83.5835H118V93.4168H59V83.5835Z" fill="#BCBEC0"/><path d="M78.6667 0H88.5V9.83333H78.6667V0Z" fill="#58595B"/></g><defs><clipPath id="clip0_174_446"><rect width="177" height="177" fill="white"/></clipPath></defs>
</svg>
</aside>
</div>
<div className="func-grps">
{FuncGrps.map((grp) => (
<div className="grp">
{grp.link ? (
<a className="name" href={grp.link} target='_blank'>{grp.name}<FaArrowRight /></a>
) : (
<div className='name'>{grp.name}</div>
)}
<div className="items">
{grp.items.map((item) => (
<div className='item'>{item}</div>
))}
</div>
</div>
))}
</div>
</FeatureBase>
)
}
@@ -0,0 +1,103 @@
.feature.quick {
.deploy-links {
margin: 80px 30px;
display: flex;
flex-direction: column;
.item {
cursor: pointer;
display: flex;
flex-direction: row;
flex-wrap: wrap;
place-items: flex-end;
font-size: 35px;
color: #000;
&:not(:last-child) {
margin-bottom: 1.8em;
}
&:hover {
.arrow-icon {
.block {
width: 100%;
}
svg {
stroke: #fff;
}
}
}
.bold {
font-size: 1.5em;
margin-right: .2em;
}
span {
margin-right: 1.4em;
}
.arrow-icon {
position: relative;
display: flex;
flex-direction: row;
place-items: center;
margin-top: 10px;
height: 2em;
width: calc(2em + 30px);
.block {
left: 0;
top: 0;
position: absolute;
display: block;
background: #55ACEE;
height: 2em;
width: .6em;
margin-right: .3em;
transition: width 500ms cubic-bezier(0.075, 0.820, 0.165, 1.000);
}
svg {
padding-left: 30px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
}
}
.airplane-wrap {
margin-top: 100px;
margin: 0 80px;
}
&.visible {
@keyframes airplaneAnim {
from {
transform: translate(-50%, 50%);
}
}
.airplane {
animation: airplaneAnim 2s cubic-bezier(0.075, 0.820, 0.165, 1.000);
}
}
.extra-desc {
margin-top: 250px;
margin-bottom: 250px;
text-align: center;
.golang-icon {
margin-bottom: 100px;
}
.self-compile {
margin-top: 1.5em;
}
}
}
@@ -0,0 +1,80 @@
import React from 'react'
import './QuickFeature.scss'
import { FiArrowUpRight } from 'react-icons/fi'
import { FeatureBase } from './FeatureBase'
import { FeatureTitle } from '../FeatureTitle'
import { LearnMoreLink } from '../LearnMoreLink'
import { FeatureDesc } from '../FeatureDesc'
import { Reveal } from '../Reveal'
export const QuickFeature: React.FC = () => {
const ArrowIcon = (
<div className='arrow-icon'>
<div className="block" />
<FiArrowUpRight size='2em' />
</div>
)
return (
<FeatureBase className='quick'>
<FeatureTitle text='快捷' color='#5DADEC' />
<div className='content'>
<main>
<div className="deploy-links">
<a className="item" href='https://artalk.js.org/guide/deploy.html#普通方式部署' target="_blank">
<span className='bold'>二进制</span> <span>文件部署</span>{ArrowIcon}
</a>
<a className="item" href='https://artalk.js.org/guide/deploy.html#docker-部署' target="_blank">
<span className='bold'>Docker</span> <span>镜像部署</span>{ArrowIcon}
</a>
</div>
</main>
<aside>
<div className="airplane-wrap">
<svg width="361" height="343" viewBox="0 0 361 343" fill="none" xmlns="http://www.w3.org/2000/svg">
<g className='airplane'>
<path d="M327.5 133.04C325.317 135.228 321.788 135.228 319.605 133.04L311.71 125.145C309.522 122.962 309.522 119.427 311.71 117.25L327.5 101.46C329.683 99.2772 333.212 99.2772 335.395 101.46L343.29 109.355C345.478 111.538 345.478 115.072 343.29 117.25L327.5 133.04ZM243.75 49.2896C241.567 51.4783 238.038 51.4783 235.855 49.2896L227.96 41.3948C225.772 39.2117 225.772 35.6831 227.96 33.5L243.75 17.7103C245.933 15.5272 249.462 15.5272 251.645 17.7103L259.54 25.6051C261.728 27.7882 261.728 31.3169 259.54 33.5L243.75 49.2896Z" fill="#66757F"/>
<path d="M171.167 122.833C182.333 122.833 232.583 128.417 232.583 128.417C232.583 128.417 238.167 178.667 238.167 189.833C238.167 201 227 201 221.417 195.417C215.833 189.833 199.083 161.917 199.083 161.917C199.083 161.917 171.167 145.167 165.583 139.583C160 134 160 122.833 171.167 122.833ZM182.333 33.7178C199.083 33.5 321.917 39.0833 321.917 39.0833C321.917 39.0833 327.076 161.917 327.288 178.667C327.5 195.417 311.861 201.011 305.725 178.672C299.589 156.333 277.25 83.75 277.25 83.75C277.25 83.75 199.335 59.0884 182.305 55.2638C160 50.25 165.578 33.9299 182.333 33.7178Z" fill="#55ACEE"/>
<path d="M310.75 16.75C321.917 5.58331 349.833 -2.00272e-05 355.417 5.58331C361 11.1666 355.417 39.0833 344.25 50.25C333.083 61.4166 238.167 150.75 238.167 150.75C238.167 150.75 201.875 175.875 193.5 167.5C185.125 159.125 210.25 122.833 210.25 122.833C210.25 122.833 299.583 27.9166 310.75 16.75Z" fill="#CCD6DD"/>
<path d="M238.167 122.833C238.167 122.833 240.958 125.625 215.833 150.75C190.708 175.875 187.917 173.083 187.917 173.083C187.917 173.083 185.125 170.292 210.25 145.167C235.375 120.042 238.167 122.833 238.167 122.833ZM321.917 22.3333C331.168 22.3333 338.667 29.8317 338.667 39.0833H343.161C343.798 37.3246 344.25 35.4821 344.25 33.5C344.25 24.2484 336.752 16.75 327.5 16.75C325.518 16.75 323.675 17.2022 321.917 17.8387V22.3333Z" fill="#66757F"/>
</g>
</svg>
</div>
</aside>
</div>
{/* Golang Description */}
<div className='extra-desc'>
<div className="golang-icon">
<svg width="292" height="109" viewBox="0 0 292 109" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_406_120)">
<path d="M22.035 32.9701C21.4664 32.9701 21.3242 32.6858 21.6086 32.2595L24.594 28.4225C24.8783 27.9961 25.5891 27.7119 26.1577 27.7119H76.9094C77.4781 27.7119 77.6203 28.1383 77.3359 28.5646L74.9192 32.2595C74.6349 32.6858 73.924 33.1122 73.4976 33.1122L22.035 32.9701Z" fill="#00ACD7"/>
<path d="M0.568647 46.0443C0 46.0443 -0.142162 45.7601 0.142162 45.3337L3.12756 41.4967C3.41188 41.0704 4.12269 40.7861 4.69133 40.7861H69.517C70.0857 40.7861 70.37 41.2125 70.2278 41.6388L69.0906 45.0495C68.9484 45.6179 68.3797 45.9022 67.8111 45.9022L0.568647 46.0443Z" fill="#00ACD7"/>
<path d="M34.9718 59.1185C34.4031 59.1185 34.2609 58.6922 34.5453 58.2658L36.5355 54.713C36.8199 54.2867 37.3885 53.8604 37.9571 53.8604H66.3895C66.9581 53.8604 67.2424 54.2867 67.2424 54.8551L66.9581 58.2658C66.9581 58.8343 66.3895 59.2606 65.963 59.2606L34.9718 59.1185Z" fill="#00ACD7"/>
<path d="M182.536 30.412C173.579 32.6857 167.466 34.3911 158.652 36.6649C156.52 37.2333 156.378 37.3754 154.53 35.2438C152.397 32.8279 150.834 31.2646 147.848 29.8435C138.892 25.438 130.22 26.717 122.117 31.9752C112.45 38.2281 107.474 47.4654 107.616 58.9765C107.759 70.3455 115.577 79.7249 126.808 81.2881C136.475 82.5671 144.578 79.1564 150.976 71.9087C152.255 70.3455 153.392 68.6401 154.814 66.6506C149.696 66.6506 143.299 66.6506 127.377 66.6506C124.391 66.6506 123.681 64.8031 124.676 62.3872C126.524 57.9817 129.936 50.5919 131.926 46.897C132.353 46.0443 133.348 44.6232 135.48 44.6232C142.73 44.6232 169.457 44.6232 187.227 44.6232C186.943 48.4602 186.943 52.2972 186.374 56.1343C184.81 66.3663 180.972 75.7457 174.717 83.9882C164.481 97.4889 151.118 105.873 134.201 108.147C120.269 109.995 107.332 107.295 95.9591 98.7679C85.4392 90.8096 79.4684 80.2933 77.9046 67.219C76.0565 51.7288 80.6057 37.8018 89.9883 25.5801C100.082 12.3637 113.445 3.97909 129.794 0.994739C143.157 -1.42117 155.951 0.142066 167.466 7.95823C175.001 12.9322 180.403 19.7535 183.957 27.996C184.81 29.2751 184.241 29.9856 182.536 30.412Z" fill="#00ACD7"/>
<path d="M229.591 109C216.654 108.716 204.855 105.021 194.904 96.4941C186.516 89.2464 181.256 80.0091 179.55 69.0664C176.991 53.0078 181.398 38.7965 191.065 26.1486C201.443 12.5058 213.953 5.4002 230.871 2.41584C245.371 -0.142175 259.019 1.27895 271.387 9.66356C282.617 17.3376 289.583 27.7118 291.431 41.3546C293.848 60.5397 288.304 76.172 275.083 89.5306C265.7 99.0521 254.185 105.021 240.964 107.721C237.126 108.431 233.287 108.574 229.591 109ZM263.426 51.5866C263.283 49.7392 263.283 48.3181 262.999 46.8969C260.44 32.8278 247.503 24.8696 233.998 27.996C220.777 30.9804 212.247 39.365 209.12 52.7235C206.561 63.8083 211.963 75.0351 222.199 79.5827C230.018 82.9934 237.836 82.5671 245.371 78.7301C256.602 72.9035 262.715 63.8083 263.426 51.5866Z" fill="#00ACD7"/>
</g>
<defs>
<clipPath id="clip0_406_120">
<rect width="292" height="109" fill="white"/>
</clipPath>
</defs>
</svg>
</div>
<div className="text">
<Reveal>
<FeatureDesc>
Artalk 采用 Golang 编写,支持跨平台,仅需下载二进制文件即可执行,<br />无额外依赖,快速启动你的评论服务器。同时提供 Docker 镜像,满足更多部署需求。
</FeatureDesc>
</Reveal>
</div>
<div className="self-compile">
<LearnMoreLink prompt='打算使用代码自编译?' link='https://artalk.js.org/guide/backend/build.html' />
</div>
</div>
</FeatureBase>
)
}
@@ -0,0 +1,46 @@
.safe.feature {
main {
margin-top: 80px;
}
.data-icon {
margin: 80px 0;
margin-left: 80px;
}
&.visible {
@keyframes dataIconTextAnim {
from {
transform: translateX(-40%);
opacity: 0;
}
}
@keyframes dataIconBgAnim {
from {
opacity: 0;
transform: translate(40%, -50%);
}
}
@keyframes dataIconFrameAnim {
from {
transform: translateX(20%);
}
}
.data-icon {
.bg {
animation: 1s cubic-bezier(0.25, 1, 0.5, 1) dataIconBgAnim;
}
.text {
animation: 1s cubic-bezier(0.25, 1, 0.5, 1) dataIconTextAnim;
}
.frame {
animation: 1s cubic-bezier(0.25, 1, 0.5, 1) dataIconFrameAnim;
}
}
}
}
@@ -0,0 +1,30 @@
import React from 'react'
import './SafeFeature.scss'
import { FeatureBase } from './FeatureBase'
import { FeatureTitle } from '../FeatureTitle'
import { FeatureDesc } from '../FeatureDesc'
import { Reveal } from '../Reveal'
export const SafeFeature: React.FC = () => {
return (
<FeatureBase className='safe'>
<FeatureTitle text='安全' color='#FFAC33' />
<div className='content'>
<aside>
<svg className='data-icon' width="336" height="407" viewBox="0 0 336 407" fill="none" xmlns="http://www.w3.org/2000/svg">
<path className='bg' fill-rule="evenodd" clip-rule="evenodd" d="M55 300V228.255L283.255 0H336V19.0001L55 300ZM55 345.255V371H101L336 136V64.2549L55 345.255ZM218 371H146.255L336 181.255V253L218 371ZM263.255 371H336V298.255L263.255 371ZM166.255 0H238L55 183V111.255L166.255 0ZM121 0H55V66L121 0Z" fill="#FFAC33"/>
<path className='text' d="M8.30273 195V85.084H53.0156C64.0352 85.084 73.4805 87.166 81.3516 91.3301C89.2227 95.4941 95.2402 101.613 99.4043 109.688C103.568 117.711 105.65 127.562 105.65 139.242V139.395C105.65 151.074 103.568 161.078 99.4043 169.406C95.2402 177.684 89.2227 184.031 81.3516 188.449C73.5312 192.816 64.0859 195 53.0156 195H8.30273ZM36.2578 172.453H49.2832C55.3262 172.453 60.4297 171.234 64.5938 168.797C68.7578 166.309 71.8809 162.627 73.9629 157.752C76.0957 152.877 77.1621 146.783 77.1621 139.471V139.318C77.1621 132.463 76.0703 126.674 73.8867 121.951C71.7031 117.229 68.5039 113.674 64.2891 111.287C60.125 108.85 55.123 107.631 49.2832 107.631H36.2578V172.453ZM107.936 195L144.955 85.084H170.701V109.002H161.789L137.338 195H107.936ZM129.568 170.93L135.814 150.744H188.373L194.619 170.93H129.568ZM186.85 195L162.322 109.002V85.084H179.232L216.252 195H186.85ZM33.8203 345V257.555H3.50391V235.084H92.0156V257.555H61.7754V345H33.8203ZM83.1035 345L120.123 235.084H145.869V259.002H136.957L112.506 345H83.1035ZM104.736 320.93L110.982 300.744H163.541L169.787 320.93H104.736ZM162.018 345L137.49 259.002V235.084H154.4L191.42 345H162.018Z" fill="black"/>
<path className='frame' fill-rule="evenodd" clip-rule="evenodd" d="M34 47H258V383H34V374H10V383V407H34H258H282V383V47V23H258H34H10V47V56H34V47Z" fill="#AAB8C2"/>
</svg>
</aside>
<main>
<FeatureDesc>
<Reveal>开源自由软件,自托管评论数据,透明可控。<br/>内置数据迁移工具,轻松转移您的评论内容。<br /><br /></Reveal>
<Reveal delay={500}>我们始终将安全放在首位,并持续提供更新。</Reveal>
</FeatureDesc>
</main>
</div>
</FeatureBase>
)
}
@@ -0,0 +1,70 @@
.feature.slight {
.progress-wrap {
width: 100%;
margin-top: 80px;
margin-bottom: 50px;
.progress {
margin-bottom: 15px;
position: relative;
height: 24px;
width: 100%;
background: #D9D9D9;
border-radius: 80px;
overflow: hidden;
.bar {
position: absolute;
height: 100%;
width: 0%;
background: #77B255;
transition: all 800ms cubic-bezier(0.230, 1.000, 0.320, 1.000);
}
}
.text {
display: flex;
flex-direction: row;
justify-content: space-between;
place-items: center;
padding: 0 5px;
font-size: 23px;
.size {
color: #77B255;
font-weight: bold;
}
.percent {
color: #67757F;
}
}
}
&.visible {
.leaf-wrap {
@keyframes windAnim {
from {
opacity: 0;
filter: blur(10px);
transform: translate(-40%, 35%) rotate(20deg);
}
to {
}
}
.wind {
animation: windAnim .5s cubic-bezier(0.230, 1.000, 0.320, 1.000);
}
@keyframes leafAnim {
0% {
transform: translate(-10%, -10%) rotate(10deg);
}
}
.leaf {
animation: leafAnim 1s cubic-bezier(0.250, 0.460, 0.450, 0.940);
}
}
}
}
@@ -0,0 +1,69 @@
import React, { useState } from 'react'
import { FeatureTitle } from '../FeatureTitle'
import { Reveal } from '../Reveal'
import { FeatureBase } from './FeatureBase'
import './SlightFeature.scss'
import { FeatureDesc } from '../FeatureDesc'
export const SlightFeature: React.FC = () => {
const [percent, setPercent] = useState(0)
const [visible, setVisible] = useState(false)
const onVisibleChange = (visible: boolean) => {
setVisible(visible)
if (visible) {
const fn = setInterval(() => {
setPercent((prev) => {
const next = Math.round(prev+0.5)
if (next <= 100) {
return next
} else {
clearInterval(fn)
return 100
}
})
}, 40)
} else {
setPercent(0)
}
}
return (
<FeatureBase
className='slight'
onVisibleChange={onVisibleChange}
>
<FeatureTitle text='轻量' color='#77B255' />
<div className="content">
<main>
<div className="progress-wrap">
<div className="progress">
<div className="bar" style={{width: `${percent >= 100 || visible ? 100 : 0}%`}} />
</div>
<div className="text">
<div className="size">40KB</div>
<div className="percent">{percent}%</div>
</div>
</div>
<Reveal delay={200}>
<FeatureDesc>
原生 JS 前端程序,核心功能无依赖框架,<br/>小巧体积、快速加载,迅速响应每刻交互。
</FeatureDesc>
</Reveal>
</main>
<aside>
<svg className='leaf-wrap' width="338" height="330" viewBox="0 0 338 330" fill="none" xmlns="http://www.w3.org/2000/svg">
<g className='leaf'>
<path d="M100.05 227.714C129.808 257.472 198.146 298.863 275.608 227.298C231.821 218.875 224.117 163.163 194.353 133.411C164.595 103.647 123.073 96.9183 93.3094 126.676C63.5515 156.434 70.2866 197.95 100.05 227.714Z" fill="#A6D388"/>
<path d="M208.733 235.204C245.16 244.917 272.089 230.817 274.573 228.332C274.573 228.332 252.068 238.741 212.995 219.558C218.654 212.353 235.905 202.266 235.905 202.266C232.617 200.209 211.663 200.928 197.355 210.76C190.471 206.403 183.183 201.148 175.521 194.859L180.514 189.681C199.834 169.565 221.793 174.321 221.793 174.321C218 170.677 188.51 153.866 164.827 178.482L161.183 182.275C158.651 179.903 156.095 177.448 153.491 174.844L142.494 163.847L148.819 157.516C168.56 137.78 197.718 137.002 197.718 137.002C193.996 133.274 156.921 121.255 132.78 145.395L128.405 149.764L79.1141 100.461C75.3928 96.734 69.0561 97.0431 65.3408 100.758C61.6196 104.48 61.3164 110.81 65.0376 114.532L114.335 163.829L113.984 164.18C89.8438 188.326 99.8661 227.393 103.593 231.12C103.593 231.12 106.375 199.959 126.117 180.224L128.417 177.917L149.229 198.735C149.568 199.074 149.9 199.365 150.239 199.704C135.824 228.219 157.545 261.169 161.879 263.809C161.879 263.809 153.17 235.733 164.732 212.478C173.803 219.582 182.684 224.932 191.226 228.855C191.72 246.647 207.318 262.739 211.104 264.112C211.104 264.112 205.689 246.261 207.93 237.439L208.733 235.204Z" fill="#77B255"/>
</g>
<path className='wind' d="M193.271 117.444C190.917 117.444 188.688 116.03 187.755 113.711C186.536 110.668 188.004 107.22 191.042 105.989C192.029 105.585 214.915 95.8958 217.293 69.3539C217.59 66.0904 220.408 63.7007 223.742 63.9623C227.018 64.2535 229.425 67.1425 229.134 70.412C226.09 104.361 196.731 116.511 195.482 117.01C194.757 117.308 194.008 117.444 193.271 117.444ZM222.993 141.222C220.532 141.222 218.232 139.683 217.382 137.233C216.306 134.142 217.929 130.766 221.008 129.678C222.125 129.274 247.907 119.507 252.989 81.005C253.417 77.7534 256.324 75.4469 259.659 75.8928C262.916 76.3208 265.205 79.3049 264.771 82.5624C258.702 128.632 226.322 140.42 224.949 140.895C224.301 141.115 223.641 141.222 222.993 141.222ZM240.833 176.889C238.734 176.889 236.707 175.771 235.619 173.804C234.038 170.933 235.09 167.318 237.961 165.737C238.122 165.648 254.315 156.606 259.367 144.825C260.663 141.805 264.171 140.414 267.167 141.704C270.186 142.994 271.583 146.489 270.287 149.509C263.612 165.083 244.494 175.712 243.686 176.158C242.776 176.651 241.802 176.889 240.833 176.889Z" fill="#5DADEC"/>
</svg>
</aside>
</div>
</FeatureBase>
)
}
Loaded 100 of 611 files, more files were not shown because too many files have changed in this diff. Show more