artalk 2.6.4 → 2.7.1

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 (316) hide show
  1. package/dist/Artalk.css +1 -1
  2. package/dist/Artalk.es.js +7756 -7432
  3. package/dist/Artalk.es.js.map +1 -0
  4. package/dist/Artalk.iife.js +2 -1
  5. package/dist/Artalk.iife.js.map +1 -0
  6. package/dist/Artalk.js +2 -1
  7. package/dist/Artalk.js.map +1 -0
  8. package/dist/ArtalkLite.css +1 -1
  9. package/dist/ArtalkLite.es.js +5576 -5252
  10. package/dist/ArtalkLite.es.js.map +1 -0
  11. package/dist/ArtalkLite.iife.js +2 -1
  12. package/dist/ArtalkLite.iife.js.map +1 -0
  13. package/dist/ArtalkLite.js +2 -1
  14. package/dist/ArtalkLite.js.map +1 -0
  15. package/dist/i18n/jp.js +1 -1
  16. package/dist/i18n/zh-TW.js +1 -1
  17. package/dist/src/api/{api-base.d.ts → _base.d.ts} +3 -7
  18. package/dist/src/api/_options.d.ts +19 -0
  19. package/dist/src/api/{request.d.ts → _request.d.ts} +3 -3
  20. package/dist/src/api/admin.d.ts +1 -1
  21. package/dist/src/api/captcha.d.ts +1 -1
  22. package/dist/src/api/comment.d.ts +2 -2
  23. package/dist/src/api/index.d.ts +2 -4
  24. package/dist/src/api/page.d.ts +3 -3
  25. package/dist/src/api/site.d.ts +2 -2
  26. package/dist/src/api/system.d.ts +13 -3
  27. package/dist/src/api/upload.d.ts +1 -1
  28. package/dist/src/api/user.d.ts +2 -2
  29. package/dist/src/artalk.d.ts +12 -37
  30. package/dist/src/comment/actions.d.ts +1 -1
  31. package/dist/src/comment/comment.d.ts +3 -4
  32. package/dist/src/comment/render.d.ts +21 -3
  33. package/dist/src/comment/renders/actions.d.ts +2 -2
  34. package/dist/src/comment/renders/avatar.d.ts +2 -2
  35. package/dist/src/comment/renders/content.d.ts +2 -2
  36. package/dist/src/comment/renders/header.d.ts +2 -2
  37. package/dist/src/comment/renders/index.d.ts +2 -2
  38. package/dist/src/comment/renders/pending.d.ts +2 -2
  39. package/dist/src/comment/renders/reply-at.d.ts +2 -2
  40. package/dist/src/comment/renders/reply-to.d.ts +2 -2
  41. package/dist/src/components/action-btn.d.ts +3 -5
  42. package/dist/src/{lib → components}/checker/admin.d.ts +1 -1
  43. package/dist/src/{lib → components}/checker/captcha-renders.d.ts +1 -1
  44. package/dist/src/{lib → components}/checker/captcha.d.ts +1 -1
  45. package/dist/src/{lib → components}/checker/index.d.ts +10 -7
  46. package/dist/src/components/dark-mode.d.ts +2 -0
  47. package/dist/src/components/dialog.d.ts +1 -3
  48. package/dist/src/components/error-dialog.d.ts +8 -0
  49. package/dist/src/components/pagination.d.ts +4 -3
  50. package/dist/src/components/read-more-btn.d.ts +3 -3
  51. package/dist/src/config.d.ts +24 -8
  52. package/dist/src/context.d.ts +16 -35
  53. package/dist/src/data.d.ts +27 -0
  54. package/dist/src/defaults.d.ts +1 -1
  55. package/dist/src/editor/editor.d.ts +14 -61
  56. package/dist/src/editor/state.d.ts +20 -0
  57. package/dist/src/i18n/en.d.ts +3 -0
  58. package/dist/src/i18n/external.d.ts +3 -0
  59. package/dist/src/i18n/index.d.ts +9 -0
  60. package/dist/src/i18n/jp.d.ts +3 -0
  61. package/dist/src/i18n/zh-CN.d.ts +1 -1
  62. package/dist/src/i18n/zh-TW.d.ts +3 -0
  63. package/dist/src/layer/index.d.ts +4 -3
  64. package/dist/src/layer/layer-manager.d.ts +9 -0
  65. package/dist/src/layer/layer.d.ts +8 -28
  66. package/dist/src/layer/scrollbar-helper.d.ts +5 -0
  67. package/dist/src/layer/sidebar-layer.d.ts +6 -6
  68. package/dist/src/layer/wrap.d.ts +13 -0
  69. package/dist/src/lib/component.d.ts +3 -8
  70. package/dist/src/lib/event-manager.d.ts +28 -0
  71. package/dist/src/lib/highlight.d.ts +1 -0
  72. package/dist/src/lib/marked-renderer.d.ts +4 -0
  73. package/dist/src/lib/marked.d.ts +4 -2
  74. package/dist/src/lib/sanitizer.d.ts +1 -0
  75. package/dist/src/lib/ui.d.ts +3 -2
  76. package/dist/src/lib/user.d.ts +8 -5
  77. package/dist/src/lib/utils.d.ts +11 -5
  78. package/dist/src/list/comment.d.ts +3 -0
  79. package/dist/src/list/layout.d.ts +23 -0
  80. package/dist/src/list/list.d.ts +13 -25
  81. package/dist/src/list/{list-nest.d.ts → nest.d.ts} +1 -1
  82. package/dist/src/list/page.d.ts +2 -0
  83. package/dist/src/list/paginator/index.d.ts +22 -7
  84. package/dist/src/list/paginator/read-more.d.ts +18 -0
  85. package/dist/src/list/paginator/up-down.d.ts +14 -0
  86. package/dist/src/main.d.ts +5 -0
  87. package/dist/src/plugins/admin-only-elem.d.ts +2 -0
  88. package/dist/src/plugins/conf-remoter.d.ts +2 -0
  89. package/dist/src/plugins/dark-mode.d.ts +2 -0
  90. package/dist/src/plugins/editor/_kit.d.ts +27 -0
  91. package/dist/src/plugins/editor/_plug.d.ts +29 -0
  92. package/dist/src/plugins/editor/_sample.d.ts +5 -0
  93. package/dist/src/plugins/editor/closable.d.ts +7 -0
  94. package/dist/src/{editor/plugs/emoticons-plug.d.ts → plugins/editor/emoticons.d.ts} +10 -16
  95. package/dist/src/plugins/editor/header-event.d.ts +6 -0
  96. package/dist/src/plugins/editor/header-link.d.ts +6 -0
  97. package/dist/src/plugins/editor/header-user.d.ts +16 -0
  98. package/dist/src/plugins/editor/index.d.ts +6 -0
  99. package/dist/src/plugins/editor/local-storage.d.ts +6 -0
  100. package/dist/src/plugins/editor/mover.d.ts +6 -0
  101. package/dist/src/plugins/editor/preview.d.ts +7 -0
  102. package/dist/src/plugins/editor/state-edit.d.ts +11 -0
  103. package/dist/src/plugins/editor/state-reply.d.ts +8 -0
  104. package/dist/src/plugins/editor/submit-add.d.ts +18 -0
  105. package/dist/src/plugins/editor/submit-btn.d.ts +5 -0
  106. package/dist/src/plugins/editor/submit.d.ts +17 -0
  107. package/dist/src/plugins/editor/textarea.d.ts +8 -0
  108. package/dist/src/plugins/editor/upload.d.ts +9 -0
  109. package/dist/src/plugins/editor-kit.d.ts +42 -0
  110. package/dist/src/plugins/index.d.ts +2 -0
  111. package/dist/src/plugins/list/copyright.d.ts +2 -0
  112. package/dist/src/plugins/list/count.d.ts +2 -0
  113. package/dist/src/plugins/list/dropdown.d.ts +2 -0
  114. package/dist/src/plugins/list/error-dialog.d.ts +2 -0
  115. package/dist/src/plugins/list/fetch.d.ts +2 -0
  116. package/dist/src/plugins/list/goto-first.d.ts +3 -0
  117. package/dist/src/plugins/list/goto.d.ts +2 -0
  118. package/dist/src/plugins/list/index.d.ts +3 -0
  119. package/dist/src/plugins/list/loading.d.ts +2 -0
  120. package/dist/src/plugins/list/no-comment.d.ts +2 -0
  121. package/dist/src/plugins/list/reach-bottom.d.ts +2 -0
  122. package/dist/src/plugins/list/sidebar-btn.d.ts +2 -0
  123. package/dist/src/plugins/list/time-ticking.d.ts +3 -0
  124. package/dist/src/plugins/list/unread-badge.d.ts +2 -0
  125. package/dist/src/plugins/list/unread.d.ts +2 -0
  126. package/dist/src/plugins/list/with-editor.d.ts +2 -0
  127. package/dist/src/plugins/markdown.d.ts +2 -0
  128. package/dist/src/plugins/stat.d.ts +13 -0
  129. package/dist/src/plugins/version-check.d.ts +2 -0
  130. package/dist/src/service.d.ts +19 -23
  131. package/dist/tests/e2e/comment-add.spec.d.ts +1 -0
  132. package/dist/types/config.d.ts +110 -0
  133. package/dist/types/context.d.ts +56 -136
  134. package/dist/types/data.d.ts +194 -0
  135. package/dist/types/editor.d.ts +76 -0
  136. package/dist/types/event.d.ts +36 -25
  137. package/dist/types/index.d.ts +7 -0
  138. package/dist/types/plugin.d.ts +2 -0
  139. package/dist/types/sidebar.d.ts +3 -0
  140. package/package.json +5 -2
  141. package/src/api/_base.ts +18 -0
  142. package/src/api/_options.ts +25 -0
  143. package/src/api/{request.ts → _request.ts} +19 -18
  144. package/src/api/admin.ts +1 -1
  145. package/src/api/captcha.ts +1 -1
  146. package/src/api/comment.ts +4 -4
  147. package/src/api/index.ts +3 -12
  148. package/src/api/page.ts +5 -5
  149. package/src/api/site.ts +2 -2
  150. package/src/api/system.ts +19 -7
  151. package/src/api/upload.ts +3 -3
  152. package/src/api/user.ts +3 -3
  153. package/src/artalk.ts +47 -99
  154. package/src/comment/actions.ts +1 -4
  155. package/src/comment/comment.ts +9 -6
  156. package/src/comment/render.ts +27 -5
  157. package/src/comment/renders/actions.ts +45 -45
  158. package/src/comment/renders/avatar.ts +7 -7
  159. package/src/comment/renders/content.ts +18 -18
  160. package/src/comment/renders/header.ts +13 -13
  161. package/src/comment/renders/index.ts +3 -3
  162. package/src/comment/renders/pending.ts +5 -5
  163. package/src/comment/renders/reply-at.ts +8 -8
  164. package/src/comment/renders/reply-to.ts +13 -13
  165. package/src/components/action-btn.ts +11 -13
  166. package/src/{lib → components}/checker/admin.ts +6 -6
  167. package/src/{lib → components}/checker/captcha-renders.ts +7 -6
  168. package/src/{lib → components}/checker/captcha.ts +1 -1
  169. package/src/{lib → components}/checker/index.ts +21 -26
  170. package/src/components/dark-mode.ts +30 -0
  171. package/src/components/dialog.ts +4 -9
  172. package/src/components/error-dialog.ts +35 -0
  173. package/src/components/pagination.ts +10 -6
  174. package/src/components/read-more-btn.ts +7 -7
  175. package/src/config.ts +60 -6
  176. package/src/context.ts +39 -144
  177. package/src/data.ts +110 -0
  178. package/src/defaults.ts +1 -1
  179. package/src/editor/editor.ts +39 -152
  180. package/src/editor/state.ts +52 -0
  181. package/src/i18n/en.ts +3 -0
  182. package/src/i18n/jp.ts +3 -0
  183. package/src/i18n/zh-CN.ts +4 -1
  184. package/src/i18n/zh-TW.ts +3 -0
  185. package/src/layer/index.ts +4 -4
  186. package/src/layer/layer-manager.ts +32 -0
  187. package/src/layer/layer.ts +20 -144
  188. package/src/layer/scrollbar-helper.ts +24 -0
  189. package/src/layer/sidebar-layer.ts +74 -64
  190. package/src/layer/wrap.ts +77 -0
  191. package/src/lib/component.ts +4 -11
  192. package/src/lib/detect.ts +1 -1
  193. package/src/lib/event-manager.ts +47 -0
  194. package/src/lib/highlight.ts +5 -0
  195. package/src/lib/marked-renderer.ts +32 -0
  196. package/src/lib/marked.ts +43 -104
  197. package/src/lib/sanitizer.ts +57 -0
  198. package/src/lib/ui.ts +22 -15
  199. package/src/lib/user.ts +21 -16
  200. package/src/lib/utils.ts +29 -18
  201. package/src/list/comment.ts +20 -0
  202. package/src/list/layout.ts +120 -0
  203. package/src/list/list.html +3 -1
  204. package/src/list/list.ts +56 -200
  205. package/src/list/{list-nest.ts → nest.ts} +1 -1
  206. package/src/list/page.ts +97 -0
  207. package/src/list/paginator/index.ts +20 -8
  208. package/src/list/paginator/read-more.ts +67 -0
  209. package/src/list/paginator/up-down.ts +51 -0
  210. package/src/main.ts +11 -0
  211. package/src/plugins/admin-only-elem.ts +40 -0
  212. package/src/plugins/conf-remoter.ts +54 -0
  213. package/src/plugins/dark-mode.ts +12 -0
  214. package/src/plugins/editor/_kit.ts +57 -0
  215. package/src/plugins/editor/_plug.ts +60 -0
  216. package/src/plugins/editor/_sample.ts +8 -0
  217. package/src/plugins/editor/closable.ts +44 -0
  218. package/src/{editor/plugs/emoticons-plug.ts → plugins/editor/emoticons.ts} +51 -52
  219. package/src/plugins/editor/header-event.ts +35 -0
  220. package/src/plugins/editor/header-link.ts +31 -0
  221. package/src/plugins/editor/header-user.ts +98 -0
  222. package/src/plugins/editor/index.ts +40 -0
  223. package/src/plugins/editor/local-storage.ts +36 -0
  224. package/src/plugins/editor/mover.ts +29 -0
  225. package/src/plugins/editor/preview.ts +41 -0
  226. package/src/plugins/editor/state-edit.ts +116 -0
  227. package/src/plugins/editor/state-reply.ts +91 -0
  228. package/src/plugins/editor/submit-add.ts +33 -0
  229. package/src/plugins/editor/submit-btn.ts +25 -0
  230. package/src/plugins/editor/submit.ts +73 -0
  231. package/src/plugins/editor/textarea.ts +55 -0
  232. package/src/plugins/editor/upload.ts +145 -0
  233. package/src/plugins/editor-kit.ts +161 -0
  234. package/src/plugins/index.ts +16 -0
  235. package/src/plugins/list/copyright.ts +16 -0
  236. package/src/plugins/list/count.ts +31 -0
  237. package/src/plugins/list/dropdown.ts +95 -0
  238. package/src/plugins/list/error-dialog.ts +22 -0
  239. package/src/plugins/list/fetch.ts +64 -0
  240. package/src/plugins/list/goto-first.ts +23 -0
  241. package/src/plugins/list/goto.ts +72 -0
  242. package/src/plugins/list/index.ts +25 -0
  243. package/src/plugins/list/loading.ts +17 -0
  244. package/src/plugins/list/no-comment.ts +25 -0
  245. package/src/plugins/list/reach-bottom.ts +46 -0
  246. package/src/plugins/list/sidebar-btn.ts +39 -0
  247. package/src/plugins/list/time-ticking.ts +22 -0
  248. package/src/plugins/list/unread-badge.ts +26 -0
  249. package/src/plugins/list/unread.ts +38 -0
  250. package/src/plugins/list/with-editor.ts +59 -0
  251. package/src/plugins/markdown.ts +10 -0
  252. package/src/plugins/stat.ts +82 -0
  253. package/src/plugins/version-check.ts +36 -0
  254. package/src/service.ts +34 -61
  255. package/src/style/comment.scss +5 -7
  256. package/src/style/editor.scss +5 -0
  257. package/src/style/list.scss +1 -1
  258. package/types/{artalk-config.d.ts → config.ts} +16 -2
  259. package/types/context.ts +84 -0
  260. package/types/{artalk-data.d.ts → data.ts} +41 -0
  261. package/types/editor.ts +95 -0
  262. package/types/event.ts +40 -0
  263. package/types/index.ts +7 -0
  264. package/types/plugin.ts +3 -0
  265. package/types/sidebar.ts +3 -0
  266. package/dist/src/comment/render-ctx.d.ts +0 -23
  267. package/dist/src/editor/edit-mode.d.ts +0 -9
  268. package/dist/src/editor/funcs.d.ts +0 -2
  269. package/dist/src/editor/index.d.ts +0 -2
  270. package/dist/src/editor/mover.d.ts +0 -8
  271. package/dist/src/editor/plug-manager.d.ts +0 -17
  272. package/dist/src/editor/plugs/editor-plug.d.ts +0 -38
  273. package/dist/src/editor/plugs/header-input-plug.d.ts +0 -13
  274. package/dist/src/editor/plugs/preview-plug.d.ts +0 -10
  275. package/dist/src/editor/plugs/upload-plug.d.ts +0 -10
  276. package/dist/src/editor/reply.d.ts +0 -9
  277. package/dist/src/editor/submit.d.ts +0 -16
  278. package/dist/src/lib/dark-mode.d.ts +0 -4
  279. package/dist/src/lib/stat.d.ts +0 -15
  280. package/dist/src/list/index.d.ts +0 -2
  281. package/dist/src/list/list-lite.d.ts +0 -77
  282. package/dist/src/list/list-ui.d.ts +0 -16
  283. package/dist/src/list/paginator/adaptors/index.d.ts +0 -12
  284. package/dist/src/list/paginator/adaptors/pagination.d.ts +0 -9
  285. package/dist/src/list/paginator/adaptors/read-more.d.ts +0 -10
  286. package/dist/src/list/paginator/holder.d.ts +0 -16
  287. package/dist/types/artalk-config.d.ts +0 -139
  288. package/dist/types/artalk-data.d.ts +0 -230
  289. package/dist/types/plug.d.ts +0 -4
  290. package/src/api/api-base.ts +0 -27
  291. package/src/comment/render-ctx.ts +0 -30
  292. package/src/editor/edit-mode.ts +0 -105
  293. package/src/editor/funcs.ts +0 -150
  294. package/src/editor/index.ts +0 -3
  295. package/src/editor/mover.ts +0 -43
  296. package/src/editor/plug-manager.ts +0 -172
  297. package/src/editor/plugs/editor-plug.ts +0 -69
  298. package/src/editor/plugs/header-input-plug.ts +0 -79
  299. package/src/editor/plugs/preview-plug.ts +0 -44
  300. package/src/editor/plugs/upload-plug.ts +0 -122
  301. package/src/editor/reply.ts +0 -62
  302. package/src/editor/submit.ts +0 -99
  303. package/src/lib/dark-mode.ts +0 -48
  304. package/src/lib/stat.ts +0 -71
  305. package/src/list/index.ts +0 -3
  306. package/src/list/list-lite.ts +0 -424
  307. package/src/list/list-ui.ts +0 -137
  308. package/src/list/paginator/adaptors/index.ts +0 -21
  309. package/src/list/paginator/adaptors/pagination.ts +0 -42
  310. package/src/list/paginator/adaptors/read-more.ts +0 -56
  311. package/src/list/paginator/holder.ts +0 -56
  312. package/types/context.d.ts +0 -142
  313. package/types/event.d.ts +0 -28
  314. package/types/plug.d.ts +0 -4
  315. /package/src/{editor/plugs/emoticons-plug.scss → plugins/editor/emoticons.scss} +0 -0
  316. /package/src/{editor/plugs/preview-plug.scss → plugins/editor/preview.scss} +0 -0
