artalk 2.1.8 → 2.2.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.
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "artalk",
3
- "version": "2.1.8",
4
- "license": "GPL-3.0",
3
+ "version": "2.2.1",
4
+ "backendMinVersion": "2.1.1",
5
+ "license": "LGPL-3.0",
5
6
  "description": "A Selfhosted Comment System",
6
7
  "keywords": [
7
8
  "comment-system",
@@ -22,16 +23,6 @@
22
23
  "jsdelivr": "./dist/Artalk.iife.js",
23
24
  "module": "./dist/Artalk.es.js",
24
25
  "types": "./types/index.d.ts",
25
- "scripts": {
26
- "dev": "vite",
27
- "build": "yarn lint && vite build",
28
- "build:demo": "vite build --config vite-demo.config.ts",
29
- "serve": "vite preview",
30
- "deploy": "gh-pages -d deploy",
31
- "predeploy": "yarn run build && yarn run build:demo && yarn run copy:deploy",
32
- "copy:deploy": "cp -r dist deploy && cp CNAME deploy",
33
- "lint": "eslint --ext .js,.ts src"
34
- },
35
26
  "devDependencies": {
36
27
  "@types/marked": "^4.0.3"
37
28
  },
@@ -39,5 +30,15 @@
39
30
  "hanabi": "^0.4.0",
40
31
  "insane": "^2.6.2",
41
32
  "marked": "^4.0.14"
33
+ },
34
+ "scripts": {
35
+ "dev": "vite",
36
+ "build": "pnpm lint && vite build",
37
+ "build:demo": "vite build --config vite-demo.config.ts",
38
+ "serve": "vite preview",
39
+ "deploy": "gh-pages -d deploy",
40
+ "predeploy": "pnpm build && pnpm build:demo && pnpm copy:deploy",
41
+ "copy:deploy": "cp -r dist deploy && cp CNAME deploy",
42
+ "lint": "eslint --ext .js,.ts src"
42
43
  }
43
- }
44
+ }
package/src/api/index.ts CHANGED
@@ -117,6 +117,12 @@ export default class Api {
117
117
  }
118
118
  }
119
119
 
120
+ /** 用户 · 登录状态 */
121
+ public async loginStatus() {
122
+ const data = await POST<any>(this.ctx, `${this.baseURL}/login-status`)
123
+ return (data || { is_login: false }) as { is_login: boolean }
124
+ }
125
+
120
126
  // ============================
121
127
  // 页面 Page
122
128
  // ============================
@@ -293,13 +299,19 @@ export default class Api {
293
299
 
294
300
  /** 验证码 · 获取 */
295
301
  public async captchaGet() {
296
- const data = await GET<any>(this.ctx, `${this.baseURL}/captcha/refresh`)
302
+ const data = await POST<any>(this.ctx, `${this.baseURL}/captcha/refresh`)
297
303
  return (data.img_data || '') as string
298
304
  }
299
305
 
300
306
  /** 验证码 · 检验 */
301
307
  public async captchaCheck(value: string) {
302
- const data = await GET<any>(this.ctx, `${this.baseURL}/captcha/check`, { value })
308
+ const data = await POST<any>(this.ctx, `${this.baseURL}/captcha/check`, { value })
303
309
  return (data.img_data || '') as string
304
310
  }
311
+
312
+ /** 验证码 · 状态 */
313
+ public async captchaStatus() {
314
+ const data = await POST<any>(this.ctx, `${this.baseURL}/captcha/status`)
315
+ return (data || { is_pass: false }) as { is_pass: boolean }
316
+ }
305
317
  }
