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,99 @@
1
+ import type { CommentData } from '~/types/artalk-data'
2
+ import Editor from './editor'
3
+ import User from '../lib/user'
4
+
5
+ export interface SubmitManager {
6
+ editor: Editor
7
+ do: () => Promise<void>
8
+ customs: CustomSubmit[]
9
+ registerCustom(c: CustomSubmit): void
10
+ }
11
+
12
+ export function createSubmitManager(editor: Editor) {
13
+ const m: SubmitManager = {
14
+ editor,
15
+ do: () => Do(m),
16
+ customs: [],
17
+ registerCustom: (c) => { m.customs.push(c) }
18
+ }
19
+
20
+ return m
21
+ }
22
+
23
+ async function Do(m: SubmitManager) {
24
+ if (m.editor.getFinalContent().trim() === '') {
25
+ m.editor.focus()
26
+ return
27
+ }
28
+
29
+ const custom = m.customs.find(o => o.activeCond())
30
+
31
+ m.editor.ctx.trigger('editor-submit')
32
+ m.editor.showLoading()
33
+
34
+ try {
35
+ // pre submit
36
+ if (custom?.pre) custom.pre()
37
+
38
+ let nComment: CommentData
39
+
40
+ // submit request
41
+ if (custom?.req) nComment = await custom.req()
42
+ else nComment = await reqAdd(m)
43
+
44
+ // post submit
45
+ if (custom?.post) custom.post(nComment)
46
+ else postSubmitAdd(m, nComment)
47
+ } catch (err: any) {
48
+ // submit error
49
+ console.error(err)
50
+ m.editor.showNotify(`${m.editor.$t('commentFail')},${err.msg || String(err)}`, 'e')
51
+ }
52
+
53
+ // 复原编辑器
54
+ m.editor.reset()
55
+
56
+ m.editor.ctx.trigger('editor-submitted')
57
+ m.editor.hideLoading()
58
+ }
59
+
60
+ interface CustomSubmit {
61
+ activeCond: () => void
62
+ pre?: () => void
63
+ req?: () => Promise<CommentData>
64
+ post?: (nComment: CommentData) => void
65
+ }
66
+
67
+ // ================== Submit Add =======================
68
+ function getSubmitAddParams(m: SubmitManager) {
69
+ const { nick, email, link } = User.data
70
+ const conf = m.editor.ctx.conf
71
+ const reply = m.editor.getReplyManager()?.comment
72
+
73
+ return {
74
+ content: m.editor.getFinalContent(),
75
+ nick, email, link,
76
+ rid: (!reply) ? 0 : reply.id,
77
+ page_key: (!reply) ? conf.pageKey : reply.page_key,
78
+ page_title: (!reply) ? conf.pageTitle : undefined,
79
+ site_name: (!reply) ? conf.site : reply.site_name
80
+ }
81
+ }
82
+
83
+ async function reqAdd(m: SubmitManager) {
84
+ const nComment = await m.editor.ctx.getApi().comment.add({
85
+ ...getSubmitAddParams(m)
86
+ })
87
+ return nComment
88
+ }
89
+
90
+ function postSubmitAdd(m: SubmitManager, commentNew: CommentData) {
91
+ // 回复不同页面的评论,跳转到新页面
92
+ const reply = m.editor.getReplyManager()
93
+ const conf = m.editor.ctx.conf
94
+ if (!!reply?.comment && reply.comment.page_key !== conf.pageKey) {
95
+ window.open(`${reply.comment.page_url}#atk-comment-${commentNew.id}`)
96
+ }
97
+
98
+ m.editor.ctx.insertComment(commentNew)
99
+ }
@@ -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
+ }
@@ -0,0 +1,10 @@
1
+ import type { I18n } from '.'
2
+
3
+ export const GLOBAL_LOCALES_KEY = "ArtalkI18n"
4
+
5
+ export function defineLocaleExternal(lang: string, locale: I18n, aliases?: string[]) {
6
+ if (!window[GLOBAL_LOCALES_KEY]) window[GLOBAL_LOCALES_KEY] = {}
7
+ window[GLOBAL_LOCALES_KEY][lang] = locale
8
+ if (aliases) aliases.forEach(l => { window[GLOBAL_LOCALES_KEY][l] = locale })
9
+ return locale
10
+ }
package/src/i18n/index.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  import en from './en'
2
2
  import zhCN from './zh-CN'
3
+ import { GLOBAL_LOCALES_KEY } from './external'
3
4
 
4
5
  export type I18n = typeof en
6
+ export type I18nKeys = keyof I18n
5
7
 
