artalk 2.4.4 → 2.5.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 (156) hide show
  1. package/README.md +27 -0
  2. package/dist/Artalk.css +1 -1
  3. package/dist/Artalk.es.js +7066 -6256
  4. package/dist/Artalk.iife.js +1 -1
  5. package/dist/Artalk.js +1 -1
  6. package/dist/ArtalkLite.css +1 -1
  7. package/dist/ArtalkLite.es.js +5095 -4396
  8. package/dist/ArtalkLite.iife.js +1 -1
  9. package/dist/ArtalkLite.js +1 -1
  10. package/dist/i18n/jp.js +1 -1
  11. package/dist/i18n/zh-TW.js +1 -1
  12. package/dist/src/api/index.d.ts +2 -2
  13. package/dist/src/api/system.d.ts +5 -2
  14. package/dist/src/artalk.d.ts +42 -34
  15. package/dist/src/comment/comment.d.ts +2 -2
  16. package/dist/src/comment/height-limit.d.ts +30 -0
  17. package/dist/src/comment/render-ctx.d.ts +23 -0
  18. package/dist/src/comment/render.d.ts +2 -50
  19. package/dist/src/comment/renders/actions.d.ts +5 -0
  20. package/dist/src/comment/renders/avatar.d.ts +5 -0
  21. package/dist/src/comment/renders/content.d.ts +5 -0
  22. package/dist/src/comment/renders/header.d.ts +5 -0
  23. package/dist/src/comment/renders/index.d.ts +2 -0
  24. package/dist/src/comment/renders/pending.d.ts +5 -0
  25. package/dist/src/comment/renders/reply-at.d.ts +5 -0
  26. package/dist/src/comment/renders/reply-to.d.ts +5 -0
  27. package/dist/src/components/dialog.d.ts +1 -1
  28. package/dist/src/config.d.ts +8 -0
  29. package/dist/src/context.d.ts +16 -29
  30. package/dist/src/editor/edit-mode.d.ts +9 -0
  31. package/dist/src/editor/editor.d.ts +37 -45
  32. package/dist/src/editor/funcs.d.ts +2 -0
  33. package/dist/src/editor/mover.d.ts +8 -0
  34. package/dist/src/editor/plug-manager.d.ts +17 -0
  35. package/dist/src/editor/plugs/editor-plug.d.ts +13 -1
  36. package/dist/src/editor/reply.d.ts +9 -0
  37. package/dist/src/editor/submit.d.ts +16 -0
  38. package/dist/src/editor/ui.d.ts +25 -0
  39. package/dist/src/i18n/external.d.ts +70 -0
  40. package/dist/src/i18n/index.d.ts +11 -75
  41. package/dist/src/layer/index.d.ts +2 -2
  42. package/dist/src/layer/layer.d.ts +1 -1
  43. package/dist/src/lib/checker/captcha-renders.d.ts +5 -0
  44. package/dist/src/lib/checker/index.d.ts +16 -10
  45. package/dist/src/lib/dark-mode.d.ts +4 -0
  46. package/dist/src/lib/marked.d.ts +9 -0
  47. package/dist/src/lib/user.d.ts +6 -3
  48. package/dist/src/lib/utils.d.ts +1 -5
  49. package/dist/src/list/list-lite.d.ts +15 -11
  50. package/dist/src/list/list-nest.d.ts +2 -2
  51. package/dist/src/list/list-ui.d.ts +16 -0
  52. package/dist/src/list/list.d.ts +0 -1
  53. package/dist/src/list/paginator/adaptors/index.d.ts +12 -0
  54. package/dist/src/list/paginator/adaptors/pagination.d.ts +9 -0
  55. package/dist/src/list/paginator/adaptors/read-more.d.ts +10 -0
  56. package/dist/src/list/paginator/holder.d.ts +16 -0
  57. package/dist/src/list/paginator/index.d.ts +10 -0
  58. package/dist/src/service.d.ts +46 -0
  59. package/dist/types/artalk-config.d.ts +5 -2
  60. package/dist/types/artalk-data.d.ts +3 -3
  61. package/dist/types/context.d.ts +13 -20
  62. package/dist/types/global.d.ts +1 -0
  63. package/package.json +6 -7
  64. package/src/api/comment.ts +7 -7
  65. package/src/api/page.ts +0 -2
  66. package/src/api/request.ts +6 -4
  67. package/src/api/system.ts +3 -12
  68. package/src/api/upload.ts +3 -4
  69. package/src/api/user.ts +6 -8
  70. package/src/artalk.ts +103 -158
  71. package/src/comment/comment.ts +7 -6
  72. package/src/comment/height-limit.ts +100 -0
  73. package/src/comment/render-ctx.ts +30 -0
  74. package/src/comment/render.ts +31 -375
  75. package/src/comment/renders/actions.ts +114 -0
  76. package/src/comment/renders/avatar.ts +22 -0
  77. package/src/comment/renders/content.ts +39 -0
  78. package/src/comment/renders/header.ts +84 -0
  79. package/src/comment/renders/index.ts +19 -0
  80. package/src/comment/renders/pending.ts +12 -0
  81. package/src/comment/renders/reply-at.ts +16 -0
  82. package/src/comment/renders/reply-to.ts +24 -0
  83. package/src/config.ts +65 -0
  84. package/src/context.ts +33 -86
  85. package/src/defaults.ts +1 -0
  86. package/src/editor/edit-mode.ts +103 -0
  87. package/src/editor/editor.ts +101 -395
  88. package/src/editor/funcs.ts +97 -0
  89. package/src/editor/mover.ts +43 -0
  90. package/src/editor/plug-manager.ts +172 -0
  91. package/src/editor/plugs/editor-plug.ts +20 -2
  92. package/src/editor/plugs/{emoticons-plug.less → emoticons-plug.scss} +5 -4
  93. package/src/editor/plugs/emoticons-plug.ts +2 -1
  94. package/src/editor/plugs/header-input-plug.ts +11 -10
  95. package/src/editor/plugs/{preview-plug.less → preview-plug.scss} +1 -1
  96. package/src/editor/plugs/preview-plug.ts +8 -4
  97. package/src/editor/plugs/upload-plug.ts +12 -14
  98. package/src/editor/reply.ts +62 -0
  99. package/src/editor/submit.ts +99 -0
  100. package/src/editor/ui.ts +36 -0
  101. package/src/i18n/external.ts +10 -0
  102. package/src/i18n/index.ts +22 -19
  103. package/src/i18n/jp.ts +1 -1
  104. package/src/i18n/zh-TW.ts +1 -1
  105. package/src/layer/index.ts +2 -2
  106. package/src/layer/layer.ts +4 -4
  107. package/src/layer/sidebar-layer.ts +4 -2
  108. package/src/lib/checker/admin.ts +37 -0
  109. package/src/lib/checker/captcha-renders.ts +68 -0
  110. package/src/lib/checker/captcha.ts +26 -0
  111. package/src/lib/checker/index.ts +36 -20
  112. package/src/lib/dark-mode.ts +48 -0
  113. package/src/lib/marked.ts +133 -0
  114. package/src/lib/user.ts +13 -7
  115. package/src/lib/utils.ts +1 -123
  116. package/src/list/list-lite.ts +59 -162
  117. package/src/list/list-ui.ts +137 -0
  118. package/src/list/list.ts +33 -60
  119. package/src/list/paginator/adaptors/index.ts +21 -0
  120. package/src/list/paginator/adaptors/pagination.ts +42 -0
  121. package/src/list/paginator/adaptors/read-more.ts +56 -0
  122. package/src/list/paginator/holder.ts +56 -0
  123. package/src/list/paginator/index.ts +13 -0
  124. package/src/service.ts +118 -0
  125. package/src/style/{_color.less → _color.scss} +3 -3
  126. package/src/style/{_extend.less → _extend.scss} +1 -1
  127. package/src/style/_variables.scss +3 -0
  128. package/src/style/{comment.less → comment.scss} +36 -12
  129. package/src/style/{editor.less → editor.scss} +11 -11
  130. package/src/style/{list.less → list.scss} +12 -10
  131. package/src/style/{main.less → main.scss} +24 -23
  132. package/src/style/{sidebar-layer.less → sidebar-layer.scss} +8 -8
  133. package/types/artalk-config.d.ts +5 -2
  134. package/types/artalk-data.d.ts +3 -3
  135. package/types/context.d.ts +13 -20
  136. package/types/global.d.ts +1 -0
  137. package/CNAME +0 -1
  138. package/LICENSE +0 -165
  139. package/index.html +0 -157
  140. package/postcss.config.js +0 -5
  141. package/scripts/build-i18n.mjs +0 -46
  142. package/src/lib/checker/admin-checker.ts +0 -36
  143. package/src/lib/checker/captcha-checker.ts +0 -84
  144. package/src/style/_variables.less +0 -3
  145. package/test/ArtalkCount.html +0 -16
  146. package/test/ArtalkI18n.html +0 -16
  147. package/test/ArtalkLite.html +0 -15
  148. package/test/CSRF.html +0 -26
  149. package/tsconfig.json +0 -12
  150. package/vite-base.config.ts +0 -21
  151. package/vite-demo.config.ts +0 -12
  152. package/vite-lite.config.ts +0 -22
  153. package/vite.config.ts +0 -27
  154. /package/dist/src/lib/checker/{admin-checker.d.ts → admin.d.ts} +0 -0
  155. /package/dist/src/lib/checker/{captcha-checker.d.ts → captcha.d.ts} +0 -0
  156. /package/src/style/{pagination.less → pagination.scss} +0 -0
