artalk 2.4.1 → 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.css +1 -1
- package/dist/Artalk.es.js +206 -104
- package/dist/Artalk.iife.js +1 -1
- package/dist/Artalk.js +1 -1
- package/dist/ArtalkLite.css +1 -1
- package/dist/ArtalkLite.es.js +206 -104
- package/dist/ArtalkLite.iife.js +1 -1
- package/dist/ArtalkLite.js +1 -1
- package/dist/i18n/jp.js +1 -0
- package/dist/i18n/zh-TW.js +1 -0
- 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/i18n/index.d.ts +68 -74
- 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 +4 -3
- package/scripts/build-i18n.mjs +46 -0
- 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/i18n/index.ts +18 -90
- package/src/i18n/jp.ts +83 -0
- package/src/i18n/zh-TW.ts +83 -0
- 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/src/style/main.less +17 -0
- package/test/ArtalkI18n.html +1 -0
- 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/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()
|
package/src/i18n/index.ts
CHANGED
|
@@ -12,92 +12,13 @@ export const internal = {
|
|
|
12
12
|
'zh-CN': zhCN,
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
'jp-JP': {},
|
|
17
|
-
'zh-TW': {
|
|
18
|
-
/* Editor */
|
|
19
|
-
placeholder: '鍵入內容...',
|
|
20
|
-
noComment: '「此時無聲勝有聲」',
|
|
21
|
-
send: '發送評論',
|
|
22
|
-
save: '保存評論',
|
|
23
|
-
nick: '昵稱',
|
|
24
|
-
email: '郵箱',
|
|
25
|
-
link: '網址',
|
|
26
|
-
emoticon: '表情',
|
|
27
|
-
preview: '預覽',
|
|
28
|
-
image: '圖片',
|
|
29
|
-
uploadFail: '上傳失敗',
|
|
30
|
-
commentFail: '評論失敗',
|
|
31
|
-
restoredMsg: '內容已自動恢復',
|
|
32
|
-
onlyAdminCanReply: '僅管理員可評論',
|
|
33
|
-
uploadLoginMsg: '填入你的名字郵箱才能上傳哦',
|
|
15
|
+
const GLOBAL_LOCALES_KEY = "ArtalkI18n";
|
|
34
16
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
sortAuthor: '作者',
|
|
41
|
-
openComment: '打開評論',
|
|
42
|
-
closeComment: '關閉評論',
|
|
43
|
-
listLoadFailMsg: '無法獲取評論列表數據',
|
|
44
|
-
listRetry: '點擊重新獲取',
|
|
45
|
-
loadMore: '加載更多',
|
|
46
|
-
|
|
47
|
-
/* Comment */
|
|
48
|
-
admin: '管理員',
|
|
49
|
-
reply: '回覆',
|
|
50
|
-
voteUp: '贊同',
|
|
51
|
-
voteDown: '反對',
|
|
52
|
-
voteFail: '投票失敗',
|
|
53
|
-
readMore: '閱讀更多',
|
|
54
|
-
actionConfirm: '確認操作',
|
|
55
|
-
collapse: '摺疊',
|
|
56
|
-
collapsed: '已摺疊',
|
|
57
|
-
collapsedMsg: '該評論已被系統或管理員摺疊',
|
|
58
|
-
expand: '展開',
|
|
59
|
-
approved: '已審',
|
|
60
|
-
pending: '待審',
|
|
61
|
-
pendingMsg: '審核中,僅本人可見。',
|
|
62
|
-
edit: '編輯',
|
|
63
|
-
editCancel: '取消編輯',
|
|
64
|
-
delete: '刪除',
|
|
65
|
-
deleteConfirm: '確認刪除',
|
|
66
|
-
pin: '置頂',
|
|
67
|
-
unpin: '取消置頂',
|
|
68
|
-
|
|
69
|
-
/* Time */
|
|
70
|
-
seconds: '秒前',
|
|
71
|
-
minutes: '分鐘前',
|
|
72
|
-
hours: '小時前',
|
|
73
|
-
days: '天前',
|
|
74
|
-
now: '剛剛',
|
|
75
|
-
|
|
76
|
-
/* Checker */
|
|
77
|
-
adminCheck: '鍵入密碼來驗證管理員身份:',
|
|
78
|
-
captchaCheck: '鍵入驗證碼繼續:',
|
|
79
|
-
confirm: '確認',
|
|
80
|
-
cancel: '取消',
|
|
81
|
-
|
|
82
|
-
/* Sidebar */
|
|
83
|
-
msgCenter: '通知中心',
|
|
84
|
-
ctrlCenter: '控制中心',
|
|
85
|
-
|
|
86
|
-
/* General */
|
|
87
|
-
frontend: '前端',
|
|
88
|
-
backend: '後端',
|
|
89
|
-
loading: '加載中',
|
|
90
|
-
loadFail: '加載失敗',
|
|
91
|
-
editing: '修改中',
|
|
92
|
-
editFail: '修改失敗',
|
|
93
|
-
deleting: '刪除中',
|
|
94
|
-
deleteFail: '刪除失敗',
|
|
95
|
-
reqGot: '請求響應',
|
|
96
|
-
reqAborted: '請求超時或意外終止',
|
|
97
|
-
},
|
|
98
|
-
'fr-FR': {},
|
|
99
|
-
'de-DE': {},
|
|
100
|
-
'bn-IN': {},
|
|
17
|
+
export function defineLocaleExternal(lang: string, locale: I18n, aliases?: string[]) {
|
|
18
|
+
if (!window[GLOBAL_LOCALES_KEY]) window[GLOBAL_LOCALES_KEY] = {}
|
|
19
|
+
window[GLOBAL_LOCALES_KEY][lang] = locale
|
|
20
|
+
if (aliases) aliases.forEach(l => { window[GLOBAL_LOCALES_KEY][l] = locale })
|
|
21
|
+
return locale
|
|
101
22
|
}
|
|
102
23
|
|
|
103
24
|
/**
|
|
@@ -110,12 +31,19 @@ function getLocaleSet(lang: string): I18n {
|
|
|
110
31
|
(_, p1: string, p2: string) => (p1.toLowerCase() + (p2 || '').toUpperCase())
|
|
111
32
|
)
|
|
112
33
|
|
|
113
|
-
//
|
|
114
|
-
if (
|
|
115
|
-
return internal
|
|
34
|
+
// internal finding
|
|
35
|
+
if (internal[lang]) {
|
|
36
|
+
return internal[lang]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// external finding
|
|
40
|
+
if (window[GLOBAL_LOCALES_KEY] && window[GLOBAL_LOCALES_KEY][lang]) {
|
|
41
|
+
return window[GLOBAL_LOCALES_KEY][lang]
|
|
116
42
|
}
|
|
117
43
|
|
|
118
|
-
|
|
44
|
+
// case when not found:
|
|
45
|
+
// use `en` by default
|
|
46
|
+
return internal.en
|
|
119
47
|
}
|
|
120
48
|
|
|
121
49
|
/**
|
|
@@ -132,5 +60,5 @@ function getI18n(locale: I18n|string, key: keyof I18n, args: {[key: string]: str
|
|
|
132
60
|
return str
|
|
133
61
|
}
|
|
134
62
|
|
|
135
|
-
export { getLocaleSet
|
|
63
|
+
export { getLocaleSet }
|
|
136
64
|
export default getI18n
|
package/src/i18n/jp.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { defineLocaleExternal } from '.'
|
|
2
|
+
|
|
3
|
+
export default defineLocaleExternal('jp', {
|
|
4
|
+
/* エディタ */
|
|
5
|
+
placeholder: 'コメントを残す',
|
|
6
|
+
noComment: 'コメントなし',
|
|
7
|
+
send: '送信',
|
|
8
|
+
save: '保存',
|
|
9
|
+
nick: 'ニックネーム',
|
|
10
|
+
email: 'メール',
|
|
11
|
+
link: 'ウェブサイト',
|
|
12
|
+
emoticon: '絵文字',
|
|
13
|
+
preview: 'プレビュー',
|
|
14
|
+
image: '画像',
|
|
15
|
+
uploadFail: 'アップロードに失敗しました',
|
|
16
|
+
commentFail: 'コメントに失敗しました',
|
|
17
|
+
restoredMsg: 'コンテンツが復元されました',
|
|
18
|
+
onlyAdminCanReply: '管理者のみが返信できます',
|
|
19
|
+
uploadLoginMsg: 'アップロードするには、名前とメールアドレスを入力してください',
|
|
20
|
+
|
|
21
|
+
/* リスト */
|
|
22
|
+
counter: '{count} コメント',
|
|
23
|
+
sortLatest: '最新のもの',
|
|
24
|
+
sortOldest: '最も古い',
|
|
25
|
+
sortBest: 'ベスト',
|
|
26
|
+
sortAuthor: '著者',
|
|
27
|
+
openComment: 'コメントを開く',
|
|
28
|
+
closeComment: 'コメントを閉じる',
|
|
29
|
+
listLoadFailMsg: 'コメントの読み込みに失敗しました',
|
|
30
|
+
listRetry: 'クリックで再試行',
|
|
31
|
+
loadMore: 'もっと読み込む',
|
|
32
|
+
|
|
33
|
+
/* コメント */
|
|
34
|
+
admin: '管理者',
|
|
35
|
+
reply: '返信',
|
|
36
|
+
voteUp: 'アップ',
|
|
37
|
+
voteDown: 'ダウン',
|
|
38
|
+
voteFail: '投票に失敗しました',
|
|
39
|
+
readMore: '続きを読む',
|
|
40
|
+
actionConfirm: '確認',
|
|
41
|
+
collapse: '折りたたむ',
|
|
42
|
+
collapsed: '折りたたまれた',
|
|
43
|
+
collapsedMsg: 'このコメントは折りたたまれました',
|
|
44
|
+
expand: '展開',
|
|
45
|
+
approved: '承認されました',
|
|
46
|
+
pending: '保留中',
|
|
47
|
+
pendingMsg: '保留中、コメント投稿者にのみ表示されます',
|
|
48
|
+
edit: '編集',
|
|
49
|
+
editCancel: '編集をキャンセル',
|
|
50
|
+
delete: '削除',
|
|
51
|
+
deleteConfirm: '確認',
|
|
52
|
+
pin: 'ピン',
|
|
53
|
+
unpin: 'ピン外し',
|
|
54
|
+
|
|
55
|
+
/* 時間 */
|
|
56
|
+
seconds: '秒前',
|
|
57
|
+
minutes: '分前',
|
|
58
|
+
hours: '時間前',
|
|
59
|
+
days: '数日前',
|
|
60
|
+
now: 'たった今',
|
|
61
|
+
|
|
62
|
+
/* チェッカー */
|
|
63
|
+
adminCheck: '管理者パスワードを入力してください:',
|
|
64
|
+
captchaCheck: '続けるにはCAPTCHAを入力してください:',
|
|
65
|
+
confirm: '確認',
|
|
66
|
+
cancel: '取消',
|
|
67
|
+
|
|
68
|
+
/* サイドバー */
|
|
69
|
+
msgCenter: 'メッセージ',
|
|
70
|
+
ctrlCenter: 'コンソール',
|
|
71
|
+
|
|
72
|
+
/* 一般 */
|
|
73
|
+
frontend: 'フロントエンド',
|
|
74
|
+
backend: 'バックエンド',
|
|
75
|
+
loading: '読み込み中',
|
|
76
|
+
loadFail: '読み込みに失敗しました',
|
|
77
|
+
editing: '編集中',
|
|
78
|
+
editFail: '編集に失敗しました',
|
|
79
|
+
deleting: '削除中',
|
|
80
|
+
deleteFail: '削除に失敗しました',
|
|
81
|
+
reqGot: 'リクエストを取得しました',
|
|
82
|
+
reqAborted: 'リクエストがタイムアウトしたか、予期せず終了した',
|
|
83
|
+
}, ['jp-JP'])
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { defineLocaleExternal } from '.'
|
|
2
|
+
|
|
3
|
+
export default defineLocaleExternal('zh-TW', {
|
|
4
|
+
/* Editor */
|
|
5
|
+
placeholder: '鍵入內容...',
|
|
6
|
+
noComment: '「此時無聲勝有聲」',
|
|
7
|
+
send: '發送評論',
|
|
8
|
+
save: '保存評論',
|
|
9
|
+
nick: '昵稱',
|
|
10
|
+
email: '郵箱',
|
|
11
|
+
link: '網址',
|
|
12
|
+
emoticon: '表情',
|
|
13
|
+
preview: '預覽',
|
|
14
|
+
image: '圖片',
|
|
15
|
+
uploadFail: '上傳失敗',
|
|
16
|
+
commentFail: '評論失敗',
|
|
17
|
+
restoredMsg: '內容已自動恢復',
|
|
18
|
+
onlyAdminCanReply: '僅管理員可評論',
|
|
19
|
+
uploadLoginMsg: '填入你的名字郵箱才能上傳哦',
|
|
20
|
+
|
|
21
|
+
/* List */
|
|
22
|
+
counter: '{count} 條評論',
|
|
23
|
+
sortLatest: '最新',
|
|
24
|
+
sortOldest: '最早',
|
|
25
|
+
sortBest: '最熱',
|
|
26
|
+
sortAuthor: '作者',
|
|
27
|
+
openComment: '打開評論',
|
|
28
|
+
closeComment: '關閉評論',
|
|
29
|
+
listLoadFailMsg: '無法獲取評論列表數據',
|
|
30
|
+
listRetry: '點擊重新獲取',
|
|
31
|
+
loadMore: '加載更多',
|
|
32
|
+
|
|
33
|
+
/* Comment */
|
|
34
|
+
admin: '管理員',
|
|
35
|
+
reply: '回覆',
|
|
36
|
+
voteUp: '贊同',
|
|
37
|
+
voteDown: '反對',
|
|
38
|
+
voteFail: '投票失敗',
|
|
39
|
+
readMore: '閱讀更多',
|
|
40
|
+
actionConfirm: '確認操作',
|
|
41
|
+
collapse: '摺疊',
|
|
42
|
+
collapsed: '已摺疊',
|
|
43
|
+
collapsedMsg: '該評論已被系統或管理員摺疊',
|
|
44
|
+
expand: '展開',
|
|
45
|
+
approved: '已審',
|
|
46
|
+
pending: '待審',
|
|
47
|
+
pendingMsg: '審核中,僅本人可見。',
|
|
48
|
+
edit: '編輯',
|
|
49
|
+
editCancel: '取消編輯',
|
|
50
|
+
delete: '刪除',
|
|
51
|
+
deleteConfirm: '確認刪除',
|
|
52
|
+
pin: '置頂',
|
|
53
|
+
unpin: '取消置頂',
|
|
54
|
+
|
|
55
|
+
/* Time */
|
|
56
|
+
seconds: '秒前',
|
|
57
|
+
minutes: '分鐘前',
|
|
58
|
+
hours: '小時前',
|
|
59
|
+
days: '天前',
|
|
60
|
+
now: '剛剛',
|
|
61
|
+
|
|
62
|
+
/* Checker */
|
|
63
|
+
adminCheck: '鍵入密碼來驗證管理員身份:',
|
|
64
|
+
captchaCheck: '鍵入驗證碼繼續:',
|
|
65
|
+
confirm: '確認',
|
|
66
|
+
cancel: '取消',
|
|
67
|
+
|
|
68
|
+
/* Sidebar */
|
|
69
|
+
msgCenter: '通知中心',
|
|
70
|
+
ctrlCenter: '控制中心',
|
|
71
|
+
|
|
72
|
+
/* General */
|
|
73
|
+
frontend: '前端',
|
|
74
|
+
backend: '後端',
|
|
75
|
+
loading: '加載中',
|
|
76
|
+
loadFail: '加載失敗',
|
|
77
|
+
editing: '修改中',
|
|
78
|
+
editFail: '修改失敗',
|
|
79
|
+
deleting: '刪除中',
|
|
80
|
+
deleteFail: '刪除失敗',
|
|
81
|
+
reqGot: '請求響應',
|
|
82
|
+
reqAborted: '請求超時或意外終止',
|
|
83
|
+
})
|
|
@@ -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',
|
package/src/list/list-lite.ts
CHANGED
|
@@ -15,17 +15,56 @@ export default class ListLite extends Component {
|
|
|
15
15
|
protected data?: ListData
|
|
16
16
|
protected isLoading: boolean = false
|
|
17
17
|
|
|
18
|
-
public noCommentText
|
|
18
|
+
public noCommentText?: string // 无评论时显示
|
|
19
|
+
|
|
20
|
+
/** 嵌套模式下的排序方式 */
|
|
21
|
+
private _nestSortBy?: ListNest.SortByType
|
|
22
|
+
public get nestSortBy() {
|
|
23
|
+
if (this._nestSortBy !== undefined) return this._nestSortBy
|
|
24
|
+
return this.ctx.conf.nestSort || 'DATE_ASC'
|
|
25
|
+
}
|
|
26
|
+
public set nestSortBy(val: ListNest.SortByType) {
|
|
27
|
+
this._nestSortBy = val
|
|
28
|
+
}
|
|
19
29
|
|
|
20
30
|
/** 平铺模式 */
|
|
21
|
-
|
|
31
|
+
private _flatMode?:boolean
|
|
32
|
+
public get flatMode() {
|
|
33
|
+
if (this._flatMode !== undefined)
|
|
34
|
+
return this._flatMode
|
|
35
|
+
|
|
36
|
+
// 配置开启平铺模式
|
|
37
|
+
if (this.ctx.conf.flatMode === true || Number(this.ctx.conf.nestMax) <= 1)
|
|
38
|
+
return true
|
|
22
39
|
|
|
23
|
-
|
|
24
|
-
|
|
40
|
+
// 自动判断启用平铺模式
|
|
41
|
+
if (this.ctx.conf.flatMode === 'auto' && window.matchMedia("(max-width: 768px)").matches)
|
|
42
|
+
return true
|
|
43
|
+
|
|
44
|
+
return false
|
|
45
|
+
}
|
|
46
|
+
public set flatMode(val: boolean) {
|
|
47
|
+
this._flatMode = val
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** 分页方式 */
|
|
51
|
+
public _pageMode?: 'pagination'|'read-more'
|
|
52
|
+
public get pageMode() {
|
|
53
|
+
return this._pageMode || (this.conf.pagination.readMore ? 'read-more' : 'pagination')
|
|
54
|
+
}
|
|
55
|
+
public set pageMode(val: 'pagination'|'read-more') {
|
|
56
|
+
this._pageMode = val
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** 每页数量 (每次请求获取量) */
|
|
60
|
+
private _pageSize?: number
|
|
61
|
+
public get pageSize() {
|
|
62
|
+
return this._pageSize || this.conf.pagination.pageSize
|
|
63
|
+
}
|
|
64
|
+
public set pageSize(val: number) {
|
|
65
|
+
this._pageSize = val
|
|
66
|
+
}
|
|
25
67
|
|
|
26
|
-
/** 分页 */
|
|
27
|
-
public pageMode: 'pagination'|'read-more' = 'pagination'
|
|
28
|
-
public pageSize: number = 20 // 每次请求获取量
|
|
29
68
|
public scrollListenerAt?: HTMLElement // 监听指定元素上的滚动
|
|
30
69
|
public repositionAt?: HTMLElement // 翻页归位到指定元素
|
|
31
70
|
|
|
@@ -51,12 +90,6 @@ export default class ListLite extends Component {
|
|
|
51
90
|
</div>`)
|
|
52
91
|
this.$commentsWrap = this.$el.querySelector('.atk-list-comments-wrap')!
|
|
53
92
|
|
|
54
|
-
// 评论为空时显示字符
|
|
55
|
-
this.noCommentText = ctx.conf.noComment || ctx.$t('noComment')
|
|
56
|
-
|
|
57
|
-
// 嵌套排序方式
|
|
58
|
-
this.nestSortBy = this.ctx.conf.nestSort || 'DATE_ASC'
|
|
59
|
-
|
|
60
93
|
// 评论时间自动更新
|
|
61
94
|
window.setInterval(() => {
|
|
62
95
|
this.$el.querySelectorAll<HTMLElement>('[data-atk-comment-date]').forEach(el => {
|
|
@@ -64,6 +97,10 @@ export default class ListLite extends Component {
|
|
|
64
97
|
el.innerText = Utils.timeAgo(new Date(Number(date)), this.ctx)
|
|
65
98
|
})
|
|
66
99
|
}, 30 * 1000) // 30s 更新一次
|
|
100
|
+
|
|
101
|
+
// 事件监听
|
|
102
|
+
this.ctx.on('conf-loaded', () => {
|
|
103
|
+
})
|
|
67
104
|
}
|
|
68
105
|
|
|
69
106
|
public getData() {
|
|
@@ -99,8 +136,8 @@ export default class ListLite extends Component {
|
|
|
99
136
|
// 事件通知(开始加载评论)
|
|
100
137
|
this.ctx.trigger('list-load')
|
|
101
138
|
|
|
102
|
-
//
|
|
103
|
-
if (
|
|
139
|
+
// 清空评论(按钮加载更多的第一页、每次加载分页页面)
|
|
140
|
+
if ((this.pageMode === 'read-more' && isFirstLoad) || this.pageMode === 'pagination') {
|
|
104
141
|
this.ctx.clearAllComments()
|
|
105
142
|
}
|
|
106
143
|
|
|
@@ -130,22 +167,25 @@ export default class ListLite extends Component {
|
|
|
130
167
|
}
|
|
131
168
|
}
|
|
132
169
|
|
|
133
|
-
|
|
134
|
-
// 清空评论(分页条)
|
|
135
|
-
if (this.pageMode === 'pagination') { this.ctx.clearAllComments() }
|
|
170
|
+
private confLoaded = false
|
|
136
171
|
|
|
172
|
+
protected onLoad(data: ListData, offset: number) {
|
|
137
173
|
this.data = data
|
|
138
174
|
|
|
175
|
+
// 装载后端提供的配置
|
|
176
|
+
if (!this.confLoaded) {
|
|
177
|
+
if (this.conf.useBackendConf)
|
|
178
|
+
this.ctx.updateConf(data.conf.frontend_conf)
|
|
179
|
+
else
|
|
180
|
+
this.ctx.updateConf({})
|
|
181
|
+
this.confLoaded = true
|
|
182
|
+
}
|
|
183
|
+
|
|
139
184
|
// 版本检测
|
|
140
185
|
const feMinVersion = data.api_version?.fe_min_version || '0.0.0'
|
|
141
186
|
if (this.ctx.conf.versionCheck && this.versionCheck('frontend', feMinVersion, ARTALK_VERSION)) return
|
|
142
187
|
if (this.ctx.conf.versionCheck && this.versionCheck('backend', backendMinVersion, data.api_version?.version)) return
|
|
143
188
|
|
|
144
|
-
// 图片上传功能
|
|
145
|
-
if (data.conf && typeof data.conf.img_upload === "boolean") {
|
|
146
|
-
this.ctx.conf.imgUpload = data.conf.img_upload
|
|
147
|
-
}
|
|
148
|
-
|
|
149
189
|
// 导入数据
|
|
150
190
|
this.importComments(data.comments)
|
|
151
191
|
|
|
@@ -157,7 +197,6 @@ export default class ListLite extends Component {
|
|
|
157
197
|
|
|
158
198
|
this.ctx.updateNotifies(data.unread || [])
|
|
159
199
|
this.ctx.trigger('list-loaded')
|
|
160
|
-
this.ctx.trigger('conf-updated')
|
|
161
200
|
|
|
162
201
|
if (this.onAfterLoad) this.onAfterLoad(data)
|
|
163
202
|
}
|
|
@@ -238,6 +277,10 @@ export default class ListLite extends Component {
|
|
|
238
277
|
|
|
239
278
|
/** 错误处理 */
|
|
240
279
|
protected onError(msg: any, offset: number, errData?: any) {
|
|
280
|
+
if (!this.confLoaded) {
|
|
281
|
+
this.ctx.updateConf({})
|
|
282
|
+
}
|
|
283
|
+
|
|
241
284
|
msg = String(msg)
|
|
242
285
|
console.error(msg)
|
|
243
286
|
|
|
@@ -286,7 +329,7 @@ export default class ListLite extends Component {
|
|
|
286
329
|
if (isNoComment) {
|
|
287
330
|
if (!$noComment) {
|
|
288
331
|
$noComment = Utils.createElement('<div class="atk-list-no-comment"></div>')
|
|
289
|
-
$noComment.innerHTML = this.noCommentText
|
|
332
|
+
$noComment.innerHTML = this.noCommentText || this.ctx.conf.noComment || this.ctx.$t('noComment')
|
|
290
333
|
this.$commentsWrap.appendChild($noComment)
|
|
291
334
|
}
|
|
292
335
|
} else {
|
|
@@ -473,7 +516,6 @@ export default class ListLite extends Component {
|
|
|
473
516
|
ignoreBtn.onclick = () => {
|
|
474
517
|
Ui.setError(this.$el.parentElement!, null)
|
|
475
518
|
this.ctx.conf.versionCheck = false
|
|
476
|
-
this.ctx.trigger('conf-updated')
|
|
477
519
|
this.fetchComments(0)
|
|
478
520
|
}
|
|
479
521
|
errEl.append(ignoreBtn)
|