artalk 2.4.4 → 2.5.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 (145) hide show
  1. package/README.md +27 -0
  2. package/dist/Artalk.css +1 -1
  3. package/dist/Artalk.es.js +4872 -4072
  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 +2468 -1779
  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 +15 -29
  30. package/dist/src/editor/edit-mode.d.ts +9 -0
  31. package/dist/src/editor/editor-ui.d.ts +25 -0
  32. package/dist/src/editor/editor.d.ts +37 -45
  33. package/dist/src/editor/funcs.d.ts +2 -0
  34. package/dist/src/editor/mover.d.ts +8 -0
  35. package/dist/src/editor/plug-manager.d.ts +17 -0
  36. package/dist/src/editor/plugs/editor-plug.d.ts +13 -1
  37. package/dist/src/editor/reply.d.ts +9 -0
  38. package/dist/src/editor/submit.d.ts +16 -0
  39. package/dist/src/editor/ui.d.ts +25 -0
  40. package/dist/src/i18n/index.d.ts +11 -7
  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 +18 -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/utils.d.ts +1 -5
  48. package/dist/src/list/list-lite.d.ts +15 -11
  49. package/dist/src/list/list-nest.d.ts +2 -2
  50. package/dist/src/list/list-ui.d.ts +7 -0
  51. package/dist/src/list/paginator/adaptors/index.d.ts +12 -0
  52. package/dist/src/list/paginator/adaptors/pagination.d.ts +9 -0
  53. package/dist/src/list/paginator/adaptors/read-more.d.ts +10 -0
  54. package/dist/src/list/paginator/holder.d.ts +16 -0
  55. package/dist/src/list/paginator/index.d.ts +10 -0
  56. package/dist/src/service.d.ts +40 -0
  57. package/dist/types/artalk-config.d.ts +5 -2
  58. package/dist/types/artalk-data.d.ts +3 -3
  59. package/dist/types/context.d.ts +11 -17
  60. package/dist/types/global.d.ts +1 -0
  61. package/package.json +6 -7
  62. package/src/api/system.ts +3 -12
  63. package/src/artalk.ts +101 -158
  64. package/src/comment/comment.ts +7 -6
  65. package/src/comment/height-limit.ts +100 -0
  66. package/src/comment/render-ctx.ts +30 -0
  67. package/src/comment/render.ts +31 -375
  68. package/src/comment/renders/actions.ts +114 -0
  69. package/src/comment/renders/avatar.ts +22 -0
  70. package/src/comment/renders/content.ts +39 -0
  71. package/src/comment/renders/header.ts +84 -0
  72. package/src/comment/renders/index.ts +19 -0
  73. package/src/comment/renders/pending.ts +12 -0
  74. package/src/comment/renders/reply-at.ts +16 -0
  75. package/src/comment/renders/reply-to.ts +24 -0
  76. package/src/config.ts +65 -0
  77. package/src/context.ts +31 -86
  78. package/src/defaults.ts +1 -0
  79. package/src/editor/edit-mode.ts +102 -0
  80. package/src/editor/editor.ts +100 -395
  81. package/src/editor/funcs.ts +96 -0
  82. package/src/editor/mover.ts +43 -0
  83. package/src/editor/plug-manager.ts +172 -0
  84. package/src/editor/plugs/editor-plug.ts +20 -2
  85. package/src/editor/plugs/{emoticons-plug.less → emoticons-plug.scss} +5 -4
  86. package/src/editor/plugs/emoticons-plug.ts +2 -1
  87. package/src/editor/plugs/header-input-plug.ts +5 -5
  88. package/src/editor/plugs/{preview-plug.less → preview-plug.scss} +1 -1
  89. package/src/editor/plugs/preview-plug.ts +8 -4
  90. package/src/editor/plugs/upload-plug.ts +10 -13
  91. package/src/editor/reply.ts +62 -0
  92. package/src/editor/submit.ts +98 -0
  93. package/src/editor/ui.ts +36 -0
  94. package/src/i18n/index.ts +21 -10
  95. package/src/layer/index.ts +2 -2
  96. package/src/layer/layer.ts +4 -4
  97. package/src/layer/sidebar-layer.ts +2 -1
  98. package/src/lib/checker/admin.ts +36 -0
  99. package/src/lib/checker/captcha-renders.ts +68 -0
  100. package/src/lib/checker/captcha.ts +26 -0
  101. package/src/lib/checker/index.ts +38 -20
  102. package/src/lib/dark-mode.ts +48 -0
  103. package/src/lib/marked.ts +133 -0
  104. package/src/lib/utils.ts +1 -123
  105. package/src/list/list-lite.ts +59 -162
  106. package/src/list/list-ui.ts +80 -0
  107. package/src/list/list.ts +16 -8
  108. package/src/list/paginator/adaptors/index.ts +21 -0
  109. package/src/list/paginator/adaptors/pagination.ts +42 -0
  110. package/src/list/paginator/adaptors/read-more.ts +56 -0
  111. package/src/list/paginator/holder.ts +55 -0
  112. package/src/list/paginator/index.ts +13 -0
  113. package/src/service.ts +118 -0
  114. package/src/style/{_color.less → _color.scss} +3 -3
  115. package/src/style/{_extend.less → _extend.scss} +1 -1
  116. package/src/style/_variables.scss +3 -0
  117. package/src/style/{comment.less → comment.scss} +36 -12
  118. package/src/style/{editor.less → editor.scss} +11 -11
  119. package/src/style/{list.less → list.scss} +8 -6
  120. package/src/style/{main.less → main.scss} +24 -23
  121. package/src/style/{sidebar-layer.less → sidebar-layer.scss} +8 -8
  122. package/types/artalk-config.d.ts +5 -2
  123. package/types/artalk-data.d.ts +3 -3
  124. package/types/context.d.ts +11 -17
  125. package/types/global.d.ts +1 -0
  126. package/CNAME +0 -1
  127. package/LICENSE +0 -165
  128. package/index.html +0 -157
  129. package/postcss.config.js +0 -5
  130. package/scripts/build-i18n.mjs +0 -46
  131. package/src/lib/checker/admin-checker.ts +0 -36
  132. package/src/lib/checker/captcha-checker.ts +0 -84
  133. package/src/style/_variables.less +0 -3
  134. package/test/ArtalkCount.html +0 -16
  135. package/test/ArtalkI18n.html +0 -16
  136. package/test/ArtalkLite.html +0 -15
  137. package/test/CSRF.html +0 -26
  138. package/tsconfig.json +0 -12
  139. package/vite-base.config.ts +0 -21
  140. package/vite-demo.config.ts +0 -12
  141. package/vite-lite.config.ts +0 -22
  142. package/vite.config.ts +0 -27
  143. /package/dist/src/lib/checker/{admin-checker.d.ts → admin.d.ts} +0 -0
  144. /package/dist/src/lib/checker/{captcha-checker.d.ts → captcha.d.ts} +0 -0
  145. /package/src/style/{pagination.less → pagination.scss} +0 -0