package/src/lib/marked.ts CHANGED
@@ -1,133 +1,72 @@
1
- import { marked as libMarked } from 'marked'
2
- import insane from 'insane'
3
- import hanabi from 'hanabi'
4
- import Context from '~/types/context'
1
+ import { marked as libMarked, MarkedOptions } from 'marked'
5
2
 
6
- let instance: (typeof libMarked)|undefined
3
+ import { sanitize } from './sanitizer'
4
+ import { renderCode } from './highlight'
5
+ import { getRenderer } from './marked-renderer'
7
6
 
7
+ type Replacer = (raw: string) => string
8
8
  export type TMarked = typeof libMarked
9
9
 
10
+ let instance: (typeof libMarked)|undefined
11
+ let replacers: Replacer[] = []
12
+
13
+ const markedOptions: MarkedOptions = {
14
+ pedantic: false,
15
+ gfm: true,
16
+ breaks: true,
17
+ smartLists: true,
18
+ smartypants: true,
19
+ xhtml: false,
20
+ sanitize: false,
21
+ silent: true,
22
+ }
23
+
10
24
  /** Get Marked instance */
11
25
  export function getInstance() {
12
26
  return instance
13
27
  }
14
28
 
29
+ export function setReplacers(arr: Replacer[]) {
30
+ replacers = arr
31
+ }
32
+
15
33
  /** 初始化 marked */
