artalk 2.4.2 → 2.4.3
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.es.js +199 -101
- package/dist/Artalk.iife.js +1 -1
- package/dist/Artalk.js +1 -1
- package/dist/ArtalkLite.es.js +199 -101
- package/dist/ArtalkLite.iife.js +1 -1
- package/dist/ArtalkLite.js +1 -1
- package/dist/src/api/index.d.ts +1 -1
- package/dist/src/api/system.d.ts +7 -0
- package/dist/src/api/user.d.ts +16 -2
- package/dist/src/artalk.d.ts +0 -6
- package/dist/src/context.d.ts +4 -0
- package/dist/src/list/list-lite.d.ts +17 -7
- package/dist/src/list/list.d.ts +1 -0
- package/dist/types/artalk-config.d.ts +5 -1
- package/dist/types/artalk-data.d.ts +23 -0
- package/dist/types/context.d.ts +3 -0
- package/dist/types/event.d.ts +1 -1
- package/package.json +2 -2
- package/src/api/index.ts +3 -2
- package/src/api/system.ts +7 -0
- package/src/api/user.ts +59 -2
- package/src/artalk.ts +0 -35
- package/src/comment/render.ts +2 -1
- package/src/context.ts +33 -3
- package/src/defaults.ts +2 -2
- package/src/editor/editor.ts +8 -8
- package/src/editor/plugs/upload-plug.ts +1 -1
- package/src/layer/sidebar-layer.ts +10 -8
- package/src/lib/checker/admin-checker.ts +4 -1
- package/src/lib/checker/captcha-checker.ts +1 -0
- package/src/lib/stat.ts +9 -1
- package/src/lib/utils.ts +8 -1
- package/src/list/list-lite.ts +68 -26
- package/src/list/list.ts +10 -18
- package/types/artalk-config.d.ts +5 -1
- package/types/artalk-data.d.ts +23 -0
- package/types/context.d.ts +3 -0
- package/types/event.d.ts +1 -1
package/dist/src/api/index.d.ts
CHANGED
package/dist/src/api/system.d.ts
CHANGED
|
@@ -10,4 +10,11 @@ export default class SystemApi extends ApiBase {
|
|
|
10
10
|
getSettings(): Promise<string>;
|
|
11
11
|
/** 保存配置数据 */
|
|
12
12
|
saveSettings(yamlStr: string): Promise<boolean>;
|
|
13
|
+
/** 获取 API 版本信息 */
|
|
14
|
+
version(): Promise<{
|
|
15
|
+
app: string;
|
|
16
|
+
version: string;
|
|
17
|
+
commit_hash: string;
|
|
18
|
+
fe_min_version: string;
|
|
19
|
+
}>;
|
|
13
20
|
}
|
package/dist/src/api/user.d.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import { UserData, NotifyData } from '../../types/artalk-data';
|
|
1
|
+
import { UserData, NotifyData, UserDataForAdmin } from '../../types/artalk-data';
|
|
2
2
|
import ApiBase from './api-base';
|
|
3
3
|
/**
|
|
4
4
|
* 用户 API
|
|
5
5
|
*/
|
|
6
6
|
export default class UserApi extends ApiBase {
|
|
7
7
|
/** 用户 · 登录 */
|
|
8
|
-
login(name: string, email: string, password: string): Promise<
|
|
8
|
+
login(name: string, email: string, password: string): Promise<{
|
|
9
|
+
token: string;
|
|
10
|
+
user: UserData;
|
|
11
|
+
}>;
|
|
9
12
|
/** 用户 · 获取 */
|
|
10
13
|
userGet(name: string, email: string): {
|
|
11
14
|
req: Promise<{
|
|
@@ -25,4 +28,15 @@ export default class UserApi extends ApiBase {
|
|
|
25
28
|
logout(): Promise<unknown>;
|
|
26
29
|
/** 已读标记 */
|
|
27
30
|
markRead(notifyKey: string, readAll?: boolean): Promise<unknown>;
|
|
31
|
+
/** 用户 · 列表 */
|
|
32
|
+
userList(offset?: number, limit?: number, type?: 'all' | 'admin' | 'in_conf'): Promise<{
|
|
33
|
+
users: UserDataForAdmin[];
|
|
34
|
+
total: number;
|
|
35
|
+
}>;
|
|
36
|
+
/** 用户 · 新增 */
|
|
37
|
+
userAdd(user: Partial<UserDataForAdmin>, password?: string): Promise<UserDataForAdmin>;
|
|
38
|
+
/** 用户 · 修改 */
|
|
39
|
+
userEdit(user: Partial<UserDataForAdmin>, password?: string): Promise<UserDataForAdmin>;
|
|
40
|
+
/** 用户 · 删除 */
|
|
41
|
+
userDel(userID: number): Promise<unknown>;
|
|
28
42
|
}
|
package/dist/src/artalk.d.ts
CHANGED
|
@@ -25,18 +25,12 @@ export default class Artalk {
|
|
|
25
25
|
private initComponents;
|
|
26
26
|
/** 基本配置初始化 */
|
|
27
27
|
static HandelBaseConf(customConf: Partial<ArtalkConfig>): ArtalkConfig;
|
|
28
|
-
/** 初始化组件根据远程加载的配置 */
|
|
29
|
-
private loadConfRemoteAndInitComponents;
|
|
30
|
-
/** 远程加载配置 */
|
|
31
|
-
private loadConfRemote;
|
|
32
28
|
/** 事件绑定初始化 */
|
|
33
29
|
private initEventBind;
|
|
34
30
|
/** 语言初始化 */
|
|
35
31
|
private initLocale;
|
|
36
32
|
/** Layer 初始化 */
|
|
37
33
|
private initLayer;
|
|
38
|
-
/** 暗黑模式初始化 */
|
|
39
|
-
private initDarkMode;
|
|
40
34
|
/** 监听事件 */
|
|
41
35
|
on<K extends keyof EventPayloadMap>(name: K, handler: Handler<EventPayloadMap[K]>): void;
|
|
42
36
|
/** 解除监听事件 */
|
package/dist/src/context.d.ts
CHANGED
|
@@ -69,4 +69,8 @@ export default class Context implements ContextApi {
|
|
|
69
69
|
[key: string]: string;
|
|
70
70
|
}): string;
|
|
71
71
|
setDarkMode(darkMode: boolean): void;
|
|
72
|
+
private darkModeMedia;
|
|
73
|
+
private darkModeAutoFunc?;
|
|
74
|
+
private refreshDarkModeConf;
|
|
75
|
+
updateConf(conf: Partial<ArtalkConfig>): void;
|
|
72
76
|
}
|
|
@@ -9,14 +9,23 @@ export default class ListLite extends Component {
|
|
|
9
9
|
protected $commentsWrap: HTMLElement;
|
|
10
10
|
protected data?: ListData;
|
|
11
11
|
protected isLoading: boolean;
|
|
12
|
-
noCommentText
|
|
12
|
+
noCommentText?: string;
|
|
13
|
+
/** 嵌套模式下的排序方式 */
|
|
14
|
+
private _nestSortBy?;
|
|
15
|
+
get nestSortBy(): ListNest.SortByType;
|
|
16
|
+
set nestSortBy(val: ListNest.SortByType);
|
|
13
17
|
/** 平铺模式 */
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
private _flatMode?;
|
|
19
|
+
get flatMode(): boolean;
|
|
20
|
+
set flatMode(val: boolean);
|
|
21
|
+
/** 分页方式 */
|
|
22
|
+
_pageMode?: 'pagination' | 'read-more';
|
|
23
|
+
get pageMode(): 'pagination' | 'read-more';
|
|
24
|
+
set pageMode(val: 'pagination' | 'read-more');
|
|
25
|
+
/** 每页数量 (每次请求获取量) */
|
|
26
|
+
private _pageSize?;
|
|
27
|
+
get pageSize(): number;
|
|
28
|
+
set pageSize(val: number);
|
|
20
29
|
scrollListenerAt?: HTMLElement;
|
|
21
30
|
repositionAt?: HTMLElement;
|
|
22
31
|
protected pagination?: Pagination;
|
|
@@ -35,6 +44,7 @@ export default class ListLite extends Component {
|
|
|
35
44
|
setLoading(val: boolean, isFirstLoad?: boolean): void;
|
|
36
45
|
/** 评论获取 */
|
|
37
46
|
fetchComments(offset: number): Promise<void>;
|
|
47
|
+
private confLoaded;
|
|
38
48
|
protected onLoad(data: ListData, offset: number): void;
|
|
39
49
|
/** 分页模式 */
|
|
40
50
|
private refreshPagination;
|
package/dist/src/list/list.d.ts
CHANGED
|
@@ -20,6 +20,7 @@ export default class List extends ListLite {
|
|
|
20
20
|
/** 管理员设置页面信息 */
|
|
21
21
|
adminPageEditSave(): void;
|
|
22
22
|
showUnreadBadge(count: number): void;
|
|
23
|
+
private dropdownLoaded;
|
|
23
24
|
/** 初始化选择下拉层 */
|
|
24
25
|
protected initDropdown(): void;
|
|
25
26
|
updateUnread(notifies: NotifyData[]): void;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { I18n } from '../src/i18n'
|
|
2
|
+
import { CommentData } from './artalk-data'
|
|
2
3
|
|
|
3
4
|
export default interface ArtalkConfig {
|
|
4
5
|
/** 装载元素 */
|
|
@@ -39,6 +40,9 @@ export default interface ArtalkConfig {
|
|
|
39
40
|
default: string
|
|
40
41
|
}
|
|
41
42
|
|
|
43
|
+
/** 头像链接生成器 */
|
|
44
|
+
avatarURLBuilder?: (comment: CommentData) => string,
|
|
45
|
+
|
|
42
46
|
/** 分页配置 */
|
|
43
47
|
pagination: {
|
|
44
48
|
/** 每次请求获取数量 */
|
|
@@ -103,7 +107,7 @@ export default interface ArtalkConfig {
|
|
|
103
107
|
/** 版本检测 */
|
|
104
108
|
versionCheck: boolean
|
|
105
109
|
|
|
106
|
-
/**
|
|
110
|
+
/** 引用后端配置 */
|
|
107
111
|
useBackendConf: boolean
|
|
108
112
|
|
|
109
113
|
/** 语言本地化 */
|
|
@@ -159,6 +159,29 @@ export interface UserData {
|
|
|
159
159
|
|
|
160
160
|
/** 是否属于管理员 */
|
|
161
161
|
is_admin: boolean
|
|
162
|
+
|
|
163
|
+
/** 所属站点名 */
|
|
164
|
+
site_names: string[]
|
|
165
|
+
|
|
166
|
+
/** 所属站点名 (字符串) */
|
|
167
|
+
site_names_raw: string,
|
|
168
|
+
|
|
169
|
+
/** 是否允许接收邮件通知 */
|
|
170
|
+
receive_email: boolean
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export interface UserDataForAdmin extends UserData {
|
|
174
|
+
/** 最后一次操作 IP */
|
|
175
|
+
last_ip: string
|
|
176
|
+
|
|
177
|
+
/** 最后一次操作 UA */
|
|
178
|
+
last_ua: string
|
|
179
|
+
|
|
180
|
+
/** 是否存在于配置文件中 */
|
|
181
|
+
is_in_conf: boolean
|
|
182
|
+
|
|
183
|
+
/** 评论数 */
|
|
184
|
+
comment_count: number
|
|
162
185
|
}
|
|
163
186
|
|
|
164
187
|
export interface NotifyData {
|
package/dist/types/context.d.ts
CHANGED
package/dist/types/event.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export interface EventPayloadMap {
|
|
|
9
9
|
'editor-submit': undefined // 编辑器提交时
|
|
10
10
|
'editor-submitted': undefined // 编辑器提交后
|
|
11
11
|
'user-changed': LocalUser // 本地用户数据变更时
|
|
12
|
-
'conf-
|
|
12
|
+
'conf-loaded': undefined // Artalk 配置变更时
|
|
13
13
|
'sidebar-show': undefined // 侧边栏显示
|
|
14
14
|
'sidebar-hide': undefined // 侧边栏隐藏
|
|
15
15
|
}
|
package/package.json
CHANGED
package/src/api/index.ts
CHANGED
|
@@ -18,11 +18,12 @@ const ApiComponents = {
|
|
|
18
18
|
|
|
19
19
|
class Api {
|
|
20
20
|
protected ctx: Context
|
|
21
|
-
public baseURL
|
|
21
|
+
public get baseURL() {
|
|
22
|
+
return `${this.ctx.conf.server}/api`
|
|
23
|
+
}
|
|
22
24
|
|
|
23
25
|
constructor (ctx: Context) {
|
|
24
26
|
this.ctx = ctx
|
|
25
|
-
this.baseURL = `${ctx.conf.server}/api`
|
|
26
27
|
|
|
27
28
|
Object.entries(ApiComponents).forEach(([key, ApiComponent]) => {
|
|
28
29
|
this[key] = new ApiComponent(this, this.ctx)
|
package/src/api/system.ts
CHANGED
|
@@ -36,4 +36,11 @@ export default class SystemApi extends ApiBase {
|
|
|
36
36
|
})
|
|
37
37
|
return data
|
|
38
38
|
}
|
|
39
|
+
|
|
40
|
+
/** 获取 API 版本信息 */
|
|
41
|
+
public async version() {
|
|
42
|
+
const resp = await fetch(`${this.api.baseURL}/version`, { method: 'POST' })
|
|
43
|
+
const data = await resp.json()
|
|
44
|
+
return data as { app: string, version: string, commit_hash: string, fe_min_version: string }
|
|
45
|
+
}
|
|
39
46
|
}
|
package/src/api/user.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { UserData, NotifyData } from '~/types/artalk-data'
|
|
1
|
+
import { UserData, NotifyData, UserDataForAdmin } from '~/types/artalk-data'
|
|
2
2
|
import ApiBase from './api-base'
|
|
3
3
|
import { ToFormData } from './request'
|
|
4
4
|
|
|
@@ -15,7 +15,7 @@ export default class UserApi extends ApiBase {
|
|
|
15
15
|
if (this.ctx.conf.site) params.site_name = this.ctx.conf.site
|
|
16
16
|
|
|
17
17
|
const data = await this.POST<any>('/login', params)
|
|
18
|
-
return
|
|
18
|
+
return data as { token: string, user: UserData }
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
/** 用户 · 获取 */
|
|
@@ -72,4 +72,61 @@ export default class UserApi extends ApiBase {
|
|
|
72
72
|
|
|
73
73
|
return this.POST(`/mark-read`, params)
|
|
74
74
|
}
|
|
75
|
+
|
|
76
|
+
/** 用户 · 列表 */
|
|
77
|
+
public async userList(offset?: number, limit?: number, type?: 'all'|'admin'|'in_conf') {
|
|
78
|
+
const params: any = {
|
|
79
|
+
offset: offset || 0,
|
|
80
|
+
limit: limit || 15,
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (type) params.type = type
|
|
84
|
+
|
|
85
|
+
const d = await this.POST<any>('/admin/user-get', params)
|
|
86
|
+
return (d as { users: UserDataForAdmin[], total: number })
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** 用户 · 新增 */
|
|
90
|
+
public async userAdd(user: Partial<UserDataForAdmin>, password?: string) {
|
|
91
|
+
const params: any = {
|
|
92
|
+
name: user.name || '',
|
|
93
|
+
email: user.email || '',
|
|
94
|
+
password: password || '',
|
|
95
|
+
link: user.link || '',
|
|
96
|
+
is_admin: user.is_admin || false,
|
|
97
|
+
site_names: user.site_names_raw || '',
|
|
98
|
+
receive_email: user.receive_email || true,
|
|
99
|
+
badge_name: user.badge_name || '',
|
|
100
|
+
badge_color: user.badge_color || '',
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const d = await this.POST<any>('/admin/user-add', params)
|
|
104
|
+
return (d.user as UserDataForAdmin)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** 用户 · 修改 */
|
|
108
|
+
public async userEdit(user: Partial<UserDataForAdmin>, password?: string) {
|
|
109
|
+
const params: any = {
|
|
110
|
+
id: user.id,
|
|
111
|
+
name: user.name || '',
|
|
112
|
+
email: user.email || '',
|
|
113
|
+
password: password || '',
|
|
114
|
+
link: user.link || '',
|
|
115
|
+
is_admin: user.is_admin || false,
|
|
116
|
+
site_names: user.site_names_raw || '',
|
|
117
|
+
receive_email: user.receive_email || true,
|
|
118
|
+
badge_name: user.badge_name || '',
|
|
119
|
+
badge_color: user.badge_color || '',
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const d = await this.POST<any>('/admin/user-edit', params)
|
|
123
|
+
return (d.user as UserDataForAdmin)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** 用户 · 删除 */
|
|
127
|
+
public userDel(userID: number) {
|
|
128
|
+
return this.POST('/admin/user-del', {
|
|
129
|
+
id: String(userID)
|
|
130
|
+
})
|
|
131
|
+
}
|
|
75
132
|
}
|
package/src/artalk.ts
CHANGED
|
@@ -48,9 +48,6 @@ export default class Artalk {
|
|
|
48
48
|
/* 初始化 Context */
|
|
49
49
|
this.ctx = new ConcreteContext(this.conf, this.$root)
|
|
50
50
|
|
|
51
|
-
// @ts-ignore 远程加载引用后端的配置
|
|
52
|
-
if (this.conf.useBackendConf) return this.loadConfRemoteAndInitComponents()
|
|
53
|
-
|
|
54
51
|
/* 初始化组件 */
|
|
55
52
|
this.initComponents()
|
|
56
53
|
}
|
|
@@ -59,7 +56,6 @@ export default class Artalk {
|
|
|
59
56
|
private initComponents() {
|
|
60
57
|
this.initLocale()
|
|
61
58
|
this.initLayer()
|
|
62
|
-
this.initDarkMode()
|
|
63
59
|
Utils.initMarked(this.ctx)
|
|
64
60
|
|
|
65
61
|
const Components: { [name: string]: () => void } = {
|
|
@@ -91,7 +87,6 @@ export default class Artalk {
|
|
|
91
87
|
sidebarLayer: () => {
|
|
92
88
|
const sidebarLayer = new SidebarLayer(this.ctx)
|
|
93
89
|
this.ctx.setSidebarLayer(sidebarLayer)
|
|
94
|
-
this.$root.appendChild(sidebarLayer.$el)
|
|
95
90
|
},
|
|
96
91
|
|
|
97
92
|
// 默认事件绑定
|
|
@@ -148,24 +143,6 @@ export default class Artalk {
|
|
|
148
143
|
return conf
|
|
149
144
|
}
|
|
150
145
|
|
|
151
|
-
/** 初始化组件根据远程加载的配置 */
|
|
152
|
-
private async loadConfRemoteAndInitComponents() {
|
|
153
|
-
await this.loadConfRemote()
|
|
154
|
-
this.initComponents()
|
|
155
|
-
return this
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/** 远程加载配置 */
|
|
159
|
-
private async loadConfRemote() {
|
|
160
|
-
Ui.showLoading(this.$root)
|
|
161
|
-
let backendConf = {}
|
|
162
|
-
try {
|
|
163
|
-
backendConf = await this.ctx.getApi().system.conf()
|
|
164
|
-
} catch (err) { console.error("Load config from remote err", err) }
|
|
165
|
-
this.ctx.conf = Utils.mergeDeep(this.ctx.conf, backendConf)
|
|
166
|
-
Ui.hideLoading(this.$root)
|
|
167
|
-
}
|
|
168
|
-
|
|
169
146
|
/** 事件绑定初始化 */
|
|
170
147
|
private initEventBind() {
|
|
171
148
|
// 锚点快速跳转评论
|
|
@@ -194,18 +171,6 @@ export default class Artalk {
|
|
|
194
171
|
Layer.BodyOrgPaddingRight = document.body.style.paddingRight
|
|
195
172
|
}
|
|
196
173
|
|
|
197
|
-
/** 暗黑模式初始化 */
|
|
198
|
-
private initDarkMode() {
|
|
199
|
-
if (this.conf.darkMode === 'auto') {
|
|
200
|
-
// 自动切换暗黑模式,事件监听
|
|
201
|
-
const darkModeMedia = window.matchMedia('(prefers-color-scheme: dark)')
|
|
202
|
-
darkModeMedia.addEventListener('change', (e) => { this.setDarkMode(e.matches) })
|
|
203
|
-
this.setDarkMode(darkModeMedia.matches)
|
|
204
|
-
} else {
|
|
205
|
-
this.setDarkMode(this.conf.darkMode || false)
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
174
|
/** 监听事件 */
|
|
210
175
|
public on<K extends keyof EventPayloadMap>(name: K, handler: Handler<EventPayloadMap[K]>) {
|
|
211
176
|
this.ctx.on(name, handler, 'external')
|
package/src/comment/render.ts
CHANGED
|
@@ -58,7 +58,8 @@ export default class CommentRender {
|
|
|
58
58
|
private renderAvatar() {
|
|
59
59
|
const $avatar = this.$el.querySelector<HTMLElement>('.atk-avatar')!
|
|
60
60
|
const $avatarImg = Utils.createElement<HTMLImageElement>('<img />')
|
|
61
|
-
|
|
61
|
+
const avatarURLBuilder = this.ctx.conf.avatarURLBuilder
|
|
62
|
+
$avatarImg.src = avatarURLBuilder ? avatarURLBuilder(this.data) : this.comment.getGravatarURL()
|
|
62
63
|
if (this.data.link) {
|
|
63
64
|
const $avatarA = Utils.createElement<HTMLLinkElement>('<a target="_blank" rel="noreferrer noopener nofollow"></a>')
|
|
64
65
|
$avatarA.href = Utils.isValidURL(this.data.link) ? this.data.link : `https://${this.data.link}`
|
package/src/context.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { CommentData, NotifyData } from '~/types/artalk-data'
|
|
|
4
4
|
import { Event } from '~/types/event'
|
|
5
5
|
import getI18n, { I18n } from './i18n'
|
|
6
6
|
import User from './lib/user'
|
|
7
|
+
import * as Utils from './lib/utils'
|
|
7
8
|
import ContextApi from '../types/context'
|
|
8
9
|
import Editor from './editor'
|
|
9
10
|
import Comment from './comment'
|
|
@@ -49,6 +50,10 @@ export default class Context implements ContextApi {
|
|
|
49
50
|
this.$root.innerHTML = ''
|
|
50
51
|
|
|
51
52
|
this.api = new Api(this)
|
|
53
|
+
|
|
54
|
+
this.on('conf-loaded', () => {
|
|
55
|
+
this.refreshDarkModeConf()
|
|
56
|
+
})
|
|
52
57
|
}
|
|
53
58
|
|
|
54
59
|
/* 设置持有的同事类 */
|
|
@@ -258,19 +263,44 @@ export default class Context implements ContextApi {
|
|
|
258
263
|
}
|
|
259
264
|
|
|
260
265
|
public setDarkMode(darkMode: boolean): void {
|
|
266
|
+
if (this.conf.darkMode === darkMode) return
|
|
267
|
+
|
|
261
268
|
const darkModeClassName = 'atk-dark-mode'
|
|
262
269
|
|
|
263
270
|
this.conf.darkMode = darkMode
|
|
264
|
-
this.trigger('conf-updated')
|
|
265
271
|
|
|
266
|
-
if (
|
|
272
|
+
if (darkMode) this.$root.classList.add(darkModeClassName)
|
|
267
273
|
else this.$root.classList.remove(darkModeClassName)
|
|
268
274
|
|
|
269
275
|
// for Layer
|
|
270
276
|
const { $wrap: $layerWrap } = GetLayerWrap(this)
|
|
271
277
|
if ($layerWrap) {
|
|
272
|
-
if (
|
|
278
|
+
if (darkMode) $layerWrap.classList.add(darkModeClassName)
|
|
273
279
|
else $layerWrap.classList.remove(darkModeClassName)
|
|
274
280
|
}
|
|
275
281
|
}
|
|
282
|
+
|
|
283
|
+
private darkModeMedia = window.matchMedia('(prefers-color-scheme: dark)')
|
|
284
|
+
private darkModeAutoFunc?: (evt: MediaQueryListEvent) => void
|
|
285
|
+
private refreshDarkModeConf(): void {
|
|
286
|
+
if (this.conf.darkMode === 'auto') {
|
|
287
|
+
// 自动切换暗黑模式,事件监听
|
|
288
|
+
this.setDarkMode(this.darkModeMedia.matches)
|
|
289
|
+
if (!this.darkModeAutoFunc) {
|
|
290
|
+
this.darkModeAutoFunc = (evt) => { this.setDarkMode(evt.matches) }
|
|
291
|
+
this.darkModeMedia.addEventListener('change', this.darkModeAutoFunc)
|
|
292
|
+
}
|
|
293
|
+
} else {
|
|
294
|
+
if (this.darkModeAutoFunc) {
|
|
295
|
+
// 解除事件监听绑定
|
|
296
|
+
this.darkModeMedia.removeEventListener('change', this.darkModeAutoFunc)
|
|
297
|
+
}
|
|
298
|
+
this.setDarkMode(this.conf.darkMode || false)
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
public updateConf(conf: Partial<ArtalkConfig>): void {
|
|
303
|
+
this.conf = Utils.mergeDeep(this.conf, conf)
|
|
304
|
+
this.trigger('conf-loaded')
|
|
305
|
+
}
|
|
276
306
|
}
|
package/src/defaults.ts
CHANGED
|
@@ -29,7 +29,7 @@ const defaults: ArtalkConfig = {
|
|
|
29
29
|
|
|
30
30
|
gravatar: {
|
|
31
31
|
default: 'mp',
|
|
32
|
-
mirror: 'https://
|
|
32
|
+
mirror: 'https://cravatar.cn/avatar/',
|
|
33
33
|
},
|
|
34
34
|
|
|
35
35
|
pagination: {
|
|
@@ -46,7 +46,7 @@ const defaults: ArtalkConfig = {
|
|
|
46
46
|
imgUpload: true,
|
|
47
47
|
reqTimeout: 15000,
|
|
48
48
|
versionCheck: true,
|
|
49
|
-
useBackendConf:
|
|
49
|
+
useBackendConf: true,
|
|
50
50
|
|
|
51
51
|
locale: 'zh-CN',
|
|
52
52
|
}
|
package/src/editor/editor.ts
CHANGED
|
@@ -68,15 +68,15 @@ export default class Editor extends Component {
|
|
|
68
68
|
this.$plugBtnWrap = this.$el.querySelector('.atk-plug-btn-wrap')!
|
|
69
69
|
this.$plugPanelWrap = this.$el.querySelector('.atk-plug-panel-wrap')!
|
|
70
70
|
|
|
71
|
-
// 执行初始化
|
|
72
|
-
this.initLocalStorage()
|
|
73
|
-
this.initHeader()
|
|
74
|
-
this.initTextarea()
|
|
75
|
-
this.initPlugs()
|
|
76
|
-
this.initSubmitBtn()
|
|
77
|
-
|
|
78
71
|
// 监听事件
|
|
79
|
-
this.ctx.on('conf-
|
|
72
|
+
this.ctx.on('conf-loaded', () => {
|
|
73
|
+
// 执行初始化
|
|
74
|
+
this.initLocalStorage()
|
|
75
|
+
this.initHeader()
|
|
76
|
+
this.initTextarea()
|
|
77
|
+
this.initSubmitBtn()
|
|
78
|
+
this.initPlugs()
|
|
79
|
+
})
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
private initLocalStorage() {
|
|
@@ -33,7 +33,7 @@ export default class UploadPlug extends EditorPlug {
|
|
|
33
33
|
$input.click() // 显示选择图片对话框
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
this.ctx.on('conf-
|
|
36
|
+
this.ctx.on('conf-loaded', () => {
|
|
37
37
|
if (!this.ctx.conf.imgUpload) {
|
|
38
38
|
this.getBtn()!.setAttribute('atk-only-admin-show', '')
|
|
39
39
|
this.ctx.checkAdminShowEl()
|
|
@@ -53,11 +53,6 @@ export default class SidebarLayer extends Component {
|
|
|
53
53
|
// viewWrap 滚动条归位
|
|
54
54
|
// this.$viewWrap.scrollTo(0, 0)
|
|
55
55
|
|
|
56
|
-
// 执行动画
|
|
57
|
-
setTimeout(() => {
|
|
58
|
-
this.$el.style.transform = 'translate(0, 0)'
|
|
59
|
-
}, 20)
|
|
60
|
-
|
|
61
56
|
// 管理员身份验证 (若身份失效,弹出验证窗口)
|
|
62
57
|
;(async () => {
|
|
63
58
|
const resp = await this.ctx.getApi().user.loginStatus()
|
|
@@ -76,9 +71,6 @@ export default class SidebarLayer extends Component {
|
|
|
76
71
|
}
|
|
77
72
|
})()
|
|
78
73
|
|
|
79
|
-
// 清空 unread
|
|
80
|
-
this.ctx.updateNotifies([])
|
|
81
|
-
|
|
82
74
|
// 第一次加载
|
|
83
75
|
if (this.firstShow) {
|
|
84
76
|
this.$iframeWrap.innerHTML = ''
|
|
@@ -114,6 +106,16 @@ export default class SidebarLayer extends Component {
|
|
|
114
106
|
this.iframeLoad(this.$iframe!.src.replace('&darkMode=1', ''))
|
|
115
107
|
}
|
|
116
108
|
|
|
109
|
+
// 执行滑动显示动画
|
|
110
|
+
setTimeout(() => {
|
|
111
|
+
this.$el.style.transform = 'translate(0, 0)'
|
|
112
|
+
}, 100)
|
|
113
|
+
|
|
114
|
+
// 清空 unread
|
|
115
|
+
setTimeout(() => {
|
|
116
|
+
this.ctx.updateNotifies([])
|
|
117
|
+
}, 0)
|
|
118
|
+
|
|
117
119
|
this.ctx.trigger('sidebar-show')
|
|
118
120
|
}
|
|
119
121
|
|
|
@@ -11,7 +11,10 @@ const AdminChecker: Checker = {
|
|
|
11
11
|
password: inputVal
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
return
|
|
14
|
+
return (async () => {
|
|
15
|
+
const resp = await that.ctx.getApi().user.login(data.name, data.email, data.password)
|
|
16
|
+
return resp.token
|
|
17
|
+
})()
|
|
15
18
|
},
|
|
16
19
|
|
|
17
20
|
body(that, ctx) {
|
package/src/lib/stat.ts
CHANGED
|
@@ -9,7 +9,15 @@ export interface CountConf {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export const PvCountWidget: ArtalkPlug = (ctx: ContextApi) => {
|
|
12
|
-
|
|
12
|
+
if (!ctx.conf.useBackendConf) {
|
|
13
|
+
// 不使用后端配置,在 Artalk 实例被创建后,立刻初始化
|
|
14
|
+
initCountWidget({ ctx, pvAdd: true })
|
|
15
|
+
} else {
|
|
16
|
+
// 若使用后端配置,需待配置成功获取后 (来自后端设定的 pvEl 等),再初始化
|
|
17
|
+
ctx.on('list-loaded', () => {
|
|
18
|
+
initCountWidget({ ctx, pvAdd: true })
|
|
19
|
+
})
|
|
20
|
+
}
|
|
13
21
|
}
|
|
14
22
|
|
|
15
23
|
/** 初始化评论数和 PV 数量展示元素 */
|
package/src/lib/utils.ts
CHANGED
|
@@ -204,7 +204,14 @@ export function marked(ctx: Context, src: string): string {
|
|
|
204
204
|
// @link https://gist.github.com/lionel-rowe/bb384465ba4e4c81a9c8dada84167225
|
|
205
205
|
let dest = insane(markedContent, {
|
|
206
206
|
allowedClasses: {},
|
|
207
|
-
|
|
207
|
+
// @refer CVE-2018-8495
|
|
208
|
+
// @link https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-8495
|
|
209
|
+
// @link https://leucosite.com/Microsoft-Edge-RCE/
|
|
210
|
+
// @link https://medium.com/@knownsec404team/analysis-of-the-security-issues-of-url-scheme-in-pc-from-cve-2018-8495-934478a36756
|
|
211
|
+
allowedSchemes: [
|
|
212
|
+
'http', 'https', 'mailto',
|
|
213
|
+
'data' // for support base64 encoded image (安全性有待考虑)
|
|
214
|
+
],
|
|
208
215
|
allowedTags: [
|
|
209
216
|
'a', 'abbr', 'article', 'b', 'blockquote', 'br', 'caption', 'code', 'del', 'details', 'div', 'em',
|
|
210
217
|
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'ins', 'kbd', 'li', 'main', 'mark',
|