@@ -0,0 +1,98 @@
1
+ import type { CommentData } from '~/types/artalk-data'
2
+ import Editor from './editor'
3
+
4
+ export interface SubmitManager {
5
+ editor: Editor
6
+ do: () => Promise<void>
7
+ customs: CustomSubmit[]
8
+ registerCustom(c: CustomSubmit): void
9
+ }
10
+
11
+ export function createSubmitManager(editor: Editor) {
12
+ const m: SubmitManager = {
13
+ editor,
14
+ do: () => Do(m),
15
+ customs: [],
16
+ registerCustom: (c) => { m.customs.push(c) }
17
+ }
18
+
19
+ return m
20
+ }
21
+
22
+ async function Do(m: SubmitManager) {
23
+ if (m.editor.getFinalContent().trim() === '') {
24
+ m.editor.focus()
25
+ return
26
+ }
27
+
28
+ const custom = m.customs.find(o => o.activeCond())
29
+
30
+ m.editor.ctx.trigger('editor-submit')
31
+ m.editor.showLoading()
32
+
33
+ try {
34
+ // pre submit
35
+ if (custom?.pre) custom.pre()
36
+
37
+ let nComment: CommentData
38
+
39
+ // submit request
40
+ if (custom?.req) nComment = await custom.req()
41
+ else nComment = await reqAdd(m)
42
+
43
+ // post submit
44
+ if (custom?.post) custom.post(nComment)
45
+ else postSubmitAdd(m, nComment)
46
+ } catch (err: any) {
47
+ // submit error
48
+ console.error(err)
49
+ m.editor.showNotify(`${m.editor.$t('commentFail')},${err.msg || String(err)}`, 'e')
50
+ }
51
+
52
+ // 复原编辑器
53
+ m.editor.reset()
54
+
55
+ m.editor.ctx.trigger('editor-submitted')
56
+ m.editor.hideLoading()
57
+ }
58
+
59
+ interface CustomSubmit {
60
+ activeCond: () => void
61
+ pre?: () => void
62
+ req?: () => Promise<CommentData>
63
+ post?: (nComment: CommentData) => void
64
+ }
65
+
66
+ // ================== Submit Add =======================
67
+ function getSubmitAddParams(m: SubmitManager) {
68
+ const { nick, email, link } = m.editor.ctx.user.data
69
+ const conf = m.editor.ctx.conf
70
+ const reply = m.editor.getReplyManager()?.comment
71
+
72
+ return {
73
+ content: m.editor.getFinalContent(),
74
+ nick, email, link,
75
+ rid: (!reply) ? 0 : reply.id,
76
+ page_key: (!reply) ? conf.pageKey : reply.page_key,
77
+ page_title: (!reply) ? conf.pageTitle : undefined,
78
+ site_name: (!reply) ? conf.site : reply.site_name
79
+ }
80
+ }
81
+
82
+ async function reqAdd(m: SubmitManager) {
83
+ const nComment = await m.editor.ctx.getApi().comment.add({
84
+ ...getSubmitAddParams(m)
85
+ })
86
+ return nComment
87
+ }
88
+
89
+ function postSubmitAdd(m: SubmitManager, commentNew: CommentData) {
90
+ // 回复不同页面的评论,跳转到新页面
91
+ const reply = m.editor.getReplyManager()
92
+ const conf = m.editor.ctx.conf
93
+ if (!!reply?.comment && reply.comment.page_key !== conf.pageKey) {
94
+ window.open(`${reply.comment.page_url}#atk-comment-${commentNew.id}`)
95
+ }
96
+
97
+ m.editor.ctx.insertComment(commentNew)
98
+ }
@@ -0,0 +1,36 @@
1
+ import * as Utils from '../lib/utils'
2
+ import EditorHTML from './editor.html?raw'
3
+
4
+ const Sel = {
5
+ $header: '.atk-header',
6
+ $nick: '.atk-header [name="nick"]',
7
+ $email: '.atk-header [name="email"]',
8
+ $link: '.atk-header [name="link"]',
9
+ $textareaWrap: '.atk-textarea-wrap',
10
+ $textarea: '.atk-textarea',
11
+ $bottom: '.atk-bottom',
12
+ $submitBtn: '.atk-send-btn',
13
+ $notifyWrap: '.atk-notify-wrap',
14
+ $plugBtnWrap: '.atk-plug-btn-wrap',
15
+ $plugPanelWrap: '.atk-plug-panel-wrap',
16
+ }
17
+
18
+ export interface EditorUI extends Record<keyof typeof Sel, HTMLElement> {
19
+ $el: HTMLElement
20
+ $nick: HTMLInputElement
21
+ $email: HTMLInputElement
22
+ $link: HTMLInputElement
23
+ $textarea: HTMLTextAreaElement
24
+ $submitBtn: HTMLButtonElement
25
+ $sendReply?: HTMLElement
26
+ $editCancelBtn?: HTMLElement
27
+ }
28
+
29
+ export function render() {
30
+ const $el = Utils.createElement(EditorHTML)
31
+ const ui = { $el }
32
+ Object.entries(Sel).forEach(([k, sel]) => {
33
+ ui[k] = $el.querySelector(sel)
34
+ })
35
+ return ui as EditorUI
36
+ }
package/src/i18n/index.ts CHANGED
@@ -2,6 +2,7 @@ import en from './en'
2
2
  import zhCN from './zh-CN'