16
34
  export function initMarked() {
17
35
  try { if (!libMarked.name) return } catch { return }
18
36
 
19
- const renderer = new libMarked.Renderer()
20
- const orgLinkRenderer = renderer.link
21
- renderer.link = (href, title, text) => {
22
- const localLink = href?.startsWith(`${window.location.protocol}//${window.location.hostname}`);
23
- const html = orgLinkRenderer.call(renderer as any, href, title, text);
24
- return html.replace(/^<a /, `<a target="_blank" ${!localLink ? `rel="noreferrer noopener nofollow"` : ''} `);
25
- }
26
-
27
- renderer.code = (block, lang) => {
28
- // Colorize the block only if the language is known to highlight.js
29
- const realLang = (!lang ? 'plaintext' : lang)
30
- let colorized = block
31
- if ((window as any).hljs) {
32
- if (realLang && (window as any).hljs.getLanguage(realLang)) {
33
- colorized = (window as any).hljs.highlight(realLang, block).value
34
- }
35
- } else {
36
- colorized = hanabi(block)
37
- }
38
-
39
- return `<pre rel="${realLang}">\n`
40
- + `<code class="hljs language-${realLang}">${colorized.replace(/&amp;/g, '&')}</code>\n`
41
- + `</pre>`
42
- }
43
-
44
37
  // @see https://github.com/markedjs/marked/blob/4afb228d956a415624c4e5554bb8f25d047676fe/src/Tokenizer.js#L329
45
- const nMarked = libMarked
46
38
  libMarked.setOptions({
47
- renderer,
48
- pedantic: false,
49
- gfm: true,
50
- breaks: true,
51
- smartLists: true,
52
- smartypants: true,
53
- xhtml: false,
54
- sanitize: false,
55
- silent: true,
39
+ renderer: getRenderer(),
40
+ ...markedOptions
56
41
  })
57
42
 
58
- instance = nMarked
43
+ instance = libMarked
59
44
  }