@@ -0,0 +1,22 @@
1
+ import * as Utils from '../../lib/utils'
2
+ import RenderCtx from '../render-ctx'
3
+
4
+ /**
5
+ * 评论头像界面
6
+ */
7
+ export default function renderAvatar(ctx: RenderCtx) {
8
+ const $avatar = ctx.$el.querySelector<HTMLElement>('.atk-avatar')!
9
+ const $avatarImg = Utils.createElement<HTMLImageElement>('<img />')
10
+
11
+ const avatarURLBuilder = ctx.conf.avatarURLBuilder
12
+ $avatarImg.src = avatarURLBuilder ? avatarURLBuilder(ctx.data) : ctx.comment.getGravatarURL()
13
+
14
+ if (ctx.data.link) {
15
+ const $avatarA = Utils.createElement<HTMLLinkElement>('<a target="_blank" rel="noreferrer noopener nofollow"></a>')
16
+ $avatarA.href = Utils.isValidURL(ctx.data.link) ? ctx.data.link : `https://${ctx.data.link}`
17
+ $avatarA.append($avatarImg)
18
+ $avatar.append($avatarA)
19
+ } else {
20
+ $avatar.append($avatarImg)
21
+ }
22
+ }
@@ -0,0 +1,39 @@
1
+ import * as Utils from '../../lib/utils'
2
+ import * as Ui from '../../lib/ui'
3
+ import RenderCtx from '../render-ctx'
4
+
5
+ /**
6
+ * 评论内容界面
7
+ */
8
+ export default function renderContent(ctx: RenderCtx) {
9
+ if (!ctx.data.is_collapsed) {
10
+ ctx.$content.innerHTML = ctx.comment.getContentMarked()
11
+ ctx.$content.classList.remove('atk-hide', 'atk-collapsed')
12
+ return
13
+ }
14
+
15
+ // 内容 & 折叠
16
+ ctx.$content.classList.add('atk-hide', 'atk-type-collapsed')
17
+ const collapsedInfoEl = Utils.createElement(`
18
+ <div class="atk-collapsed">
19
+ <span class="atk-text">${ctx.ctx.$t('collapsedMsg')}</span>
20
+ <span class="atk-show-btn">${ctx.ctx.$t('expand')}</span>
21
+ </div>`)
22
+ ctx.$body.insertAdjacentElement('beforeend', collapsedInfoEl)
23
+
24
+ const contentShowBtn = collapsedInfoEl.querySelector('.atk-show-btn')!
25
+ contentShowBtn.addEventListener('click', (e) => {
26
+ e.stopPropagation() // 防止穿透
27
+
28
+ if (ctx.$content.classList.contains('atk-hide')) {
29
+ ctx.$content.innerHTML = ctx.comment.getContentMarked()
30
+ ctx.$content.classList.remove('atk-hide')
31
+ Ui.playFadeInAnim(ctx.$content)
32
+ contentShowBtn.innerHTML = ctx.ctx.$t('collapse')
33
+ } else {
34
+ ctx.$content.innerHTML = ''
35
+ ctx.$content.classList.add('atk-hide')
36
+ contentShowBtn.innerHTML = ctx.ctx.$t('expand')
37
+ }
38
+ })
39
+ }
@@ -0,0 +1,84 @@
1
+ import * as Utils from '../../lib/utils'
2
+ import RenderCtx from '../render-ctx'
3
+
4
+ /**
5
+ * 评论头部界面
6
+ */
7
+ export default function renderHeader(ctx: RenderCtx) {
8
+ Object.entries({
9
+ renderNick, renderVerifyBadge, renderDate, renderUABadge
10
+ }).forEach(([name, render]) => {
11
+ render(ctx)
12
+ })
13
+ }
14
+
15
+ function renderNick(ctx: RenderCtx) {
16
+ ctx.$headerNick = ctx.$el.querySelector<HTMLElement>('.atk-nick')!
17
+
18
+ if (ctx.data.link) {
19
+ const $nickA = Utils.createElement<HTMLLinkElement>('<a target="_blank" rel="noreferrer noopener nofollow"></a>')
20
+ $nickA.innerText = ctx.data.nick
21
+ $nickA.href = Utils.isValidURL(ctx.data.link) ? ctx.data.link : `https://${ctx.data.link}`
22
+ ctx.$headerNick.append($nickA)
23
+ } else {
24
+ ctx.$headerNick.innerText = ctx.data.nick
25
+ }
26
+ }
27
+
28
+ function renderVerifyBadge(ctx: RenderCtx) {
29
+ ctx.$headerBadgeWrap = ctx.$el.querySelector<HTMLElement>('.atk-badge-wrap')!
30
+ ctx.$headerBadgeWrap.innerHTML = ''
31
+
32
+ const badgeText = ctx.data.badge_name
33
+ const badgeColor = ctx.data.badge_color
34
+ if (badgeText) {
35
+ const $badge = Utils.createElement(`<span class="atk-badge"></span>`)
36
+ $badge.innerText = badgeText.replace('管理员', ctx.ctx.$t('admin')) // i18n patch
37
+ $badge.style.backgroundColor = badgeColor || ''
38
+ ctx.$headerBadgeWrap.append($badge)
39
+ }
40
+
41
+ if (ctx.data.is_pinned) {
42
+ const $pinnedBadge = Utils.createElement(`<span class="atk-pinned-badge">${ctx.ctx.$t('pin')}</span>`) // 置顶徽章
43
+ ctx.$headerBadgeWrap.append($pinnedBadge)
44
+ }
45
+ }
46
+
47
+ function renderDate(ctx: RenderCtx) {
48
+ const $date = ctx.$el.querySelector<HTMLElement>('.atk-date')!
49
+ $date.innerText = ctx.comment.getDateFormatted()
50
+ $date.setAttribute('data-atk-comment-date', String(+new Date(ctx.data.date)))
51
+ }
52
+
53
+ function renderUABadge(ctx: RenderCtx) {
54
+ if (!ctx.ctx.conf.uaBadge && !ctx.data.ip_region) return
55
+
56
+ let $uaWrap = ctx.$header.querySelector('atk-ua-wrap')
57
+ if (!$uaWrap) {
58
+ $uaWrap = Utils.createElement(`<span class="atk-ua-wrap"></span>`)
59
+ ctx.$header.append($uaWrap)
60
+ }
61
+
62
+ $uaWrap.innerHTML = ''
63
+
64
+ if (ctx.data.ip_region) {
65
+ const $regionBadge = Utils.createElement(`<span class="atk-region-badge"></span>`)
66
+ $regionBadge.innerText = ctx.data.ip_region
67
+ $uaWrap.append($regionBadge)
68
+ }
69
+
70
+ if (ctx.ctx.conf.uaBadge) {
71
+ const { browser, os } = ctx.comment.getUserUA()
72
+ if (String(browser).trim()) {
73
+ const $uaBrowser = Utils.createElement(`<span class="atk-ua ua-browser"></span>`)
74
+ $uaBrowser.innerText = browser
75
+ $uaWrap.append($uaBrowser)
76
+ }
77
+
78
+ if (String(os).trim()) {
79
+ const $usOS = Utils.createElement(`<span class="atk-ua ua-os"></span>`)
80
+ $usOS.innerText = os
81
+ $uaWrap.append($usOS)
82
+ }
83
+ }
84
+ }
@@ -0,0 +1,19 @@
1
+ import RenderCtx from '../render-ctx'
2
+ import Avatar from './avatar'
3
+ import Header from './header'
4
+ import Content from './content'
5
+ import ReplyAt from './reply-at'
6
+ import ReplyTo from './reply-to'
7
+ import Pending from './pending'
8
+ import Actions from './actions'
9
+
10
+ const Renders = {
11
+ Avatar, Header, Content, ReplyAt,
12
+ ReplyTo, Pending, Actions
13
+ }
14
+
15
+ export default function loadRenders(ctx: RenderCtx) {
16
+ Object.entries(Renders).forEach(([ name, render ]) => {
17
+ render(ctx)
18
+ })
19
+ }
@@ -0,0 +1,12 @@
1
+ import * as Utils from '../../lib/utils'
2
+ import RenderCtx from '../render-ctx'
3
+
4
+ /**
5
+ * 待审核状态界面
6
+ */
7
+ export default function renderPending(ctx: RenderCtx) {
8
+ if (!ctx.data.is_pending) return
9
+
10
+ const pendingEl = Utils.createElement(`<div class="atk-pending">${ctx.ctx.$t('pendingMsg')}</div>`)
11
+ ctx.$body.prepend(pendingEl)
12
+ }
@@ -0,0 +1,16 @@
1
+ import * as Utils from '../../lib/utils'
2
+ import RenderCtx from '../render-ctx'
3
+
4
+ /**
5
+ * 层级嵌套模式显示 AT 界面
6
+ */
7
+ export default function renderReplyAt(ctx: RenderCtx) {
8
+ if (ctx.cConf.isFlatMode || ctx.data.rid === 0) return // not 平铺模式 或 根评论
9
+ if (!ctx.cConf.replyTo) return
10
+
11
+ ctx.$replyAt = Utils.createElement(`<span class="atk-item atk-reply-at"><span class="atk-arrow"></span><span class="atk-nick"></span></span>`)
12
+ ctx.$replyAt.querySelector<HTMLElement>('.atk-nick')!.innerText = `${ctx.cConf.replyTo.nick}`
13
+ ctx.$replyAt.onclick = () => { ctx.comment.getActions().goToReplyComment() }
14
+
15
+ ctx.$headerBadgeWrap.insertAdjacentElement('afterend', ctx.$replyAt)
16
+ }
@@ -0,0 +1,24 @@
1
+ import * as Utils from '../../lib/utils'
2
+ import marked from '../../lib/marked'
3
+ import RenderCtx from '../render-ctx'
4
+
5
+ /**
6
+ * 回复的对象界面
7
+ */
8
+ export default function renderReplyTo(ctx: RenderCtx) {
9
+ if (!ctx.cConf.isFlatMode) return // 仅平铺模式显示
10
+ if (!ctx.cConf.replyTo) return
11
+
12
+ ctx.$replyTo = Utils.createElement(`
13
+ <div class="atk-reply-to">
14
+ <div class="atk-meta">${ctx.ctx.$t('reply')} <span class="atk-nick"></span>:</div>
15
+ <div class="atk-content"></div>
16
+ </div>`)
17
+ const $nick = ctx.$replyTo.querySelector<HTMLElement>('.atk-nick')!
18
+ $nick.innerText = `@${ctx.cConf.replyTo.nick}`
19
+ $nick.onclick = () => { ctx.comment.getActions().goToReplyComment() }
20
+ let replyContent = marked(ctx.ctx, ctx.cConf.replyTo.content)
21
+ if (ctx.cConf.replyTo.is_collapsed) replyContent = `[${ctx.ctx.$t('collapsed')}]`
22
+ ctx.$replyTo.querySelector<HTMLElement>('.atk-content')!.innerHTML = replyContent
23
+ ctx.$body.prepend(ctx.$replyTo)
24
+ }
package/src/config.ts ADDED
@@ -0,0 +1,65 @@
1
+ import type ArtalkConfig from '~/types/artalk-config'
2
+ import * as Utils from './lib/utils'
3
+ import Defaults from './defaults'
4
+
5
+ export default { handelBaseConf }
6
+
7
+ /** 基本配置初始化 */
8
+ export function handelBaseConf(customConf: Partial<ArtalkConfig>): ArtalkConfig {
9
+ // 合并默认配置
10
+ const conf: ArtalkConfig = Utils.mergeDeep(Defaults, customConf)
11
+
12
+ // 绑定元素
13
+ if (typeof conf.el === 'string' && !!conf.el) {
14
+ try {
15
+ const findEl = document.querySelector<HTMLElement>(conf.el)
16
+ if (!findEl) throw Error(`Target element "${conf.el}" was not found.`)
17
+ conf.el = findEl
18
+ } catch (e) {
19
+ console.error(e)
20
+ throw new Error('Please check your Artalk `el` config.')
21
+ }
22
+ }
23
+
24
+ // 服务器配置
25
+ conf.server = conf.server.replace(/\/$/, '').replace(/\/api\/?$/, '')
26
+
27
+ // 默认 pageKey
28
+ if (!conf.pageKey) {
29
+ // @link http://bl.ocks.org/abernier/3070589
30
+ conf.pageKey = `${window.location.pathname}`
31
+ }
32
+
33
+ // 默认 pageTitle
34
+ if (!conf.pageTitle) {
35
+ conf.pageTitle = `${document.title}`
36
+ }
37
+
38
+ // 自适应语言
39
+ if (conf.locale === 'auto') {
40
+ conf.locale = navigator.language
41
+ }
42
+
43
+ return conf
44
+ }
45
+
46
+ export function handleBackendRefConf(conf: Partial<ArtalkConfig>) {
47
+ const DisabledKeys: (keyof ArtalkConfig)[] = [
48
+ 'el', 'pageKey', 'pageTitle', 'server', 'site', 'darkMode'
49
+ ]
50
+ Object.keys(conf).forEach(k => {
51
+ if (DisabledKeys.includes(k as any)) delete conf[k]
52
+ })
53
+
54
+ // Patch: `emoticons` config string to json
55
+ if (conf.emoticons && typeof conf.emoticons === "string") {
56
+ conf.emoticons = conf.emoticons.trim()
57
+ if (conf.emoticons.startsWith("[") || conf.emoticons.startsWith("{")) {
58
+ conf.emoticons = JSON.parse(conf.emoticons) // pase json
59
+ } else if (conf.emoticons === "false") {
60
+ conf.emoticons = false
61
+ }
62
+ }
63
+
64
+ return conf
65
+ }
package/src/context.ts CHANGED
@@ -1,37 +1,31 @@
1
- import { marked as libMarked } from 'marked'
2
- import ArtalkConfig from '~/types/artalk-config'
3
- import { CommentData, NotifyData } from '~/types/artalk-data'
4
- import { Event } from '~/types/event'
5
- import getI18n, { I18n } from './i18n'
6
- import User from './lib/user'
1
+ import type ArtalkConfig from '~/types/artalk-config'
2
+ import type { CommentData, NotifyData } from '~/types/artalk-data'
3
+ import type { Event } from '~/types/event'
4
+ import type ContextApi from '~/types/context'
5
+ import type { TInjectedServices } from './service'
6
+
7
7
  import * as Utils from './lib/utils'
