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
package/src/lib/user.ts CHANGED
@@ -1,13 +1,14 @@
1
- import { LocalUser } from "~/types/artalk-config"
2
- import Context from '~/types/context'
1
+ import type { LocalUser } from '~/types'
2
+
3
+ const LOCAL_USER_KEY = 'ArtalkUser'
3
4
 
4
5
  class User {
5
- public ctx?: Context
6
- public data: LocalUser
6
+ data: LocalUser
7
+ onUserChanged: ((user: LocalUser) => void) | null = null
7
8
 
8
- public constructor() {
9
+ constructor() {
9
10
  // 从 localStorage 导入
10
- const localUser = JSON.parse(window.localStorage.getItem('ArtalkUser') || '{}')
11
+ const localUser = JSON.parse(window.localStorage.getItem(LOCAL_USER_KEY) || '{}')
11
12
  this.data = {
12
13
  nick: localUser.nick || '',
13
14
  email: localUser.email || '',
@@ -17,22 +18,26 @@ class User {
17
18
  }
18
19
  }
19
20
 
20
- public setContext(ctx: Context) {
21
- this.ctx = ctx
22
- }
23
-
24
21
  /** 保存用户到 localStorage 中 */
25
- public update(obj: Partial<LocalUser> = {}) {
22
+ update(obj: Partial<LocalUser> = {}) {
26
23
  Object.entries(obj).forEach(([key, value]) => {
27
24
  this.data[key] = value
28
25
  })
29
26
 
30
- window.localStorage.setItem('ArtalkUser', JSON.stringify(this.data))
31
- this.ctx?.trigger('user-changed', this.data)
27
+ window.localStorage.setItem(LOCAL_USER_KEY, JSON.stringify(this.data))
28
+ this.onUserChanged && this.onUserChanged(this.data)
29
+ }
30
+
31
+ public setOnUserChanged(fn: ((user: LocalUser) => void) | null) {
32
+ this.onUserChanged = fn
32
33
  }
33
34
 
34
- /** 注销,清除用户登录状态 */
35
- public logout() {
35
+ /**
36
+ * Logout
37
+ *
38
+ * @description Logout will clear login status, but not clear user data (nick, email, link)
39
+ */
40
+ logout() {
36
41
  this.update({
37
42
  token: '',
38
43
  isAdmin: false
@@ -40,7 +45,7 @@ class User {
40
45
  }
41
46
 
42
47
  /** 是否已填写基本用户信息 */
43
- public checkHasBasicUserInfo() {
48
+ checkHasBasicUserInfo() {
44
49
  return !!this.data.nick && !!this.data.email
45
50
  }
46
51
  }
package/src/lib/utils.ts CHANGED
@@ -1,5 +1,3 @@
1
- import Context from '~/types/context'
2
-
3
1
  export function createElement<E extends HTMLElement = HTMLElement>(htmlStr: string = ''): E {
4
2
  const div = document.createElement('div')
5
3
  div.innerHTML = htmlStr.trim()
@@ -29,11 +27,25 @@ export function getQueryParam(name: string) {
29
27
  return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
30
28
  }
31
29
 
32
- export function getOffset(el: HTMLElement) {
33
- const rect = el.getBoundingClientRect()
30
+ export function getOffset(el: HTMLElement, relativeTo?: HTMLElement) {
31
+ const getOffsetRecursive = (element: HTMLElement): { top: number, left: number } => {
32
+ const rect = element.getBoundingClientRect()
33
+ const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft
34
+ const scrollTop = window.pageYOffset || document.documentElement.scrollTop
35
+ return {
36
+ top: rect.top + scrollTop,
37
+ left: rect.left + scrollLeft
38
+ }
39
+ }
40
+
41
+ const elOffset = getOffsetRecursive(el)
42
+ if (!relativeTo) return elOffset
43
+
44
+ const relativeToOffset = getOffsetRecursive(relativeTo)
45
+
34
46
  return {
35
- top: rect.top + window.scrollY,
36
- left: rect.left + window.scrollX
47
+ top: elOffset.top - relativeToOffset.top,
48
+ left: elOffset.left - relativeToOffset.left
37
49
  }
38
50
  }
39
51
 
@@ -55,7 +67,7 @@ export function dateFormat(date: Date) {
55
67
  return `${vYear}-${vMonth}-${vDay}`
56
68
  }
57
69
 
58
- export function timeAgo(date: Date, ctx: Context) {
70
+ export function timeAgo(date: Date, $t: Function = (n: string) => n) {
59
71
  try {
60
72
  const oldTime = date.getTime()
61
73
  const currTime = new Date().getTime()
@@ -74,17 +86,17 @@ export function timeAgo(date: Date, ctx: Context) {
74
86
  // 计算相差秒数
75
87
  const leave3 = leave2 % (60 * 1000) // 计算分钟数后剩余的毫秒数
76
88
  const seconds = Math.round(leave3 / 1000)
77
- if (seconds < 10) return ctx.$t('now')
78
- return `${seconds} ${ctx.$t('seconds')}`
89
+ if (seconds < 10) return $t('now')
90
+ return `${seconds} ${$t('seconds')}`
79
91
  }
80
- return `${minutes} ${ctx.$t('minutes')}`
92
+ return `${minutes} ${$t('minutes')}`
81
93
  }
82
- return `${hours} ${ctx.$t('hours')}`
94
+ return `${hours} ${$t('hours')}`
83
95
  }
84
- if (days < 0) return ctx.$t('now')
96
+ if (days < 0) return $t('now')
85
97
 
86
98
  if (days < 8) {
87
- return `${days} ${ctx.$t('days')}`
99
+ return `${days} ${$t('days')}`
88
100
  }
89
101
 
90
102
  return dateFormat(date)
@@ -115,9 +127,8 @@ export function onImagesLoaded($container: HTMLElement, event: Function) {
115
127
  }
116
128
  }
117
129
 
118
- export function getGravatarURL(ctx: Context, emailMD5: string) {
119
- const { mirror, params } = ctx.conf.gravatar
120
- return `${mirror.replace(/\/$/, '')}/${emailMD5}?${params.replace(/^\?/, '')}`
130
+ export function getGravatarURL(opts: { params: string, mirror: string, emailMD5: string }) {
131
+ return `${opts.mirror.replace(/\/$/, '')}/${opts.emailMD5}?${opts.params.replace(/^\?/, '')}`
121
132
  }
122
133
 
123
134
  export function sleep(ms: number) {
@@ -185,8 +196,8 @@ export function isValidURL(urlRaw: string) {
185
196
  }
186
197
 
187
198
  /** 获取基于 conf.server 的 URL */
188
- export function getURLBasedOnApi(ctx: Context, path: string) {
189
- return getURLBasedOn(ctx.conf.server, path)
199
+ export function getURLBasedOnApi(opts: { base: string, path: string }) {
200
+ return getURLBasedOn(opts.base, opts.path)
190
201
  }
191
202
 
192
203
  /** 获取基于某个 baseURL 的 URL */
@@ -0,0 +1,20 @@
1
+ import type { ContextApi, CommentData } from '~/types'
2
+ import Comment from '../comment/comment'
3
+
4
+ export function createComment(ctx: ContextApi, comment: CommentData, ctxComments: CommentData[]): Comment {
5
+ const instance = new Comment(ctx, comment, {
6
+ isFlatMode: ctx.getData().getListLastFetch()?.params.flatMode!,
7
+ afterRender: () => {
8
+ ctx.trigger('comment-rendered', instance)
9
+ },
10
+ onDelete: (c: Comment) => {
11
+ ctx.getData().deleteComment(c.getID())
12
+ },
13
+ replyTo: (comment.rid ? ctxComments.find(c => c.id === comment.rid) : undefined)
14
+ })
15
+
16
+ // 渲染元素
17
+ instance.render()
18
+
19
+ return instance
20
+ }
@@ -0,0 +1,120 @@
1
+ import type { CommentData } from '~/types'
2
+ import * as Ui from '@/lib/ui'
3
+ import Comment from '../comment/comment'
4
+ import * as ListNest from './nest'
5
+
6
+ export interface LayoutOptions {
7
+ $commentsWrap: HTMLElement
8
+ nestSortBy: ListNest.SortByType
9
+ nestMax: number
10
+ flatMode: boolean
11
+
12
+ createCommentNode(comment: CommentData, ctxComments: CommentData[]): Comment
13
+ findCommentNode(id: number): Comment|undefined
14
+ getCommentDataList(): CommentData[]
15
+ }
16
+
17
+ export default class ListLayout {
18
+ constructor(private options: LayoutOptions) {}
19
+
20
+ // TODO refactor `if syntax` to strategy pattern
21
+ import(comments: CommentData[]) {
22
+ if (this.options.flatMode) {
23
+ comments.forEach((commentData: CommentData) => {
24
+ this.putCommentFlatMode(commentData, comments, 'append')
25
+ })
26
+ } else {
27
+ this.importCommentsNestMode(comments)
28
+ }
29
+ }
30
+
31
+ insert(comment: CommentData) {
32
+ if (!this.options.flatMode) {
33
+ this.insertCommentNest(comment)
34
+ } else {
35
+ this.insertCommentFlatMode(comment)
36
+ }
37
+ }
38
+
39
+ // 导入评论 · 嵌套模式
40
+ private importCommentsNestMode(srcData: CommentData[]) {
41
+ // 遍历 root 评论
42
+ const rootNodes = ListNest.makeNestCommentNodeList(srcData, this.options.nestSortBy, this.options.nestMax)
43
+ rootNodes.forEach((rootNode: ListNest.CommentNode) => {
44
+ const rootC = this.options.createCommentNode(rootNode.comment, srcData)
45
+
46
+ // 显示并播放渐入动画
47
+ this.options.$commentsWrap?.appendChild(rootC.getEl())
48
+ rootC.getRender().playFadeAnim()
49
+
50
+ // 加载子评论
51
+ const loadChildren = (parentC: Comment, parentNode: ListNest.CommentNode) => {
52
+ parentNode.children.forEach((node: ListNest.CommentNode) => {
53
+ const childD = node.comment
54
+ const childC = this.options.createCommentNode(childD, srcData)
55
+
56
+ // 插入到父评论中
57
+ parentC.putChild(childC)
58
+
59
+ // 递归加载子评论
60
+ loadChildren(childC, node)
61
+ })
62
+ }
63
+ loadChildren(rootC, rootNode)
64
+
65
+ // 限高检测
66
+ rootC.getRender().checkHeightLimit()
67
+ })
68
+ }
69
+
70
+ /** 导入评论 · 平铺模式 */
71
+ private putCommentFlatMode(cData: CommentData, ctxData: CommentData[], insertMode: 'append'|'prepend') {
72
+ if (cData.is_collapsed) cData.is_allow_reply = false
73
+ const comment = this.options.createCommentNode(cData, ctxData)
74
+
75
+ // 可见评论添加到界面
76
+ // 注:不可见评论用于显示 “引用内容”
77
+ if (cData.visible) {
78
+ if (insertMode === 'append') this.options.$commentsWrap?.append(comment.getEl())
79
+ if (insertMode === 'prepend') this.options.$commentsWrap?.prepend(comment.getEl())
80
+ comment.getRender().playFadeAnim()
81
+ }
82
+
83
+ // 平铺评论插入后 · 内容限高检测
84
+ comment.getRender().checkHeightLimit()
85
+
86
+ return comment
87
+ }
88
+
89
+ private insertCommentNest(commentData: CommentData) {
90
+ // 嵌套模式
91
+ const comment = this.options.createCommentNode(commentData, this.options.getCommentDataList())
92
+
93
+ if (commentData.rid === 0) {
94
+ // root评论 新增
95
+ this.options.$commentsWrap?.prepend(comment.getEl())
96
+ } else {
97
+ // 子评论 新增
98
+ const parent = this.options.findCommentNode(commentData.rid)
99
+ if (parent) {
100
+ parent.putChild(comment, (this.options.nestSortBy === 'DATE_ASC' ? 'append' : 'prepend'))
101
+
102
+ // 若父评论存在 “子评论部分” 限高,取消限高
103
+ comment.getParents().forEach((p) => {
104
+ p.getRender().heightLimitRemoveForChildren()
105
+ })
106
+ }
107
+ }
108
+
109
+ comment.getRender().checkHeightLimit()
110
+
111
+ Ui.scrollIntoView(comment.getEl()) // 滚动到可以见
112
+ comment.getRender().playFadeAnim() // 播放评论渐出动画
113
+ }
114
+
115
+ private insertCommentFlatMode(commentData: CommentData) {
116
+ // 平铺模式
117
+ const comment = this.putCommentFlatMode(commentData, this.options.getCommentDataList(), 'prepend')
118
+ Ui.scrollIntoView(comment.getEl()) // 滚动到可见
119
+ }
120
+ }
@@ -11,7 +11,9 @@
11
11
  </span>
12
12
  </div>
13
13
  </div>
14
- <div class="atk-list-body"></div>
14
+ <div class="atk-list-body">
15
+ <div class="atk-list-comments-wrap"></div>
16
+ </div>
15
17
  <div class="atk-list-footer">
16
18
  <div class="atk-copyright"></div>
17
19
  </div>
package/src/list/list.ts CHANGED
@@ -1,223 +1,79 @@
1
- import { ListData, NotifyData } from '~/types/artalk-data'
2
- import Context from '~/types/context'
3
- import { version as ARTALK_VERSION } from '../../package.json'
1
+ import type { ContextApi } from '~/types'
2
+ import Component from '../lib/component'
4
3
  import * as Utils from '../lib/utils'
5
- import * as Ui from '../lib/ui'
6
- import User from '../lib/user'
4
+ import CommentNode from '../comment/comment'
7
5
  import ListHTML from './list.html?raw'
8
- import ListLite from './list-lite'
9
- import * as ListUi from './list-ui'
6
+ import ListLayout from './layout'
7
+ import { createComment as createCommentNode } from './comment'
8
+ import { initListPaginatorFunc } from './page'
10
9
 
11
- export default class List extends ListLite {
12
- private $closeCommentBtn!: HTMLElement
13
- private $openSidebarBtn!: HTMLElement
14
- private $unreadBadge!: HTMLElement
15
- private $commentCount!: HTMLElement
16
- private $commentCountNum!: HTMLElement
17
- private $dropdownWrap?: HTMLElement
10
+ export default class List extends Component {
11
+ /** 列表评论集区域元素 */
12
+ $commentsWrap!: HTMLElement
13
+ public getCommentsWrapEl() { return this.$commentsWrap }
18
14
 
19
- constructor (ctx: Context) {
20
- const el = Utils.createElement(ListHTML)
15
+ protected commentNodes: CommentNode[] = []
16
+ getCommentNodes() { return this.commentNodes }
21
17
 
18
+ constructor (ctx: ContextApi) {
22
19
  super(ctx)
23
20
 
24
- // 把 listLite $el 变为子元素
25
- el.querySelector('.atk-list-body')!.append(this.$el)
26
- this.$el = el
21
+ // Init base element
22
+ this.$el = Utils.createElement(ListHTML)
23
+ this.$commentsWrap = this.$el.querySelector('.atk-list-comments-wrap')!
27
24
 
28
- // 分页模式
29
- this.repositionAt = this.$el
25
+ // Init paginator
26
+ initListPaginatorFunc(ctx)
30
27
 
31
- // 操作按钮
32
- this.initListActionBtn()
33
-
34
- const countNumHTML = '<span class="atk-comment-count-num">0</span>'
35
- this.$commentCount = this.$el.querySelector('.atk-comment-count')!
36
-
37
- const refreshCountNumEl = () => {
38
- this.$commentCount.innerHTML = this.$t('counter', {
39
- count: countNumHTML,
40
- })
41
- this.$commentCountNum = this.$commentCount.querySelector('.atk-comment-count-num')!
42
- }
43
- refreshCountNumEl()
44
-
45
- // copyright
46
- this.$el.querySelector<HTMLElement>('.atk-copyright')!.innerHTML = `Powered By <a href="https://artalk.js.org" target="_blank" title="Artalk v${ARTALK_VERSION}">Artalk</a>`
28
+ // Bind events
29
+ this.initCrudEvents()
30
+ }
47
31
 
48
- // event listen
49
- this.ctx.on('conf-loaded', () => {
50
- // i18n support when locale changed
51
- refreshCountNumEl()
52
- this.refreshUI()
32
+ getListLayout() {
33
+ return new ListLayout({
34
+ $commentsWrap: this.$commentsWrap,
35
+ nestSortBy: this.ctx.conf.nestSort,
36
+ nestMax: this.ctx.conf.nestMax,
37
+ flatMode: this.ctx.conf.flatMode as boolean,
38
+ // flatMode must be boolean because it had been handled when Artalk.init
39
+ createCommentNode: (d, c) => {
40
+ const node = createCommentNode(this.ctx, d, c)
41
+ this.commentNodes.push(node)
42
+ return node
43
+ },
44
+ findCommentNode: (id) => this.commentNodes.find(c => c.getID() === id),
45
+ getCommentDataList: () => this.ctx.getData().getComments(),
53
46
  })
54
47
  }
55
48
 
56
- private initListActionBtn() {
57
- // 侧边栏呼出按钮
58
- this.$openSidebarBtn = this.$el.querySelector('[data-action="open-sidebar"]')!
59
- this.$closeCommentBtn = this.$el.querySelector('[data-action="admin-close-comment"]')!
60
- this.$unreadBadge = this.$el.querySelector('.atk-unread-badge')!
61
-
62
- this.$openSidebarBtn.addEventListener('click', () => {
63
- this.ctx.showSidebar()
49
+ private initCrudEvents() {
50
+ this.ctx.on('list-load', (comments) => {
51
+ // 导入数据
52
+ this.getListLayout().import(comments)
64
53
  })
65
54
 
66
- // 关闭评论按钮
67
- this.$closeCommentBtn.addEventListener('click', () => {
68
- if (!this.data) return
69
-
70
- this.data.page.admin_only = !this.data.page.admin_only
71
- this.adminPageEditSave()
55
+ this.ctx.on('list-loaded', (comments) => {
56
+ if (comments.length === 0) this.$commentsWrap.innerHTML = ''
72
57
  })
73
- }
74
-
75
- /** 刷新界面 */
76
- public refreshUI() {
77
- super.refreshUI()
78
-
79
- this.$commentCountNum.innerText = String(Number(this.data?.total) || 0)
80
-
81
- // 已输入个人信息
82
- if (!!User.data.nick && !!User.data.email) {
83
- this.$openSidebarBtn.classList.remove('atk-hide')
84
- } else {
85
- this.$openSidebarBtn.classList.add('atk-hide')
86
- }
87
-
88
- this.$openSidebarBtn.querySelector<HTMLElement>('.atk-text')!
89
- .innerText = (!User.data.isAdmin) ? this.$t('msgCenter') : this.$t('ctrlCenter')
90
-
91
- // 关闭评论
92
- if (!!this.data && !!this.data.page && this.data.page.admin_only === true) {
93
- this.ctx.editorClose()
94
- this.$closeCommentBtn.innerHTML = this.$t('openComment')
95
- } else {
96
- this.ctx.editorOpen()
97
- this.$closeCommentBtn.innerHTML = this.$t('closeComment')
98
- }
99
-
100
- // 评论列表排序 Dropdown 下拉选择层
101
- if (this.ctx.conf.listSort) {
102
- this.initDropdown()
103
- } else {
104
- ListUi.removeDropdown({
105
- $dropdownWrap: this.$commentCount
106
- })
107
- }
108
- }
109
-
110
- protected onLoad(data: ListData, offset: number) {
111
- super.onLoad(data, offset)
112
-
113
- // 检测锚点跳转
114
- if (!this.goToCommentFounded) this.checkGoToCommentByUrlHash()
115
-
116
- // 防止评论框被吞
117
- if (this.ctx.conf.editorTravel === true) {
118
- this.ctx.editorTravelBack()
119
- }
120
- }
121
58
 
122
- private goToCommentFounded = false
123
- public goToCommentDelay = true
124
-
125
- /** 跳到评论项位置 - 根据 `location.hash` */
126
- public checkGoToCommentByUrlHash() {
127
- let commentId = Number(Utils.getQueryParam('atk_comment')) // same as backend GetReplyLink()
128
- if (!commentId) {
129
- const match = window.location.hash.match(/#atk-comment-([0-9]+)/)
130
- if (!match || !match[1] || Number.isNaN(Number(match[1]))) return
131
- commentId = Number(match[1])
132
- }
133
- if (!commentId) return
134
-
135
- const comment = this.ctx.findComment(commentId)
136
- if (!comment) { // 若找不到评论
137
- // 自动翻页
138
- this.pgHolder?.next()
139
- return
140
- }
141
-
142
- // 已阅 API
143
- const notifyKey = Utils.getQueryParam('atk_notify_key')
144
- if (notifyKey) {
145
- this.ctx.getApi().user.markRead(commentId, notifyKey)
146
- .then(() => {
147
- this.unread = this.unread.filter(o => o.comment_id !== commentId)
148
- this.updateUnread(this.unread)
149
- })
150
- }
151
-
152
- // 若父评论存在 “子评论部分” 限高,取消限高
153
- comment.getParents().forEach((p) => {
154
- p.getRender().heightLimitRemoveForChildren()
59
+ // When comment insert
60
+ this.ctx.on('comment-inserted', (comment) => {
61
+ this.getListLayout().insert(comment)
155
62
  })
156
63
 
157
- const goTo = () => {
158
- Ui.scrollIntoView(comment.getEl(), false)
159
-
160
- comment.getEl().classList.remove('atk-flash-once')
161
- window.setTimeout(() => {
162
- comment.getEl().classList.add('atk-flash-once')
163
- }, 150)
164
- }
165
-
166
- if (!this.goToCommentDelay) goTo()
167
- else window.setTimeout(() => goTo(), 350)
168
-
169
- this.goToCommentFounded = true
170
- this.goToCommentDelay = true // reset
171
- }
172
-
173
- /** 管理员设置页面信息 */
174
- public adminPageEditSave () {
175
- if (!this.data || !this.data.page) return
176
-
177
- this.ctx.editorShowLoading()
178
- this.ctx.getApi().page.pageEdit(this.data.page)
179
- .then((page) => {
180
- if (this.data)
181
- this.data.page = { ...page }
182
- this.refreshUI()
183
- })
184
- .catch(err => {
185
- this.ctx.editorShowNotify(`${this.$t('editFail')}: ${err.msg || String(err)}`, 'e')
186
- })
187
- .finally(() => {
188
- this.ctx.editorHideLoading()
189
- })
190
- }
191
-
192
- public showUnreadBadge(count: number) {
193
- if (count > 0) {
194
- this.$unreadBadge.innerText = `${Number(count || 0)}`
195
- this.$unreadBadge.style.display = 'block'
196
- } else {
197
- this.$unreadBadge.style.display = 'none'
198
- }
199
- }
200
-
201
- /** 初始化选择下拉层 */
202
- protected initDropdown() {
203
- const reloadUseParamsEditor = (func: (p: any) => void) => {
204
- this.paramsEditor = func
205
- this.fetchComments(0)
206
- }
207
-
208
- ListUi.renderDropdown({
209
- $dropdownWrap: this.$commentCount,
210
- dropdownList: [
211
- [this.$t('sortLatest'), () => { reloadUseParamsEditor(p => { p.sort_by = 'date_desc' }) }],
212
- [this.$t('sortBest'), () => { reloadUseParamsEditor(p => { p.sort_by = 'vote' }) }],
213
- [this.$t('sortOldest'), () => { reloadUseParamsEditor(p => { p.sort_by = 'date_asc' }) }],
214
- [this.$t('sortAuthor'), () => { reloadUseParamsEditor(p => { p.view_only_admin = true }) }],
215
- ]
64
+ // When comment delete
65
+ this.ctx.on('comment-deleted', (comment) => {
66
+ const node = this.commentNodes.find(c => c.getID() === comment.id)
67
+ if (!node) { console.error(`comment node id=${comment.id} not found`);return }
68
+ node.getEl().remove()
69
+ this.commentNodes = this.commentNodes.filter(c => c.getID() !== comment.id)
70
+ // TODO remove child nodes
216
71
  })
217
- }
218
72
 
219
- public updateUnread(notifies: NotifyData[]): void {
220
- super.updateUnread(notifies)
221
- this.showUnreadBadge(notifies?.length || 0)
73
+ // When comment update
74
+ this.ctx.on('comment-updated', (comment) => {
75
+ const node = this.commentNodes.find(c => c.getID() === comment.id)
76
+ node && node.setData(comment)
77
+ })
222
78
  }
223
79
  }
@@ -1,4 +1,4 @@
1
- import { CommentData } from '~/types/artalk-data'
1
+ import type { CommentData } from '~/types'
2
2
 
3
3
  export type CommentNode = {
4
4
  id: number