artalk 2.6.3 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (300) hide show
  1. package/dist/Artalk.css +1 -1
  2. package/dist/Artalk.es.js +7673 -7330
  3. package/dist/Artalk.iife.js +1 -1
  4. package/dist/Artalk.js +1 -1
  5. package/dist/ArtalkLite.css +1 -1
  6. package/dist/ArtalkLite.es.js +5304 -4961
  7. package/dist/ArtalkLite.iife.js +1 -1
  8. package/dist/ArtalkLite.js +1 -1
  9. package/dist/src/api/{api-base.d.ts → _base.d.ts} +3 -7
  10. package/dist/src/api/_options.d.ts +19 -0
  11. package/dist/src/api/{request.d.ts → _request.d.ts} +3 -3
  12. package/dist/src/api/admin.d.ts +1 -1
  13. package/dist/src/api/captcha.d.ts +1 -1
  14. package/dist/src/api/comment.d.ts +2 -2
  15. package/dist/src/api/index.d.ts +2 -4
  16. package/dist/src/api/page.d.ts +3 -3
  17. package/dist/src/api/site.d.ts +2 -2
  18. package/dist/src/api/system.d.ts +13 -3
  19. package/dist/src/api/upload.d.ts +1 -1
  20. package/dist/src/api/user.d.ts +2 -2
  21. package/dist/src/artalk.d.ts +14 -37
  22. package/dist/src/comment/actions.d.ts +1 -1
  23. package/dist/src/comment/comment.d.ts +3 -4
  24. package/dist/src/comment/render.d.ts +21 -3
  25. package/dist/src/comment/renders/actions.d.ts +2 -2
  26. package/dist/src/comment/renders/avatar.d.ts +2 -2
  27. package/dist/src/comment/renders/content.d.ts +2 -2
  28. package/dist/src/comment/renders/header.d.ts +2 -2
  29. package/dist/src/comment/renders/index.d.ts +2 -2
  30. package/dist/src/comment/renders/pending.d.ts +2 -2
  31. package/dist/src/comment/renders/reply-at.d.ts +2 -2
  32. package/dist/src/comment/renders/reply-to.d.ts +2 -2
  33. package/dist/src/components/action-btn.d.ts +3 -5
  34. package/dist/src/{lib → components}/checker/admin.d.ts +1 -1
  35. package/dist/src/{lib → components}/checker/captcha-renders.d.ts +1 -1
  36. package/dist/src/{lib → components}/checker/captcha.d.ts +1 -1
  37. package/dist/src/{lib → components}/checker/index.d.ts +10 -7
  38. package/dist/src/components/dark-mode.d.ts +2 -0
  39. package/dist/src/components/dialog.d.ts +1 -3
  40. package/dist/src/components/error-dialog.d.ts +8 -0
  41. package/dist/src/components/pagination.d.ts +4 -3
  42. package/dist/src/components/read-more-btn.d.ts +3 -3
  43. package/dist/src/config.d.ts +24 -8
  44. package/dist/src/context.d.ts +16 -35
  45. package/dist/src/data.d.ts +27 -0
  46. package/dist/src/defaults.d.ts +1 -1
  47. package/dist/src/editor/editor.d.ts +14 -59
  48. package/dist/src/editor/state.d.ts +20 -0
  49. package/dist/src/i18n/zh-CN.d.ts +1 -1
  50. package/dist/src/layer/index.d.ts +4 -3
  51. package/dist/src/layer/layer-manager.d.ts +9 -0
  52. package/dist/src/layer/layer.d.ts +8 -28
  53. package/dist/src/layer/scrollbar-helper.d.ts +5 -0
  54. package/dist/src/layer/sidebar-layer.d.ts +6 -6
  55. package/dist/src/layer/wrap.d.ts +13 -0
  56. package/dist/src/lib/component.d.ts +3 -8
  57. package/dist/src/lib/event-manager.d.ts +28 -0
  58. package/dist/src/lib/highlight.d.ts +1 -0
  59. package/dist/src/lib/marked-renderer.d.ts +4 -0
  60. package/dist/src/lib/marked.d.ts +4 -2
  61. package/dist/src/lib/sanitizer.d.ts +1 -0
  62. package/dist/src/lib/ui.d.ts +3 -2
  63. package/dist/src/lib/user.d.ts +8 -5
  64. package/dist/src/lib/utils.d.ts +11 -5
  65. package/dist/src/list/comment.d.ts +3 -0
  66. package/dist/src/list/layout.d.ts +23 -0
  67. package/dist/src/list/list.d.ts +13 -25
  68. package/dist/src/list/{list-nest.d.ts → nest.d.ts} +1 -1
  69. package/dist/src/list/page.d.ts +2 -0
  70. package/dist/src/list/paginator/index.d.ts +22 -7
  71. package/dist/src/list/paginator/read-more.d.ts +18 -0
  72. package/dist/src/list/paginator/up-down.d.ts +14 -0
  73. package/dist/src/main.d.ts +2 -0
  74. package/dist/src/plugins/admin-only-elem.d.ts +2 -0
  75. package/dist/src/plugins/conf-remoter.d.ts +2 -0
  76. package/dist/src/plugins/dark-mode.d.ts +2 -0
  77. package/dist/src/plugins/editor/_kit.d.ts +27 -0
  78. package/dist/src/plugins/editor/_plug.d.ts +29 -0
  79. package/dist/src/plugins/editor/_sample.d.ts +5 -0
  80. package/dist/src/plugins/editor/closable.d.ts +7 -0
  81. package/dist/src/{editor/plugs/emoticons-plug.d.ts → plugins/editor/emoticons.d.ts} +10 -16
  82. package/dist/src/plugins/editor/header-event.d.ts +6 -0
  83. package/dist/src/plugins/editor/header-link.d.ts +6 -0
  84. package/dist/src/plugins/editor/header-user.d.ts +16 -0
  85. package/dist/src/plugins/editor/index.d.ts +6 -0
  86. package/dist/src/plugins/editor/local-storage.d.ts +6 -0
  87. package/dist/src/plugins/editor/mover.d.ts +6 -0
  88. package/dist/src/plugins/editor/preview.d.ts +7 -0
  89. package/dist/src/plugins/editor/state-edit.d.ts +11 -0
  90. package/dist/src/plugins/editor/state-reply.d.ts +8 -0
  91. package/dist/src/plugins/editor/submit-add.d.ts +18 -0
  92. package/dist/src/plugins/editor/submit-btn.d.ts +5 -0
  93. package/dist/src/plugins/editor/submit.d.ts +17 -0
  94. package/dist/src/plugins/editor/textarea.d.ts +8 -0
  95. package/dist/src/plugins/editor/upload.d.ts +9 -0
  96. package/dist/src/plugins/editor-kit.d.ts +42 -0
  97. package/dist/src/plugins/index.d.ts +2 -0
  98. package/dist/src/plugins/list/copyright.d.ts +2 -0
  99. package/dist/src/plugins/list/count.d.ts +2 -0
  100. package/dist/src/plugins/list/dropdown.d.ts +2 -0
  101. package/dist/src/plugins/list/error-dialog.d.ts +2 -0
  102. package/dist/src/plugins/list/fetch.d.ts +2 -0
  103. package/dist/src/plugins/list/goto-first.d.ts +3 -0
  104. package/dist/src/plugins/list/goto.d.ts +2 -0
  105. package/dist/src/plugins/list/index.d.ts +3 -0
  106. package/dist/src/plugins/list/loading.d.ts +2 -0
  107. package/dist/src/plugins/list/no-comment.d.ts +2 -0
  108. package/dist/src/plugins/list/reach-bottom.d.ts +2 -0
  109. package/dist/src/plugins/list/sidebar-btn.d.ts +2 -0
  110. package/dist/src/plugins/list/time-ticking.d.ts +3 -0
  111. package/dist/src/plugins/list/unread-badge.d.ts +2 -0
  112. package/dist/src/plugins/list/unread.d.ts +2 -0
  113. package/dist/src/plugins/list/with-editor.d.ts +2 -0
  114. package/dist/src/plugins/markdown.d.ts +2 -0
  115. package/dist/src/plugins/stat.d.ts +13 -0
  116. package/dist/src/plugins/version-check.d.ts +2 -0
  117. package/dist/src/service.d.ts +19 -23
  118. package/dist/tests/e2e/comment-add.spec.d.ts +1 -0
  119. package/dist/types/config.d.ts +110 -0
  120. package/dist/types/context.d.ts +56 -136
  121. package/dist/types/data.d.ts +194 -0
  122. package/dist/types/editor.d.ts +76 -0
  123. package/dist/types/event.d.ts +36 -25
  124. package/dist/types/index.d.ts +7 -0
  125. package/dist/types/plugin.d.ts +2 -0
  126. package/dist/types/sidebar.d.ts +3 -0
  127. package/package.json +5 -2
  128. package/src/api/_base.ts +18 -0
  129. package/src/api/_options.ts +25 -0
  130. package/src/api/{request.ts → _request.ts} +19 -18
  131. package/src/api/admin.ts +1 -1
  132. package/src/api/captcha.ts +1 -1
  133. package/src/api/comment.ts +4 -4
  134. package/src/api/index.ts +3 -12
  135. package/src/api/page.ts +5 -5
  136. package/src/api/site.ts +2 -2
  137. package/src/api/system.ts +19 -7
  138. package/src/api/upload.ts +3 -3
  139. package/src/api/user.ts +3 -3
  140. package/src/artalk.ts +49 -97
  141. package/src/comment/actions.ts +1 -4
  142. package/src/comment/comment.ts +9 -6
  143. package/src/comment/render.ts +27 -5
  144. package/src/comment/renders/actions.ts +45 -45
  145. package/src/comment/renders/avatar.ts +7 -7
  146. package/src/comment/renders/content.ts +18 -18
  147. package/src/comment/renders/header.ts +13 -13
  148. package/src/comment/renders/index.ts +3 -3
  149. package/src/comment/renders/pending.ts +5 -5
  150. package/src/comment/renders/reply-at.ts +8 -8
  151. package/src/comment/renders/reply-to.ts +13 -13
  152. package/src/components/action-btn.ts +11 -13
  153. package/src/{lib → components}/checker/admin.ts +6 -6
  154. package/src/{lib → components}/checker/captcha-renders.ts +7 -6
  155. package/src/{lib → components}/checker/captcha.ts +1 -1
  156. package/src/{lib → components}/checker/index.ts +21 -26
  157. package/src/components/dark-mode.ts +30 -0
  158. package/src/components/dialog.ts +4 -9
  159. package/src/components/error-dialog.ts +35 -0
  160. package/src/components/pagination.ts +10 -6
  161. package/src/components/read-more-btn.ts +7 -7
  162. package/src/config.ts +60 -6
  163. package/src/context.ts +39 -144
  164. package/src/data.ts +110 -0
  165. package/src/defaults.ts +1 -1
  166. package/src/editor/editor.ts +41 -146
  167. package/src/editor/state.ts +52 -0
  168. package/src/i18n/zh-CN.ts +1 -1
  169. package/src/layer/index.ts +4 -4
  170. package/src/layer/layer-manager.ts +32 -0
  171. package/src/layer/layer.ts +20 -144
  172. package/src/layer/scrollbar-helper.ts +24 -0
  173. package/src/layer/sidebar-layer.ts +74 -64
  174. package/src/layer/wrap.ts +77 -0
  175. package/src/lib/component.ts +4 -11
  176. package/src/lib/detect.ts +1 -1
  177. package/src/lib/event-manager.ts +47 -0
  178. package/src/lib/highlight.ts +5 -0
  179. package/src/lib/marked-renderer.ts +32 -0
  180. package/src/lib/marked.ts +43 -104
  181. package/src/lib/sanitizer.ts +57 -0
  182. package/src/lib/ui.ts +22 -15
  183. package/src/lib/user.ts +21 -16
  184. package/src/lib/utils.ts +29 -18
  185. package/src/list/comment.ts +20 -0
  186. package/src/list/layout.ts +120 -0
  187. package/src/list/list.html +3 -1
  188. package/src/list/list.ts +56 -200
  189. package/src/list/{list-nest.ts → nest.ts} +1 -1
  190. package/src/list/page.ts +97 -0
  191. package/src/list/paginator/index.ts +20 -8
  192. package/src/list/paginator/read-more.ts +67 -0
  193. package/src/list/paginator/up-down.ts +51 -0
  194. package/src/main.ts +2 -0
  195. package/src/plugins/admin-only-elem.ts +40 -0
  196. package/src/plugins/conf-remoter.ts +54 -0
  197. package/src/plugins/dark-mode.ts +12 -0
  198. package/src/plugins/editor/_kit.ts +57 -0
  199. package/src/plugins/editor/_plug.ts +60 -0
  200. package/src/plugins/editor/_sample.ts +8 -0
  201. package/src/plugins/editor/closable.ts +44 -0
  202. package/src/{editor/plugs/emoticons-plug.ts → plugins/editor/emoticons.ts} +51 -52
  203. package/src/plugins/editor/header-event.ts +35 -0
  204. package/src/plugins/editor/header-link.ts +31 -0
  205. package/src/plugins/editor/header-user.ts +98 -0
  206. package/src/plugins/editor/index.ts +40 -0
  207. package/src/plugins/editor/local-storage.ts +36 -0
  208. package/src/plugins/editor/mover.ts +29 -0
  209. package/src/plugins/editor/preview.ts +41 -0
  210. package/src/plugins/editor/state-edit.ts +116 -0
  211. package/src/plugins/editor/state-reply.ts +91 -0
  212. package/src/plugins/editor/submit-add.ts +33 -0
  213. package/src/plugins/editor/submit-btn.ts +25 -0
  214. package/src/plugins/editor/submit.ts +73 -0
  215. package/src/plugins/editor/textarea.ts +55 -0
  216. package/src/plugins/editor/upload.ts +145 -0
  217. package/src/plugins/editor-kit.ts +161 -0
  218. package/src/plugins/index.ts +16 -0
  219. package/src/plugins/list/copyright.ts +16 -0
  220. package/src/plugins/list/count.ts +31 -0
  221. package/src/plugins/list/dropdown.ts +95 -0
  222. package/src/plugins/list/error-dialog.ts +22 -0
  223. package/src/plugins/list/fetch.ts +64 -0
  224. package/src/plugins/list/goto-first.ts +23 -0
  225. package/src/plugins/list/goto.ts +72 -0
  226. package/src/plugins/list/index.ts +25 -0
  227. package/src/plugins/list/loading.ts +17 -0
  228. package/src/plugins/list/no-comment.ts +25 -0
  229. package/src/plugins/list/reach-bottom.ts +46 -0
  230. package/src/plugins/list/sidebar-btn.ts +39 -0
  231. package/src/plugins/list/time-ticking.ts +22 -0
  232. package/src/plugins/list/unread-badge.ts +26 -0
  233. package/src/plugins/list/unread.ts +38 -0
  234. package/src/plugins/list/with-editor.ts +59 -0
  235. package/src/plugins/markdown.ts +10 -0
  236. package/src/plugins/stat.ts +79 -0
  237. package/src/plugins/version-check.ts +37 -0
  238. package/src/service.ts +34 -61
  239. package/src/style/comment.scss +5 -7
  240. package/src/style/editor.scss +5 -0
  241. package/src/style/list.scss +2 -7
  242. package/types/{artalk-config.d.ts → config.ts} +16 -2
  243. package/types/context.ts +84 -0
  244. package/{dist/types/artalk-data.d.ts → types/data.ts} +41 -0
  245. package/types/editor.ts +95 -0
  246. package/types/event.ts +40 -0
  247. package/types/index.ts +7 -0
  248. package/types/plugin.ts +3 -0
  249. package/types/sidebar.ts +3 -0
  250. package/dist/src/comment/render-ctx.d.ts +0 -23
  251. package/dist/src/editor/edit-mode.d.ts +0 -9
  252. package/dist/src/editor/funcs.d.ts +0 -2
  253. package/dist/src/editor/index.d.ts +0 -2
  254. package/dist/src/editor/mover.d.ts +0 -8
  255. package/dist/src/editor/plug-manager.d.ts +0 -17
  256. package/dist/src/editor/plugs/editor-plug.d.ts +0 -38
  257. package/dist/src/editor/plugs/header-input-plug.d.ts +0 -13
  258. package/dist/src/editor/plugs/preview-plug.d.ts +0 -10
  259. package/dist/src/editor/plugs/upload-plug.d.ts +0 -10
  260. package/dist/src/editor/reply.d.ts +0 -9
  261. package/dist/src/editor/submit.d.ts +0 -16
  262. package/dist/src/lib/dark-mode.d.ts +0 -4
  263. package/dist/src/lib/stat.d.ts +0 -15
  264. package/dist/src/list/index.d.ts +0 -2
  265. package/dist/src/list/list-lite.d.ts +0 -77
  266. package/dist/src/list/list-ui.d.ts +0 -16
  267. package/dist/src/list/paginator/adaptors/index.d.ts +0 -12
  268. package/dist/src/list/paginator/adaptors/pagination.d.ts +0 -9
  269. package/dist/src/list/paginator/adaptors/read-more.d.ts +0 -10
  270. package/dist/src/list/paginator/holder.d.ts +0 -16
  271. package/dist/types/artalk-config.d.ts +0 -139
  272. package/dist/types/plug.d.ts +0 -4
  273. package/src/api/api-base.ts +0 -27
  274. package/src/comment/render-ctx.ts +0 -30
  275. package/src/editor/edit-mode.ts +0 -103
  276. package/src/editor/funcs.ts +0 -97
  277. package/src/editor/index.ts +0 -3
  278. package/src/editor/mover.ts +0 -43
  279. package/src/editor/plug-manager.ts +0 -172
  280. package/src/editor/plugs/editor-plug.ts +0 -69
  281. package/src/editor/plugs/header-input-plug.ts +0 -79
  282. package/src/editor/plugs/preview-plug.ts +0 -44
  283. package/src/editor/plugs/upload-plug.ts +0 -122
  284. package/src/editor/reply.ts +0 -62
  285. package/src/editor/submit.ts +0 -99
  286. package/src/lib/dark-mode.ts +0 -48
  287. package/src/lib/stat.ts +0 -71
  288. package/src/list/index.ts +0 -3
  289. package/src/list/list-lite.ts +0 -424
  290. package/src/list/list-ui.ts +0 -137
  291. package/src/list/paginator/adaptors/index.ts +0 -21
  292. package/src/list/paginator/adaptors/pagination.ts +0 -42
  293. package/src/list/paginator/adaptors/read-more.ts +0 -56
  294. package/src/list/paginator/holder.ts +0 -56
  295. package/types/artalk-data.d.ts +0 -230
  296. package/types/context.d.ts +0 -142
  297. package/types/event.d.ts +0 -28
  298. package/types/plug.d.ts +0 -4
  299. /package/src/{editor/plugs/emoticons-plug.scss → plugins/editor/emoticons.scss} +0 -0
  300. /package/src/{editor/plugs/preview-plug.scss → plugins/editor/preview.scss} +0 -0