60
45
 
61
46
  /** 解析 markdown */
62
- export default function marked(ctx: Context, src: string): string {
47
+ export default function marked(src: string): string {
63
48
  let markedContent = getInstance()?.parse(src)
64
- if (!markedContent) {
65
- // 无 Markdown 模式简单处理
66
- markedContent = src
67
- // .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
68
- .replace(/```\s*([^]+?.*?[^]+?[^]+?)```/g, (_, code) => `<pre><code>${hanabi(code)}</code></pre>`)
69
- // .replace(/`([^`]+?)`/g, '<code>$1</code>')
70
- .replace(/!\[(.*?)\]\((.*?)\)/g, (_, alt, imgSrc) => `<img src="${imgSrc}" alt="${alt}" />`)
71
- .replace(/\[(.*?)\]\((.*?)\)/g, (_, text, link) => `<a href="${link}" target="_blank">${text}</a>`)
72
- .replace(/\n/g, '<br>')
49
+ if (!markedContent) { // 无 Markdown 模式简单处理
50
+ markedContent = simpleMarked(src)
73
51
  }
74
52
 
75
- // @link https://github.com/markedjs/marked/discussions/1232
76
- // @link https://gist.github.com/lionel-rowe/bb384465ba4e4c81a9c8dada84167225
77
- let dest = insane(markedContent, {
78
- allowedClasses: {},
79
- // @refer CVE-2018-8495
80
- // @link https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-8495
81
- // @link https://leucosite.com/Microsoft-Edge-RCE/
82
- // @link https://medium.com/@knownsec404team/analysis-of-the-security-issues-of-url-scheme-in-pc-from-cve-2018-8495-934478a36756
83
- allowedSchemes: [
84
- 'http', 'https', 'mailto',
85
- 'data' // for support base64 encoded image (安全性有待考虑)
86
- ],
87
- allowedTags: [
88
- 'a', 'abbr', 'article', 'b', 'blockquote', 'br', 'caption', 'code', 'del', 'details', 'div', 'em',
89
- 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'ins', 'kbd', 'li', 'main', 'mark',
90
- 'ol', 'p', 'pre', 'section', 'span', 'strike', 'strong', 'sub', 'summary', 'sup', 'table',
91
- 'tbody', 'td', 'th', 'thead', 'tr', 'u', 'ul'
92
- ],
93
- allowedAttributes: {
94
- '*': ['title', 'accesskey'],
95
- a: ['href', 'name', 'target', 'aria-label', 'rel'],
96
- img: ['src', 'alt', 'title', 'atk-emoticon', 'aria-label'],
97
- // for code highlight
98
- code: ['class'],
99
- span: ['class', 'style'],
100
- },
101
- filter: node => {
102
- // allow hljs style
103
- const allowed = [
104
- [ 'code', /^hljs\W+language-(.*)$/ ],
105
- [ 'span', /^(hljs-.*)$/ ]
106
- ]
107
- allowed.forEach(([ tag, reg ]) => {
108
- if (
109
- node.tag === tag
110
- && !!node.attrs.class
111
- && !(reg as RegExp).test(node.attrs.class)
112
- ) {
113
- delete node.attrs.class
114
- }
115
- })
116
-
117
- // allow <span> set color sty
118
- if (node.tag === 'span' && !!node.attrs.style
119
- && !/^color:(\W+)?#[0-9a-f]{3,6};?$/i.test(node.attrs.style)) {
120
- delete node.attrs.style
121
- }
122
-
123
- return true
124
- }
125
- })
53
+ let dest = sanitize(markedContent)
126
54
 
127
55
  // 内容替换器
128
- ctx.markedReplacers.forEach((replacer) => {
129
- if (typeof replacer === 'function') dest = replacer(dest)
56
+ replacers.forEach((replacer) => {
57
+ if (typeof replacer === 'function')
58
+ dest = replacer(dest)
130
59
  })
131
60
 
132
61
  return dest
133
62
  }
63
+
64
+ function simpleMarked(src: string) {
65
+ return src
66
+ // .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
67
+ .replace(/```\s*([^]+?.*?[^]+?[^]+?)```/g, (_, code) => `<pre><code>${renderCode(code)}</code></pre>`)
68
+ // .replace(/`([^`]+?)`/g, '<code>$1</code>')
69
+ .replace(/!\[(.*?)\]\((.*?)\)/g, (_, alt, imgSrc) => `<img src="${imgSrc}" alt="${alt}" />`)
70
+ .replace(/\[(.*?)\]\((.*?)\)/g, (_, text, link) => `<a href="${link}" target="_blank">${text}</a>`)
71
+ .replace(/\n/g, '<br>')
72
+ }
@@ -0,0 +1,57 @@
1
+ import insane from 'insane'
2
+
3
+ const insaneOptions = {
4
+ allowedClasses: {},
5
+ // @refer CVE-2018-8495
6
+ // @link https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-8495
7
+ // @link https://leucosite.com/Microsoft-Edge-RCE/
8
+ // @link https://medium.com/@knownsec404team/analysis-of-the-security-issues-of-url-scheme-in-pc-from-cve-2018-8495-934478a36756
9
+ allowedSchemes: [
10
+ 'http', 'https', 'mailto',
11
+ 'data' // for support base64 encoded image (安全性有待考虑)
12
+ ],
13
+ allowedTags: [
14
+ 'a', 'abbr', 'article', 'b', 'blockquote', 'br', 'caption', 'code', 'del', 'details', 'div', 'em',
15
+ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'ins', 'kbd', 'li', 'main', 'mark',
16
+ 'ol', 'p', 'pre', 'section', 'span', 'strike', 'strong', 'sub', 'summary', 'sup', 'table',
17
+ 'tbody', 'td', 'th', 'thead', 'tr', 'u', 'ul'
18
+ ],
19
+ allowedAttributes: {
20
+ '*': ['title', 'accesskey'],
21
+ a: ['href', 'name', 'target', 'aria-label', 'rel'],
22
+ img: ['src', 'alt', 'title', 'atk-emoticon', 'aria-label'],
23
+ // for code highlight
24
+ code: ['class'],
25
+ span: ['class', 'style'],
26
+ },
27
+ filter: node => {
28
+ // allow hljs style
29
+ const allowed = [
30
+ [ 'code', /^hljs\W+language-(.*)$/ ],
31
+ [ 'span', /^(hljs-.*)$/ ]
32
+ ]
33
+ allowed.forEach(([ tag, reg ]) => {
34
+ if (
35
+ node.tag === tag
36
+ && !!node.attrs.class
37
+ && !(reg as RegExp).test(node.attrs.class)
38
+ ) {
39
+ delete node.attrs.class
40
+ }
41
+ })
42
+
43
+ // allow <span> set color sty
44
+ if (node.tag === 'span' && !!node.attrs.style
45
+ && !/^color:(\W+)?#[0-9a-f]{3,6};?$/i.test(node.attrs.style)) {
46
+ delete node.attrs.style
47
+ }
48
+
49
+ return true
50
+ }
51
+ }
52
+
53
+ export function sanitize(content: string): string {
54
+ // @link https://github.com/markedjs/marked/discussions/1232
55
+ // @link https://gist.github.com/lionel-rowe/bb384465ba4e4c81a9c8dada84167225
56
+ return insane(content, insaneOptions)
57
+ }
package/src/lib/ui.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { NotifyLevel } from '~/types'
1
2
  import * as Utils from './utils'
2
3
 
3
4
  /** 显示加载 */
@@ -53,29 +54,35 @@ export function isVisible(el: HTMLElement, viewport: HTMLElement = document.docu
53
54
  }
54
55
 
55
56
  /** 滚动到元素中心 */
56
- export function scrollIntoView(elem: HTMLElement, enableAnim: boolean = true) {
57
- const top =
58
- Utils.getOffset(elem).top +
59
- Utils.getHeight(elem) / 2 -
60
- document.documentElement.clientHeight / 2
61
-
62
- if (enableAnim) {
63
- window.scroll({
64
- top: top > 0 ? top : 0,
65
- left: 0,
66
- // behavior: 'smooth',
67
- })
57
+ export function scrollIntoView(elem: HTMLElement, enableAnim: boolean = true, relativeTo?: HTMLElement) {
58
+ let top: number
59
+
60
+ if (relativeTo) {
61
+ const containerRect = relativeTo.getBoundingClientRect()
62
+ const elementRect = elem.getBoundingClientRect()
63
+ top = elementRect.top - containerRect.top + relativeTo.scrollTop
64
+ top += Utils.getHeight(relativeTo) / 2 - Utils.getHeight(elem) / 2
68
65
  } else {
69
- // 无动画
70
- window.scroll(0, top > 0 ? top : 0)
66
+ const rect = elem.getBoundingClientRect()
67
+ const elemTop = rect.top + window.scrollY
68
+ top = elemTop - (window.innerHeight / 2 - rect.height / 2)
71
69
  }
70
+
71
+ const scrollOptions: ScrollToOptions = {
72
+ top, left: 0,
73
+ // behavior: enableAnim ? 'smooth' : 'instant',
74
+ behavior: 'instant'
75
+ }
76
+
77
+ if (relativeTo) relativeTo.scroll(scrollOptions)
78
+ else window.scroll(scrollOptions)
72
79
  }
73
80
 
74
81
  /** 显示消息 */
75
82
  export function showNotify(
76
83
  wrapElem: HTMLElement,
77
84
  msg: string,
78
- type: 's' | 'e' | 'w' | 'i',
85
+ type: NotifyLevel,
79
86
  ) {
80
87
  const colors = { s: '#57d59f', e: '#ff6f6c', w: '#ffc721', i: '#2ebcfc' }
81
88
  const timeout = 3000 // 持续显示时间 ms
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>