artalk 2.2.11 → 2.2.12
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/dist/Artalk.css +1 -1
- package/dist/Artalk.es.js +460 -91
- package/dist/Artalk.iife.js +1 -1
- package/dist/Artalk.js +1 -1
- package/index.html +3 -1
- package/package.json +4 -3
- package/src/api/index.ts +4 -3
- package/src/api/request.ts +49 -80
- package/src/artalk.ts +15 -7
- package/src/components/editor.ts +14 -11
- package/src/components/list-lite.ts +6 -14
- package/src/defaults.ts +1 -1
- package/types/artalk-config.d.ts +2 -2
- package/vite.config.ts +0 -6
package/index.html
CHANGED
|
@@ -38,7 +38,9 @@
|
|
|
38
38
|
<script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
|
|
39
39
|
<script>
|
|
40
40
|
// VConsole will be exported to `window.VConsole` by default.
|
|
41
|
-
|
|
41
|
+
if (navigator.userAgentData.mobile) {
|
|
42
|
+
var vConsole = new window.VConsole();
|
|
43
|
+
}
|
|
42
44
|
</script>
|
|
43
45
|
</head>
|
|
44
46
|
<body>
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "artalk",
|
|
3
|
-
"version": "2.2.
|
|
4
|
-
"backendMinVersion": "2.1.
|
|
3
|
+
"version": "2.2.12",
|
|
4
|
+
"backendMinVersion": "2.1.6",
|
|
5
5
|
"license": "LGPL-3.0",
|
|
6
6
|
"description": "A Selfhosted Comment System",
|
|
7
7
|
"keywords": [
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"hanabi": "^0.4.0",
|
|
31
31
|
"insane": "^2.6.2",
|
|
32
|
-
"marked": "^4.0.14"
|
|
32
|
+
"marked": "^4.0.14",
|
|
33
|
+
"abortcontroller-polyfill": "^1.7.3"
|
|
33
34
|
},
|
|
34
35
|
"scripts": {
|
|
35
36
|
"dev": "vite",
|
package/src/api/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ import ArtalkConfig from '~/types/artalk-config'
|
|
|
3
3
|
import Context from '../context'
|
|
4
4
|
import { Fetch, ToFormData, POST, GET } from './request'
|
|
5
5
|
import * as Utils from '../lib/utils'
|
|
6
|
+
import 'abortcontroller-polyfill/dist/polyfill-patch-fetch'
|
|
6
7
|
|
|
7
8
|
export default class Api {
|
|
8
9
|
private ctx: Context
|
|
@@ -95,8 +96,6 @@ export default class Api {
|
|
|
95
96
|
/** 用户 · 获取 */
|
|
96
97
|
public userGet(name: string, email: string) {
|
|
97
98
|
const ctrl = new AbortController()
|
|
98
|
-
const { signal } = ctrl
|
|
99
|
-
|
|
100
99
|
const params: any = {
|
|
101
100
|
name, email, site_name: (this.ctx.conf.site || '')
|
|
102
101
|
}
|
|
@@ -104,7 +103,7 @@ export default class Api {
|
|
|
104
103
|
const req = Fetch(this.ctx, `${this.baseURL}/user-get`, {
|
|
105
104
|
method: 'POST',
|
|
106
105
|
body: ToFormData(params),
|
|
107
|
-
signal,
|
|
106
|
+
signal: ctrl.signal,
|
|
108
107
|
}).then((json) => ({
|
|
109
108
|
user: json.data.user as UserData|null,
|
|
110
109
|
is_login: json.data.is_login as boolean,
|
|
@@ -307,6 +306,8 @@ export default class Api {
|
|
|
307
306
|
conf.emoticons = conf.emoticons.trim()
|
|
308
307
|
if (conf.emoticons.startsWith("[") || conf.emoticons.startsWith("{")) {
|
|
309
308
|
conf.emoticons = JSON.parse(conf.emoticons) // pase json
|
|
309
|
+
} else if (conf.emoticons === "false") {
|
|
310
|
+
conf.emoticons = false
|
|
310
311
|
}
|
|
311
312
|
}
|
|
312
313
|
|
package/src/api/request.ts
CHANGED
|
@@ -4,81 +4,55 @@ import Context from '../context'
|
|
|
4
4
|
export async function Fetch(ctx: Context, input: RequestInfo, init: RequestInit, timeout?: number): Promise<any> {
|
|
5
5
|
// JWT
|
|
6
6
|
if (ctx.user.data.token) {
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
init.headers = requestHeaders
|
|
7
|
+
const headers = new Headers(init.headers) // 保留原有 headers
|
|
8
|
+
headers.set('Authorization', `Bearer ${ctx.user.data.token}`)
|
|
9
|
+
init.headers = headers
|
|
11
10
|
}
|
|
12
11
|
|
|
13
12
|
// 请求操作
|
|
14
|
-
|
|
13
|
+
const resp = await timeoutFetch(input, timeout || ctx.conf.reqTimeout || 15000, init)
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
} else {
|
|
20
|
-
// 请求超时检测
|
|
21
|
-
resp = await timeoutPromise(timeout || ctx.conf.reqTimeout || 15000, fetch(input, init))
|
|
22
|
-
}
|
|
15
|
+
const respHttpCode = resp.status
|
|
16
|
+
const noAccessCodes = [401, 400]
|
|
17
|
+
const isNoAccess = noAccessCodes.includes(respHttpCode)
|
|
23
18
|
|
|
24
|
-
|
|
19
|
+
if (!resp.ok && !isNoAccess)
|
|
20
|
+
throw new Error(`请求响应 ${respHttpCode}`)
|
|
25
21
|
|
|
26
|
-
|
|
22
|
+
// 解析获取响应的 json
|
|
23
|
+
let json: any = await resp.json()
|
|
27
24
|
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
// 重新发起请求
|
|
26
|
+
const recall = (resolve, reject) => {
|
|
27
|
+
Fetch(ctx, input, init)
|
|
28
|
+
.then(d => { resolve(d) })
|
|
29
|
+
.catch(e => { reject(e) })
|
|
30
|
+
}
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
// 请求弹出层验证
|
|
33
|
+
if (json.data?.need_captcha) {
|
|
34
|
+
// 请求需要验证码
|
|
35
|
+
json = await (new Promise<any>((resolve, reject) => {
|
|
36
|
+
ctx.trigger('checker-captcha', {
|
|
37
|
+
imgData: json.data.img_data,
|
|
38
|
+
iframe: json.data.iframe,
|
|
39
|
+
onSuccess: () => { recall(resolve, reject) },
|
|
40
|
+
onCancel: () => { reject(json) }
|
|
37
41
|
})
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
recall(resolve, reject)
|
|
49
|
-
},
|
|
50
|
-
onCancel: () => {
|
|
51
|
-
reject(json)
|
|
52
|
-
}
|
|
53
|
-
})
|
|
54
|
-
}))
|
|
55
|
-
} else if ((json.data && json.data.need_login) || noAccessCodes.includes(resp.status)) {
|
|
56
|
-
// 请求需要管理员权限
|
|
57
|
-
json = await (new Promise<any>((resolve, reject) => {
|
|
58
|
-
ctx.trigger('checker-admin', {
|
|
59
|
-
onSuccess: () => {
|
|
60
|
-
recall(resolve, reject)
|
|
61
|
-
},
|
|
62
|
-
onCancel: () => {
|
|
63
|
-
reject(json)
|
|
64
|
-
}
|
|
65
|
-
})
|
|
66
|
-
}))
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (!json.success) throw json // throw 相当于 reject(json)
|
|
70
|
-
|
|
71
|
-
return json
|
|
72
|
-
|
|
73
|
-
} catch (err) {
|
|
74
|
-
// 错误处理
|
|
75
|
-
console.error(err)
|
|
42
|
+
}))
|
|
43
|
+
} else if (json.data?.need_login || isNoAccess) {
|
|
44
|
+
// 请求需要管理员权限
|
|
45
|
+
json = await (new Promise<any>((resolve, reject) => {
|
|
46
|
+
ctx.trigger('checker-admin', {
|
|
47
|
+
onSuccess: () => { recall(resolve, reject) },
|
|
48
|
+
onCancel: () => { reject(json) }
|
|
49
|
+
})
|
|
50
|
+
}))
|
|
51
|
+
}
|
|
76
52
|
|
|
77
|
-
|
|
78
|
-
throw new Error(`网络错误`)
|
|
53
|
+
if (!json.success) throw json // throw 相当于 reject(json)
|
|
79
54
|
|
|
80
|
-
|
|
81
|
-
}
|
|
55
|
+
return json
|
|
82
56
|
}
|
|
83
57
|
|
|
84
58
|
/** 公共 POST 请求 */
|
|
@@ -108,21 +82,16 @@ export function ToFormData(object: {[key: string]: any}): FormData {
|
|
|
108
82
|
}
|
|
109
83
|
|
|
110
84
|
/** 我靠,fetch 一个 timeout,都要丑陋的实现 */
|
|
111
|
-
function
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
promise.
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
},
|
|
122
|
-
(err) => {
|
|
123
|
-
clearTimeout(timeoutId)
|
|
124
|
-
reject(err)
|
|
125
|
-
}
|
|
126
|
-
)
|
|
85
|
+
function timeoutFetch(url: RequestInfo, ms: number, opts: RequestInit) {
|
|
86
|
+
const controller = new AbortController()
|
|
87
|
+
opts.signal?.addEventListener('abort', () => controller.abort()) // 保留原有 signal 功能
|
|
88
|
+
let promise = fetch(url, { ...opts, signal: controller.signal })
|
|
89
|
+
if (ms > 0) {
|
|
90
|
+
const timer = setTimeout(() => controller.abort(), ms)
|
|
91
|
+
promise.finally(() => { clearTimeout(timer) })
|
|
92
|
+
}
|
|
93
|
+
promise = promise.catch((err) => {
|
|
94
|
+
throw ((err || {}).name === 'AbortError') ? new Error('请求超时或意外终止') : err
|
|
127
95
|
})
|
|
96
|
+
return promise
|
|
128
97
|
}
|
package/src/artalk.ts
CHANGED
|
@@ -186,8 +186,23 @@ export default class Artalk {
|
|
|
186
186
|
|
|
187
187
|
/** 暗黑模式 · 初始化 */
|
|
188
188
|
public initDarkMode() {
|
|
189
|
+
if (this.conf.darkMode === 'auto') {
|
|
190
|
+
// 自动切换暗黑模式,事件监听
|
|
191
|
+
const darkModeMedia = window.matchMedia('(prefers-color-scheme: dark)')
|
|
192
|
+
darkModeMedia.addEventListener('change', (e) => { this.setDarkMode(e.matches) })
|
|
193
|
+
this.setDarkMode(darkModeMedia.matches)
|
|
194
|
+
} else {
|
|
195
|
+
this.setDarkMode(this.conf.darkMode || false)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** 暗黑模式 · 设定 */
|
|
200
|
+
public setDarkMode(darkMode: boolean) {
|
|
189
201
|
const darkModeClassName = 'atk-dark-mode'
|
|
190
202
|
|
|
203
|
+
this.ctx.conf.darkMode = darkMode
|
|
204
|
+
this.ctx.trigger('conf-updated')
|
|
205
|
+
|
|
191
206
|
if (this.conf.darkMode) {
|
|
192
207
|
this.$root.classList.add(darkModeClassName)
|
|
193
208
|
} else {
|
|
@@ -205,13 +220,6 @@ export default class Artalk {
|
|
|
205
220
|
}
|
|
206
221
|
}
|
|
207
222
|
|
|
208
|
-
/** 暗黑模式 · 设定 */
|
|
209
|
-
public setDarkMode(darkMode: boolean) {
|
|
210
|
-
this.ctx.conf.darkMode = darkMode
|
|
211
|
-
this.ctx.trigger('conf-updated')
|
|
212
|
-
this.initDarkMode()
|
|
213
|
-
}
|
|
214
|
-
|
|
215
223
|
/** PV */
|
|
216
224
|
public async initPV() {
|
|
217
225
|
const pvNum = await new Api(this.ctx).pv()
|
package/src/components/editor.ts
CHANGED
|
@@ -153,6 +153,7 @@ export default class Editor extends Component {
|
|
|
153
153
|
this.getInputEl('link')!.value = data.user.link
|
|
154
154
|
}
|
|
155
155
|
})
|
|
156
|
+
.catch(() => {})
|
|
156
157
|
.finally(() => {
|
|
157
158
|
this.queryUserInfo.abortFunc = null // 清理
|
|
158
159
|
})
|
|
@@ -213,22 +214,14 @@ export default class Editor extends Component {
|
|
|
213
214
|
this.openedPlugName = null
|
|
214
215
|
this.$plugBtnWrap.innerHTML = ''
|
|
215
216
|
|
|
216
|
-
//
|
|
217
|
+
// 初始化 plug 按钮
|
|
217
218
|
this.LOADABLE_PLUG_LIST.forEach((PlugObj) => {
|
|
219
|
+
if (PlugObj.Name === 'emoticons' && !this.conf.emoticons) return
|
|
220
|
+
|
|
218
221
|
// 切换按钮
|
|
219
222
|
const btnElem = Utils.createElement(`<span class="atk-plug-btn" data-plug-name="${PlugObj.Name}">${PlugObj.BtnHTML}</span>`)
|
|
220
223
|
this.$plugBtnWrap.appendChild(btnElem)
|
|
221
224
|
|
|
222
|
-
// 表情包插件预加载
|
|
223
|
-
if (PlugObj.Name === 'emoticons') {
|
|
224
|
-
const emoPlug = new PlugObj(this) as EmoticonsPlug
|
|
225
|
-
this.plugList[PlugObj.Name] = emoPlug
|
|
226
|
-
|
|
227
|
-
window.setTimeout(() => {
|
|
228
|
-
emoPlug.loadEmoticonsData()
|
|
229
|
-
}, 1000) // 延迟 1s 加载
|
|
230
|
-
}
|
|
231
|
-
|
|
232
225
|
btnElem.addEventListener('click', () => {
|
|
233
226
|
let plug = this.plugList[PlugObj.Name]
|
|
234
227
|
if (!plug) {
|
|
@@ -272,6 +265,16 @@ export default class Editor extends Component {
|
|
|
272
265
|
})
|
|
273
266
|
})
|
|
274
267
|
|
|
268
|
+
// 表情包插件预加载
|
|
269
|
+
if (this.conf.emoticons) {
|
|
270
|
+
const emoPlug = new EmoticonsPlug(this)
|
|
271
|
+
this.plugList[EmoticonsPlug.Name] = emoPlug
|
|
272
|
+
|
|
273
|
+
window.setTimeout(() => {
|
|
274
|
+
emoPlug.loadEmoticonsData()
|
|
275
|
+
}, 1000) // 延迟 1s 加载
|
|
276
|
+
}
|
|
277
|
+
|
|
275
278
|
this.initImgUpload()
|
|
276
279
|
}
|
|
277
280
|
|
|
@@ -308,6 +308,12 @@ export default class ListLite extends Component {
|
|
|
308
308
|
// 子评论查找回复对象
|
|
309
309
|
comment.replyTo = (cData.rid ? ctxData.find(c => c.id === cData.rid) : undefined)
|
|
310
310
|
|
|
311
|
+
// 渲染元素
|
|
312
|
+
comment.render()
|
|
313
|
+
|
|
314
|
+
// 放入 comment 总表中
|
|
315
|
+
this.commentList.push(comment)
|
|
316
|
+
|
|
311
317
|
return comment
|
|
312
318
|
}
|
|
313
319
|
|
|
@@ -328,28 +334,21 @@ export default class ListLite extends Component {
|
|
|
328
334
|
const rootNodes = ListNest.makeNestCommentNodeList(srcData, this.nestSortBy, this.conf.nestMax)
|
|
329
335
|
rootNodes.forEach((rootNode: ListNest.CommentNode) => {
|
|
330
336
|
const rootC = this.createComment(rootNode.comment, srcData)
|
|
331
|
-
rootC.render()
|
|
332
337
|
|
|
333
338
|
// 显示并播放渐入动画
|
|
334
339
|
this.$commentsWrap.appendChild(rootC.getEl())
|
|
335
340
|
rootC.playFadeInAnim()
|
|
336
341
|
|
|
337
|
-
// 评论放入 comments 总表中
|
|
338
|
-
this.commentList.push(rootC)
|
|
339
|
-
|
|
340
342
|
// 加载子评论
|
|
341
343
|
const that = this
|
|
342
344
|
;(function loadChildren(parentC: Comment, parentNode: ListNest.CommentNode) {
|
|
343
345
|
parentNode.children.forEach((node: ListNest.CommentNode) => {
|
|
344
346
|
const childD = node.comment
|
|
345
347
|
const childC = that.createComment(childD, srcData)
|
|
346
|
-
childC.render()
|
|
347
348
|
|
|
348
349
|
// 插入到父评论中
|
|
349
350
|
parentC.putChild(childC)
|
|
350
351
|
|
|
351
|
-
that.commentList.push(childC)
|
|
352
|
-
|
|
353
352
|
// 递归加载子评论
|
|
354
353
|
loadChildren(childC, node)
|
|
355
354
|
})
|
|
@@ -364,10 +363,6 @@ export default class ListLite extends Component {
|
|
|
364
363
|
private putCommentFlatMode(cData: CommentData, ctxData: CommentData[], insertMode: 'append'|'prepend') {
|
|
365
364
|
if (cData.is_collapsed) cData.is_allow_reply = false
|
|
366
365
|
const comment = this.createComment(cData, ctxData)
|
|
367
|
-
comment.render()
|
|
368
|
-
|
|
369
|
-
// 将评论导入 comments 总表中
|
|
370
|
-
this.commentList.push(comment)
|
|
371
366
|
|
|
372
367
|
// 可见评论添加到界面
|
|
373
368
|
// 注:不可见评论用于显示 “引用内容”
|
|
@@ -391,14 +386,11 @@ export default class ListLite extends Component {
|
|
|
391
386
|
|
|
392
387
|
if (commentData.rid === 0) {
|
|
393
388
|
// root评论 新增
|
|
394
|
-
comment.render()
|
|
395
389
|
this.$commentsWrap.prepend(comment.getEl())
|
|
396
|
-
this.commentList.push(comment)
|
|
397
390
|
} else {
|
|
398
391
|
// 子评论 新增
|
|
399
392
|
const parent = this.findComment(commentData.rid)
|
|
400
393
|
if (parent) {
|
|
401
|
-
comment.render()
|
|
402
394
|
parent.putChild(comment, (this.nestSortBy === 'DATE_ASC' ? 'append' : 'prepend'))
|
|
403
395
|
|
|
404
396
|
// 若父评论存在 “子评论部分” 限高,取消限高
|
package/src/defaults.ts
CHANGED
package/types/artalk-config.d.ts
CHANGED
|
@@ -27,7 +27,7 @@ export default interface ArtalkConfig {
|
|
|
27
27
|
editorTravel?: boolean
|
|
28
28
|
|
|
29
29
|
/** 表情包 */
|
|
30
|
-
emoticons?: any
|
|
30
|
+
emoticons?: object|any[]|string|false
|
|
31
31
|
|
|
32
32
|
/** Gravatar 头像 */
|
|
33
33
|
gravatar?: {
|
|
@@ -66,7 +66,7 @@ export default interface ArtalkConfig {
|
|
|
66
66
|
pvEl?: string
|
|
67
67
|
|
|
68
68
|
/** 暗黑模式 */
|
|
69
|
-
darkMode?: boolean
|
|
69
|
+
darkMode?: boolean|'auto'
|
|
70
70
|
|
|
71
71
|
/** 请求超时(单位:秒) */
|
|
72
72
|
reqTimeout?: number
|
package/vite.config.ts
CHANGED
|
@@ -15,18 +15,12 @@ export default defineConfig({
|
|
|
15
15
|
formats: ["es", "umd", "iife"]
|
|
16
16
|
},
|
|
17
17
|
rollupOptions: {
|
|
18
|
-
// 确保外部化处理那些你不想打包进库的依赖
|
|
19
|
-
external: ['vue'],
|
|
20
18
|
output: {
|
|
21
19
|
assetFileNames: assetInfo => {
|
|
22
20
|
if (/\.css$/.test(assetInfo.name)) {
|
|
23
21
|
return 'Artalk.css'
|
|
24
22
|
}
|
|
25
23
|
return "[name].[ext]"
|
|
26
|
-
},
|
|
27
|
-
// 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量
|
|
28
|
-
globals: {
|
|
29
|
-
vue: 'Vue'
|
|
30
24
|
}
|
|
31
25
|
}
|
|
32
26
|
}
|