6
8
  // @note the key of language is followed by `ISO 639`
7
9
  // https://en.wikipedia.org/wiki/ISO_639
@@ -12,19 +14,10 @@ export const internal = {
12
14
  'zh-CN': zhCN,
13
15
  }
14
16
 
15
- const GLOBAL_LOCALES_KEY = "ArtalkI18n";
16
-
17
- export function defineLocaleExternal(lang: string, locale: I18n, aliases?: string[]) {
18
- if (!window[GLOBAL_LOCALES_KEY]) window[GLOBAL_LOCALES_KEY] = {}
19
- window[GLOBAL_LOCALES_KEY][lang] = locale
20
- if (aliases) aliases.forEach(l => { window[GLOBAL_LOCALES_KEY][l] = locale })
21
- return locale
22
- }
23
-
24
17
  /**
25
- * get a locale object by language name
18
+ * find a locale object by language name
26
19
  */
27
- function getLocaleSet(lang: string): I18n {
20
+ export function findLocaleSet(lang: string): I18n {
28
21
  // normalize a key of language to `ISO 639`
29
22
  lang = lang.replace(
30
23
  /^([a-zA-Z]+)(-[a-zA-Z]+)?$/,
@@ -47,18 +40,28 @@ function getLocaleSet(lang: string): I18n {
47
40
  }
48
41
 
49
42
  /**
50
- * get a i18n message by key
43
+ * System locale setting
51
44
  */
52
- function getI18n(locale: I18n|string, key: keyof I18n, args: {[key: string]: string} = {}) {
53
- if (typeof locale === 'string') {
54
- locale = getLocaleSet(locale)
55
- }
45
+ let LocaleConf: I18n|string = 'en'
46
+ let LocaleDict: I18n = findLocaleSet(LocaleConf) // en by default
47
+
48
+ /**
49
+ * Set system locale
50
+ */
51
+ export function setLocale(locale: I18n|string) {
52
+ if (locale === LocaleConf) return
53
+ LocaleConf = locale
54
+ LocaleDict = (typeof locale === 'string') ? findLocaleSet(locale) : locale
55
+ }
56
56
 
57
- let str = locale?.[key] || key
57
+ /**
58
+ * Get an i18n message by key
59
+ */
60
+ export function t(key: I18nKeys, args: {[key: string]: string} = {}) {
61
+ let str = LocaleDict?.[key] || key
58
62
  str = str.replace(/\{\s*(\w+?)\s*\}/g, (_, token) => args[token] || '')
59
63
 
60
64
  return str
61
65
  }
62
66
 
63
- export { getLocaleSet }
64
- export default getI18n
67
+ export default t
package/src/i18n/jp.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { defineLocaleExternal } from '.'
1
+ import { defineLocaleExternal } from './external'
2
2
 
3
3
  export default defineLocaleExternal('jp', {
4
4
  /* エディタ */
package/src/i18n/zh-TW.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { defineLocaleExternal } from '.'
1
+ import { defineLocaleExternal } from './external'
2
2
 
3
3
  export default defineLocaleExternal('zh-TW', {
4
4
  /* Editor */
@@ -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
  }
@@ -3,6 +3,7 @@ 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
+ import User from '../lib/user'
6
7
  import Layer from './layer'
7
8
 
8
9
  export interface SidebarShowPayload {
@@ -83,12 +84,13 @@ export default class SidebarLayer extends Component {
83
84
  const query: any = {
84
85
  pageKey: this.conf.pageKey,
85
86
  site: this.conf.site || '',
86
- user: JSON.stringify(this.ctx.user.data),
87
+ user: JSON.stringify(User.data),
87
88
  time: +new Date()
88
89
  }
89
90
 
90
91
  if (conf.view) query.view = conf.view
91
92
  if (this.conf.darkMode) query.darkMode = '1'
93
+ if (typeof this.conf.locale === 'string') query.locale = this.conf.locale
92
94
 
93
95
  const urlParams = new URLSearchParams(query);
94
96
  this.iframeLoad(`${baseURL}?${urlParams.toString()}`)
@@ -96,7 +98,7 @@ export default class SidebarLayer extends Component {
96
98
  this.$iframeWrap.append(this.$iframe)
97
99
  this.firstShow = false
98
100
  } else {
99
- // 暗黑模式
101
+ // 夜间模式
100
102
  const isIframeSrcDarkMode = this.$iframe!.src.includes('darkMode=1')
101
103
 
102
104
  if (this.conf.darkMode && !isIframeSrcDarkMode)
@@ -0,0 +1,37 @@
1
+ import * as Utils from '../utils'
2
+ import User from '../user'
3
+ import { Checker } from '.'
4
+
5
+ const AdminChecker: Checker = {
6
+ inputType: 'password',
7
+
8
+ request(checker, inputVal) {
9
+ const data = {
10
+ name: User.data.nick,
11
+ email: User.data.email,
12
+ password: inputVal
13
+ }
14
+
15
+ return (async () => {
16
+ const resp = await checker.getApi().user.login(data.name, data.email, data.password)
17
+ return resp.token
18
+ })()
19
+ },
20
+
21
+ body(checker) {
22
+ return Utils.createElement(`<span>${checker.getCtx().$t('adminCheck')}</span>`)
23
+ },
24
+
25
+ onSuccess(checker, userToken, inputVal, formEl) {
26
+ User.update({
27
+ isAdmin: true,
28
+ token: userToken
29
+ })
30
+ checker.getCtx().trigger('user-changed', User.data)
31
+ checker.getCtx().listReload()
32
+ },
33
+
34
+ onError(checker, err, inputVal, formEl) {}
35
+ }
36
+
37
+ 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,21 @@ 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()),
56
60
  getLayer: () => layer,
57
61
  hideInteractInput: () => {
58
62
  hideInteractInput = true
59
63
  },
60
64
  triggerSuccess: () => {
61
65
  this.close(checker, layer)
62
- if (checker.onSuccess) checker.onSuccess(this, checkerCtx, "", "", formEl)
66
+ if (checker.onSuccess) checker.onSuccess(checkerCtx, "", "", formEl)
63
67
  if (payload.onSuccess) payload.onSuccess("", dialog.$el)
64
68
  },
65
69
  cancel: () => {
@@ -68,9 +72,11 @@ export default class CheckerLauncher {
68
72
  }
69
73
  }
70
74
 
75
+ if (postFire) postFire(checkerCtx)
76
+
71
77
  // 创建表单
72
78
  const formEl = Utils.createElement()
73
- formEl.appendChild(checker.body(this, checkerCtx))
79
+ formEl.appendChild(checker.body(checkerCtx))
74
80
 
75
81
  // 输入框
76
82
  const $input = Utils.createElement<HTMLInputElement>(
@@ -113,19 +119,19 @@ export default class CheckerLauncher {
113
119
 
114
120
  // 发送请求
115
121
  checker
116
- .request(this, checkerCtx, inputVal)
122
+ .request(checkerCtx, inputVal)
117
123
  .then((data) => {
118
124
  // 请求成功
119
125
  this.close(checker, layer)
120
126
 
121
- if (checker.onSuccess) checker.onSuccess(this, checkerCtx, data, inputVal, formEl)
127
+ if (checker.onSuccess) checker.onSuccess(checkerCtx, data, inputVal, formEl)
122
128
  if (payload.onSuccess) payload.onSuccess(inputVal, dialog.$el)
123
129
  })
124
130
  .catch((err) => {
125
131
  // 请求失败
126
132
  btnTextSet(String(err.msg || String(err)))
127
133
 
128
- if (checker.onError) checker.onError(this, checkerCtx, err, inputVal, formEl)
134
+ if (checker.onError) checker.onError(checkerCtx, err, inputVal, formEl)
129
135
 
130
136
  // 错误显示 3s 后恢复按钮
131
137
  const tf = setTimeout(() => btnTextRestore(), 3000)
@@ -167,13 +173,23 @@ export default class CheckerLauncher {
167
173
  export interface Checker {
168
174
  el?: HTMLElement
169
175
  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
176
+ body: (checker: CheckerCtx) => HTMLElement
177
+ request: (checker: CheckerCtx, inputVal: string) => Promise<string>
178
+ onSuccess?: (checker: CheckerCtx, respData: string, inputVal: string, formEl: HTMLElement) => void
179
+ onError?: (checker: CheckerCtx, err: any, inputVal: string, formEl: HTMLElement) => void
180
+ }
181
+
182
+ interface CheckerStore {
183
+ val?: string
184
+ img_data?: string
185
+ iframe?: string
174
186
  }
175
187
 
176
188
  export interface CheckerCtx {
189
+ get<K extends keyof CheckerStore>(key: K): CheckerStore[K]
190
+ set<K extends keyof CheckerStore>(key: K, val: CheckerStore[K]): void
191
+ getCtx(): Context
192
+ getApi(): Api
177
193
  getLayer(): Layer
178
194
  hideInteractInput(): void
179
195
  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
+ }