8
- import ContextApi from '../types/context'
9
- import Editor from './editor'
8
+ import * as DarkMode from './lib/dark-mode'
9
+ import * as marked from './lib/marked'
10
+ import { CheckerCaptchaPayload, CheckerPayload } from './lib/checker'
11
+
12
+ import * as I18n from './i18n'
13
+ import { getLayerWrap } from './layer'
14
+ import { SidebarShowPayload } from './layer/sidebar-layer'
10
15
  import Comment from './comment'
11
- import ListLite from './list/list-lite'
12
- import SidebarLayer, { SidebarShowPayload } from './layer/sidebar-layer'
13
- import CheckerLauncher, { CheckerCaptchaPayload, CheckerPayload } from './lib/checker'
14
- import Layer, { GetLayerWrap } from './layer'
15
16
  import Api from './api'
16
17
  import List from './list'
17
18
 
19
+ // Auto dependency injection
20
+ interface Context extends TInjectedServices { }
21
+
18
22
  /**
19
23
  * Artalk Context
20
24
  */
21
- export default class Context implements ContextApi {
22
- /* 持有同事类 */
23
- private api!: Api
24
- private editor!: Editor
25
- private list?: ListLite
26
- private sidebarLayer!: SidebarLayer
27
- private checkerLauncher!: CheckerLauncher
28
-
25
+ class Context implements ContextApi {
29
26
  /* 运行参数 */
30
- public cid: number // Context 唯一标识
31
27
  public conf: ArtalkConfig
32
- public user: User
33
28
  public $root: HTMLElement
34
- public markedInstance?: typeof libMarked
35
29
  public markedReplacers: ((raw: string) => string)[] = []
36
30
 
37
31
  private commentList: Comment[] = [] // Note: 无层级结构 + 无须排列
@@ -40,41 +34,19 @@ export default class Context implements ContextApi {
40
34
  private eventList: Event[] = []
41
35
 
42
36
  public constructor(conf: ArtalkConfig, $root?: HTMLElement) {
43
- this.cid = +new Date()
44
37
  this.conf = conf
45
- this.user = new User(this)
46
38
 
47
39
  this.$root = $root || document.createElement('div')
48
- this.$root.setAttribute('atk-run-id', this.cid.toString())
49
40
  this.$root.classList.add('artalk')
50
41
  this.$root.innerHTML = ''
51
-
52
- this.api = new Api(this)
53
-
54
- this.on('conf-loaded', () => {
55
- this.refreshDarkModeConf()
56
- })
57
- }
58
-
59
- /* 设置持有的同事类 */
60
- public setApi(api: Api): void {
61
- this.api = api
62
- }
63
-
64
- public setEditor(editor: Editor): void {
65
- this.editor = editor
66
42
  }
67
43
 
68
- public setList(list: ListLite): void {
69
- this.list = list
44
+ public inject(depName: string, obj: any) {
45
+ this[depName] = obj
70
46
  }
71
47
 
72
- public setSidebarLayer(sidebarLayer: SidebarLayer): void {
73
- this.sidebarLayer = sidebarLayer
74
- }
75
-
76
- public setCheckerLauncher(checkerLauncher: CheckerLauncher): void {
77
- this.checkerLauncher = checkerLauncher
48
+ public get(depName: string) {
49
+ return this[depName]
78
50
  }
79
51
 
80
52
  public getApi() {
@@ -164,7 +136,7 @@ export default class Context implements ContextApi {
164
136
  }
165
137
 
166
138
  public listHashGotoCheck(): void {
167
- if (!this.list || this.list !instanceof List) return
139
+ if (!this.list || !(this.list instanceof List)) return
168
140
  const list = this.list as List
169
141
 
170
142
  list.goToCommentDelay = false
@@ -224,7 +196,7 @@ export default class Context implements ContextApi {
224
196
  this.$root.querySelectorAll<HTMLElement>(`[atk-only-admin-show]`).forEach(item => items.push(item))
225
197
 
226
198
  // for layer
227
- const { $wrap: $layerWrap } = GetLayerWrap(this)
199
+ const { $wrap: $layerWrap } = getLayerWrap()
228
200
  if ($layerWrap) $layerWrap.querySelectorAll<HTMLElement>(`[atk-only-admin-show]`).forEach(item => items.push(item))
229
201
 
230
202
  // for sidebar
@@ -258,47 +230,22 @@ export default class Context implements ContextApi {
258
230
  }
259
231
 
260
232
  /* i18n */
261
- public $t(key: keyof I18n, args: {[key: string]: string} = {}): string {
262
- return getI18n(this.conf.locale, key, args)
233
+ public $t(key: I18n.I18nKeys, args: {[key: string]: string} = {}): string {
234
+ return I18n.t(key, args)
263
235
  }
264
236
 
265
237
  public setDarkMode(darkMode: boolean): void {
266
- const darkModeClassName = 'atk-dark-mode'
267
-
268
- this.conf.darkMode = darkMode
269
-
270
- if (darkMode) this.$root.classList.add(darkModeClassName)
271
- else this.$root.classList.remove(darkModeClassName)
272
-
273
- // for Layer
274
- const { $wrap: $layerWrap } = GetLayerWrap(this)
275
- if ($layerWrap) {
276
- if (darkMode) $layerWrap.classList.add(darkModeClassName)
277
- else $layerWrap.classList.remove(darkModeClassName)
278
- }
238
+ DarkMode.setDarkMode(this, darkMode)
279
239
  }
280
240
 
281
- private darkModeMedia = window.matchMedia('(prefers-color-scheme: dark)')
282
- private darkModeAutoFunc?: (evt: MediaQueryListEvent) => void
283
- private refreshDarkModeConf(): void {
284
- if (this.conf.darkMode === 'auto') {
285
- // 自动切换暗黑模式,事件监听
286
- this.setDarkMode(this.darkModeMedia.matches)
287
- if (!this.darkModeAutoFunc) {
288
- this.darkModeAutoFunc = (evt) => { this.setDarkMode(evt.matches) }
289
- this.darkModeMedia.addEventListener('change', this.darkModeAutoFunc)
290
- }
291
- } else {
292
- if (this.darkModeAutoFunc) {
293
- // 解除事件监听绑定
294
- this.darkModeMedia.removeEventListener('change', this.darkModeAutoFunc)
295
- }
296
- this.setDarkMode(this.conf.darkMode || false)
297
- }
241
+ public updateConf(nConf: Partial<ArtalkConfig>): void {
242
+ this.conf = Utils.mergeDeep(this.conf, nConf)
243
+ this.trigger('conf-loaded')
298
244
  }
299
245
 
300
- public updateConf(conf: Partial<ArtalkConfig>): void {
301
- this.conf = Utils.mergeDeep(this.conf, conf)
302
- this.trigger('conf-loaded')
246
+ public getMarkedInstance() {
247
+ return marked.getInstance()
303
248
  }
304
249
  }
250
+
251
+ export default Context
package/src/defaults.ts CHANGED
@@ -41,6 +41,7 @@ const defaults: ArtalkConfig = {
41
41
  heightLimit: {
42
42
  content: 300,
43
43
  children: 400,
44
+ scrollable: false,
44
45
  },
45
46
 
46
47
  imgUpload: true,
@@ -0,0 +1,103 @@
1
+ import type { CommentData } from '~/types/artalk-data'
2
+ import * as Utils from '../lib/utils'
3
+ import Editor from './editor'
4
+ import User from '../lib/user'
5
+
6
+ export interface EditModeManager {
7
+ editor: Editor
8
+ comment?: CommentData
9
+ setEdit: (comment: CommentData, $comment: HTMLElement) => void
10
+ cancelEdit: () => void
11
+ }
12
+
13
+ export function createEditModeManager(editor: Editor) {
14
+ const m: EditModeManager = {
15
+ editor,
16
+ comment: undefined,
17
+ setEdit: (c, e) => edit(m, c, e),
18
+ cancelEdit: () => cancelEdit(m)
19
+ }
20
+
21
+ initEditModeSubmit(m)
22
+
23
+ return m
24
+ }
25
+
26
+ function edit(m: EditModeManager, comment: CommentData, $comment: HTMLElement) {
27
+ cancelEdit(m)
28
+ m.editor.cancelReply()
29
+
30
+ const ui = m.editor.getUI()
31
+ if (!ui.$editCancelBtn) {
32
+ const $btn = Utils.createElement(
33
+ `<div class="atk-send-reply">` +
34
+ `${m.editor.$t('editCancel')} ` +
35
+ `<span class="atk-cancel">×</span>` +
36
+ `</div>`
37
+ )
38
+ $btn.onclick = () => {
39
+ cancelEdit(m)
40
+ }
41
+ ui.$textareaWrap.append($btn)
42
+ ui.$editCancelBtn = $btn
43
+ }
44
+ m.comment = comment
45
+
46
+ ui.$header.style.display = 'none' // TODO support modify header information
47
+
48
+ m.editor.travel($comment)
49
+
50
+ ui.$nick.value = comment.nick || ''
51
+ ui.$email.value = comment.email || ''
52
+ ui.$link.value = comment.link || ''
53
+
54
+ m.editor.setContent(comment.content)
55
+ ui.$textarea.focus()
56
+
57
+ m.editor.refreshSendBtnText()
58
+ }
59
+
60
+ function cancelEdit(m: EditModeManager) {
61
+ if (!m.comment) return
62
+
63
+ const ui = m.editor.getUI()
64
+
65
+ if (ui.$editCancelBtn) {
66
+ ui.$editCancelBtn.remove()
67
+ ui.$editCancelBtn = undefined
68
+ }
69
+
70
+ m.comment = undefined
71
+ m.editor.travelBack()
72
+
73
+ const { nick, email, link } = User.data
74
+ ui.$nick.value = nick
75
+ ui.$email.value = email
76
+ ui.$link.value = link
77
+
78
+ m.editor.setContent('')
79
+ m.editor.refreshSendBtnText()
80
+
81
+ ui.$header.style.display = '' // TODO support modify header information
82
+ }
83
+
84
+ function initEditModeSubmit(m: EditModeManager) {
85
+ m.editor.getSubmitManager()!.registerCustom({
86
+ activeCond: () => !!m.comment, // active this custom submit when edit mode
87
+ req: async () => {
88
+ const saveData = {
89
+ content: m.editor.getFinalContent(),
90
+ nick: m.editor.getUI().$nick.value,
91
+ email: m.editor.getUI().$email.value,
92
+ link: m.editor.getUI().$link.value,
93
+ }
94
+ const nComment = await m.editor.ctx.getApi().comment.commentEdit({
95
+ ...m.comment, ...saveData
96
+ })
97
+ return nComment
98
+ },
99
+ post: (nComment: CommentData) => {
100
+ m.editor.ctx.updateComment(nComment)
101
+ }
102
+ })
103
+ }