artalk 2.2.11 → 2.3.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/dist/Artalk.css +1 -1
- package/dist/Artalk.es.js +1872 -1143
- package/dist/Artalk.iife.js +1 -1
- package/dist/Artalk.js +1 -1
- package/dist/ArtalkLite.css +1 -0
- package/dist/ArtalkLite.es.js +5123 -0
- package/dist/ArtalkLite.iife.js +1 -0
- package/dist/ArtalkLite.js +1 -0
- package/index.html +5 -3
- package/package.json +7 -5
- package/src/api/index.ts +24 -4
- package/src/api/request.ts +50 -81
- package/src/artalk.ts +131 -127
- package/src/comment/actions.ts +86 -0
- package/src/{components/html → comment}/comment.html +1 -1
- package/src/comment/comment.ts +191 -0
- package/src/comment/index.ts +3 -0
- package/src/comment/render.ts +425 -0
- package/src/components/action-btn.ts +6 -3
- package/src/components/dialog.ts +7 -3
- package/src/components/read-more-btn.ts +6 -2
- package/src/context.ts +204 -13
- package/src/defaults.ts +15 -4
- package/src/editor/editor.html +18 -0
- package/src/editor/editor.ts +419 -0
- package/src/editor/index.ts +3 -0
- package/src/editor/plugs/editor-plug.ts +51 -0
- package/src/{components/editor-plugs → editor/plugs}/emoticons-plug.less +2 -1
- package/src/{components/editor-plugs → editor/plugs}/emoticons-plug.ts +65 -63
- package/src/editor/plugs/header-input-plug.ts +78 -0
- package/src/{components/editor-plugs → editor/plugs}/preview-plug.less +0 -0
- package/src/editor/plugs/preview-plug.ts +40 -0
- package/src/editor/plugs/upload-plug.ts +124 -0
- package/src/i18n/en.ts +78 -0
- package/src/i18n/index.ts +21 -0
- package/src/i18n/zh-CN.ts +82 -0
- package/src/layer/index.ts +4 -0
- package/src/{components → layer}/layer.ts +9 -9
- package/src/{components/html → layer}/sidebar-layer.html +0 -0
- package/src/{components → layer}/sidebar-layer.ts +17 -47
- package/src/lib/checker/admin-checker.ts +7 -7
- package/src/lib/checker/captcha-checker.ts +5 -6
- package/src/lib/checker/index.ts +22 -17
- package/src/lib/component.ts +7 -2
- package/src/lib/stat.ts +63 -0
- package/src/lib/ui.ts +3 -10
- package/src/lib/user.ts +20 -3
- package/src/lib/utils.ts +23 -9
- package/src/list/index.ts +3 -0
- package/src/{components → list}/list-lite.ts +83 -111
- package/src/{components → list}/list-nest.ts +0 -0
- package/src/{components/html → list}/list.html +3 -6
- package/src/{components → list}/list.ts +44 -42
- package/src/style/_variables.less +1 -2
- package/src/style/comment.less +26 -13
- package/src/style/editor.less +22 -14
- package/src/style/list.less +6 -9
- package/src/style/main.less +9 -8
- package/src/style/pagination.less +1 -1
- package/test/ArtalkCount.html +16 -0
- package/test/ArtalkI18n.html +15 -0
- package/test/ArtalkLite.html +15 -0
- package/types/artalk-config.d.ts +50 -32
- package/types/context.d.ts +128 -0
- package/types/event.d.ts +6 -66
- package/types/global.d.ts +1 -0
- package/types/plug.d.ts +4 -0
- package/{vite-common.config.ts → vite-base.config.ts} +6 -4
- package/vite-demo.config.ts +2 -4
- package/vite-lite.config.ts +22 -0
- package/vite.config.ts +9 -18
- package/src/components/_component.ts.txt +0 -10
- package/src/components/comment.ts +0 -605
- package/src/components/editor-plugs/_example-plug.ts.txt +0 -29
- package/src/components/editor-plugs/editor-plug.ts +0 -20
- package/src/components/editor-plugs/preview-plug.ts +0 -48
- package/src/components/editor.ts +0 -604
- package/src/components/html/editor.html +0 -18
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "artalk",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"backendMinVersion": "2.1.
|
|
3
|
+
"version": "2.3.1",
|
|
4
|
+
"backendMinVersion": "2.1.7",
|
|
5
5
|
"license": "LGPL-3.0",
|
|
6
6
|
"description": "A Selfhosted Comment System",
|
|
7
7
|
"keywords": [
|
|
@@ -24,16 +24,18 @@
|
|
|
24
24
|
"module": "./dist/Artalk.es.js",
|
|
25
25
|
"types": "./types/index.d.ts",
|
|
26
26
|
"devDependencies": {
|
|
27
|
-
"@types/marked": "
|
|
27
|
+
"@types/marked": "4.0.3"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"hanabi": "^0.4.0",
|
|
31
31
|
"insane": "^2.6.2",
|
|
32
|
-
"marked": "^4.0.
|
|
32
|
+
"marked": "^4.0.16",
|
|
33
|
+
"abortcontroller-polyfill": "^1.7.3"
|
|
33
34
|
},
|
|
34
35
|
"scripts": {
|
|
35
36
|
"dev": "vite",
|
|
36
|
-
"build": "pnpm lint && vite build",
|
|
37
|
+
"build": "pnpm lint && vite build && pnpm build:lite",
|
|
38
|
+
"build:lite": "vite build --config vite-lite.config.ts",
|
|
37
39
|
"build:demo": "vite build --config vite-demo.config.ts",
|
|
38
40
|
"serve": "vite preview",
|
|
39
41
|
"deploy": "gh-pages -d deploy",
|
package/src/api/index.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { CommentData, ListData, UserData, PageData, SiteData, NotifyData } from '~/types/artalk-data'
|
|
2
2
|
import ArtalkConfig from '~/types/artalk-config'
|
|
3
|
-
import Context from '
|
|
3
|
+
import Context from '~/types/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,
|
|
@@ -275,6 +274,25 @@ export default class Api {
|
|
|
275
274
|
return p.pv as number
|
|
276
275
|
}
|
|
277
276
|
|
|
277
|
+
/** 统计 */
|
|
278
|
+
public async stat(
|
|
279
|
+
type: 'latest_comments'|'latest_pages'|'pv_most_pages'|'comment_most_pages'|
|
|
280
|
+
'page_pv'|'site_pv'|'page_comment'|'site_comment',
|
|
281
|
+
pageKeys?: string|string[],
|
|
282
|
+
limit?: number
|
|
283
|
+
) {
|
|
284
|
+
const params: any = {
|
|
285
|
+
type,
|
|
286
|
+
site_name: this.ctx.conf.site || '',
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (pageKeys) params.page_keys = Array.isArray(pageKeys) ? pageKeys.join(',') : pageKeys
|
|
290
|
+
if (limit) params.limit = limit
|
|
291
|
+
|
|
292
|
+
const data = await POST<PageData[]|CommentData[]|object|number>(this.ctx, `${this.baseURL}/stat`, params)
|
|
293
|
+
return data
|
|
294
|
+
}
|
|
295
|
+
|
|
278
296
|
/** 图片上传 */
|
|
279
297
|
public async imgUpload(file: File) {
|
|
280
298
|
const params: any = {
|
|
@@ -307,6 +325,8 @@ export default class Api {
|
|
|
307
325
|
conf.emoticons = conf.emoticons.trim()
|
|
308
326
|
if (conf.emoticons.startsWith("[") || conf.emoticons.startsWith("{")) {
|
|
309
327
|
conf.emoticons = JSON.parse(conf.emoticons) // pase json
|
|
328
|
+
} else if (conf.emoticons === "false") {
|
|
329
|
+
conf.emoticons = false
|
|
310
330
|
}
|
|
311
331
|
}
|
|
312
332
|
|
package/src/api/request.ts
CHANGED
|
@@ -1,84 +1,58 @@
|
|
|
1
|
-
import Context from '
|
|
1
|
+
import Context from '~/types/context'
|
|
2
2
|
|
|
3
3
|
/** 公共请求函数 */
|
|
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(ctx, 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(`${ctx.$t('reqGot')} ${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.checkCaptcha({
|
|
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.checkAdmin({
|
|
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(ctx: Context, 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(ctx.$t('reqAborted')) : err
|
|
127
95
|
})
|
|
96
|
+
return promise
|
|
128
97
|
}
|
package/src/artalk.ts
CHANGED
|
@@ -2,28 +2,32 @@ import './style/main.less'
|
|
|
2
2
|
|
|
3
3
|
import ArtalkConfig from '~/types/artalk-config'
|
|
4
4
|
import { EventPayloadMap, Handler } from '~/types/event'
|
|
5
|
-
import
|
|
5
|
+
import ArtalkPlug from '~/types/plug'
|
|
6
|
+
import Context from '~/types/context'
|
|
7
|
+
import ConcreteContext from './context'
|
|
6
8
|
import defaults from './defaults'
|
|
9
|
+
import { internal as internalLocales } from './i18n'
|
|
7
10
|
|
|
8
11
|
import CheckerLauncher from './lib/checker'
|
|
9
|
-
import Editor from './
|
|
10
|
-
import List from './
|
|
11
|
-
import SidebarLayer from './
|
|
12
|
+
import Editor from './editor'
|
|
13
|
+
import List from './list'
|
|
14
|
+
import SidebarLayer from './layer/sidebar-layer'
|
|
12
15
|
|
|
13
|
-
import Layer, { GetLayerWrap } from './
|
|
14
|
-
import Api from './api'
|
|
16
|
+
import Layer, { GetLayerWrap } from './layer'
|
|
15
17
|
import * as Utils from './lib/utils'
|
|
16
18
|
import * as Ui from './lib/ui'
|
|
19
|
+
import * as Stat from './lib/stat'
|
|
17
20
|
|
|
18
21
|
/**
|
|
19
22
|
* Artalk
|
|
20
|
-
*
|
|
23
|
+
*
|
|
24
|
+
* @website https://artalk.js.org
|
|
21
25
|
*/
|
|
22
26
|
export default class Artalk {
|
|
23
27
|
public static readonly defaults: ArtalkConfig = defaults
|
|
24
28
|
|
|
25
|
-
public ctx!: Context
|
|
26
29
|
public conf!: ArtalkConfig
|
|
30
|
+
public ctx!: Context
|
|
27
31
|
public $root!: HTMLElement
|
|
28
32
|
|
|
29
33
|
public checkerLauncher!: CheckerLauncher
|
|
@@ -31,79 +35,49 @@ export default class Artalk {
|
|
|
31
35
|
public list!: List
|
|
32
36
|
public sidebarLayer!: SidebarLayer
|
|
33
37
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
this.conf.server = this.conf.server.replace(/\/$/, '').replace(/\/api\/?$/, '')
|
|
38
|
-
|
|
39
|
-
// 默认 pageKey
|
|
40
|
-
if (!this.conf.pageKey) {
|
|
41
|
-
// @link http://bl.ocks.org/abernier/3070589
|
|
42
|
-
this.conf.pageKey = `${window.location.pathname}`
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// 默认 pageTitle
|
|
46
|
-
if (!this.conf.pageTitle) {
|
|
47
|
-
this.conf.pageTitle = `${document.title}`
|
|
48
|
-
}
|
|
38
|
+
protected static Plugins: ArtalkPlug[] = [
|
|
39
|
+
Stat.PvCountWidget
|
|
40
|
+
]
|
|
49
41
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
42
|
+
constructor(customConf: Partial<ArtalkConfig>) {
|
|
43
|
+
/* 初始化基本配置 */
|
|
44
|
+
this.conf = Artalk.HandelBaseConf(customConf)
|
|
45
|
+
if (this.conf.el instanceof HTMLElement) this.$root = this.conf.el
|
|
54
46
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
this.$root = this.conf.el
|
|
58
|
-
} else {
|
|
59
|
-
try {
|
|
60
|
-
const $root = document.querySelector<HTMLElement>(this.conf.el)
|
|
61
|
-
if (!$root) throw Error(`Sorry, target element "${this.conf.el}" was not found.`)
|
|
62
|
-
this.$root = $root
|
|
63
|
-
} catch (e) {
|
|
64
|
-
console.error(e)
|
|
65
|
-
throw new Error('Please check your Artalk `el` config.')
|
|
66
|
-
}
|
|
67
|
-
}
|
|
47
|
+
/* 初始化 Context */
|
|
48
|
+
this.ctx = new ConcreteContext(this.conf, this.$root)
|
|
68
49
|
|
|
69
|
-
//
|
|
70
|
-
this.
|
|
71
|
-
|
|
72
|
-
// 界面初始化
|
|
73
|
-
this.$root.classList.add('artalk')
|
|
74
|
-
this.$root.innerHTML = ''
|
|
75
|
-
|
|
76
|
-
// 初始化组件
|
|
77
|
-
if (this.conf.useBackendConf) {
|
|
78
|
-
// 复用后端的配置
|
|
79
|
-
// @ts-ignore
|
|
80
|
-
return (async () => {
|
|
81
|
-
await this.loadConfRemote()
|
|
82
|
-
this.initCore()
|
|
83
|
-
return this // Promise<Artalk>
|
|
84
|
-
})()
|
|
85
|
-
}
|
|
50
|
+
// @ts-ignore 远程加载引用后端的配置
|
|
51
|
+
if (this.conf.useBackendConf) return this.loadConfRemoteAndInitComponents()
|
|
86
52
|
|
|
87
|
-
|
|
53
|
+
/* 初始化组件 */
|
|
54
|
+
this.initComponents()
|
|
88
55
|
}
|
|
89
56
|
|
|
90
|
-
|
|
91
|
-
|
|
57
|
+
/** 组件初始化 */
|
|
58
|
+
private initComponents() {
|
|
59
|
+
this.initLocale()
|
|
92
60
|
this.initLayer()
|
|
93
61
|
this.initDarkMode()
|
|
62
|
+
Utils.initMarked(this.ctx)
|
|
63
|
+
|
|
64
|
+
// CheckerLauncher
|
|
94
65
|
this.checkerLauncher = new CheckerLauncher(this.ctx)
|
|
95
|
-
|
|
66
|
+
this.ctx.setCheckerLauncher(this.checkerLauncher)
|
|
96
67
|
|
|
97
68
|
// 编辑器
|
|
98
69
|
this.editor = new Editor(this.ctx)
|
|
70
|
+
this.ctx.setEditor(this.editor)
|
|
99
71
|
this.$root.appendChild(this.editor.$el)
|
|
100
72
|
|
|
101
73
|
// 评论列表
|
|
102
74
|
this.list = new List(this.ctx)
|
|
75
|
+
this.ctx.setList(this.list)
|
|
103
76
|
this.$root.appendChild(this.list.$el)
|
|
104
77
|
|
|
105
78
|
// 侧边栏
|
|
106
79
|
this.sidebarLayer = new SidebarLayer(this.ctx)
|
|
80
|
+
this.ctx.setSidebarLayer(this.sidebarLayer)
|
|
107
81
|
this.$root.appendChild(this.sidebarLayer.$el)
|
|
108
82
|
|
|
109
83
|
// 评论获取
|
|
@@ -112,29 +86,66 @@ export default class Artalk {
|
|
|
112
86
|
// 事件绑定初始化
|
|
113
87
|
this.initEventBind()
|
|
114
88
|
|
|
115
|
-
// 其他
|
|
116
|
-
this.initPV()
|
|
117
|
-
|
|
118
89
|
// 插件初始化
|
|
119
90
|
Artalk.Plugins.forEach(plugin => {
|
|
120
|
-
if (typeof plugin === 'function')
|
|
91
|
+
if (typeof plugin === 'function')
|
|
121
92
|
plugin(this.ctx)
|
|
122
|
-
}
|
|
123
93
|
})
|
|
124
94
|
}
|
|
125
95
|
|
|
126
|
-
/**
|
|
96
|
+
/** 基本配置初始化 */
|
|
97
|
+
private static HandelBaseConf(customConf: Partial<ArtalkConfig>): ArtalkConfig {
|
|
98
|
+
// 合并默认配置
|
|
99
|
+
const conf: ArtalkConfig = Utils.mergeDeep(Artalk.defaults, customConf)
|
|
100
|
+
|
|
101
|
+
// 绑定元素
|
|
102
|
+
if (typeof conf.el === 'string' && !!conf.el) {
|
|
103
|
+
try {
|
|
104
|
+
const findEl = document.querySelector<HTMLElement>(conf.el)
|
|
105
|
+
if (!findEl) throw Error(`Target element "${conf.el}" was not found.`)
|
|
106
|
+
conf.el = findEl
|
|
107
|
+
} catch (e) {
|
|
108
|
+
console.error(e)
|
|
109
|
+
throw new Error('Please check your Artalk `el` config.')
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 服务器配置
|
|
114
|
+
conf.server = conf.server.replace(/\/$/, '').replace(/\/api\/?$/, '')
|
|
115
|
+
|
|
116
|
+
// 默认 pageKey
|
|
117
|
+
if (!conf.pageKey) {
|
|
118
|
+
// @link http://bl.ocks.org/abernier/3070589
|
|
119
|
+
conf.pageKey = `${window.location.pathname}`
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// 默认 pageTitle
|
|
123
|
+
if (!conf.pageTitle) {
|
|
124
|
+
conf.pageTitle = `${document.title}`
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return conf
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** 初始化组件根据远程加载的配置 */
|
|
131
|
+
private async loadConfRemoteAndInitComponents() {
|
|
132
|
+
await this.loadConfRemote()
|
|
133
|
+
this.initComponents()
|
|
134
|
+
return this
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** 远程加载配置 */
|
|
127
138
|
private async loadConfRemote() {
|
|
128
139
|
Ui.showLoading(this.$root)
|
|
129
140
|
let backendConf = {}
|
|
130
141
|
try {
|
|
131
|
-
backendConf = await
|
|
132
|
-
} catch (err) { console.error("
|
|
142
|
+
backendConf = await this.ctx.getApi().conf()
|
|
143
|
+
} catch (err) { console.error("Load config from remote err", err) }
|
|
133
144
|
this.ctx.conf = Utils.mergeDeep(this.ctx.conf, backendConf)
|
|
134
145
|
Ui.hideLoading(this.$root)
|
|
135
146
|
}
|
|
136
147
|
|
|
137
|
-
/**
|
|
148
|
+
/** 事件绑定初始化 */
|
|
138
149
|
private initEventBind() {
|
|
139
150
|
// 锚点快速跳转评论
|
|
140
151
|
window.addEventListener('hashchange', () => {
|
|
@@ -142,52 +153,58 @@ export default class Artalk {
|
|
|
142
153
|
this.list.checkGoToCommentByUrlHash()
|
|
143
154
|
})
|
|
144
155
|
|
|
145
|
-
// 仅管理员显示控制
|
|
146
|
-
this.ctx.on('check-admin-show-el', () => {
|
|
147
|
-
const items: HTMLElement[] = []
|
|
148
|
-
|
|
149
|
-
this.$root.querySelectorAll<HTMLElement>(`[atk-only-admin-show]`).forEach(item => items.push(item))
|
|
150
|
-
|
|
151
|
-
// for layer
|
|
152
|
-
const { $wrap: $layerWrap } = GetLayerWrap(this.ctx)
|
|
153
|
-
if ($layerWrap) $layerWrap.querySelectorAll<HTMLElement>(`[atk-only-admin-show]`).forEach(item => items.push(item))
|
|
154
|
-
|
|
155
|
-
// for sidebar
|
|
156
|
-
// TODO: 这个其实应该写在 packages/artalk-sidebar 里面的
|
|
157
|
-
const $sidebarEl = document.querySelector<HTMLElement>('.atk-sidebar')
|
|
158
|
-
if ($sidebarEl) $sidebarEl.querySelectorAll<HTMLElement>(`[atk-only-admin-show]`).forEach(item => items.push(item))
|
|
159
|
-
|
|
160
|
-
items.forEach(($item: HTMLElement) => {
|
|
161
|
-
if (this.ctx.user.data.isAdmin)
|
|
162
|
-
$item.classList.remove('atk-hide')
|
|
163
|
-
else
|
|
164
|
-
$item.classList.add('atk-hide')
|
|
165
|
-
})
|
|
166
|
-
})
|
|
167
|
-
|
|
168
156
|
// 本地用户数据变更
|
|
169
157
|
this.ctx.on('user-changed', () => {
|
|
170
|
-
this.ctx.
|
|
171
|
-
this.ctx.
|
|
158
|
+
this.ctx.checkAdminShowEl()
|
|
159
|
+
this.ctx.listRefreshUI()
|
|
172
160
|
})
|
|
173
161
|
}
|
|
174
162
|
|
|
175
|
-
/**
|
|
176
|
-
|
|
177
|
-
this.
|
|
163
|
+
/** 语言初始化 */
|
|
164
|
+
private initLocale() {
|
|
165
|
+
if (typeof this.conf.locale === 'string') {
|
|
166
|
+
if (this.conf.locale === 'auto') { // 自动切换
|
|
167
|
+
this.conf.locale = ((navigator.languages) ? navigator.languages[0] : navigator.language)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
this.conf.locale = this.conf.locale.replace(
|
|
171
|
+
/^([a-zA-Z]+)(-[a-zA-Z]+)?$/,
|
|
172
|
+
(_, p1: string, p2: string) => (p1.toLowerCase() + (p2 || '').toUpperCase())
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
if (!internalLocales[this.conf.locale]) { // 语言不存在时,使用 en
|
|
176
|
+
console.log(`Locale "${this.conf.locale}" not found.`)
|
|
177
|
+
this.conf.locale = 'en'
|
|
178
|
+
}
|
|
179
|
+
}
|
|
178
180
|
}
|
|
179
181
|
|
|
180
182
|
/** Layer 初始化 */
|
|
181
|
-
initLayer() {
|
|
183
|
+
private initLayer() {
|
|
182
184
|
// 记录页面原始 Styles
|
|
183
185
|
Layer.BodyOrgOverflow = document.body.style.overflow
|
|
184
186
|
Layer.BodyOrgPaddingRight = document.body.style.paddingRight
|
|
185
187
|
}
|
|
186
188
|
|
|
187
|
-
/**
|
|
188
|
-
|
|
189
|
+
/** 暗黑模式初始化 */
|
|
190
|
+
private initDarkMode() {
|
|
191
|
+
if (this.conf.darkMode === 'auto') {
|
|
192
|
+
// 自动切换暗黑模式,事件监听
|
|
193
|
+
const darkModeMedia = window.matchMedia('(prefers-color-scheme: dark)')
|
|
194
|
+
darkModeMedia.addEventListener('change', (e) => { this.setDarkMode(e.matches) })
|
|
195
|
+
this.setDarkMode(darkModeMedia.matches)
|
|
196
|
+
} else {
|
|
197
|
+
this.setDarkMode(this.conf.darkMode || false)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** 设置暗黑模式 */
|
|
202
|
+
public setDarkMode(darkMode: boolean) {
|
|
189
203
|
const darkModeClassName = 'atk-dark-mode'
|
|
190
204
|
|
|
205
|
+
this.ctx.conf.darkMode = darkMode
|
|
206
|
+
this.ctx.trigger('conf-updated')
|
|
207
|
+
|
|
191
208
|
if (this.conf.darkMode) {
|
|
192
209
|
this.$root.classList.add(darkModeClassName)
|
|
193
210
|
} else {
|
|
@@ -205,28 +222,6 @@ export default class Artalk {
|
|
|
205
222
|
}
|
|
206
223
|
}
|
|
207
224
|
|
|
208
|
-
/** 暗黑模式 · 设定 */
|
|
209
|
-
public setDarkMode(darkMode: boolean) {
|
|
210
|
-
this.ctx.conf.darkMode = darkMode
|
|
211
|
-
this.ctx.trigger('conf-updated')
|
|
212
|
-
this.initDarkMode()
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/** PV */
|
|
216
|
-
public async initPV() {
|
|
217
|
-
const pvNum = await new Api(this.ctx).pv()
|
|
218
|
-
if (Number.isNaN(Number(pvNum)))
|
|
219
|
-
return
|
|
220
|
-
|
|
221
|
-
if (!this.conf.pvEl || !document.querySelector(this.conf.pvEl))
|
|
222
|
-
return
|
|
223
|
-
|
|
224
|
-
const $pv = document.querySelector<HTMLElement>(this.conf.pvEl)!
|
|
225
|
-
// $pv.innerText = '-' // 默认占位符
|
|
226
|
-
|
|
227
|
-
$pv.innerText = String(pvNum)
|
|
228
|
-
}
|
|
229
|
-
|
|
230
225
|
/** 监听事件 */
|
|
231
226
|
public on<K extends keyof EventPayloadMap>(name: K, handler: Handler<EventPayloadMap[K]>) {
|
|
232
227
|
this.ctx.on(name, handler, 'external')
|
|
@@ -242,11 +237,20 @@ export default class Artalk {
|
|
|
242
237
|
this.ctx.trigger(name, payload, 'external')
|
|
243
238
|
}
|
|
244
239
|
|
|
245
|
-
/**
|
|
246
|
-
|
|
240
|
+
/** 重新加载 */
|
|
241
|
+
public reload() {
|
|
242
|
+
this.ctx.listReload()
|
|
243
|
+
}
|
|
247
244
|
|
|
248
|
-
/**
|
|
249
|
-
public static Use(plugin:
|
|
245
|
+
/** Use Plugin */
|
|
246
|
+
public static Use(plugin: ArtalkPlug) {
|
|
250
247
|
this.Plugins.push(plugin)
|
|
251
248
|
}
|
|
249
|
+
|
|
250
|
+
/** 装载数量统计元素 */
|
|
251
|
+
public static LoadCountWidget(customConf: Partial<ArtalkConfig>) {
|
|
252
|
+
const conf = this.HandelBaseConf(customConf)
|
|
253
|
+
const ctx = new ConcreteContext(conf)
|
|
254
|
+
Stat.initCountWidget({ ctx, pvAdd: false })
|
|
255
|
+
}
|
|
252
256
|
}
|