3
3
 
4
4
  export type I18n = typeof en
5
+ export type I18nKeys = keyof I18n
5
6
 
6
7
  // @note the key of language is followed by `ISO 639`
7
8
  // https://en.wikipedia.org/wiki/ISO_639
@@ -22,9 +23,9 @@ export function defineLocaleExternal(lang: string, locale: I18n, aliases?: strin
22
23
  }
23
24
 
24
25
  /**
25
- * get a locale object by language name
26
+ * find a locale object by language name
26
27
  */
27
- function getLocaleSet(lang: string): I18n {
28
+ export function findLocaleSet(lang: string): I18n {
28
29
  // normalize a key of language to `ISO 639`
29
30
  lang = lang.replace(
30
31
  /^([a-zA-Z]+)(-[a-zA-Z]+)?$/,
@@ -47,18 +48,28 @@ function getLocaleSet(lang: string): I18n {
47
48
  }
48
49
 
49
50
  /**
50
- * get a i18n message by key
51
+ * System locale setting
51
52
  */
52
- function getI18n(locale: I18n|string, key: keyof I18n, args: {[key: string]: string} = {}) {
53
- if (typeof locale === 'string') {
54
- locale = getLocaleSet(locale)
55
- }
53
+ let LocaleConf: I18n|string = 'en'
54
+ let LocaleDict: I18n = findLocaleSet(LocaleConf) // en by default
56
55
 
57
- let str = locale?.[key] || key
56
+ /**
57
+ * Set system locale
58
+ */
59
+ export function setLocale(locale: I18n|string) {
60
+ if (locale === LocaleConf) return
61
+ LocaleConf = locale
62
+ LocaleDict = (typeof locale === 'string') ? findLocaleSet(locale) : locale
63
+ }
64
+
65
+ /**
66
+ * Get an i18n message by key
67
+ */
68
+ export function t(key: I18nKeys, args: {[key: string]: string} = {}) {
69
+ let str = LocaleDict?.[key] || key
58
70
  str = str.replace(/\{\s*(\w+?)\s*\}/g, (_, token) => args[token] || '')
59
71
 
60
72
  return str
61
73
  }
62
74
 
63
- export { getLocaleSet }
64
- export default getI18n
75
+ export default t
@@ -1,4 +1,4 @@
1
- import Layer, { GetLayerWrap } from './layer'
1
+ import Layer, { getLayerWrap } from './layer'
2
2
 
3
3
  export default Layer
4
- export { GetLayerWrap }
4
+ export { getLayerWrap }
@@ -19,7 +19,7 @@ export default class Layer extends Component {
19
19
  super(ctx)
20
20
 
21
21
  this.name = name
22
- const { $wrap, $mask } = GetLayerWrap(ctx)
22
+ const { $wrap, $mask } = getLayerWrap()
23
23
  this.$wrap = $wrap
24
24
  this.$mask = $mask
25
25
 
@@ -148,11 +148,11 @@ export default class Layer extends Component {
148
148
  }
149
149
  }
150
150
 
151
- export function GetLayerWrap(ctx: Context): { $wrap: HTMLElement, $mask: HTMLElement } {
152
- let $wrap = document.querySelector<HTMLElement>(`.atk-layer-wrap#ctx-${ctx.cid}`)
151
+ export function getLayerWrap(): { $wrap: HTMLElement, $mask: HTMLElement } {
152
+ let $wrap = document.querySelector<HTMLElement>(`.atk-layer-wrap`)
153
153
  if (!$wrap) {
154
154
  $wrap = Utils.createElement(
155
- `<div class="atk-layer-wrap" id="ctx-${ctx.cid}" style="display: none;"><div class="atk-layer-mask"></div></div>`
155
+ `<div class="atk-layer-wrap" style="display: none;"><div class="atk-layer-mask"></div></div>`
156
156
  )
157
157
  document.body.appendChild($wrap)
158
158
  }
@@ -89,6 +89,7 @@ export default class SidebarLayer extends Component {
89
89
 
90
90
  if (conf.view) query.view = conf.view
91
91
  if (this.conf.darkMode) query.darkMode = '1'
92
+ if (typeof this.conf.locale === 'string') query.locale = this.conf.locale
92
93
 
93
94
  const urlParams = new URLSearchParams(query);
94
95
  this.iframeLoad(`${baseURL}?${urlParams.toString()}`)
@@ -96,7 +97,7 @@ export default class SidebarLayer extends Component {
96
97
  this.$iframeWrap.append(this.$iframe)
97
98
  this.firstShow = false
98
99
  } else {
99
- // 暗黑模式
100
+ // 夜间模式
100
101
  const isIframeSrcDarkMode = this.$iframe!.src.includes('darkMode=1')
101
102
 
102
103
  if (this.conf.darkMode && !isIframeSrcDarkMode)
@@ -0,0 +1,36 @@
1
+ import * as Utils from '../utils'
2
+ import { Checker } from '.'
3
+
4
+ const AdminChecker: Checker = {
5
+ inputType: 'password',
6
+
7
+ request(checker, inputVal) {
8
+ const data = {
9
+ name: checker.getUser().data.nick,
10
+ email: checker.getUser().data.email,
11
+ password: inputVal
12
+ }
13
+
14
+ return (async () => {
15
+ const resp = await checker.getApi().user.login(data.name, data.email, data.password)
16
+ return resp.token
17
+ })()
18
+ },
19
+
20
+ body(checker) {
21
+ return Utils.createElement(`<span>${checker.getCtx().$t('adminCheck')}</span>`)
22
+ },
23
+
24
+ onSuccess(checker, userToken, inputVal, formEl) {
25
+ checker.getUser().update({
26
+ isAdmin: true,
27
+ token: userToken
28
+ })
29
+ checker.getCtx().trigger('user-changed', checker.getUser().data)
30
+ checker.getCtx().listReload()
31
+ },
32
+
33
+ onError(checker, err, inputVal, formEl) {}
34
+ }
35
+
36
+ export default AdminChecker
@@ -0,0 +1,68 @@
1
+ import * as Utils from '../utils'
2
+ import * as Ui from '../ui'
3
+ import { CheckerCtx } from '.'
4
+
5
+ /** 图片验证码 */
6
+ export function imgBody(checker: CheckerCtx) {
7
+ // 图片验证方式
8
+ const elem = Utils.createElement(
9
+ `<span><img class="atk-captcha-img" src="${checker.get('img_data') || ''}">${checker.getCtx().$t('captchaCheck')}</span>`
10
+ );
11
+
12
+ // 刷新验证码
13
+ elem.querySelector<HTMLElement>('.atk-captcha-img')!.onclick = () => {
14
+ const imgEl = elem.querySelector('.atk-captcha-img')
15
+ checker.getApi().captcha.captchaGet()
16
+ .then((imgData) => {
17
+ imgEl!.setAttribute('src', imgData)
18
+ })
19
+ .catch((err) => {
20
+ console.error('Failed to get captcha image ', err)
21
+ })
22
+ }
23
+ return elem
24
+ }
25
+
26
+ /** iframe 形式的通用验证服务 */
27
+ export function iframeBody(checker: CheckerCtx) {
28
+ const $iframeWrap = Utils.createElement(`<div class="atk-checker-iframe-wrap"></div>`)
29
+ const $iframe = Utils.createElement<HTMLIFrameElement>(`<iframe class="atk-fade-in"></iframe>`)
30
+ $iframe.style.display = 'none'
31
+ Ui.showLoading($iframeWrap, { transparentBg: true })
32
+ $iframe.src = `${checker.getCtx().conf.server}/api/captcha/get?t=${+new Date()}`
33
+ $iframe.onload = () => {
34
+ $iframe.style.display = ''
35
+ Ui.hideLoading($iframeWrap)
36
+ }
37
+ $iframeWrap.append($iframe)
38
+
39
+ const $closeBtn = Utils.createElement(`<div class="atk-close-btn"><i class="atk-icon atk-icon-close"></i></div>`)
40
+ $iframeWrap.append($closeBtn)
41
+
42
+ checker.hideInteractInput()
43
+
44
+ // 轮询状态
45
+ let stop = false // 打断
46
+ const sleep = (ms: number) => new Promise((resolve) => { window.setTimeout(() => { resolve(null) }, ms) })
47
+ ;(async function queryStatus() {
48
+ await sleep(1000)
49
+ if (stop) return
50
+ let isPass = false
51
+ try {
52
+ const resp = await checker.getCtx().getApi().captcha.captchaStatus()
53
+ isPass = resp.is_pass
54
+ } catch { isPass = false }
55
+ if (isPass) {
56
+ checker.triggerSuccess()
57
+ } else {
58
+ queryStatus()
59
+ }
60
+ })()
61
+
62
+ $closeBtn.onclick = () => {
63
+ stop = true
64
+ checker.cancel()
65
+ }
66
+
67
+ return $iframeWrap
68
+ }
@@ -0,0 +1,26 @@
1
+
2
+ import { Checker } from '.'
3
+ import * as render from './captcha-renders'
4
+
5
+ const CaptchaChecker: Checker = {
6
+ request(checker, inputVal) {
7
+ return checker.getApi().captcha.captchaCheck(inputVal)
8
+ },
9
+
10
+ body(checker) {
11
+ if (checker.get('iframe')) return render.iframeBody(checker)
12
+ return render.imgBody(checker)
13
+ },
14
+
15
+ onSuccess(checker, data, inputVal, formEl) {
16
+ checker.set('val', inputVal)
17
+ },
18
+
19
+ onError(checker, err, inputVal, formEl) {
20
+ formEl.querySelector<HTMLElement>('.atk-captcha-img')!.click() // 刷新验证码
21
+ formEl.querySelector<HTMLInputElement>('input[type="text"]')!.value = '' // 清空输入框输入
22
+ }
23
+ }
24
+
25
+ export default CaptchaChecker
26
+
@@ -2,9 +2,10 @@ import Context from '~/types/context'
2
2
  import Dialog from '@/components/dialog'
3
3
  import Layer from '@/layer'
4
4
  import * as Utils from '../utils'
5
- import * as Ui from '../ui'
6
- import CaptchaChecker from './captcha-checker'
7
- import AdminChecker from './admin-checker'
5
+ import CaptchaChecker from './captcha'
6
+ import AdminChecker from './admin'
7
+ import type Api from '../../api'
8
+ import type User from '../user'
8
9
 
9
10
  export interface CheckerCaptchaPayload extends CheckerPayload {
10
11
  imgData?: string
@@ -22,26 +23,24 @@ export interface CheckerPayload {
22
23
  */
23
24
  export default class CheckerLauncher {
24
25
  public ctx: Context
25
-
26
26
  public launched: Checker[] = []
27
- public captchaConf: { val?: string, imgData?: string, iframe?: string } = {}
28
27
 
29
28
  constructor(ctx: Context) {
30
29
  this.ctx = ctx
31
30
  }
32
31
 
33
32
  public checkCaptcha(payload: CheckerCaptchaPayload) {
34
- this.captchaConf.imgData = payload.imgData
35
- this.captchaConf.iframe = payload.iframe
36
-
37
- this.fire(CaptchaChecker, payload)
33
+ this.fire(CaptchaChecker, payload, (ctx) => {
34
+ ctx.set('img_data', payload.imgData)
35
+ ctx.set('iframe', payload.iframe)
36
+ })
38
37
  }
39
38
 
40
39
  public checkAdmin(payload: CheckerPayload) {
41
40
  this.fire(AdminChecker, payload)
42
41
  }
43
42
 
44
- public fire(checker: Checker, payload: CheckerPayload) {
43
+ public fire(checker: Checker, payload: CheckerPayload, postFire?: (c: CheckerCtx) => void) {
45
44
  if (this.launched.includes(checker)) return // 阻止同时 fire 相同的 checker
46
45
  this.launched.push(checker)
47
46
 
@@ -50,16 +49,22 @@ export default class CheckerLauncher {
50
49
  layer.setMaskClickHide(false)
51
50
  layer.show()
52
51
 
53
- // Checker 的上下文
52
+ // 构建 Checker 的上下文
53
+ const checkerStore: CheckerStore = {}
54
54
  let hideInteractInput = false
55
55
  const checkerCtx: CheckerCtx = {
56
+ set: (key, val) => { checkerStore[key] = val },
57
+ get: (key) => (checkerStore[key]),
58
+ getCtx: () => (this.ctx),
59
+ getApi: () => (this.ctx.getApi()),
60
+ getUser: () => (this.ctx.user),
56
61
  getLayer: () => layer,
57
62
  hideInteractInput: () => {
58
63
  hideInteractInput = true
59
64
  },
60
65
  triggerSuccess: () => {
61
66
  this.close(checker, layer)
62
- if (checker.onSuccess) checker.onSuccess(this, checkerCtx, "", "", formEl)
67
+ if (checker.onSuccess) checker.onSuccess(checkerCtx, "", "", formEl)
63
68
  if (payload.onSuccess) payload.onSuccess("", dialog.$el)
64
69
  },
65
70
  cancel: () => {
@@ -68,9 +73,11 @@ export default class CheckerLauncher {
68
73
  }
69
74
  }
70
75
 
76
+ if (postFire) postFire(checkerCtx)
77
+
71
78
  // 创建表单
72
79
  const formEl = Utils.createElement()
73
- formEl.appendChild(checker.body(this, checkerCtx))
80
+ formEl.appendChild(checker.body(checkerCtx))
74
81
 
75
82
  // 输入框
76
83
  const $input = Utils.createElement<HTMLInputElement>(
@@ -113,19 +120,19 @@ export default class CheckerLauncher {
113
120
 
114
121
  // 发送请求
115
122
  checker
116
- .request(this, checkerCtx, inputVal)
123
+ .request(checkerCtx, inputVal)
117
124
  .then((data) => {
118
125
  // 请求成功
119
126
  this.close(checker, layer)
120
127
 
121
- if (checker.onSuccess) checker.onSuccess(this, checkerCtx, data, inputVal, formEl)
128
+ if (checker.onSuccess) checker.onSuccess(checkerCtx, data, inputVal, formEl)
122
129
  if (payload.onSuccess) payload.onSuccess(inputVal, dialog.$el)
123
130
  })
124
131
  .catch((err) => {
125
132
  // 请求失败
126
133
  btnTextSet(String(err.msg || String(err)))
127
134
 
128
- if (checker.onError) checker.onError(this, checkerCtx, err, inputVal, formEl)
135
+ if (checker.onError) checker.onError(checkerCtx, err, inputVal, formEl)
129
136
 
130
137
  // 错误显示 3s 后恢复按钮
131
138
  const tf = setTimeout(() => btnTextRestore(), 3000)
@@ -167,13 +174,24 @@ export default class CheckerLauncher {
167
174
  export interface Checker {
168
175
  el?: HTMLElement
169
176
  inputType?: 'password' | 'text'
170
- body: (launcher: CheckerLauncher, ctx: CheckerCtx) => HTMLElement
171
- request: (launcher: CheckerLauncher, ctx: CheckerCtx, inputVal: string) => Promise<string>
172
- onSuccess?: (launcher: CheckerLauncher, ctx: CheckerCtx, data: string, inputVal: string, formEl: HTMLElement) => void
173
- onError?: (launcher: CheckerLauncher, ctx: CheckerCtx, err: any, inputVal: string, formEl: HTMLElement) => void
177
+ body: (checker: CheckerCtx) => HTMLElement
178
+ request: (checker: CheckerCtx, inputVal: string) => Promise<string>
179
+ onSuccess?: (checker: CheckerCtx, respData: string, inputVal: string, formEl: HTMLElement) => void
180
+ onError?: (checker: CheckerCtx, err: any, inputVal: string, formEl: HTMLElement) => void
181
+ }
182
+
183
+ interface CheckerStore {
184
+ val?: string
185
+ img_data?: string
186
+ iframe?: string
174
187
  }
175
188
 
176
189
  export interface CheckerCtx {
190
+ get<K extends keyof CheckerStore>(key: K): CheckerStore[K]
191
+ set<K extends keyof CheckerStore>(key: K, val: CheckerStore[K]): void
192
+ getCtx(): Context
193
+ getApi(): Api
194
+ getUser(): User
177
195
  getLayer(): Layer
178
196
  hideInteractInput(): void
179
197
  triggerSuccess(): void
@@ -0,0 +1,48 @@
1
+ import type Context from '~/types/context'
2
+ import { getLayerWrap } from '../layer'
3
+
4
+ const darkModeMedia = window.matchMedia('(prefers-color-scheme: dark)')
5
+ let darkModeAutoFunc: (evt: MediaQueryListEvent) => void
6
+
7
+ export function syncDarkModeConf(ctx: Context) {
8
+ setDarkMode(ctx, ctx.conf.darkMode, false) // ref `conf.darkMode` data, so no need to `alterConf`
9
+ }
10
+
11
+ export function setDarkMode(ctx: Context, darkMode: boolean|'auto', alterConf = true) {
12
+ const apply = (d: boolean) => {
13
+ updateClassName(ctx, d)
14
+ if (alterConf) alterCtxConf(ctx, d)
15
+ }
16
+
17
+ if (darkMode === 'auto') {
18
+ if (!darkModeAutoFunc) { // 自动切换夜间模式,事件监听
19
+ darkModeAutoFunc = (evt) => apply(evt.matches)
20
+ darkModeMedia.addEventListener('change', darkModeAutoFunc)
21
+ }
22
+
23
+ apply(darkModeMedia.matches)
24
+ } else {
25
+ // if the type of darkMode is boolean
26
+ if (darkModeAutoFunc) // 解除事件监听绑定
27
+ darkModeMedia.removeEventListener('change', darkModeAutoFunc)
28
+
29
+ apply(darkMode)
30
+ }
31
+ }
32
+
33
+ function alterCtxConf(ctx: Context, darkMode: boolean) {
34
+ ctx.conf.darkMode = darkMode
35
+ }
36
+
37
+ const DarkModeClassName = 'atk-dark-mode'
38
+ export function updateClassName(ctx: Context, darkMode: boolean) {
39
+ if (darkMode) ctx.$root.classList.add(DarkModeClassName)
40
+ else ctx.$root.classList.remove(DarkModeClassName)
41
+
42
+ // for Layer
43
+ const { $wrap: $layerWrap } = getLayerWrap()
44
+ if ($layerWrap) {
45
+ if (darkMode) $layerWrap.classList.add(DarkModeClassName)
46
+ else $layerWrap.classList.remove(DarkModeClassName)
47
+ }
48
+ }