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
@@ -1,14 +1,10 @@
1
- import Context from '~/types/context'
1
+ import type { ContextApi, SidebarShowPayload } from '~/types'
2
2
  import Component from '@/lib/component'
3
3
  import * as Utils from '@/lib/utils'
4
4
  import * as Ui from '@/lib/ui'
5
5
  import SidebarHTML from './sidebar-layer.html?raw'
6
6
  import User from '../lib/user'
7
- import Layer from './layer'
8
-
9
- export interface SidebarShowPayload {
10
- view?: 'comments'|'sites'|'pages'|'transfer'
11
- }
7
+ import type { Layer } from './layer'
12
8
 
13
9
  export default class SidebarLayer extends Component {
14
10
  public layer?: Layer
@@ -17,7 +13,7 @@ export default class SidebarLayer extends Component {
17
13
  public $iframeWrap: HTMLElement
18
14
  public $iframe?: HTMLIFrameElement
19
15
 
20
- constructor(ctx: Context) {
16
+ constructor(ctx: ContextApi) {
21
17
  super(ctx)
22
18
 
23
19
  this.$el = Utils.createElement(SidebarHTML)
@@ -40,72 +36,31 @@ export default class SidebarLayer extends Component {
40
36
  this.$el.style.transform = '' // 动画清除,防止二次打开失效
41
37
 
42
38
  // 获取 Layer
43
- if (this.layer == null) {
44
- this.layer = new Layer(this.ctx, 'sidebar', this.$el)
45
- this.layer.afterHide = () => {
46
- // 防止评论框被吞
47
- if (this.ctx.conf.editorTravel === true) {
48
- this.ctx.editorTravelBack()
49
- }
50
- }
51
- }
52
- this.layer.show()
53
-
54
- // viewWrap 滚动条归位
55
- // this.$viewWrap.scrollTo(0, 0)
39
+ this.initLayer()
40
+ this.layer!.show()
56
41
 
57
42
  // 管理员身份验证 (若身份失效,弹出验证窗口)
58
- ;(async () => {
59
- const resp = await this.ctx.getApi().user.loginStatus()
60
- if (resp.is_admin && !resp.is_login) {
61
- this.layer?.hide()
62
- this.firstShow = true
63
-
64
- this.ctx.checkAdmin({
65
- onSuccess: () => {
66
- setTimeout(() => {
67
- this.show(conf)
68
- }, 500)
69
- },
70
- onCancel: () => {}
71
- })
72
- }
73
- })()
43
+ this.authCheck({
44
+ onSuccess: () => this.show(conf) // retry show after auth check
45
+ })
74
46
 
75
47
  // 第一次加载
76
48
  if (this.firstShow) {
77
49
  this.$iframeWrap.innerHTML = ''
78
- this.$iframe = Utils.createElement<HTMLIFrameElement>('<iframe></iframe>')
79
-
80
- // 准备 Iframe 参数
81
- const baseURL = (import.meta.env.DEV) ? 'http://localhost:23367/'
82
- : Utils.getURLBasedOnApi(this.ctx, '/sidebar/')
83
-
84
- const query: any = {
85
- pageKey: this.conf.pageKey,
86
- site: this.conf.site || '',
87
- user: JSON.stringify(User.data),
88
- time: +new Date()
89
- }
90
-
91
- if (conf.view) query.view = conf.view
92
- if (this.conf.darkMode) query.darkMode = '1'
93
- if (typeof this.conf.locale === 'string') query.locale = this.conf.locale
94
-
95
- const urlParams = new URLSearchParams(query);
96
- this.iframeLoad(`${baseURL}?${urlParams.toString()}`)
97
-
50
+ this.$iframe = this.createIframe(conf.view)
98
51
  this.$iframeWrap.append(this.$iframe)
99
52
  this.firstShow = false
100
53
  } else {
54
+ const $iframe = this.$iframe!
55
+
101
56
  // 夜间模式
102
- const isIframeSrcDarkMode = this.$iframe!.src.includes('darkMode=1')
57
+ const isIframeSrcDarkMode = $iframe.src.includes('darkMode=1')
103
58
 
104
59
  if (this.conf.darkMode && !isIframeSrcDarkMode)
105
- this.iframeLoad(`${this.$iframe!.src}&darkMode=1`)
60
+ this.iframeLoad($iframe, `${this.$iframe!.src}&darkMode=1`)
106
61
 
107
62
  if (!this.conf.darkMode && isIframeSrcDarkMode)
108
- this.iframeLoad(this.$iframe!.src.replace('&darkMode=1', ''))
63
+ this.iframeLoad($iframe, this.$iframe!.src.replace('&darkMode=1', ''))
109
64
  }
110
65
 
111
66
  // 执行滑动显示动画
@@ -115,7 +70,7 @@ export default class SidebarLayer extends Component {
115
70
 
116
71
  // 清空 unread
117
72
  setTimeout(() => {
118
- this.ctx.updateNotifies([])
73
+ this.ctx.getData().updateUnreads([])
119
74
  }, 0)
120
75
 
121
76
  this.ctx.trigger('sidebar-show')
@@ -131,14 +86,69 @@ export default class SidebarLayer extends Component {
131
86
  this.ctx.trigger('sidebar-hide')
132
87
  }
133
88
 
134
- private iframeLoad(src: string) {
135
- if (!this.$iframe) return
89
+ // --------------------------------------------------
90
+
91
+ private async authCheck(opts: { onSuccess: () => void }) {
92
+ const resp = await this.ctx.getApi().user.loginStatus()
93
+ if (resp.is_admin && !resp.is_login) {
94
+ this.firstShow = true
95
+
96
+ this.ctx.checkAdmin({
97
+ onSuccess: () => {
98
+ setTimeout(() => {
99
+ opts.onSuccess()
100
+ }, 500)
101
+ },
102
+ onCancel: () => {
103
+ this.hide()
104
+ }
105
+ })
106
+ }
107
+ }
108
+
109
+ private initLayer() {
110
+ if (this.layer) return
111
+
112
+ this.layer = this.ctx.get('layerManager').create('sidebar', this.$el)
113
+ this.layer.setOnAfterHide(() => {
114
+ // 防止评论框被吞
115
+ this.ctx.editorResetState()
116
+ })
117
+ }
118
+
119
+ private createIframe(view?: string) {
120
+ const $iframe = Utils.createElement<HTMLIFrameElement>('<iframe></iframe>')
121
+
122
+ // 准备 Iframe 参数
123
+ const baseURL = (import.meta.env.DEV) ? 'http://localhost:23367/'
124
+ : Utils.getURLBasedOnApi({
125
+ base: this.ctx.conf.server,
126
+ path: '/sidebar/',
127
+ })
128
+
129
+ const query: any = {
130
+ pageKey: this.conf.pageKey,
131
+ site: this.conf.site || '',
132
+ user: JSON.stringify(User.data),
133
+ time: +new Date()
134
+ }
135
+
136
+ if (view) query.view = view
137
+ if (this.conf.darkMode) query.darkMode = '1'
138
+ if (typeof this.conf.locale === 'string') query.locale = this.conf.locale
139
+
140
+ const urlParams = new URLSearchParams(query);
141
+ this.iframeLoad($iframe, `${baseURL}?${urlParams.toString()}`)
142
+
143
+ return $iframe
144
+ }
136
145
 
137
- this.$iframe.src = src
146
+ private iframeLoad($iframe: HTMLIFrameElement, src: string) {
147
+ $iframe.src = src
138
148
 
139
149
  // 加载动画
140
150
  Ui.showLoading(this.$iframeWrap)
141
- this.$iframe.onload = () => {
151
+ $iframe.onload = () => {
142
152
  Ui.hideLoading(this.$iframeWrap)
143
153
  }
144
154
  }
@@ -0,0 +1,77 @@
1
+ import * as Utils from '@/lib/utils'
2
+ import { getScrollbarHelper } from './scrollbar-helper'
3
+
4
+ export class LayerWrap {
5
+ private $wrap: HTMLElement
6
+ private $mask: HTMLElement
7
+ private allowMaskClose: boolean = true
8
+ private items: HTMLElement[] = []
9
+
10
+ constructor() {
11
+ this.$wrap = Utils.createElement(
12
+ `<div class="atk-layer-wrap" style="display: none;"><div class="atk-layer-mask"></div></div>`
13
+ )
14
+ this.$mask = this.$wrap.querySelector<HTMLElement>('.atk-layer-mask')!
15
+ }
16
+
17
+ createItem(name: string, el?: HTMLElement) {
18
+ if (!el) {
19
+ el = document.createElement('div')
20
+ el.classList.add('atk-layer-item')
21
+ el.setAttribute('data-layer-name', name)
22
+ el.style.display = 'none'
23
+ }
24
+ this.$wrap.appendChild(el)
25
+ this.items.push(el)
26
+ return el
27
+ }
28
+
29
+ getWrap() {
30
+ return this.$wrap
31
+ }
32
+
33
+ getMask() {
34
+ return this.$mask
35
+ }
36
+
37
+ setMaskClose(enabled: boolean) {
38
+ this.allowMaskClose = enabled
39
+ }
40
+
41
+ show() {
42
+ this.$wrap.style.display = 'block'
43
+ this.$mask.style.display = 'block'
44
+ this.$mask.classList.add('atk-fade-in')
45
+ this.$mask.onclick = () => {
46
+ if (this.allowMaskClose) this.hide()
47
+ }
48
+ getScrollbarHelper().lock()
49
+ }
50
+
51
+ hide(callback?: () => void) {
52
+ // if wrap contains more than one item, do not hide entire wrap
53
+ if (this.items.filter(e => e.isConnected && e.style.display !== 'none').length > 1) {
54
+ callback && callback()
55
+ return
56
+ }
57
+
58
+ const onAfterHide = () => {
59
+ this.$wrap.style.display = 'none'
60
+ this.$wrap.classList.remove('atk-fade-out')
61
+
62
+ callback && callback()
63
+
64
+ getScrollbarHelper().unlock()
65
+
66
+ this.$wrap.onanimationend = null
67
+ }
68
+
69
+ // perform animation
70
+ this.$wrap.classList.add('atk-fade-out')
71
+ if (window.getComputedStyle(this.$wrap)['animation-name'] !== 'none') {
72
+ this.$wrap.onanimationend = () => onAfterHide()
73
+ } else {
74
+ onAfterHide()
75
+ }
76
+ }
77
+ }
@@ -1,19 +1,12 @@
1
- import ArtalkConfig from '~/types/artalk-config'
2
- import Context from '~/types/context'
3
- import { I18n } from '../i18n'
1
+ import type { ArtalkConfig, ContextApi } from '~/types'
4
2
 
5
3
  export default abstract class Component {
6
4
  public $el!: HTMLElement
7
-
8
- public ctx: Context
9
5
  public readonly conf: ArtalkConfig
10
6
 
11
- public constructor(ctx: Context) {
12
- this.ctx = ctx
7
+ public constructor(
8
+ public ctx: ContextApi
9
+ ) {
13
10
  this.conf = ctx.conf
14
11
  }
15
-
16
- public $t(key: keyof I18n, args: {[key: string]: string} = {}): string {
17
- return this.ctx.$t(key, args)
18
- }
19
12
  }
package/src/lib/detect.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  const win = (window as any) || {}
2
2
  const nav = navigator || {}
3
3
 
4
- function Detect (userAgent?: string) {
4
+ function Detect(userAgent?: string) {
5
5
  const u = String(userAgent || nav.userAgent)
6
6
  const dest = {
7
7
  os: '',
@@ -0,0 +1,47 @@
1
+ export type EventHandler<T> = (payload: T) => void
2
+ export interface Event<PayloadMap, K extends keyof PayloadMap = keyof PayloadMap> extends EventOptions {
3
+ name: K
4
+ handler: EventHandler<PayloadMap[K]>
5
+ }
6
+ export interface EventOptions {
7
+ once?: boolean
8
+ }
9
+
10
+ export interface EventManagerFuncs<PayloadMap> {
11
+ on<K extends keyof PayloadMap>(name: K, handler: EventHandler<PayloadMap[K]>, opts?: EventOptions): void
12
+ off<K extends keyof PayloadMap>(name: K, handler: EventHandler<PayloadMap[K]>): void
13
+ trigger<K extends keyof PayloadMap>(name: K, payload?: PayloadMap[K]): void
14
+ }
15
+
16
+ export default class EventManager<PayloadMap> implements EventManagerFuncs<PayloadMap> {
17
+ private events: Event<PayloadMap>[] = []
18
+
19
+ /**
20
+ * Add an event listener for a specific event name
21
+ */
22
+ public on<K extends keyof PayloadMap>(name: K, handler: EventHandler<PayloadMap[K]>, opts: EventOptions = {}) {
23
+ this.events.push({ name, handler: handler as EventHandler<PayloadMap[keyof PayloadMap]>, ...opts })
24
+ }
25
+
26
+ /**
27
+ * Remove an event listener for a specific event name and handler
28
+ */
29
+ public off<K extends keyof PayloadMap>(name: K, handler: EventHandler<PayloadMap[K]>) {
30
+ if (!handler) return // not allow remove all events with same name
31
+ this.events = this.events.filter((evt) =>
32
+ !(evt.name === name && evt.handler === handler))
33
+ }
34
+
35
+ /**
36
+ * Trigger an event with an optional payload
37
+ */
38
+ public trigger<K extends keyof PayloadMap>(name: K, payload?: PayloadMap[K]) {
39
+ this.events
40
+ .slice(0) // make a copy, in case listeners are removed while iterating
41
+ .filter((evt) => evt.name === name && typeof evt.handler === 'function')
42
+ .forEach((evt) => {
43
+ if (evt.once) this.off(name, evt.handler)
44
+ evt.handler(payload!)
45
+ })
46
+ }
47
+ }
@@ -0,0 +1,5 @@
1
+ import hanabi from 'hanabi'
2
+
3
+ export function renderCode(code: string) {
4
+ return hanabi(code)
5
+ }
@@ -0,0 +1,32 @@
1
+ import { marked as libMarked } from 'marked'
2
+ import { renderCode } from './highlight'
3
+
4
+ export function getRenderer() {
5
+ const renderer = new libMarked.Renderer()
6
+ renderer.link = markedLinkRenderer(renderer, renderer.link)
7
+ renderer.code = markedCodeRenderer()
8
+ return renderer
9
+ }
10
+
11
+ export const markedLinkRenderer = (renderer: any, orgLinkRenderer: Function) => (href: string, title: string, text: string): string => {
12
+ const localLink = href?.startsWith(`${window.location.protocol}//${window.location.hostname}`);
13
+ const html = orgLinkRenderer.call(renderer, href, title, text);
14
+ return html.replace(/^<a /, `<a target="_blank" ${!localLink ? `rel="noreferrer noopener nofollow"` : ''} `);
15
+ }
16
+
17
+ export const markedCodeRenderer = () => (block: string, lang: string | undefined): string => {
18
+ // Colorize the block only if the language is known to highlight.js
19
+ const realLang = (!lang ? 'plaintext' : lang)
20
+ let colorized = block
21
+ if ((window as any).hljs) {
22
+ if (realLang && (window as any).hljs.getLanguage(realLang)) {
23
+ colorized = (window as any).hljs.highlight(realLang, block).value
24
+ }
25
+ } else {
26
+ colorized = renderCode(block)
27
+ }
28
+
29
+ return `<pre rel="${realLang}">\n`
30
+ + `<code class="hljs language-${realLang}">${colorized.replace(/&amp;/g, '&')}</code>\n`
31
+ + `</pre>`
32
+ }
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