artalk 2.2.12 → 2.3.2
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 +1535 -1175
- 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 +2 -2
- package/package.json +6 -5
- package/src/api/index.ts +20 -1
- package/src/api/request.ts +7 -7
- package/src/artalk.ts +117 -121
- 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 +16 -5
- 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 +6 -12
- 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 +80 -100
- 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 -12
- 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 -607
- 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.2
|
|
4
|
-
"backendMinVersion": "2.1.
|
|
3
|
+
"version": "2.3.2",
|
|
4
|
+
"backendMinVersion": "2.1.7",
|
|
5
5
|
"license": "LGPL-3.0",
|
|
6
6
|
"description": "A Selfhosted Comment System",
|
|
7
7
|
"keywords": [
|
|
@@ -24,17 +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
33
|
"abortcontroller-polyfill": "^1.7.3"
|
|
34
34
|
},
|
|
35
35
|
"scripts": {
|
|
36
36
|
"dev": "vite",
|
|
37
|
-
"build": "pnpm lint && vite build",
|
|
37
|
+
"build": "pnpm lint && vite build && pnpm build:lite",
|
|
38
|
+
"build:lite": "vite build --config vite-lite.config.ts",
|
|
38
39
|
"build:demo": "vite build --config vite-demo.config.ts",
|
|
39
40
|
"serve": "vite preview",
|
|
40
41
|
"deploy": "gh-pages -d deploy",
|
package/src/api/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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
6
|
import 'abortcontroller-polyfill/dist/polyfill-patch-fetch'
|
|
@@ -274,6 +274,25 @@ export default class Api {
|
|
|
274
274
|
return p.pv as number
|
|
275
275
|
}
|
|
276
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
|
+
|
|
277
296
|
/** 图片上传 */
|
|
278
297
|
public async imgUpload(file: File) {
|
|
279
298
|
const params: any = {
|
package/src/api/request.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
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> {
|
|
@@ -10,14 +10,14 @@ export async function Fetch(ctx: Context, input: RequestInfo, init: RequestInit,
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
// 请求操作
|
|
13
|
-
const resp = await timeoutFetch(input, timeout || ctx.conf.reqTimeout || 15000, init)
|
|
13
|
+
const resp = await timeoutFetch(ctx, input, timeout || ctx.conf.reqTimeout || 15000, init)
|
|
14
14
|
|
|
15
15
|
const respHttpCode = resp.status
|
|
16
16
|
const noAccessCodes = [401, 400]
|
|
17
17
|
const isNoAccess = noAccessCodes.includes(respHttpCode)
|
|
18
18
|
|
|
19
19
|
if (!resp.ok && !isNoAccess)
|
|
20
|
-
throw new Error(
|
|
20
|
+
throw new Error(`${ctx.$t('reqGot')} ${respHttpCode}`)
|
|
21
21
|
|
|
22
22
|
// 解析获取响应的 json
|
|
23
23
|
let json: any = await resp.json()
|
|
@@ -33,7 +33,7 @@ export async function Fetch(ctx: Context, input: RequestInfo, init: RequestInit,
|
|
|
33
33
|
if (json.data?.need_captcha) {
|
|
34
34
|
// 请求需要验证码
|
|
35
35
|
json = await (new Promise<any>((resolve, reject) => {
|
|
36
|
-
ctx.
|
|
36
|
+
ctx.checkCaptcha({
|
|
37
37
|
imgData: json.data.img_data,
|
|
38
38
|
iframe: json.data.iframe,
|
|
39
39
|
onSuccess: () => { recall(resolve, reject) },
|
|
@@ -43,7 +43,7 @@ export async function Fetch(ctx: Context, input: RequestInfo, init: RequestInit,
|
|
|
43
43
|
} else if (json.data?.need_login || isNoAccess) {
|
|
44
44
|
// 请求需要管理员权限
|
|
45
45
|
json = await (new Promise<any>((resolve, reject) => {
|
|
46
|
-
ctx.
|
|
46
|
+
ctx.checkAdmin({
|
|
47
47
|
onSuccess: () => { recall(resolve, reject) },
|
|
48
48
|
onCancel: () => { reject(json) }
|
|
49
49
|
})
|
|
@@ -82,7 +82,7 @@ export function ToFormData(object: {[key: string]: any}): FormData {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
/** 我靠,fetch 一个 timeout,都要丑陋的实现 */
|
|
85
|
-
function timeoutFetch(url: RequestInfo, ms: number, opts: RequestInit) {
|
|
85
|
+
function timeoutFetch(ctx: Context, url: RequestInfo, ms: number, opts: RequestInit) {
|
|
86
86
|
const controller = new AbortController()
|
|
87
87
|
opts.signal?.addEventListener('abort', () => controller.abort()) // 保留原有 signal 功能
|
|
88
88
|
let promise = fetch(url, { ...opts, signal: controller.signal })
|
|
@@ -91,7 +91,7 @@ function timeoutFetch(url: RequestInfo, ms: number, opts: RequestInit) {
|
|
|
91
91
|
promise.finally(() => { clearTimeout(timer) })
|
|
92
92
|
}
|
|
93
93
|
promise = promise.catch((err) => {
|
|
94
|
-
throw ((err || {}).name === 'AbortError') ? new Error('
|
|
94
|
+
throw ((err || {}).name === 'AbortError') ? new Error(ctx.$t('reqAborted')) : err
|
|
95
95
|
})
|
|
96
96
|
return promise
|
|
97
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
|
+
protected static Plugins: ArtalkPlug[] = [
|
|
39
|
+
Stat.PvCountWidget
|
|
40
|
+
]
|
|
38
41
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
42
|
+
constructor(customConf: Partial<ArtalkConfig>) {
|
|
43
|
+
/* 初始化基本配置 */
|
|
44
|
+
this.conf = Artalk.HandelBaseConf(customConf)
|
|
45
|
+
if (this.conf.el instanceof HTMLElement) this.$root = this.conf.el
|
|
44
46
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
this.conf.pageTitle = `${document.title}`
|
|
48
|
-
}
|
|
47
|
+
/* 初始化 Context */
|
|
48
|
+
this.ctx = new ConcreteContext(this.conf, this.$root)
|
|
49
49
|
|
|
50
|
-
//
|
|
51
|
-
if (this.conf.
|
|
52
|
-
this.conf.flatMode = true
|
|
53
|
-
}
|
|
50
|
+
// @ts-ignore 远程加载引用后端的配置
|
|
51
|
+
if (this.conf.useBackendConf) return this.loadConfRemoteAndInitComponents()
|
|
54
52
|
|
|
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
|
-
}
|
|
68
|
-
|
|
69
|
-
// Context 初始化
|
|
70
|
-
this.ctx = new Context(this.$root, this.conf)
|
|
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
|
-
}
|
|
86
|
-
|
|
87
|
-
this.initCore()
|
|
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,50 +153,41 @@ 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() {
|
|
189
191
|
if (this.conf.darkMode === 'auto') {
|
|
190
192
|
// 自动切换暗黑模式,事件监听
|
|
191
193
|
const darkModeMedia = window.matchMedia('(prefers-color-scheme: dark)')
|
|
@@ -196,7 +198,7 @@ export default class Artalk {
|
|
|
196
198
|
}
|
|
197
199
|
}
|
|
198
200
|
|
|
199
|
-
/**
|
|
201
|
+
/** 设置暗黑模式 */
|
|
200
202
|
public setDarkMode(darkMode: boolean) {
|
|
201
203
|
const darkModeClassName = 'atk-dark-mode'
|
|
202
204
|
|
|
@@ -220,21 +222,6 @@ export default class Artalk {
|
|
|
220
222
|
}
|
|
221
223
|
}
|
|
222
224
|
|
|
223
|
-
/** PV */
|
|
224
|
-
public async initPV() {
|
|
225
|
-
const pvNum = await new Api(this.ctx).pv()
|
|
226
|
-
if (Number.isNaN(Number(pvNum)))
|
|
227
|
-
return
|
|
228
|
-
|
|
229
|
-
if (!this.conf.pvEl || !document.querySelector(this.conf.pvEl))
|
|
230
|
-
return
|
|
231
|
-
|
|
232
|
-
const $pv = document.querySelector<HTMLElement>(this.conf.pvEl)!
|
|
233
|
-
// $pv.innerText = '-' // 默认占位符
|
|
234
|
-
|
|
235
|
-
$pv.innerText = String(pvNum)
|
|
236
|
-
}
|
|
237
|
-
|
|
238
225
|
/** 监听事件 */
|
|
239
226
|
public on<K extends keyof EventPayloadMap>(name: K, handler: Handler<EventPayloadMap[K]>) {
|
|
240
227
|
this.ctx.on(name, handler, 'external')
|
|
@@ -250,11 +237,20 @@ export default class Artalk {
|
|
|
250
237
|
this.ctx.trigger(name, payload, 'external')
|
|
251
238
|
}
|
|
252
239
|
|
|
253
|
-
/**
|
|
254
|
-
|
|
240
|
+
/** 重新加载 */
|
|
241
|
+
public reload() {
|
|
242
|
+
this.ctx.listReload()
|
|
243
|
+
}
|
|
255
244
|
|
|
256
|
-
/**
|
|
257
|
-
public static Use(plugin:
|
|
245
|
+
/** Use Plugin */
|
|
246
|
+
public static Use(plugin: ArtalkPlug) {
|
|
258
247
|
this.Plugins.push(plugin)
|
|
259
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
|
+
}
|
|
260
256
|
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import Comment from './comment'
|
|
2
|
+
import ActionBtn from '../components/action-btn'
|
|
3
|
+
|
|
4
|
+
export default class CommentActions {
|
|
5
|
+
private comment: Comment
|
|
6
|
+
|
|
7
|
+
private get ctx() { return this.comment.ctx }
|
|
8
|
+
private get data() { return this.comment.getData() }
|
|
9
|
+
private get cConf() { return this.comment.getConf() }
|
|
10
|
+
|
|
11
|
+
public constructor(comment: Comment) {
|
|
12
|
+
this.comment = comment
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** 投票操作 */
|
|
16
|
+
public vote(type: 'up'|'down') {
|
|
17
|
+
const actionBtn = (type === 'up') ? this.comment.getRender().voteBtnUp : this.comment.getRender().voteBtnDown
|
|
18
|
+
|
|
19
|
+
this.ctx.getApi().vote(this.data.id, `comment_${type}`)
|
|
20
|
+
.then((v) => {
|
|
21
|
+
this.data.vote_up = v.up
|
|
22
|
+
this.data.vote_down = v.down
|
|
23
|
+
this.comment.getRender().voteBtnUp?.updateText()
|
|
24
|
+
this.comment.getRender().voteBtnDown?.updateText()
|
|
25
|
+
})
|
|
26
|
+
.catch((err) => {
|
|
27
|
+
actionBtn?.setError(this.ctx.$t('voteFail'))
|
|
28
|
+
console.log(err)
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** 管理员 - 评论状态修改 */
|
|
33
|
+
public adminEdit(type: 'collapsed'|'pending'|'pinned', btnElem: ActionBtn) {
|
|
34
|
+
if (btnElem.isLoading) return // 若正在修改中
|
|
35
|
+
|
|
36
|
+
btnElem.setLoading(true, `${this.ctx.$t('editing')}...`)
|
|
37
|
+
|
|
38
|
+
// 克隆并修改当前数据
|
|
39
|
+
const modify = { ...this.data }
|
|
40
|
+
if (type === 'collapsed') {
|
|
41
|
+
modify.is_collapsed = !modify.is_collapsed
|
|
42
|
+
} else if (type === 'pending') {
|
|
43
|
+
modify.is_pending = !modify.is_pending
|
|
44
|
+
} else if (type === 'pinned') {
|
|
45
|
+
modify.is_pinned = !modify.is_pinned
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
this.ctx.getApi().commentEdit(modify).then((data) => {
|
|
49
|
+
btnElem.setLoading(false)
|
|
50
|
+
|
|
51
|
+
// 刷新当前 Comment UI
|
|
52
|
+
this.comment.setData(data)
|
|
53
|
+
|
|
54
|
+
// 刷新 List UI
|
|
55
|
+
this.ctx.listRefreshUI()
|
|
56
|
+
}).catch((err) => {
|
|
57
|
+
console.error(err)
|
|
58
|
+
btnElem.setError(this.ctx.$t('editFail'))
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** 管理员 - 评论删除 */
|
|
63
|
+
public adminDelete(btnElem: ActionBtn) {
|
|
64
|
+
if (btnElem.isLoading) return // 若正在删除中
|
|
65
|
+
|
|
66
|
+
btnElem.setLoading(true, `${this.ctx.$t('deleting')}...`)
|
|
67
|
+
this.ctx.getApi().commentDel(this.data.id, this.data.site_name)
|
|
68
|
+
.then(() => {
|
|
69
|
+
btnElem.setLoading(false)
|
|
70
|
+
if (this.cConf.onDelete) this.cConf.onDelete(this.comment)
|
|
71
|
+
})
|
|
72
|
+
.catch((e) => {
|
|
73
|
+
console.error(e)
|
|
74
|
+
btnElem.setError(this.ctx.$t('deleteFail'))
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** 快速跳转到该评论 */
|
|
79
|
+
public goToReplyComment() {
|
|
80
|
+
const origHash = window.location.hash
|
|
81
|
+
const modifyHash = `#atk-comment-${this.data.rid}`
|
|
82
|
+
|
|
83
|
+
window.location.hash = modifyHash
|
|
84
|
+
if (modifyHash === origHash) window.dispatchEvent(new Event('hashchange')) // 强制触发事件
|
|
85
|
+
}
|
|
86
|
+
}
|