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/LICENSE +165 -0
- package/dist/Artalk.css +1 -1
- package/dist/Artalk.es.js +255 -107
- package/dist/Artalk.iife.js +1 -1
- package/dist/Artalk.js +1 -1
- package/package.json +14 -13
- package/src/api/index.ts +14 -2
- package/src/api/request.ts +1 -0
- package/src/artalk.ts +17 -0
- package/src/components/editor.ts +48 -22
- package/src/components/layer.ts +19 -8
- package/src/components/list-lite.ts +12 -10
- package/src/components/list.ts +5 -2
- package/src/components/sidebar-layer.ts +24 -2
- package/src/context.ts +3 -0
- package/src/defaults.ts +1 -0
- package/src/lib/checker/admin-checker.ts +4 -4
- package/src/lib/checker/captcha-checker.ts +52 -6
- package/src/lib/checker/index.ts +65 -23
- package/src/lib/ui.ts +2 -1
- package/src/lib/utils.ts +56 -35
- package/src/style/comment.less +3 -0
- package/src/style/list.less +4 -1
- package/src/style/main.less +35 -0
- package/tsconfig.json +1 -16
- package/types/artalk-config.d.ts +3 -0
- package/types/event.d.ts +1 -0
- package/types/vite-env.d.ts +0 -1
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "artalk",
|
|
3
|
-
"version": "2.1
|
|
4
|
-
"
|
|
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
|
|
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
|
|
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
|
}
|
package/src/api/request.ts
CHANGED
|
@@ -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
|
}
|
package/src/components/editor.ts
CHANGED
|
@@ -247,7 +247,7 @@ export default class Editor extends Component {
|
|
|
247
247
|
})
|
|
248
248
|
})
|
|
249
249
|
|
|
250
|
-
this.
|
|
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
|
-
|
|
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
|
-
|
|
303
|
+
const files = evt.dataTransfer?.files
|
|
304
|
+
if (files?.length) {
|
|
292
305
|
evt.preventDefault()
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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 =
|
|
345
|
+
const uploadPlaceholderTxt = `${insertPrefix}`
|
|
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
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
}
|
|
358
|
+
|
|
359
|
+
// 若为相对路径,加上 artalk server
|
|
360
|
+
if (!Utils.isValidURL(imgURL)) imgURL = Utils.getURLBasedOnApi(this.ctx, imgURL)
|
|
333
361
|
|
|
334
362
|
// 上传成功插入图片
|
|
335
|
-
this
|
|
363
|
+
this.setContent(this.$textarea.value.replace(uploadPlaceholderTxt, `${insertPrefix}`))
|
|
336
364
|
} else {
|
|
337
365
|
// 上传失败删除加载文字
|
|
338
|
-
this
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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,
|
package/src/components/layer.ts
CHANGED
|
@@ -65,12 +65,7 @@ export default class Layer extends Component {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
// body style 禁止滚动 + 防抖
|
|
68
|
-
this.
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
477
|
-
const
|
|
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
|
|
482
|
-
+
|
|
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);"
|
|
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.
|
|
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.
|
|
496
|
+
Ui.setError(this.$el.parentElement!, errEl, '<span class="atk-warn-title">Artalk Warn</span>')
|
|
495
497
|
}
|
|
496
498
|
|
|
497
499
|
return needUpdate
|
package/src/components/list.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
:
|
|
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
|
@@ -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.
|
|
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.
|
|
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
|
}
|