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.
Files changed (49) hide show
  1. package/dist/Artalk.css +1 -1
  2. package/dist/Artalk.es.js +206 -104
  3. package/dist/Artalk.iife.js +1 -1
  4. package/dist/Artalk.js +1 -1
  5. package/dist/ArtalkLite.css +1 -1
  6. package/dist/ArtalkLite.es.js +206 -104
  7. package/dist/ArtalkLite.iife.js +1 -1
  8. package/dist/ArtalkLite.js +1 -1
  9. package/dist/i18n/jp.js +1 -0
  10. package/dist/i18n/zh-TW.js +1 -0
  11. package/dist/src/api/index.d.ts +1 -1
  12. package/dist/src/api/system.d.ts +7 -0
  13. package/dist/src/api/user.d.ts +16 -2
  14. package/dist/src/artalk.d.ts +0 -6
  15. package/dist/src/context.d.ts +4 -0
  16. package/dist/src/i18n/index.d.ts +68 -74
  17. package/dist/src/list/list-lite.d.ts +17 -7
  18. package/dist/src/list/list.d.ts +1 -0
  19. package/dist/types/artalk-config.d.ts +5 -1
  20. package/dist/types/artalk-data.d.ts +23 -0
  21. package/dist/types/context.d.ts +3 -0
  22. package/dist/types/event.d.ts +1 -1
  23. package/package.json +4 -3
  24. package/scripts/build-i18n.mjs +46 -0
  25. package/src/api/index.ts +3 -2
  26. package/src/api/system.ts +7 -0
  27. package/src/api/user.ts +59 -2
  28. package/src/artalk.ts +0 -35
  29. package/src/comment/render.ts +2 -1
  30. package/src/context.ts +33 -3
  31. package/src/defaults.ts +2 -2
  32. package/src/editor/editor.ts +8 -8
  33. package/src/editor/plugs/upload-plug.ts +1 -1
  34. package/src/i18n/index.ts +18 -90
  35. package/src/i18n/jp.ts +83 -0
  36. package/src/i18n/zh-TW.ts +83 -0
  37. package/src/layer/sidebar-layer.ts +10 -8
  38. package/src/lib/checker/admin-checker.ts +4 -1
  39. package/src/lib/checker/captcha-checker.ts +1 -0
  40. package/src/lib/stat.ts +9 -1
  41. package/src/lib/utils.ts +8 -1
  42. package/src/list/list-lite.ts +68 -26
  43. package/src/list/list.ts +10 -18
  44. package/src/style/main.less +17 -0
  45. package/test/ArtalkI18n.html +1 -0
  46. package/types/artalk-config.d.ts +5 -1
  47. package/types/artalk-data.d.ts +23 -0
  48. package/types/context.d.ts +3 -0
  49. 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 (this.conf.darkMode) this.$root.classList.add(darkModeClassName)
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 (this.conf.darkMode) $layerWrap.classList.add(darkModeClassName)
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://sdn.geekzu.org/avatar/',
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: false,
49
+ useBackendConf: true,
50
50
 
51
51
  locale: 'zh-CN',
52
52
  }
@@ -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-updated', () => {})
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-updated', () => {
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
- export const external = {
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
- /* List */
36
- counter: '{count} 條評論',
37
- sortLatest: '最新',
38
- sortOldest: '最早',
39
- sortBest: '最熱',
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
- // case not found
114
- if (!internal[lang]) {
115
- return internal.en // use `en` by default
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
- return internal[lang]
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, getI18n }
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 that.ctx.getApi().user.login(data.name, data.email, data.password)
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) {
@@ -77,6 +77,7 @@ const CaptchaChecker: Checker = {
77
77
 
78
78
  onError(that, ctx, err, inputVal, formEl) {
79
79
  formEl.querySelector<HTMLElement>('.atk-captcha-img')!.click() // 刷新验证码
80
+ formEl.querySelector<HTMLInputElement>('input[type="text"]')!.value = '' // 清空输入框输入
80
81
  }
81
82
  }
82
83
 
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
- initCountWidget({ ctx, pvAdd: true })
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
- allowedSchemes: ['http', 'https', 'mailto'],
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',
@@ -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: string // 无评论时显示
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
- public flatMode = false
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
- public nestSortBy!: ListNest.SortByType
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 (isFirstLoad && this.pageMode === 'read-more') {
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
- protected onLoad(data: ListData, offset: number) {
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)