@@ -43,6 +43,7 @@ export async function Fetch(ctx: Context, input: RequestInfo, init: RequestInit,
43
43
  json = await (new Promise<any>((resolve, reject) => {
44
44
  ctx.trigger('checker-captcha', {
45
45
  imgData: json.data.img_data,
46
+ iframe: json.data.iframe,
46
47
  onSuccess: () => {
47
48
  recall(resolve, reject)
48
49
  },
package/src/artalk.ts CHANGED
@@ -12,6 +12,7 @@ import SidebarLayer from './components/sidebar-layer'
12
12
  import { GetLayerWrap } from './components/layer'
13
13
  import { EventPayloadMap, Handler } from '~/types/event'
14
14
  import Api from './api'
15
+ import * as Utils from './lib/utils'
15
16
 
16
17
  /**
17
18
  * Artalk
@@ -59,6 +60,7 @@ export default class Artalk {
59
60
 
60
61
  // 组件初始化
61
62
  this.checkerLauncher = new CheckerLauncher(this.ctx)
63
+ Utils.initMarked(this.ctx) // 初始化 marked
62
64
 
63
65
  // 编辑器
64
66
  this.editor = new Editor(this.ctx)
@@ -80,6 +82,13 @@ export default class Artalk {
80
82
 
81
83
  // 其他
82
84
  this.initPV()
85
+
86
+ // 插件初始化
87
+ Artalk.Plugins.forEach(plugin => {
88
+ if (typeof plugin === 'function') {
89
+ plugin(this.ctx)
90
+ }
91
+ })
83
92
  }
84
93
 
85
94
  /** 事件绑定 · 初始化 */
@@ -180,4 +189,12 @@ export default class Artalk {
180
189
  public trigger<K extends keyof EventPayloadMap>(name: K, payload?: EventPayloadMap[K]) {
181
190
  this.ctx.trigger(name, payload, 'external')
182
191
  }
192
+
193
+ /** Plugins */
194
+ protected static Plugins: ((ctx: Context) => void)[] = []
195
+
196
+ /** Enable Plugin */
197
+ public static Use(plugin: ((ctx: Context) => void)) {
198
+ this.Plugins.push(plugin)
199
+ }
183
200
  }
@@ -247,7 +247,7 @@ export default class Editor extends Component {
247
247
  })
248
248
  })
249
249
 
250
- this.initImgUploadBtn()
250
+ this.initImgUpload()
251
251
  }
252
252
 
253
253
  /** 关闭编辑器插件 */
@@ -260,11 +260,12 @@ export default class Editor extends Component {
260
260
  /** 允许的图片格式 */
261
261
  allowImgExts = ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'svg', 'webp']
262
262
 
263
- /** 初始化图片上传按钮 */
264
- initImgUploadBtn() {
263
+ /** 初始化图片上传功能 */
264
+ initImgUpload() {
265
265
  this.$imgUploadBtn = Utils.createElement(`<span class="atk-plug-btn">图片</span>`)
266
266
  this.$plugBtnWrap.querySelector('[data-plug-name="preview"]')!.before(this.$imgUploadBtn) // 显示在预览图标之前
267
267
 
268
+ // 按钮点击
268
269
  this.$imgUploadBtn.onclick = () => {
269
270
  // 选择图片
270
271
  const $input = document.createElement('input')
@@ -280,6 +281,17 @@ export default class Editor extends Component {
280
281
  $input.click() // 显示选择图片对话框
281
282
  }
282
283
 
284
+ // 统一从 FileList 获取文件并上传图片方法
285
+ const uploadFromFileList = (files?: FileList) => {
286
+ if (!files) return
287
+
288
+ for (let i = 0; i < files.length; i++) {
289
+ const file = files[i]
290
+ this.uploadImg(file)
291
+ }
292
+ }
293
+
294
+ // 拖拽图片
283
295
  // @link https://developer.mozilla.org/zh-CN/docs/Web/API/HTML_Drag_and_Drop_API/File_drag_and_drop
284
296
  // 阻止浏览器的默认释放行为
285
297
  this.$textarea.addEventListener('dragover', (evt) => {
@@ -288,12 +300,19 @@ export default class Editor extends Component {
288
300
  })
289
301
 
290
302
  this.$textarea.addEventListener('drop', (evt) => {
291
- if (evt.dataTransfer?.files) {
303
+ const files = evt.dataTransfer?.files
304
+ if (files?.length) {
292
305
  evt.preventDefault()
293
- for (let i = 0; i < evt.dataTransfer.files.length; i++) {
294
- const file = evt.dataTransfer.files[i]
295
- this.uploadImg(file)
296
- }
306
+ uploadFromFileList(files)
307
+ }
308
+ })
309
+
310
+ // 粘贴图片
311
+ this.$textarea.addEventListener('paste', (evt) => {
312
+ const files = evt.clipboardData?.files
313
+ if (files?.length) {
314
+ evt.preventDefault()
315
+ uploadFromFileList(files)
297
316
  }
298
317
  })
299
318
  }
@@ -312,8 +331,18 @@ export default class Editor extends Component {
312
331
  const fileExt = /[^.]+$/.exec(file.name)
313
332
  if (!fileExt || !this.allowImgExts.includes(fileExt[0])) return
314
333
 
334
+ // 未登录提示
335
+ if (!this.ctx.user.checkHasBasicUserInfo()) {
336
+ this.showNotify('填入你的名字邮箱才能上传哦', 'w')
337
+ return
338
+ }
339
+
340
+ // 插入图片前换一行
341
+ let insertPrefix = '\n'
342
+ if (this.$textarea.value.trim() === '') insertPrefix = ''
343
+
315
344
  // 插入占位加载文字
316
- const uploadPlaceholderTxt = `\n![](Uploading ${file.name}...)`
345
+ const uploadPlaceholderTxt = `${insertPrefix}![](Uploading ${file.name}...)`
317
346
  this.insertContent(uploadPlaceholderTxt)
318
347
 
319
348
  // 上传图片
@@ -326,20 +355,16 @@ export default class Editor extends Component {
326
355
  }
327
356
  if (!!resp && resp.img_url) {
328
357
  let imgURL = resp.img_url as string
329
- if (imgURL.startsWith('.') || (imgURL.startsWith('/') && !imgURL.startsWith('//'))) {
330
- // 若为相对路径,加上 artalk server
331
- imgURL = `${this.ctx.conf.server.replace(/\/api\/?$/, '')}/${imgURL.replace(/^\//, '')}`
332
- }
358
+
359
+ // 若为相对路径,加上 artalk server
360
+ if (!Utils.isValidURL(imgURL)) imgURL = Utils.getURLBasedOnApi(this.ctx, imgURL)
333
361
 
334
362
  // 上传成功插入图片
335
- this.$textarea.value = this.$textarea.value.replace(uploadPlaceholderTxt, `\n![](${imgURL})`)
363
+ this.setContent(this.$textarea.value.replace(uploadPlaceholderTxt, `${insertPrefix}![](${imgURL})`))
336
364
  } else {
337
365
  // 上传失败删除加载文字
338
- this.$textarea.value = this.$textarea.value.replace(uploadPlaceholderTxt, '')
366
+ this.setContent(this.$textarea.value.replace(uploadPlaceholderTxt, ''))
339
367
  }
340
-
341
- // 更新内容保存到 localStorage
342
- this.saveContent()
343
368
  }
344
369
 
345
370
  insertContent (val: string) {
@@ -376,7 +401,8 @@ export default class Editor extends Component {
376
401
  this.cancelReply()
377
402
  }
378
403
 
379
- getContent () {
404
+ /** 获取最终用于 submit 的数据 */
405
+ getFinalContent () {
380
406
  let content = this.getContentOriginal()
381
407
 
382
408
  // 表情包处理
@@ -393,7 +419,7 @@ export default class Editor extends Component {
393
419
  }
394
420
 
395
421
  getContentMarked () {
396
- return Utils.marked(this.ctx, this.getContent())
422
+ return Utils.marked(this.ctx, this.getFinalContent())
397
423
  }
398
424
 
399
425
  initBottomPart () {
@@ -452,7 +478,7 @@ export default class Editor extends Component {
452
478
  }
453
479
 
454
480
  async submit () {
455
- if (this.getContent().trim() === '') {
481
+ if (this.getFinalContent().trim() === '') {
456
482
  this.$textarea.focus()
457
483
  return
458
484
  }
@@ -463,7 +489,7 @@ export default class Editor extends Component {
463
489
 
464
490
  try {
465
491
  const nComment = await new Api(this.ctx).add({
466
- content: this.getContent(),
492
+ content: this.getFinalContent(),
467
493
  nick: this.user.data.nick,
468
494
  email: this.user.data.email,
469
495
  link: this.user.data.link,
@@ -65,12 +65,7 @@ export default class Layer extends Component {
65
65
  }
66
66
 
67
67
  // body style 禁止滚动 + 防抖
68
- this.bodyStyleOrgOverflow = document.body.style.overflow
69
- this.bodyStyleOrgPaddingRight = document.body.style.paddingRight
70
- document.body.style.overflow = 'hidden'
71
-
72
- const bpr = parseInt(window.getComputedStyle(document.body, null).getPropertyValue('padding-right'), 10)
73
- document.body.style.paddingRight = `${Ui.getScrollBarWidth() + bpr || 0}px`
68
+ this.pageBodyScrollBarHide()
74
69
  }
75
70
 
76
71
  hide () {
@@ -81,8 +76,7 @@ export default class Layer extends Component {
81
76
  this.newActionTimer(() => {
82
77
  this.$wrap.style.display = 'none'
83
78
  // body style 禁止滚动解除
84
- document.body.style.overflow = this.bodyStyleOrgOverflow
85
- document.body.style.paddingRight = this.bodyStyleOrgPaddingRight
79
+ this.pageBodyScrollBarShow()
86
80
  this.checkCleanLayer()
87
81
  }, 450)
88
82
  this.newActionTimer(() => {
@@ -95,6 +89,22 @@ export default class Layer extends Component {
95
89
  this.maskClickHideEnable = enable
96
90
  }
97
91
 
92
+ // 页面滚动条隐藏
93
+ pageBodyScrollBarHide() {
94
+ this.bodyStyleOrgOverflow = document.body.style.overflow
95
+ this.bodyStyleOrgPaddingRight = document.body.style.paddingRight
96
+ document.body.style.overflow = 'hidden'
97
+
98
+ const bpr = parseInt(window.getComputedStyle(document.body, null).getPropertyValue('padding-right'), 10)
99
+ document.body.style.paddingRight = `${Ui.getScrollBarWidth() + bpr || 0}px`
100
+ }
101
+
102
+ // 页面滚动条显示
103
+ pageBodyScrollBarShow() {
104
+ document.body.style.overflow = this.bodyStyleOrgOverflow
105
+ document.body.style.paddingRight = this.bodyStyleOrgPaddingRight
106
+ }
107
+
98
108
  // Timers
99
109
  private static actionTimers: {act: Function, tid: number}[] = []
100
110
 
@@ -120,6 +130,7 @@ export default class Layer extends Component {
120
130
  disposeNow () {
121
131
  document.body.style.overflow = ''
122
132
  this.$el.remove()
133
+ this.pageBodyScrollBarShow()
123
134
  // this.$el dispose
124
135
  this.checkCleanLayer()
125
136
  }
@@ -7,6 +7,7 @@ import Comment from './comment'
7
7
  import { ListData, CommentData, NotifyData, ApiVersionData } from '~/types/artalk-data'
8
8
  import Pagination from './pagination'
9
9
  import ReadMoreBtn from './read-more-btn'
10
+ import { backendMinVersion } from '../../package.json'
10
11
 
11
12
  export default class ListLite extends Component {
12
13
  protected $commentsWrap: HTMLElement
@@ -117,7 +118,9 @@ export default class ListLite extends Component {
117
118
  this.data = data
118
119
 
119
120
  // 版本检测
120
- if (this.ctx.conf.versionCheck && this.versionCheck(data.api_version)) return
121
+ const feMinVersion = data.api_version?.fe_min_version || '0.0.0'
122
+ if (this.ctx.conf.versionCheck && this.versionCheck('前端', feMinVersion, ARTALK_VERSION)) return
123
+ if (this.ctx.conf.versionCheck && this.versionCheck('后端', backendMinVersion, data.api_version?.version)) return
121
124
 
122
125
  // 图片上传功能
123
126
  if (data.conf && typeof data.conf.img_upload === "boolean") {
@@ -472,26 +475,25 @@ export default class ListLite extends Component {
472
475
  }
473
476
  }
474
477
 
475
- /** 前端版本检测 */
476
- public versionCheck(versionData: ApiVersionData): boolean {
477
- const needVersion = versionData?.fe_min_version || '0.0.0'
478
- const needUpdate = Utils.versionCompare(needVersion, ARTALK_VERSION) === 1
478
+ /** 版本检测 */
479
+ public versionCheck(name: '前端'|'后端', needVersion: string, curtVersion: string): boolean {
480
+ const needUpdate = Utils.versionCompare(needVersion, curtVersion) === 1
479
481
  if (needUpdate) {
480
482
  // 需要更新
481
- const errEl = Utils.createElement(`<div>前端 Artalk 版本已过时,请更新以获得完整体验<br/>`
482
- + `若您是站点管理员,请前往 “<a href="https://artalk.js.org/" target="_blank">官方文档</a>” 获取帮助`
483
+ const errEl = Utils.createElement(`<div>Artalk ${name}版本已过时,请更新以获得完整体验<br/>`
484
+ + `如果你是管理员,请前往 “<a href="https://artalk.js.org/" target="_blank">官方文档</a>” 获得帮助`
483
485
  + `<br/><br/>`
484
- + `<span style="color: var(--at-color-meta);">前端版本 ${ARTALK_VERSION},需求版本 >= ${needVersion}</span><br/><br/>`
486
+ + `<span style="color: var(--at-color-meta);">当前${name}版本 ${curtVersion},需求版本 >= ${needVersion}</span><br/><br/>`
485
487
  + `</div>`)
486
488
  const ignoreBtn = Utils.createElement('<span style="cursor:pointer;">忽略</span>')
487
489
  ignoreBtn.onclick = () => {
488
- Ui.setError(this.ctx, null)
490
+ Ui.setError(this.$el.parentElement!, null)
489
491
  this.ctx.conf.versionCheck = false
490
492
  this.ctx.trigger('conf-updated')
491
493
  this.fetchComments(0)
492
494
  }
493
495
  errEl.append(ignoreBtn)
494
- Ui.setError(this.ctx, errEl, '<span class="atk-warn-title">Artalk Warn</span>')
496
+ Ui.setError(this.$el.parentElement!, errEl, '<span class="atk-warn-title">Artalk Warn</span>')
495
497
  }
496
498
 
497
499
  return needUpdate
@@ -42,9 +42,12 @@ export default class List extends ListLite {
42
42
  // 操作按钮
43
43
  this.initListActionBtn()
44
44
 
45
- // 下拉选择层
46
45
  this.$commentCount = this.$el.querySelector('.atk-comment-count')!
47
- this.initDropdown()
46
+
47
+ // 评论列表排序 Dropdown 下拉选择层
48
+ if (this.ctx.conf.listSort) {
49
+ this.initDropdown()
50
+ }
48
51
 
49
52
  // copyright
50
53
  this.$el.querySelector<HTMLElement>('.atk-copyright')!.innerHTML = `Powered By <a href="https://artalk.js.org" target="_blank" title="Artalk v${ARTALK_VERSION}">Artalk</a>`
@@ -6,6 +6,7 @@ import * as Utils from '@/lib/utils'
6
6
  import SidebarHTML from './html/sidebar-layer.html?raw'
7
7
  import * as Ui from '@/lib/ui'
8
8
  import Layer from './layer'
9
+ import Api from '../api'
9
10
 
10
11
  export default class SidebarLayer extends Component {
11
12
  public layer?: Layer
@@ -60,18 +61,39 @@ export default class SidebarLayer extends Component {
60
61
 
61
62
  // 第一次加载
62
63
  if (this.firstShow) {
64
+ // 管理员身份验证
65
+ if (this.ctx.user.data.isAdmin) {
66
+ const resp = await new Api(this.ctx).loginStatus()
67
+ if (!resp.is_login) {
68
+ await (new Promise<any>((resolve, reject) => {
69
+ this.ctx.trigger('checker-admin', {
70
+ onSuccess: () => {
71
+ resolve(null)
72
+ setTimeout(() => {
73
+ this.show()
74
+ }, 500)
75
+ },
76
+ onCancel: () => {
77
+ this.layer?.hide()
78
+ }
79
+ })
80
+ }))
81
+ }
82
+ }
83
+
63
84
  this.$iframeWrap.innerHTML = ''
64
85
  this.$iframe = Utils.createElement<HTMLIFrameElement>('<iframe></iframe>')
65
86
 
66
87
  const baseURL = (import.meta.env.MODE === 'development') ? 'http://localhost:23367/'
67
- : `${this.conf.server.replace(/\/$/, '')}/../sidebar/`
88
+ : Utils.getURLBasedOnApi(this.ctx, 'sidebar/')
68
89
  const userData = encodeURIComponent(JSON.stringify(this.ctx.user.data))
69
90
 
70
- // 测试是否能访问
91
+ const location = window.location
71
92
  this.iframeLoad(`${baseURL}`
72
93
  + `?pageKey=${encodeURIComponent(this.conf.pageKey)}`
73
94
  + `&site=${encodeURIComponent(this.conf.site || '')}`
74
95
  + `&user=${userData}`
96
+ + `&referer=${encodeURIComponent(`${location.protocol}//${location.host}${location.pathname}`)}`
75
97
  + `${((this.conf.darkMode) ? `&darkMode=1` : ``)}`)
76
98
 
77
99
  this.$iframeWrap.append(this.$iframe)
package/src/context.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { marked as libMarked } from 'marked'
1
2
  import User from './lib/user'
2
3
  import ArtalkConfig from '~/types/artalk-config'
3
4
  import { EventPayloadMap, Event, EventScopeType, Handler } from '~/types/event'
@@ -39,4 +40,6 @@ export default class Context {
39
40
  .map((evt) => evt.handler)
40
41
  .forEach((handler) => handler(payload as any))
41
42
  }
43
+
44
+ public markedInstance!: typeof libMarked
42
45
  }
package/src/defaults.ts CHANGED
@@ -17,6 +17,7 @@ const defaults: ArtalkConfig = {
17
17
  vote: true,
18
18
  voteDown: false,
19
19
  uaBadge: true,
20
+ listSort: true,
20
21
  pvEl: '#ArtalkPV',
21
22
  flatMode: 'auto',
22
23
  maxNesting: 3,
@@ -5,7 +5,7 @@ import { Checker } from '.'
5
5
  const AdminChecker: Checker = {
6
6
  inputType: 'password',
7
7
 
8
- request(that, inputVal) {
8
+ request(that, ctx, inputVal) {
9
9
  const data = {
10
10
  name: that.ctx.user.data.nick,
11
11
  email: that.ctx.user.data.email,
@@ -15,11 +15,11 @@ const AdminChecker: Checker = {
15
15
  return new Api(that.ctx).login(data.name, data.email, data.password)
16
16
  },
17
17
 
18
- body() {
18
+ body(that, ctx) {
19
19
  return Utils.createElement('<span>敲入密码来验证管理员身份:</span>')
20
20
  },
21
21
 
22
- onSuccess(that, userToken, inputVal, formEl) {
22
+ onSuccess(that, ctx, userToken, inputVal, formEl) {
23
23
  that.ctx.user.data.isAdmin = true
24
24
  that.ctx.user.data.token = userToken
25
25
  that.ctx.user.save()
@@ -27,7 +27,7 @@ const AdminChecker: Checker = {
27
27
  that.ctx.trigger('list-reload')
28
28
  },
29
29
 
30
- onError(that, err, inputVal, formEl) {}
30
+ onError(that, ctx, err, inputVal, formEl) {}
31
31
  }
32
32
 
33
33
  export default AdminChecker
@@ -1,15 +1,61 @@
1
1
  import Api from '@/api'
2
2
  import * as Utils from '../utils'
3
+ import * as Ui from '../ui'
3
4
  import { Checker } from '.'
4
5
 
5
6
  const CaptchaChecker: Checker = {
6
- request(that, inputVal) {
7
+ request(that, ctx, inputVal) {
7
8
  return new Api(that.ctx).captchaCheck(inputVal)
8
9
  },
9
10
 
10
- body(that) {
11
+ body(that, ctx) {
12
+ // Iframe 验证方式
13
+ if (that.captchaConf.iframe) {
14
+ const $iframeWrap = Utils.createElement(`<div class="atk-checker-iframe-wrap"></div>`)
15
+ const $iframe = Utils.createElement<HTMLIFrameElement>(`<iframe class="atk-fade-in"></iframe>`)
16
+ $iframe.style.display = 'none'
17
+ Ui.showLoading($iframeWrap, { transparentBg: true })
18
+ $iframe.src = `${that.ctx.conf.server}/captcha/get`
19
+ $iframe.onload = () => {
20
+ $iframe.style.display = ''
21
+ Ui.hideLoading($iframeWrap)
22
+ }
23
+ $iframeWrap.append($iframe)
24
+
25
+ const $closeBtn = Utils.createElement(`<div class="atk-close-btn"><i class="atk-icon atk-icon-close"></i></div>`)
26
+ $iframeWrap.append($closeBtn)
27
+
28
+ ctx.hideInteractInput()
29
+
30
+ // 轮询状态
31
+ let stop = false // 打断
32
+ const sleep = (ms: number) => new Promise((resolve) => { window.setTimeout(() => { resolve(null) }, ms) })
33
+ ;(async function queryStatus() {
34
+ await sleep(1000)
35
+ if (stop) return
36
+ let isPass = false
37
+ try {
38
+ const resp = await new Api(that.ctx).captchaStatus()
39
+ isPass = resp.is_pass
40
+ } catch { isPass = false }
41
+ if (isPass) {
42
+ ctx.triggerSuccess()
43
+ } else {
44
+ queryStatus()
45
+ }
46
+ })()
47
+
48
+ $closeBtn.onclick = () => {
49
+ stop = true
50
+ ctx.cancel()
51
+ }
52
+
53
+ return $iframeWrap
54
+ }
55
+
56
+ // 图片验证方式
11
57
  const elem = Utils.createElement(
12
- `<span><img class="atk-captcha-img" src="${that.submitCaptchaImgData || ''}" alt="验证码">敲入验证码继续:</span>`
58
+ `<span><img class="atk-captcha-img" src="${that.captchaConf.imgData || ''}" alt="验证码">敲入验证码继续:</span>`
13
59
  );
14
60
 
15
61
  // 刷新验证码
@@ -26,11 +72,11 @@ const CaptchaChecker: Checker = {
26
72
  return elem
27
73
  },
28
74
 
29
- onSuccess(that, data, inputVal, formEl) {
30
- that.submitCaptchaVal = inputVal
75
+ onSuccess(that, ctx, data, inputVal, formEl) {
76
+ that.captchaConf.val = inputVal
31
77
  },
32
78
 
33
- onError(that, err, inputVal, formEl) {
79
+ onError(that, ctx, err, inputVal, formEl) {
34
80
  formEl.querySelector<HTMLElement>('.atk-captcha-img')!.click() // 刷新验证码
35
81
  }
36
82
  }