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.
- package/README.md +27 -0
- package/dist/Artalk.css +1 -1
- package/dist/Artalk.es.js +4872 -4072
- package/dist/Artalk.iife.js +1 -1
- package/dist/Artalk.js +1 -1
- package/dist/ArtalkLite.css +1 -1
- package/dist/ArtalkLite.es.js +2468 -1779
- package/dist/ArtalkLite.iife.js +1 -1
- package/dist/ArtalkLite.js +1 -1
- package/dist/i18n/jp.js +1 -1
- package/dist/i18n/zh-TW.js +1 -1
- package/dist/src/api/index.d.ts +2 -2
- package/dist/src/api/system.d.ts +5 -2
- package/dist/src/artalk.d.ts +42 -34
- package/dist/src/comment/comment.d.ts +2 -2
- package/dist/src/comment/height-limit.d.ts +30 -0
- package/dist/src/comment/render-ctx.d.ts +23 -0
- package/dist/src/comment/render.d.ts +2 -50
- package/dist/src/comment/renders/actions.d.ts +5 -0
- package/dist/src/comment/renders/avatar.d.ts +5 -0
- package/dist/src/comment/renders/content.d.ts +5 -0
- package/dist/src/comment/renders/header.d.ts +5 -0
- package/dist/src/comment/renders/index.d.ts +2 -0
- package/dist/src/comment/renders/pending.d.ts +5 -0
- package/dist/src/comment/renders/reply-at.d.ts +5 -0
- package/dist/src/comment/renders/reply-to.d.ts +5 -0
- package/dist/src/components/dialog.d.ts +1 -1
- package/dist/src/config.d.ts +8 -0
- package/dist/src/context.d.ts +15 -29
- package/dist/src/editor/edit-mode.d.ts +9 -0
- package/dist/src/editor/editor-ui.d.ts +25 -0
- package/dist/src/editor/editor.d.ts +37 -45
- package/dist/src/editor/funcs.d.ts +2 -0
- package/dist/src/editor/mover.d.ts +8 -0
- package/dist/src/editor/plug-manager.d.ts +17 -0
- package/dist/src/editor/plugs/editor-plug.d.ts +13 -1
- package/dist/src/editor/reply.d.ts +9 -0
- package/dist/src/editor/submit.d.ts +16 -0
- package/dist/src/editor/ui.d.ts +25 -0
- package/dist/src/i18n/index.d.ts +11 -7
- package/dist/src/layer/index.d.ts +2 -2
- package/dist/src/layer/layer.d.ts +1 -1
- package/dist/src/lib/checker/captcha-renders.d.ts +5 -0
- package/dist/src/lib/checker/index.d.ts +18 -10
- package/dist/src/lib/dark-mode.d.ts +4 -0
- package/dist/src/lib/marked.d.ts +9 -0
- package/dist/src/lib/utils.d.ts +1 -5
- package/dist/src/list/list-lite.d.ts +15 -11
- package/dist/src/list/list-nest.d.ts +2 -2
- package/dist/src/list/list-ui.d.ts +7 -0
- package/dist/src/list/paginator/adaptors/index.d.ts +12 -0
- package/dist/src/list/paginator/adaptors/pagination.d.ts +9 -0
- package/dist/src/list/paginator/adaptors/read-more.d.ts +10 -0
- package/dist/src/list/paginator/holder.d.ts +16 -0
- package/dist/src/list/paginator/index.d.ts +10 -0
- package/dist/src/service.d.ts +40 -0
- package/dist/types/artalk-config.d.ts +5 -2
- package/dist/types/artalk-data.d.ts +3 -3
- package/dist/types/context.d.ts +11 -17
- package/dist/types/global.d.ts +1 -0
- package/package.json +6 -7
- package/src/api/system.ts +3 -12
- package/src/artalk.ts +101 -158
- package/src/comment/comment.ts +7 -6
- package/src/comment/height-limit.ts +100 -0
- package/src/comment/render-ctx.ts +30 -0
- package/src/comment/render.ts +31 -375
- package/src/comment/renders/actions.ts +114 -0
- package/src/comment/renders/avatar.ts +22 -0
- package/src/comment/renders/content.ts +39 -0
- package/src/comment/renders/header.ts +84 -0
- package/src/comment/renders/index.ts +19 -0
- package/src/comment/renders/pending.ts +12 -0
- package/src/comment/renders/reply-at.ts +16 -0
- package/src/comment/renders/reply-to.ts +24 -0
- package/src/config.ts +65 -0
- package/src/context.ts +31 -86
- package/src/defaults.ts +1 -0
- package/src/editor/edit-mode.ts +102 -0
- package/src/editor/editor.ts +100 -395
- package/src/editor/funcs.ts +96 -0
- package/src/editor/mover.ts +43 -0
- package/src/editor/plug-manager.ts +172 -0
- package/src/editor/plugs/editor-plug.ts +20 -2
- package/src/editor/plugs/{emoticons-plug.less → emoticons-plug.scss} +5 -4
- package/src/editor/plugs/emoticons-plug.ts +2 -1
- package/src/editor/plugs/header-input-plug.ts +5 -5
- package/src/editor/plugs/{preview-plug.less → preview-plug.scss} +1 -1
- package/src/editor/plugs/preview-plug.ts +8 -4
- package/src/editor/plugs/upload-plug.ts +10 -13
- package/src/editor/reply.ts +62 -0
- package/src/editor/submit.ts +98 -0
- package/src/editor/ui.ts +36 -0
- package/src/i18n/index.ts +21 -10
- package/src/layer/index.ts +2 -2
- package/src/layer/layer.ts +4 -4
- package/src/layer/sidebar-layer.ts +2 -1
- package/src/lib/checker/admin.ts +36 -0
- package/src/lib/checker/captcha-renders.ts +68 -0
- package/src/lib/checker/captcha.ts +26 -0
- package/src/lib/checker/index.ts +38 -20
- package/src/lib/dark-mode.ts +48 -0
- package/src/lib/marked.ts +133 -0
- package/src/lib/utils.ts +1 -123
- package/src/list/list-lite.ts +59 -162
- package/src/list/list-ui.ts +80 -0
- package/src/list/list.ts +16 -8
- package/src/list/paginator/adaptors/index.ts +21 -0
- package/src/list/paginator/adaptors/pagination.ts +42 -0
- package/src/list/paginator/adaptors/read-more.ts +56 -0
- package/src/list/paginator/holder.ts +55 -0
- package/src/list/paginator/index.ts +13 -0
- package/src/service.ts +118 -0
- package/src/style/{_color.less → _color.scss} +3 -3
- package/src/style/{_extend.less → _extend.scss} +1 -1
- package/src/style/_variables.scss +3 -0
- package/src/style/{comment.less → comment.scss} +36 -12
- package/src/style/{editor.less → editor.scss} +11 -11
- package/src/style/{list.less → list.scss} +8 -6
- package/src/style/{main.less → main.scss} +24 -23
- package/src/style/{sidebar-layer.less → sidebar-layer.scss} +8 -8
- package/types/artalk-config.d.ts +5 -2
- package/types/artalk-data.d.ts +3 -3
- package/types/context.d.ts +11 -17
- package/types/global.d.ts +1 -0
- package/CNAME +0 -1
- package/LICENSE +0 -165
- package/index.html +0 -157
- package/postcss.config.js +0 -5
- package/scripts/build-i18n.mjs +0 -46
- package/src/lib/checker/admin-checker.ts +0 -36
- package/src/lib/checker/captcha-checker.ts +0 -84
- package/src/style/_variables.less +0 -3
- package/test/ArtalkCount.html +0 -16
- package/test/ArtalkI18n.html +0 -16
- package/test/ArtalkLite.html +0 -15
- package/test/CSRF.html +0 -26
- package/tsconfig.json +0 -12
- package/vite-base.config.ts +0 -21
- package/vite-demo.config.ts +0 -12
- package/vite-lite.config.ts +0 -22
- package/vite.config.ts +0 -27
- /package/dist/src/lib/checker/{admin-checker.d.ts → admin.d.ts} +0 -0
- /package/dist/src/lib/checker/{captcha-checker.d.ts → captcha.d.ts} +0 -0
- /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
|
+
}
|
package/src/editor/ui.ts
ADDED
|
@@ -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
|
-
*
|
|
26
|
+
* find a locale object by language name
|
|
26
27
|
*/
|
|
27
|
-
function
|
|
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
|
-
*
|
|
51
|
+
* System locale setting
|
|
51
52
|
*/
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
locale = getLocaleSet(locale)
|
|
55
|
-
}
|
|
53
|
+
let LocaleConf: I18n|string = 'en'
|
|
54
|
+
let LocaleDict: I18n = findLocaleSet(LocaleConf) // en by default
|
|
56
55
|
|
|
57
|
-
|
|
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
|
|
64
|
-
export default getI18n
|
|
75
|
+
export default t
|
package/src/layer/index.ts
CHANGED
package/src/layer/layer.ts
CHANGED
|
@@ -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 } =
|
|
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
|
|
152
|
-
let $wrap = document.querySelector<HTMLElement>(`.atk-layer-wrap
|
|
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"
|
|
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
|
+
|
package/src/lib/checker/index.ts
CHANGED
|
@@ -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
|
|
6
|
-
import
|
|
7
|
-
import
|
|
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.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
123
|
+
.request(checkerCtx, inputVal)
|
|
117
124
|
.then((data) => {
|
|
118
125
|
// 请求成功
|
|
119
126
|
this.close(checker, layer)
|
|
120
127
|
|
|
121
|
-
if (checker.onSuccess) checker.onSuccess(
|
|
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(
|
|
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: (
|
|
171
|
-
request: (
|
|
172
|
-
onSuccess?: (
|
|
173
|
-
onError?: (
|
|
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
|
+
}
|