@@ -0,0 +1,33 @@
1
+ import type { CommentData } from '~/types'
2
+ import User from '@/lib/user'
3
+ import type PlugKit from './_kit'
4
+
5
+ export default class SubmitAddPreset {
6
+ constructor(private kit: PlugKit) {}
7
+
8
+ async reqAdd() {
9
+ const nComment = await this.kit.useApi().comment.add({
10
+ ...this.getSubmitAddParams()
11
+ })
12
+ return nComment
13
+ }
14
+
15
+ getSubmitAddParams() {
16
+ const { nick, email, link } = User.data
17
+ const conf = this.kit.useConf()
18
+
19
+ return {
20
+ content: this.kit.useEditor().getContentFinal(),
21
+ nick, email, link,
22
+ rid: 0,
23
+ page_key: conf.pageKey,
24
+ page_title: conf.pageTitle,
25
+ site_name: conf.site
26
+ }
27
+ }
28
+
29
+ postSubmitAdd(commentNew: CommentData) {
30
+ // insert the new comment to list
31
+ this.kit.useGlobalCtx().getData().insertComment(commentNew)
32
+ }
33
+ }
@@ -0,0 +1,25 @@
1
+ import $t from '@/i18n'
2
+ import EditorPlug from './_plug'
3
+ import type PlugKit from './_kit'
4
+
5
+ export default class SubmitBtn extends EditorPlug {
6
+ constructor(kit: PlugKit) {
7
+ super(kit)
8
+
9
+ const onClick = () => {
10
+ this.kit.useEditor().submit()
11
+ }
12
+
13
+ this.kit.useMounted(() => {
14
+ // apply the submit button text from user custom config
15
+ this.kit.useUI().$submitBtn.innerText = this.kit.useConf().sendBtn || $t('send')
16
+
17
+ // bind the event when click the submit button
18
+ this.kit.useUI().$submitBtn.addEventListener('click', onClick)
19
+ })
20
+
21
+ this.kit.useUnmounted(() => {
22
+ this.kit.useUI().$submitBtn.removeEventListener('click', onClick)
23
+ })
24
+ }
25
+ }
@@ -0,0 +1,73 @@
1
+ import type { CommentData } from '~/types'
2
+ import $t from '@/i18n'
3
+ import EditorPlug from './_plug'
4
+ import type PlugKit from './_kit'
5
+ import SubmitAddPreset from './submit-add'
6
+
7
+ interface CustomSubmit {
8
+ activeCond: () => void
9
+ pre?: () => void
10
+ req?: () => Promise<CommentData>
11
+ post?: (nComment: CommentData) => void
12
+ }
13
+
14
+ export default class Submit extends EditorPlug {
15
+ private customs: CustomSubmit[] = []
16
+ private defaultPreset: SubmitAddPreset
17
+
18
+ constructor(kit: PlugKit) {
19
+ super(kit)
20
+
21
+ this.defaultPreset = new SubmitAddPreset(this.kit)
22
+
23
+ const onEditorSubmit = () => this.do()
24
+
25
+ this.kit.useMounted(() => {
26
+ // invoke `do()` when event `editor-submit` is triggered
27
+ this.kit.useGlobalCtx().on('editor-submit', onEditorSubmit)
28
+ })
29
+ this.kit.useUnmounted(() => {
30
+ this.kit.useGlobalCtx().off('editor-submit', onEditorSubmit)
31
+ })
32
+ }
33
+
34
+ registerCustom(c: CustomSubmit) {
35
+ this.customs.push(c)
36
+ }
37
+
38
+ private async do() {
39
+ if (this.kit.useEditor().getContentFinal().trim() === '') {
40
+ this.kit.useEditor().focus()
41
+ return
42
+ }
43
+
44
+ const custom = this.customs.find(o => o.activeCond())
45
+
46
+ this.kit.useEditor().showLoading()
47
+
48
+ try {
49
+ // pre submit
50
+ if (custom?.pre) custom.pre()
51
+
52
+ let nComment: CommentData
53
+
54
+ // submit request
55
+ if (custom?.req) nComment = await custom.req()
56
+ else nComment = await this.defaultPreset.reqAdd()
57
+
58
+ // post submit
59
+ if (custom?.post) custom.post(nComment)
60
+ else this.defaultPreset.postSubmitAdd(nComment)
61
+ } catch (err: any) {
62
+ // submit error
63
+ console.error(err)
64
+ this.kit.useEditor().showNotify(`${$t('commentFail')},${err.msg || String(err)}`, 'e')
65
+ return
66
+ } finally {
67
+ this.kit.useEditor().hideLoading()
68
+ }
69
+
70
+ this.kit.useEditor().reset() // 复原编辑器
71
+ this.kit.useGlobalCtx().trigger('editor-submitted')
72
+ }
73
+ }
@@ -0,0 +1,55 @@
1
+ import $t from '@/i18n'
2
+ import EditorPlug from './_plug'
3
+ import type PlugKit from './_kit'
4
+
5
+ export default class Textarea extends EditorPlug {
6
+ constructor(kit: PlugKit) {
7
+ super(kit)
8
+
9
+ const onKeydown = (e: KeyboardEvent) => this.onKeydown(e)
10
+ const onInput = () => this.onInput()
11
+
12
+ this.kit.useMounted(() => {
13
+ // 占位符
14
+ this.kit.useUI().$textarea.placeholder = this.kit.useConf().placeholder || $t('placeholder')
15
+
16
+ // bind the event
17
+ this.kit.useUI().$textarea.addEventListener('keydown', onKeydown)
18
+ this.kit.useUI().$textarea.addEventListener('input', onInput)
19
+ })
20
+
21
+ this.kit.useUnmounted(() => {
22
+ // unmount the event
23
+ this.kit.useUI().$textarea.removeEventListener('keydown', onKeydown)
24
+ this.kit.useUI().$textarea.removeEventListener('input', onInput)
25
+ })
26
+
27
+ this.kit.useEvents().on('content-updated', () => {
28
+ // delay 80ms to prevent invalid execution
29
+ window.setTimeout(() => {
30
+ this.adaptiveHeightByContent()
31
+ }, 80)
32
+ })
33
+ }
34
+
35
+ // 按下 Tab 输入内容,而不是失去焦距
36
+ private onKeydown(e: KeyboardEvent) {
37
+ const keyCode = e.keyCode || e.which
38
+
39
+ if (keyCode === 9) {
40
+ e.preventDefault()
41
+ this.kit.useEditor().insertContent('\t')
42
+ }
43
+ }
44
+
45
+ private onInput() {
46
+ this.kit.useEvents().trigger('content-updated', this.kit.useEditor().getContentRaw())
47
+ }
48
+
49
+ // Resize the textarea height by content
50
+ public adaptiveHeightByContent() {
51
+ const diff = this.kit.useUI().$textarea.offsetHeight - this.kit.useUI().$textarea.clientHeight
52
+ this.kit.useUI().$textarea.style.height = '0px' // it's a magic. 若不加此行,内容减少,高度回不去
53
+ this.kit.useUI().$textarea.style.height = `${this.kit.useUI().$textarea.scrollHeight + diff}px`
54
+ }
55
+ }
@@ -0,0 +1,145 @@
1
+ import * as Utils from '@/lib/utils'
2
+ import User from '@/lib/user'
3
+ import $t from '@/i18n'
4
+ import type PlugKit from './_kit'
5
+ import EditorPlug from './_plug'
6
+
7
+ /** 允许的图片格式 */
8
+ const AllowImgExts = ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'svg', 'webp']
9
+
10
+ export default class Upload extends EditorPlug {
11
+ private $imgUploadInput?: HTMLInputElement
12
+
13
+ constructor(kit: PlugKit) {
14
+ super(kit)
15
+
16
+ this.kit.useMounted(() => this.init())
17
+
18
+ this.initDragImg()
19
+ }
20
+
21
+ private init() {
22
+ this.$imgUploadInput = document.createElement('input')
23
+ this.$imgUploadInput.type = 'file'
24
+ this.$imgUploadInput.style.display = 'none'
25
+ this.$imgUploadInput.accept = AllowImgExts.map(o => `.${o}`).join(',')
26
+
27
+ // TODO Use btn cannot refresh when mounted event is triggered
28
+ const $btn = this.useBtn(`${$t('image')}`)
29
+ $btn.after(this.$imgUploadInput)
30
+ $btn.onclick = () => {
31
+ // 选择图片
32
+ const $input = this.$imgUploadInput!
33
+ $input.onchange = () => {
34
+ (async () => { // 解决阻塞 UI 问题
35
+ if (!$input.files || $input.files.length === 0) return
36
+ const file = $input.files[0]
37
+ this.uploadImg(file)
38
+ })()
39
+ }
40
+ $input.click() // 显示选择图片对话框
41
+ }
42
+
43
+ if (!this.kit.useConf().imgUpload) {
44
+ this.$btn!.setAttribute('atk-only-admin-show', '')
45
+ }
46
+ }
47
+
48
+ private initDragImg() {
49
+ // 统一从 FileList 获取文件并上传图片方法
50
+ const uploadFromFileList = (files?: FileList) => {
51
+ if (!files) return
52
+
53
+ for (let i = 0; i < files.length; i++) {
54
+ const file = files[i]
55
+ this.uploadImg(file)
56
+ }
57
+ }
58
+
59
+ // 拖拽图片
60
+ // @link https://developer.mozilla.org/zh-CN/docs/Web/API/HTML_Drag_and_Drop_API/File_drag_and_drop
61
+ // 阻止浏览器的默认释放行为
62
+ const onDragover = (evt: Event) => {
63
+ evt.stopPropagation()
64
+ evt.preventDefault()
65
+ }
66
+
67
+ const onDrop = (evt: DragEvent) => {
68
+ const files = evt.dataTransfer?.files
69
+ if (files?.length) {
70
+ evt.preventDefault()
71
+ uploadFromFileList(files)
72
+ }
73
+ }
74
+
75
+ // 粘贴图片
76
+ const onPaste = (evt: ClipboardEvent) => {
77
+ const files = evt.clipboardData?.files
78
+ if (files?.length) {
79
+ evt.preventDefault()
80
+ uploadFromFileList(files)
81
+ }
82
+ }
83
+
84
+ this.kit.useMounted(() => {
85
+ this.kit.useUI().$textarea.addEventListener('dragover', onDragover)
86
+ this.kit.useUI().$textarea.addEventListener('drop', onDrop)
87
+ this.kit.useUI().$textarea.addEventListener('paste', onPaste)
88
+ })
89
+ this.kit.useUnmounted(() => {
90
+ this.kit.useUI().$textarea.removeEventListener('dragover', onDragover)
91
+ this.kit.useUI().$textarea.removeEventListener('drop', onDrop)
92
+ this.kit.useUI().$textarea.removeEventListener('paste', onPaste)
93
+ })
94
+ }
95
+
96
+ async uploadImg(file: File) {
97
+ const fileExt = /[^.]+$/.exec(file.name)
98
+ if (!fileExt || !AllowImgExts.includes(fileExt[0])) return
99
+
100
+ // 未登录提示
101
+ if (!User.checkHasBasicUserInfo()) {
102
+ this.kit.useEditor().showNotify($t('uploadLoginMsg'), 'w')
103
+ return
104
+ }
105
+
106
+ // 插入图片前换一行
107
+ let insertPrefix = '\n'
108
+ if (this.kit.useUI().$textarea.value.trim() === '') insertPrefix = ''
109
+
110
+ // 插入占位加载文字
111
+ const uploadPlaceholderTxt = `${insertPrefix}![](Uploading ${file.name}...)`
112
+ this.kit.useEditor().insertContent(uploadPlaceholderTxt)
113
+
114
+ // 上传图片
115
+ let resp: any
116
+ try {
117
+ const customUploaderFn = this.kit.useConf().imgUploader
118
+ if (!customUploaderFn) {
119
+ // 使用 Artalk 进行图片上传
120
+ resp = await this.kit.useApi().upload.imgUpload(file)
121
+ } else {
122
+ // 使用自定义的图片上传器
123
+ resp = {img_url: await customUploaderFn(file)}
124
+ }
125
+ } catch (err: any) {
126
+ console.error(err)
127
+ this.kit.useEditor().showNotify(`${$t('uploadFail')},${err.msg}`, 'e')
128
+ }
129
+ if (!!resp && resp.img_url) {
130
+ let imgURL = resp.img_url as string
131
+
132
+ // 若为相对路径,加上 artalk server
133
+ if (!Utils.isValidURL(imgURL)) imgURL = Utils.getURLBasedOnApi({
134
+ base: this.kit.useConf().server,
135
+ path: imgURL,
136
+ })
137
+
138
+ // 上传成功插入图片
139
+ this.kit.useEditor().setContent(this.kit.useUI().$textarea.value.replace(uploadPlaceholderTxt, `${insertPrefix}![](${imgURL})`))
140
+ } else {
141
+ // 上传失败删除加载文字
142
+ this.kit.useEditor().setContent(this.kit.useUI().$textarea.value.replace(uploadPlaceholderTxt, ''))
143
+ }
144
+ }
145
+ }
@@ -0,0 +1,161 @@
1
+ import type { EditorApi, ArtalkPlugin } from '~/types'
2
+ import EventManager from '@/lib/event-manager'
3
+ import { ENABLED_PLUGS, getDisabledPlugByConf } from './editor'
4
+ import EditorPlug from './editor/_plug'
5
+ import PlugKit from './editor/_kit'
6
+
7
+ export interface EditorEventPayloadMap {
8
+ 'mounted': undefined
9
+ 'unmounted': undefined
10
+ 'header-input': { field: string, $input: HTMLInputElement }
11
+ 'header-change': { field: string, $input: HTMLInputElement }
12
+ 'content-updated': string
13
+ 'panel-show': EditorPlug
14
+ 'panel-hide': EditorPlug
15
+ 'panel-close': undefined
16
+
17
+ 'editor-close': undefined
18
+ 'editor-open': undefined
19
+ }
20
+
21
+ export const EditorKit: ArtalkPlugin = (ctx) => {
22
+ const editor = ctx.get('editor')
23
+
24
+ const editorPlugs = new PlugManager(editor)
25
+ ctx.inject('editorPlugs', editorPlugs)
26
+ }
27
+
28
+ export class PlugManager {
29
+ private plugs: EditorPlug[] = []
30
+ private openedPlug: EditorPlug|null = null
31
+ private events = new EventManager<EditorEventPayloadMap>()
32
+
33
+ getPlugs() { return this.plugs }
34
+ getEvents() { return this.events }
35
+
36
+ private clear() {
37
+ this.plugs = []
38
+ this.events = new EventManager()
39
+ if (this.openedPlug) this.closePlugPanel()
40
+ }
41
+
42
+ constructor(
43
+ public editor: EditorApi
44
+ ) {
45
+ let confLoaded = false // config not loaded at first time
46
+ this.editor.ctx.on('conf-loaded', () => {
47
+ // trigger unmount event will call all plugs' unmount function
48
+ // (this will only be called while conf reloaded, not be called at first time)
49
+ confLoaded && this.getEvents().trigger('unmounted')
50
+
51
+ // reset the plugs
52
+ this.clear()
53
+
54
+ // init the all enabled plugs
55
+ const DISABLED = getDisabledPlugByConf(this.editor.ctx.conf)
56
+
57
+ ENABLED_PLUGS
58
+ .filter(p => !DISABLED.includes(p)) // 禁用的插件
59
+ .forEach((Plug) => {
60
+ // create the plug instance
61
+ const kit = new PlugKit(this)
62
+ this.plugs.push(new Plug(kit))
63
+ })
64
+
65
+ // trigger event for plug initialization
66
+ this.getEvents().trigger('mounted')
67
+ confLoaded = true
68
+
69
+ // refresh the plug UI
70
+ this.loadPluginUI()
71
+ })
72
+
73
+ this.events.on('panel-close', () => this.closePlugPanel())
74
+ }
75
+
76
+ private loadPluginUI() {
77
+ // handle ui, clear and reset the plug btns and plug panels
78
+ this.editor.getUI().$plugPanelWrap.innerHTML = ''
79
+ this.editor.getUI().$plugPanelWrap.style.display = 'none'
80
+ this.editor.getUI().$plugBtnWrap.innerHTML = ''
81
+
82
+ // load the plug UI
83
+ this.plugs.forEach((plug) => this.loadPluginItem(plug))
84
+ }
85
+
86
+ /** Load the plug btn and plug panel on editor ui */
87
+ private loadPluginItem(plug: EditorPlug) {
88
+ const $btn = plug.$btn
89
+ if (!$btn) return
90
+ this.editor.getUI().$plugBtnWrap.appendChild($btn)
91
+
92
+ // bind the event when click plug btn
93
+ !$btn.onclick && ($btn.onclick = () => {
94
+ // removing the active class from all the buttons
95
+ this.editor.getUI().$plugBtnWrap
96
+ .querySelectorAll('.active')
97
+ .forEach(item => item.classList.remove('active'))
98
+
99
+ // if the plug is not the same as the openedPlug,
100
+ if (plug !== this.openedPlug) {
101
+ // then open the plug current clicked plug panel
102
+ this.openPlugPanel(plug)
103
+
104
+ // add active class for current plug panel
105
+ $btn.classList.add('active')
106
+ } else {
107
+ // then close the plug
108
+ this.closePlugPanel()
109
+ }
110
+ })
111
+
112
+ // initialization of plug panel
113
+ const $panel = plug.$panel
114
+ if ($panel) {
115
+ $panel.style.display = 'none'
116
+ this.editor.getUI().$plugPanelWrap.appendChild($panel)
117
+ }
118
+ }
119
+
120
+ get<T extends typeof EditorPlug>(plug: T) {
121
+ return this.plugs.find(p => p instanceof plug) as InstanceType<T> | undefined;
122
+ }
123
+
124
+ /** Open the editor plug panel */
125
+ openPlugPanel(plug: EditorPlug) {
126
+ this.plugs.forEach((aPlug) => {
127
+ const plugPanel = aPlug.$panel
128
+ if (!plugPanel) return
129
+
130
+ if (aPlug === plug) {
131
+ plugPanel.style.display = ''
132
+ this.events.trigger('panel-show', plug)
133
+ } else {
134
+ plugPanel.style.display = 'none'
135
+ this.events.trigger('panel-hide', plug)
136
+ }
137
+ })
138
+
139
+ this.editor.getUI().$plugPanelWrap.style.display = ''
140
+ this.openedPlug = plug
141
+ }
142
+
143
+ /** Close the editor plug panel */
144
+ closePlugPanel() {
145
+ if (!this.openedPlug) return
146
+
147
+ this.editor.getUI().$plugPanelWrap.style.display = 'none'
148
+ this.events.trigger('panel-hide', this.openedPlug)
149
+ this.openedPlug = null
150
+ }
151
+
152
+ /** Get the content which is transformed by plugs */
153
+ getTransformedContent(rawContent: string) {
154
+ let result = rawContent
155
+ this.plugs.forEach((aPlug) => {
156
+ if (!aPlug.contentTransformer) return
157
+ result = aPlug.contentTransformer(result)
158
+ })
159
+ return result
160
+ }
161
+ }
@@ -0,0 +1,16 @@
1
+ import type { ArtalkPlugin } from '~/types'
2
+ import { ConfRemoter } from './conf-remoter'
3
+ import { Markdown } from './markdown'
4
+ import { EditorKit } from './editor-kit'
5
+ import { ListPlugins } from './list'
6
+ import { PvCountWidget } from './stat'
7
+ import { VersionCheck } from './version-check'
8
+ import { AdminOnlyElem } from './admin-only-elem'
9
+ import { DarkMode } from './dark-mode'
10
+
11
+ export const DefaultPlugins: ArtalkPlugin[] = [
12
+ ConfRemoter,
13
+ Markdown, EditorKit, AdminOnlyElem,
14
+ ...ListPlugins,
15
+ PvCountWidget, VersionCheck, DarkMode,
16
+ ]
@@ -0,0 +1,16 @@
1
+ import type { ArtalkPlugin } from '~/types'
2
+ import { version as ARTALK_VERSION } from '~/package.json'
3
+
4
+ export const Copyright: ArtalkPlugin = (ctx) => {
5
+ ctx.on('conf-loaded', () => {
6
+ const list = ctx.get('list')
7
+
8
+ const $copyright = list.$el.querySelector<HTMLElement>('.atk-copyright')
9
+ if (!$copyright) return
10
+
11
+ $copyright.innerHTML = (
12
+ `Powered By <a href="https://artalk.js.org" ` +
13
+ `target="_blank" title="Artalk v${ARTALK_VERSION}">` +
14
+ `Artalk</a>`)
15
+ })
16
+ }
@@ -0,0 +1,31 @@
1
+ import type { ArtalkPlugin } from '~/types'
2
+ import * as Utils from '@/lib/utils'
3
+ import $t from '@/i18n'
4
+
5
+ export const Count: ArtalkPlugin = (ctx) => {
6
+ const refreshCountNumEl = () => {
7
+ const list = ctx.get('list')
8
+
9
+ const $count = list.$el.querySelector('.atk-comment-count .atk-text')
10
+ if (!$count) return
11
+
12
+ const text = Utils.htmlEncode($t('counter', { count: `${Number(ctx.getData().getListLastFetch()?.data?.total) || 0}` }))
13
+ $count.innerHTML = text.replace(/(\d+)/, '<span class="atk-comment-count-num">$1</span>')
14
+ }
15
+
16
+ ctx.on('list-loaded', () => {
17
+ refreshCountNumEl()
18
+ })
19
+
20
+ ctx.on('comment-inserted', () => {
21
+ // 评论数增加 1
22
+ const last = ctx.getData().getListLastFetch()
23
+ if (last?.data) last.data.total += 1
24
+ })
25
+
26
+ ctx.on('comment-deleted', () => {
27
+ // 评论数减 1
28
+ const last = ctx.getData().getListLastFetch()
29
+ if (last?.data) last.data.total -= 1
30
+ })
31
+ }
@@ -0,0 +1,95 @@
1
+ import type { ArtalkPlugin } from '~/types'
2
+ import * as Utils from '@/lib/utils'
3
+ import $t from '@/i18n'
4
+
5
+ export const Dropdown: ArtalkPlugin = (ctx) => {
6
+ const reloadUseParamsEditor = (func: (p: any) => void) => {
7
+ ctx.conf.listFetchParamsModifier = func
8
+ ctx.reload()
9
+ }
10
+
11
+ const initDropdown = ($dropdownOn: HTMLElement) => {
12
+ renderDropdown({
13
+ $dropdownWrap: $dropdownOn,
14
+ dropdownList: [
15
+ [$t('sortLatest'), () => { reloadUseParamsEditor(p => { p.sort_by = 'date_desc' }) }],
16
+ [$t('sortBest'), () => { reloadUseParamsEditor(p => { p.sort_by = 'vote' }) }],
17
+ [$t('sortOldest'), () => { reloadUseParamsEditor(p => { p.sort_by = 'date_asc' }) }],
18
+ [$t('sortAuthor'), () => { reloadUseParamsEditor(p => { p.view_only_admin = true }) }],
19
+ ]
20
+ })
21
+ }
22
+
23
+ ctx.on('conf-loaded', () => {
24
+ const list = ctx.get('list')
25
+
26
+ const $count = list.$el.querySelector<HTMLElement>('.atk-comment-count')
27
+ if (!$count) return
28
+
29
+ // 评论列表排序 Dropdown 下拉选择层
30
+ if (ctx.conf.listSort) {
31
+ initDropdown($count)
32
+ } else {
33
+ removeDropdown({
34
+ $dropdownWrap: $count
35
+ })
36
+ }
37
+ })
38
+ }
39
+
40
+ /** 评论排序方式选择下拉菜单 */
41
+ function renderDropdown(conf: {
42
+ $dropdownWrap: HTMLElement,
43
+ dropdownList: [string, () => void][]
44
+ }) {
45
+ const { $dropdownWrap, dropdownList } = conf
46
+ if ($dropdownWrap.querySelector('.atk-dropdown')) return
47
+
48
+ // 修改 class
49
+ $dropdownWrap.classList.add('atk-dropdown-wrap')
50
+
51
+ // 插入图标
52
+ $dropdownWrap.append(Utils.createElement(`<span class="atk-arrow-down-icon"></span>`))
53
+
54
+ // 列表项点击事件
55
+ let curtActive = 0 // 当前选中
56
+ const onItemClick = (i: number, $item: HTMLElement, name: string, action: Function) => {
57
+ action()
58
+
59
+ // set active
60
+ curtActive = i
61
+ $dropdown.querySelectorAll('.active').forEach((e) => { e.classList.remove('active') })
62
+ $item.classList.add('active')
63
+
64
+ // 关闭层 (临时消失,取消 :hover)
65
+ $dropdown.style.display = 'none'
66
+ setTimeout(() => { $dropdown.style.display = '' }, 80)
67
+ }
68
+
69
+ // 生成列表元素
70
+ const $dropdown = Utils.createElement(`<ul class="atk-dropdown atk-fade-in"></ul>`)
71
+ dropdownList.forEach((item, i) => {
72
+ const name = item[0] as string
73
+ const action = item[1] as Function
74
+
75
+ const $item = Utils.createElement(`<li class="atk-dropdown-item"><span></span></li>`)
76
+ const $link = $item.querySelector<HTMLElement>('span')!
77
+ $link.innerText = name
78
+ $link.onclick = () => { onItemClick(i, $item, name, action) }
79
+ $dropdown.append($item)
80
+
81
+ if (i === curtActive) $item.classList.add('active') // 默认选中项
82
+ })
83
+
84
+ $dropdownWrap.append($dropdown)
85
+ }
86
+
87
+ /** 删除评论排序方式选择下拉菜单 */
88
+ function removeDropdown(conf: {
89
+ $dropdownWrap: HTMLElement
90
+ }) {
91
+ const { $dropdownWrap } = conf
92
+ $dropdownWrap.classList.remove('atk-dropdown-wrap')
93
+ $dropdownWrap.querySelector('.atk-arrow-down-icon')?.remove()
94
+ $dropdownWrap.querySelector('.atk-dropdown')?.remove()
95
+ }
@@ -0,0 +1,22 @@
1
+ import type { ArtalkPlugin, ContextApi } from '~/types'
2
+ import { showErrorDialog } from '@/components/error-dialog'
3
+ import * as Ui from '../../lib/ui'
4
+
5
+ export const ErrorDialog: ArtalkPlugin = (ctx) => {
6
+ ctx.on('list-fetch', () => {
7
+ const list = ctx.get('list')
8
+
9
+ // clear the original error when a new fetch is triggered
10
+ Ui.setError(list.$el, null)
11
+ })
12
+
13
+ ctx.on('list-error', (err) => {
14
+ showErrorDialog({
15
+ $err: ctx.get('list').$el,
16
+ errMsg: err.msg,
17
+ errData: err.data,
18
+ retryFn: () => ctx.fetch({ offset: 0 })
19
+ })
20
+ })
21
+ }
22
+