artalk 2.0.4 → 2.1.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/README.md +7 -60
- package/dist/Artalk.css +1 -1
- package/dist/Artalk.es.js +5996 -1
- package/dist/Artalk.iife.js +266 -1
- package/dist/Artalk.js +266 -1
- package/package.json +3 -4
- package/src/api/index.ts +269 -0
- package/src/api/request.ts +125 -0
- package/src/artalk.ts +153 -0
- package/src/components/_component.ts.txt +10 -0
- package/src/components/action-btn.ts +169 -0
- package/src/components/admin/page-list.ts +194 -0
- package/src/components/admin/site-list-floater.ts +98 -0
- package/src/components/admin/site-list.ts +289 -0
- package/src/components/comment.ts +478 -0
- package/src/components/dialog.ts +63 -0
- package/src/components/editor-plugs/{_ExamplePlug.ts.txt → _example-plug.ts.txt} +0 -0
- package/src/components/editor-plugs/{EditorPlug.ts → editor-plug.ts} +3 -3
- package/src/components/editor-plugs/{EmoticonsPlug.less → emoticons-plug.less} +0 -0
- package/src/components/editor-plugs/{EmoticonsPlug.ts → emoticons-plug.ts} +9 -9
- package/src/components/editor-plugs/{PreviewPlug.less → preview-plug.less} +0 -0
- package/src/components/editor-plugs/{PreviewPlug.ts → preview-plug.ts} +10 -10
- package/src/components/{Editor.ts → editor.ts} +104 -102
- package/src/components/html/comment.html +2 -6
- package/src/components/html/list.html +0 -1
- package/src/components/html/sidebar.html +18 -4
- package/src/components/item-text-editor.ts +136 -0
- package/src/components/layer.ts +135 -0
- package/src/components/list-lite.ts +507 -0
- package/src/components/list.ts +172 -0
- package/src/components/pagination.ts +150 -0
- package/src/components/read-more-btn.ts +71 -0
- package/src/components/sidebar-view.ts +26 -0
- package/src/components/sidebar-views/comments-view.ts +66 -0
- package/src/components/sidebar-views/pages-view.ts +61 -0
- package/src/components/sidebar-views/setting-view.ts +21 -0
- package/src/components/sidebar-views/sites-view.ts +42 -0
- package/src/components/sidebar-views/transfer-view.ts +218 -0
- package/src/components/sidebar.ts +288 -0
- package/src/context.ts +42 -0
- package/src/default-conf.ts +42 -0
- package/src/lib/checker/admin-checker.ts +33 -0
- package/src/lib/checker/captcha-checker.ts +38 -0
- package/src/lib/checker/index.ts +134 -0
- package/src/lib/component.ts +3 -3
- package/src/lib/md5.js +6 -0
- package/src/lib/ui.ts +64 -74
- package/src/lib/user.ts +2 -8
- package/src/lib/utils.ts +30 -11
- package/src/main.ts +1 -1
- package/src/style/_color.less +8 -2
- package/src/style/_extend.less +81 -0
- package/src/style/_variables.less +3 -0
- package/src/style/comment.less +60 -24
- package/src/style/editor.less +3 -0
- package/src/style/item-text-editor.less +35 -0
- package/src/style/list.less +51 -32
- package/src/style/main.less +75 -1
- package/src/style/page-list.less +50 -0
- package/src/style/pagination.less +58 -0
- package/src/style/sidebar.less +274 -199
- package/src/style/site-list.less +208 -0
- package/tsconfig.json +27 -0
- package/types/artalk-config.d.ts +68 -51
- package/types/artalk-data.d.ts +38 -1
- package/types/event.d.ts +73 -0
- package/types/index.d.ts +1 -6
- package/.github/workflows/npm.yml +0 -31
- package/.github/workflows/pages.yml +0 -30
- package/dist/src/Event.d.ts +0 -59
- package/dist/types/artalk-config.d.ts +0 -79
- package/dist/types/artalk-data.d.ts +0 -153
- package/dist/types/global.d.ts +0 -1
- package/dist/types/index.d.ts +0 -8
- package/dist/types/vite-env.d.ts +0 -1
- package/example/pjax/res1.html +0 -41
- package/example/pjax/res2.html +0 -41
- package/src/Artalk.ts +0 -160
- package/src/Constant.ts +0 -6
- package/src/Context.ts +0 -34
- package/src/Event.d.ts +0 -59
- package/src/components/Comment.ts +0 -402
- package/src/components/Layer.ts +0 -138
- package/src/components/List.ts +0 -162
- package/src/components/ListLite.ts +0 -407
- package/src/components/Sidebar.ts +0 -141
- package/src/components/sidebar-views/AdminView.ts +0 -523
- package/src/components/sidebar-views/MessageView.ts +0 -48
- package/src/components/sidebar-views/SidebarView.ts +0 -24
- package/src/lib/api.ts +0 -380
- package/src/lib/checker.ts +0 -164
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "artalk",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "A
|
|
3
|
+
"version": "2.1.1",
|
|
4
|
+
"description": "A Selfhosted Comment System",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"comment-system",
|
|
7
7
|
"comments-widget",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"unpkg": "./dist/Artalk.iife.js",
|
|
21
21
|
"jsdelivr": "./dist/Artalk.iife.js",
|
|
22
22
|
"module": "./dist/Artalk.es.js",
|
|
23
|
-
"types": "./
|
|
23
|
+
"types": "./types/index.d.ts",
|
|
24
24
|
"exports": {
|
|
25
25
|
".": {
|
|
26
26
|
"import": "./dist/Artalk.es.js",
|
|
@@ -57,7 +57,6 @@
|
|
|
57
57
|
"prettier": "^2.3.2",
|
|
58
58
|
"typescript": "4.3.2",
|
|
59
59
|
"vite": "^2.5.1",
|
|
60
|
-
"vite-plugin-dts": "^0.7.0",
|
|
61
60
|
"vite-tsconfig-paths": "^3.3.14"
|
|
62
61
|
},
|
|
63
62
|
"dependencies": {
|
package/src/api/index.ts
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import Context from '../context'
|
|
2
|
+
import { CommentData, ListData, UserData, PageData, SiteData, NotifyData } from '~/types/artalk-data'
|
|
3
|
+
import { Fetch, ToFormData, POST, GET } from './request'
|
|
4
|
+
|
|
5
|
+
export default class Api {
|
|
6
|
+
private ctx: Context
|
|
7
|
+
private baseURL: string
|
|
8
|
+
|
|
9
|
+
constructor (ctx: Context) {
|
|
10
|
+
this.ctx = ctx
|
|
11
|
+
this.baseURL = ctx.conf.server
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// ============================
|
|
15
|
+
// 评论 Comment
|
|
16
|
+
// ============================
|
|
17
|
+
|
|
18
|
+
/** 评论 · 获取 */
|
|
19
|
+
public get(offset: number, type?: string, flatMode?: boolean, paramsEditor?: (params: any) => void) {
|
|
20
|
+
const params: any = {
|
|
21
|
+
page_key: this.ctx.conf.pageKey,
|
|
22
|
+
site_name: this.ctx.conf.site || '',
|
|
23
|
+
limit: this.ctx.conf.pagination?.pageSize || 15,
|
|
24
|
+
offset,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (type) params.type = type
|
|
28
|
+
if (flatMode) params.flat_mode = flatMode // 平铺模式
|
|
29
|
+
if (this.ctx.user.checkHasBasicUserInfo()) {
|
|
30
|
+
params.name = this.ctx.user.data.nick
|
|
31
|
+
params.email = this.ctx.user.data.email
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (paramsEditor) paramsEditor(params)
|
|
35
|
+
|
|
36
|
+
return POST<ListData>(this.ctx, `${this.baseURL}/get`, params)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** 评论 · 创建 */
|
|
40
|
+
public async add(comment: { nick: string, email: string, link: string, content: string, rid: number }) {
|
|
41
|
+
const params: any = {
|
|
42
|
+
name: comment.nick,
|
|
43
|
+
email: comment.email,
|
|
44
|
+
link: comment.link,
|
|
45
|
+
content: comment.content,
|
|
46
|
+
rid: comment.rid,
|
|
47
|
+
page_key: this.ctx.conf.pageKey,
|
|
48
|
+
page_title: this.ctx.conf.pageTitle || '',
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (this.ctx.conf.site) params.site_name = this.ctx.conf.site
|
|
52
|
+
|
|
53
|
+
const data = await POST<any>(this.ctx, `${this.baseURL}/add`, params)
|
|
54
|
+
return (data.comment as CommentData)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** 评论 · 修改 */
|
|
58
|
+
public async commentEdit(data: CommentData) {
|
|
59
|
+
const params: any = {
|
|
60
|
+
...data,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const d = await POST<any>(this.ctx, `${this.baseURL}/admin/comment-edit`, params)
|
|
64
|
+
return (d.comment as CommentData)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** 评论 · 删除 */
|
|
68
|
+
public commentDel(commentID: number, siteName?: string) {
|
|
69
|
+
const params: any = {
|
|
70
|
+
id: String(commentID),
|
|
71
|
+
site_name: siteName || '',
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return POST(this.ctx, `${this.baseURL}/admin/comment-del`, params)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ============================
|
|
78
|
+
// 用户 User
|
|
79
|
+
// ============================
|
|
80
|
+
|
|
81
|
+
/** 用户 · 登录 */
|
|
82
|
+
public async login(name: string, email: string, password: string) {
|
|
83
|
+
const params: any = {
|
|
84
|
+
name, email, password
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (this.ctx.conf.site) params.site_name = this.ctx.conf.site
|
|
88
|
+
|
|
89
|
+
const data = await POST<any>(this.ctx, `${this.baseURL}/login`, params)
|
|
90
|
+
return (data.token as string)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** 用户 · 获取 */
|
|
94
|
+
public userGet(name: string, email: string) {
|
|
95
|
+
const ctrl = new AbortController()
|
|
96
|
+
const { signal } = ctrl
|
|
97
|
+
|
|
98
|
+
const params: any = {
|
|
99
|
+
name, email, site_name: (this.ctx.conf.site || '')
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const req = Fetch(this.ctx, `${this.baseURL}/user-get`, {
|
|
103
|
+
method: 'POST',
|
|
104
|
+
body: ToFormData(params),
|
|
105
|
+
signal,
|
|
106
|
+
}).then((json) => ({
|
|
107
|
+
user: json.data.user as UserData|null,
|
|
108
|
+
is_login: json.data.is_login as boolean,
|
|
109
|
+
unread: (json.data.unread || []) as NotifyData[],
|
|
110
|
+
unread_count: json.data.unread_count || 0,
|
|
111
|
+
}))
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
req,
|
|
115
|
+
abort: () => { ctrl.abort() },
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ============================
|
|
120
|
+
// 页面 Page
|
|
121
|
+
// ============================
|
|
122
|
+
|
|
123
|
+
/** 页面 · 获取 */
|
|
124
|
+
public async pageGet(siteName?: string, offset?: number, limit?: number) {
|
|
125
|
+
const params: any = {
|
|
126
|
+
site_name: siteName || '',
|
|
127
|
+
offset: offset || 0,
|
|
128
|
+
limit: limit || 15,
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const d = await POST<any>(this.ctx, `${this.baseURL}/admin/page-get`, params)
|
|
132
|
+
return (d as { pages: PageData[], total: number })
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** 页面 · 修改 */
|
|
136
|
+
public async pageEdit(data: PageData) {
|
|
137
|
+
const params: any = {
|
|
138
|
+
id: data.id,
|
|
139
|
+
key: data.key,
|
|
140
|
+
title: data.title,
|
|
141
|
+
admin_only: data.admin_only,
|
|
142
|
+
site_name: data.site_name || this.ctx.conf.site,
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const d = await POST<any>(this.ctx, `${this.baseURL}/admin/page-edit`, params)
|
|
146
|
+
return (d.page as PageData)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** 页面 · 删除 */
|
|
150
|
+
public pageDel(pageKey: string, siteName?: string) {
|
|
151
|
+
const params: any = {
|
|
152
|
+
key: String(pageKey),
|
|
153
|
+
site_name: siteName || '',
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return POST(this.ctx, `${this.baseURL}/admin/page-del`, params)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** 页面 · 数据更新 */
|
|
160
|
+
public async pageFetch(id: number) {
|
|
161
|
+
const params: any = {
|
|
162
|
+
id,
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const d = await POST<any>(this.ctx, `${this.baseURL}/admin/page-fetch`, params)
|
|
166
|
+
return (d.page as PageData)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ============================
|
|
170
|
+
// 站点 Site
|
|
171
|
+
// ============================
|
|
172
|
+
|
|
173
|
+
/** 站点 · 获取 */
|
|
174
|
+
public async siteGet() {
|
|
175
|
+
const params: any = {}
|
|
176
|
+
|
|
177
|
+
const d = await POST<any>(this.ctx, `${this.baseURL}/admin/site-get`, params)
|
|
178
|
+
return (d.sites as SiteData[])
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** 站点 · 创建 */
|
|
182
|
+
public async siteAdd(name: string, urls: string) {
|
|
183
|
+
const params: any = {
|
|
184
|
+
name, urls,
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const d = await POST<any>(this.ctx, `${this.baseURL}/admin/site-add`, params)
|
|
188
|
+
return (d.site as SiteData)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** 站点 · 修改 */
|
|
192
|
+
public async siteEdit(data: SiteData) {
|
|
193
|
+
const params: any = {
|
|
194
|
+
id: data.id,
|
|
195
|
+
name: data.name || '',
|
|
196
|
+
urls: data.urls || '',
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const d = await POST<any>(this.ctx, `${this.baseURL}/admin/site-edit`, params)
|
|
200
|
+
return (d.site as SiteData)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** 站点 · 删除 */
|
|
204
|
+
public siteDel(id: number, delContent = false) {
|
|
205
|
+
const params: any = { id, del_content: delContent }
|
|
206
|
+
|
|
207
|
+
return POST(this.ctx, `${this.baseURL}/admin/site-del`, params)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** 导出 */
|
|
211
|
+
public async export() {
|
|
212
|
+
const d = await Fetch(this.ctx, `${this.baseURL}/admin/export`, { method: 'POST' }, 0)
|
|
213
|
+
return (d.data?.data || '' as string)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ============================
|
|
217
|
+
// 杂项
|
|
218
|
+
// ============================
|
|
219
|
+
|
|
220
|
+
/** 投票 */
|
|
221
|
+
public async vote(targetID: number, type: string) {
|
|
222
|
+
const params: any = {
|
|
223
|
+
site_name: this.ctx.conf.site || '',
|
|
224
|
+
target_id: targetID,
|
|
225
|
+
type,
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (this.ctx.user.checkHasBasicUserInfo()) {
|
|
229
|
+
params.name = this.ctx.user.data.nick
|
|
230
|
+
params.email = this.ctx.user.data.email
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const data = await POST<any>(this.ctx, `${this.baseURL}/vote`, params)
|
|
234
|
+
return (data as {up: number, down: number})
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** 已读标记 */
|
|
238
|
+
public markRead(notifyKey: string, readAll = false) {
|
|
239
|
+
const params: any = {
|
|
240
|
+
site_name: this.ctx.conf.site || '',
|
|
241
|
+
notify_key: notifyKey,
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (readAll) {
|
|
245
|
+
delete params.notify_key
|
|
246
|
+
params.read_all = true
|
|
247
|
+
params.name = this.ctx.user.data.nick
|
|
248
|
+
params.email = this.ctx.user.data.email
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return POST(this.ctx, `${this.baseURL}/mark-read`, params)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ============================
|
|
255
|
+
// 验证码
|
|
256
|
+
// ============================
|
|
257
|
+
|
|
258
|
+
/** 验证码 · 获取 */
|
|
259
|
+
public async captchaGet() {
|
|
260
|
+
const data = await GET<any>(this.ctx, `${this.baseURL}/captcha/refresh`)
|
|
261
|
+
return (data.img_data || '') as string
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** 验证码 · 检验 */
|
|
265
|
+
public async captchaCheck(value: string) {
|
|
266
|
+
const data = await GET<any>(this.ctx, `${this.baseURL}/captcha/check`, { value })
|
|
267
|
+
return (data.img_data || '') as string
|
|
268
|
+
}
|
|
269
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import Context from '../context'
|
|
2
|
+
|
|
3
|
+
/** 公共请求函数 */
|
|
4
|
+
export async function Fetch(ctx: Context, input: RequestInfo, init: RequestInit, timeout?: number): Promise<any> {
|
|
5
|
+
// JWT
|
|
6
|
+
if (ctx.user.data.token) {
|
|
7
|
+
const requestHeaders: HeadersInit = new Headers()
|
|
8
|
+
requestHeaders.set('Authorization', `Bearer ${ctx.user.data.token}`)
|
|
9
|
+
|
|
10
|
+
init.headers = requestHeaders
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// 请求操作
|
|
14
|
+
try {
|
|
15
|
+
|
|
16
|
+
let resp: Response
|
|
17
|
+
if ((typeof timeout !== 'number' && ctx.conf.reqTimeout === 0) || timeout === 0) {
|
|
18
|
+
resp = await fetch(input, init)
|
|
19
|
+
} else {
|
|
20
|
+
// 请求超时检测
|
|
21
|
+
resp = await timeoutPromise(timeout || ctx.conf.reqTimeout || 15000, fetch(input, init))
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!resp.ok && resp.status !== 401) throw new Error(`请求响应 ${resp.status}`)
|
|
25
|
+
|
|
26
|
+
// 解析获取响应的 json
|
|
27
|
+
let json: any = await resp.json()
|
|
28
|
+
|
|
29
|
+
// 重新发起请求
|
|
30
|
+
const recall = (resolve, reject) => {
|
|
31
|
+
Fetch(ctx, input, init).then(d => {
|
|
32
|
+
resolve(d)
|
|
33
|
+
}).catch(err => {
|
|
34
|
+
reject(err)
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 请求弹出层验证
|
|
39
|
+
if (json.data && json.data.need_captcha) {
|
|
40
|
+
// 请求需要验证码
|
|
41
|
+
json = await (new Promise<any>((resolve, reject) => {
|
|
42
|
+
ctx.trigger('checker-captcha', {
|
|
43
|
+
imgData: json.data.img_data,
|
|
44
|
+
onSuccess: () => {
|
|
45
|
+
recall(resolve, reject)
|
|
46
|
+
},
|
|
47
|
+
onCancel: () => {
|
|
48
|
+
reject(json)
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
}))
|
|
52
|
+
} else if ((json.data && json.data.need_login) || resp.status === 401) {
|
|
53
|
+
// 请求需要管理员权限
|
|
54
|
+
json = await (new Promise<any>((resolve, reject) => {
|
|
55
|
+
ctx.trigger('checker-admin', {
|
|
56
|
+
onSuccess: () => {
|
|
57
|
+
recall(resolve, reject)
|
|
58
|
+
},
|
|
59
|
+
onCancel: () => {
|
|
60
|
+
reject(json)
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
}))
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!json.success) throw json // throw 相当于 reject(json)
|
|
67
|
+
|
|
68
|
+
return json
|
|
69
|
+
|
|
70
|
+
} catch (err) {
|
|
71
|
+
// 错误处理
|
|
72
|
+
console.error(err)
|
|
73
|
+
|
|
74
|
+
if (err instanceof TypeError)
|
|
75
|
+
throw new Error(`网络错误`)
|
|
76
|
+
|
|
77
|
+
throw err
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** 公共 POST 请求 */
|
|
82
|
+
export async function POST<T>(ctx: Context, url: string, data?: {[key: string]: any}) {
|
|
83
|
+
const init: RequestInit = {
|
|
84
|
+
method: 'POST',
|
|
85
|
+
}
|
|
86
|
+
if (data) init.body = ToFormData(data)
|
|
87
|
+
|
|
88
|
+
const json = await Fetch(ctx, url, init)
|
|
89
|
+
return ((json.data || {}) as T)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** 公共 GET 请求 */
|
|
93
|
+
export async function GET<T>(ctx: Context, url: string, data?: {[key: string]: any}) {
|
|
94
|
+
const json = await Fetch(ctx, url + (data ? (`?${new URLSearchParams(data)}`) : ''), {
|
|
95
|
+
method: 'GET',
|
|
96
|
+
})
|
|
97
|
+
return ((json.data || {}) as T)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** 对象转 FormData */
|
|
101
|
+
export function ToFormData(object: {[key: string]: any}): FormData {
|
|
102
|
+
const formData = new FormData()
|
|
103
|
+
Object.keys(object).forEach(key => formData.append(key, String(object[key])))
|
|
104
|
+
return formData
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** 我靠,fetch 一个 timeout,都要丑陋的实现 */
|
|
108
|
+
function timeoutPromise<T>(ms: number, promise: Promise<T>): Promise<T> {
|
|
109
|
+
return new Promise((resolve, reject) => {
|
|
110
|
+
const timeoutId = setTimeout(() => {
|
|
111
|
+
reject(new Error("请求超时"))
|
|
112
|
+
}, ms)
|
|
113
|
+
|
|
114
|
+
promise.then(
|
|
115
|
+
(res) => {
|
|
116
|
+
clearTimeout(timeoutId)
|
|
117
|
+
resolve(res)
|
|
118
|
+
},
|
|
119
|
+
(err) => {
|
|
120
|
+
clearTimeout(timeoutId)
|
|
121
|
+
reject(err)
|
|
122
|
+
}
|
|
123
|
+
)
|
|
124
|
+
})
|
|
125
|
+
}
|
package/src/artalk.ts
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import './style/main.less'
|
|
2
|
+
|
|
3
|
+
import Context from './context'
|
|
4
|
+
import ArtalkConfig from '~/types/artalk-config'
|
|
5
|
+
import defaultConf from './default-conf'
|
|
6
|
+
|
|
7
|
+
import CheckerLauncher from './lib/checker'
|
|
8
|
+
import Editor from './components/editor'
|
|
9
|
+
import List from './components/list'
|
|
10
|
+
import Sidebar from './components/sidebar'
|
|
11
|
+
|
|
12
|
+
import { GetLayerWrap } from './components/layer'
|
|
13
|
+
import { EventPayloadMap, Handler } from '~/types/event'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Artalk
|
|
17
|
+
* @link https://artalk.js.org
|
|
18
|
+
*/
|
|
19
|
+
export default class Artalk {
|
|
20
|
+
public ctx: Context
|
|
21
|
+
public conf: ArtalkConfig
|
|
22
|
+
public $root: HTMLElement
|
|
23
|
+
|
|
24
|
+
public checkerLauncher: CheckerLauncher
|
|
25
|
+
public editor: Editor
|
|
26
|
+
public list: List
|
|
27
|
+
public sidebar: Sidebar
|
|
28
|
+
|
|
29
|
+
constructor (customConf: ArtalkConfig) {
|
|
30
|
+
// 配置
|
|
31
|
+
this.conf = { ...defaultConf, ...customConf }
|
|
32
|
+
this.conf.server = this.conf.server.replace(/\/$/, '')
|
|
33
|
+
|
|
34
|
+
// 默认 pageKey
|
|
35
|
+
if (!this.conf.pageKey) {
|
|
36
|
+
this.conf.pageKey = `${window.location.protocol}//'${window.location.host}/${window.location.pathname}`
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 装载元素
|
|
40
|
+
try {
|
|
41
|
+
const $root = document.querySelector<HTMLElement>(this.conf.el)
|
|
42
|
+
if (!$root) throw Error(`Sorry, target element "${this.conf.el}" was not found.`)
|
|
43
|
+
this.$root = $root
|
|
44
|
+
} catch (e) {
|
|
45
|
+
console.error(e)
|
|
46
|
+
throw new Error('Please check your Artalk `el` config.')
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Context 初始化
|
|
50
|
+
this.ctx = new Context(this.$root, this.conf)
|
|
51
|
+
|
|
52
|
+
// 界面初始化
|
|
53
|
+
this.$root.classList.add('artalk')
|
|
54
|
+
this.$root.innerHTML = ''
|
|
55
|
+
this.initDarkMode()
|
|
56
|
+
|
|
57
|
+
// 组件初始化
|
|
58
|
+
this.checkerLauncher = new CheckerLauncher(this.ctx)
|
|
59
|
+
|
|
60
|
+
// 编辑器
|
|
61
|
+
this.editor = new Editor(this.ctx)
|
|
62
|
+
this.$root.appendChild(this.editor.$el)
|
|
63
|
+
|
|
64
|
+
// 评论列表
|
|
65
|
+
this.list = new List(this.ctx)
|
|
66
|
+
this.$root.appendChild(this.list.$el)
|
|
67
|
+
|
|
68
|
+
// 侧边栏
|
|
69
|
+
this.sidebar = new Sidebar(this.ctx)
|
|
70
|
+
this.$root.appendChild(this.sidebar.$el)
|
|
71
|
+
|
|
72
|
+
// 评论获取
|
|
73
|
+
this.list.reqComments()
|
|
74
|
+
|
|
75
|
+
// 事件绑定初始化
|
|
76
|
+
this.initEventBind()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** 事件绑定 · 初始化 */
|
|
80
|
+
private initEventBind() {
|
|
81
|
+
// 锚点快速跳转评论
|
|
82
|
+
window.addEventListener('hashchange', () => {
|
|
83
|
+
this.list.checkGoToCommentByUrlHash()
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
// 仅管理员显示控制
|
|
87
|
+
this.ctx.on('check-admin-show-el', () => {
|
|
88
|
+
const items: HTMLElement[] = []
|
|
89
|
+
|
|
90
|
+
this.$root.querySelectorAll<HTMLElement>(`[atk-only-admin-show]`).forEach(item => items.push(item))
|
|
91
|
+
|
|
92
|
+
// for layer
|
|
93
|
+
const { $wrap: $layerWrap } = GetLayerWrap(this.ctx)
|
|
94
|
+
if ($layerWrap)
|
|
95
|
+
$layerWrap.querySelectorAll<HTMLElement>(`[atk-only-admin-show]`).forEach(item => items.push(item))
|
|
96
|
+
|
|
97
|
+
items.forEach(($item: HTMLElement) => {
|
|
98
|
+
if (this.ctx.user.data.isAdmin)
|
|
99
|
+
$item.classList.remove('atk-hide')
|
|
100
|
+
else
|
|
101
|
+
$item.classList.add('atk-hide')
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
// 本地用户数据变更
|
|
106
|
+
this.ctx.on('user-changed', () => {
|
|
107
|
+
this.ctx.trigger('check-admin-show-el')
|
|
108
|
+
this.ctx.trigger('list-refresh-ui')
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** 暗黑模式 · 初始化 */
|
|
113
|
+
public initDarkMode() {
|
|
114
|
+
const darkModeClassName = 'atk-dark-mode'
|
|
115
|
+
|
|
116
|
+
if (this.conf.darkMode) {
|
|
117
|
+
this.$root.classList.add(darkModeClassName)
|
|
118
|
+
} else {
|
|
119
|
+
this.$root.classList.remove(darkModeClassName)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// for Layer
|
|
123
|
+
const { $wrap: $layerWrap } = GetLayerWrap(this.ctx)
|
|
124
|
+
if ($layerWrap) {
|
|
125
|
+
if (this.conf.darkMode) {
|
|
126
|
+
$layerWrap.classList.add(darkModeClassName)
|
|
127
|
+
} else {
|
|
128
|
+
$layerWrap.classList.remove(darkModeClassName)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** 暗黑模式 · 设定 */
|
|
134
|
+
public setDarkMode(darkMode: boolean) {
|
|
135
|
+
this.ctx.conf.darkMode = darkMode
|
|
136
|
+
this.initDarkMode()
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** 监听事件 */
|
|
140
|
+
public on<K extends keyof EventPayloadMap>(name: K, handler: Handler<EventPayloadMap[K]>) {
|
|
141
|
+
this.ctx.on(name, handler, 'external')
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** 解除监听事件 */
|
|
145
|
+
public off<K extends keyof EventPayloadMap>(name: K, handler: Handler<EventPayloadMap[K]>) {
|
|
146
|
+
this.ctx.off(name, handler, 'external')
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** 触发事件 */
|
|
150
|
+
public trigger<K extends keyof EventPayloadMap>(name: K, payload?: EventPayloadMap[K]) {
|
|
151
|
+
this.ctx.trigger(name, payload, 'external')
|
|
152
|
+
}
|
|
153
|
+
}
|