artalk 2.4.4 → 2.5.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.
Files changed (156) hide show
  1. package/README.md +27 -0
  2. package/dist/Artalk.css +1 -1
  3. package/dist/Artalk.es.js +7066 -6256
  4. package/dist/Artalk.iife.js +1 -1
  5. package/dist/Artalk.js +1 -1
  6. package/dist/ArtalkLite.css +1 -1
  7. package/dist/ArtalkLite.es.js +5095 -4396
  8. package/dist/ArtalkLite.iife.js +1 -1
  9. package/dist/ArtalkLite.js +1 -1
  10. package/dist/i18n/jp.js +1 -1
  11. package/dist/i18n/zh-TW.js +1 -1
  12. package/dist/src/api/index.d.ts +2 -2
  13. package/dist/src/api/system.d.ts +5 -2
  14. package/dist/src/artalk.d.ts +42 -34
  15. package/dist/src/comment/comment.d.ts +2 -2
  16. package/dist/src/comment/height-limit.d.ts +30 -0
  17. package/dist/src/comment/render-ctx.d.ts +23 -0
  18. package/dist/src/comment/render.d.ts +2 -50
  19. package/dist/src/comment/renders/actions.d.ts +5 -0
  20. package/dist/src/comment/renders/avatar.d.ts +5 -0
  21. package/dist/src/comment/renders/content.d.ts +5 -0
  22. package/dist/src/comment/renders/header.d.ts +5 -0
  23. package/dist/src/comment/renders/index.d.ts +2 -0
  24. package/dist/src/comment/renders/pending.d.ts +5 -0
  25. package/dist/src/comment/renders/reply-at.d.ts +5 -0
  26. package/dist/src/comment/renders/reply-to.d.ts +5 -0
  27. package/dist/src/components/dialog.d.ts +1 -1
  28. package/dist/src/config.d.ts +8 -0
  29. package/dist/src/context.d.ts +16 -29
  30. package/dist/src/editor/edit-mode.d.ts +9 -0
  31. package/dist/src/editor/editor.d.ts +37 -45
  32. package/dist/src/editor/funcs.d.ts +2 -0
  33. package/dist/src/editor/mover.d.ts +8 -0
  34. package/dist/src/editor/plug-manager.d.ts +17 -0
  35. package/dist/src/editor/plugs/editor-plug.d.ts +13 -1
  36. package/dist/src/editor/reply.d.ts +9 -0
  37. package/dist/src/editor/submit.d.ts +16 -0
  38. package/dist/src/editor/ui.d.ts +25 -0
  39. package/dist/src/i18n/external.d.ts +70 -0
  40. package/dist/src/i18n/index.d.ts +11 -75
  41. package/dist/src/layer/index.d.ts +2 -2
  42. package/dist/src/layer/layer.d.ts +1 -1
  43. package/dist/src/lib/checker/captcha-renders.d.ts +5 -0
  44. package/dist/src/lib/checker/index.d.ts +16 -10
  45. package/dist/src/lib/dark-mode.d.ts +4 -0
  46. package/dist/src/lib/marked.d.ts +9 -0
  47. package/dist/src/lib/user.d.ts +6 -3
  48. package/dist/src/lib/utils.d.ts +1 -5
  49. package/dist/src/list/list-lite.d.ts +15 -11
  50. package/dist/src/list/list-nest.d.ts +2 -2
  51. package/dist/src/list/list-ui.d.ts +16 -0
  52. package/dist/src/list/list.d.ts +0 -1
  53. package/dist/src/list/paginator/adaptors/index.d.ts +12 -0
  54. package/dist/src/list/paginator/adaptors/pagination.d.ts +9 -0
  55. package/dist/src/list/paginator/adaptors/read-more.d.ts +10 -0
  56. package/dist/src/list/paginator/holder.d.ts +16 -0
  57. package/dist/src/list/paginator/index.d.ts +10 -0
  58. package/dist/src/service.d.ts +46 -0
  59. package/dist/types/artalk-config.d.ts +5 -2
  60. package/dist/types/artalk-data.d.ts +3 -3
  61. package/dist/types/context.d.ts +13 -20
  62. package/dist/types/global.d.ts +1 -0
  63. package/package.json +6 -7
  64. package/src/api/comment.ts +7 -7
  65. package/src/api/page.ts +0 -2
  66. package/src/api/request.ts +6 -4
  67. package/src/api/system.ts +3 -12
  68. package/src/api/upload.ts +3 -4
  69. package/src/api/user.ts +6 -8
  70. package/src/artalk.ts +103 -158
  71. package/src/comment/comment.ts +7 -6
  72. package/src/comment/height-limit.ts +100 -0
  73. package/src/comment/render-ctx.ts +30 -0
  74. package/src/comment/render.ts +31 -375
  75. package/src/comment/renders/actions.ts +114 -0
  76. package/src/comment/renders/avatar.ts +22 -0
  77. package/src/comment/renders/content.ts +39 -0
  78. package/src/comment/renders/header.ts +84 -0
  79. package/src/comment/renders/index.ts +19 -0
  80. package/src/comment/renders/pending.ts +12 -0
  81. package/src/comment/renders/reply-at.ts +16 -0
  82. package/src/comment/renders/reply-to.ts +24 -0
  83. package/src/config.ts +65 -0
  84. package/src/context.ts +33 -86
  85. package/src/defaults.ts +1 -0
  86. package/src/editor/edit-mode.ts +103 -0
  87. package/src/editor/editor.ts +101 -395
  88. package/src/editor/funcs.ts +97 -0
  89. package/src/editor/mover.ts +43 -0
  90. package/src/editor/plug-manager.ts +172 -0
  91. package/src/editor/plugs/editor-plug.ts +20 -2
  92. package/src/editor/plugs/{emoticons-plug.less → emoticons-plug.scss} +5 -4
  93. package/src/editor/plugs/emoticons-plug.ts +2 -1
  94. package/src/editor/plugs/header-input-plug.ts +11 -10
  95. package/src/editor/plugs/{preview-plug.less → preview-plug.scss} +1 -1
  96. package/src/editor/plugs/preview-plug.ts +8 -4
  97. package/src/editor/plugs/upload-plug.ts +12 -14
  98. package/src/editor/reply.ts +62 -0
  99. package/src/editor/submit.ts +99 -0
  100. package/src/editor/ui.ts +36 -0
  101. package/src/i18n/external.ts +10 -0
  102. package/src/i18n/index.ts +22 -19
  103. package/src/i18n/jp.ts +1 -1
  104. package/src/i18n/zh-TW.ts +1 -1
  105. package/src/layer/index.ts +2 -2
  106. package/src/layer/layer.ts +4 -4
  107. package/src/layer/sidebar-layer.ts +4 -2
  108. package/src/lib/checker/admin.ts +37 -0
  109. package/src/lib/checker/captcha-renders.ts +68 -0
  110. package/src/lib/checker/captcha.ts +26 -0
  111. package/src/lib/checker/index.ts +36 -20
  112. package/src/lib/dark-mode.ts +48 -0
  113. package/src/lib/marked.ts +133 -0
  114. package/src/lib/user.ts +13 -7
  115. package/src/lib/utils.ts +1 -123
  116. package/src/list/list-lite.ts +59 -162
  117. package/src/list/list-ui.ts +137 -0
  118. package/src/list/list.ts +33 -60
  119. package/src/list/paginator/adaptors/index.ts +21 -0
  120. package/src/list/paginator/adaptors/pagination.ts +42 -0
  121. package/src/list/paginator/adaptors/read-more.ts +56 -0
  122. package/src/list/paginator/holder.ts +56 -0
  123. package/src/list/paginator/index.ts +13 -0
  124. package/src/service.ts +118 -0
  125. package/src/style/{_color.less → _color.scss} +3 -3
  126. package/src/style/{_extend.less → _extend.scss} +1 -1
  127. package/src/style/_variables.scss +3 -0
  128. package/src/style/{comment.less → comment.scss} +36 -12
  129. package/src/style/{editor.less → editor.scss} +11 -11
  130. package/src/style/{list.less → list.scss} +12 -10
  131. package/src/style/{main.less → main.scss} +24 -23
  132. package/src/style/{sidebar-layer.less → sidebar-layer.scss} +8 -8
  133. package/types/artalk-config.d.ts +5 -2
  134. package/types/artalk-data.d.ts +3 -3
  135. package/types/context.d.ts +13 -20
  136. package/types/global.d.ts +1 -0
  137. package/CNAME +0 -1
  138. package/LICENSE +0 -165
  139. package/index.html +0 -157
  140. package/postcss.config.js +0 -5
  141. package/scripts/build-i18n.mjs +0 -46
  142. package/src/lib/checker/admin-checker.ts +0 -36
  143. package/src/lib/checker/captcha-checker.ts +0 -84
  144. package/src/style/_variables.less +0 -3
  145. package/test/ArtalkCount.html +0 -16
  146. package/test/ArtalkI18n.html +0 -16
  147. package/test/ArtalkLite.html +0 -15
  148. package/test/CSRF.html +0 -26
  149. package/tsconfig.json +0 -12
  150. package/vite-base.config.ts +0 -21
  151. package/vite-demo.config.ts +0 -12
  152. package/vite-lite.config.ts +0 -22
  153. package/vite.config.ts +0 -27
  154. /package/dist/src/lib/checker/{admin-checker.d.ts → admin.d.ts} +0 -0
  155. /package/dist/src/lib/checker/{captcha-checker.d.ts → captcha.d.ts} +0 -0
  156. /package/src/style/{pagination.less → pagination.scss} +0 -0
@@ -0,0 +1,137 @@
1
+ import ListLite from './list-lite'
2
+ import * as Utils from '../lib/utils'
3
+ import * as Ui from '../lib/ui'
4
+ import User from '../lib/user'
5
+ import $t from '../i18n'
6
+
7
+ /** 界面刷新 */
8
+ export function refreshUI(list: ListLite) {
9
+ // 无评论
10
+ const isNoComment = list.ctx.getCommentList().length <= 0
11
+ let $noComment = list.getCommentsWrapEl().querySelector<HTMLElement>('.atk-list-no-comment')
12
+
13
+ if (isNoComment) {
14
+ if (!$noComment) {
15
+ $noComment = Utils.createElement('<div class="atk-list-no-comment"></div>')
16
+ $noComment.innerHTML = list.noCommentText || list.ctx.conf.noComment || list.ctx.$t('noComment')
17
+ list.getCommentsWrapEl().appendChild($noComment)
18
+ }
19
+ } else {
20
+ $noComment?.remove()
21
+ }
22
+
23
+ // 仅管理员显示控制
24
+ list.ctx.checkAdminShowEl()
25
+ }
26
+
27
+ /** 构建错误提示界面 */
28
+ export function renderErrorDialog(list: ListLite, errMsg: string, errData?: any): HTMLElement {
29
+ const errEl = Utils.createElement(`<span>${errMsg},${$t('listLoadFailMsg')}<br/></span>`)
30
+
31
+ const $retryBtn = Utils.createElement(`<span style="cursor:pointer;">${$t('listRetry')}</span>`)
32
+ $retryBtn.onclick = () => (list.fetchComments(0))
33
+ errEl.appendChild($retryBtn)
34
+
35
+ const adminBtn = Utils.createElement('<span atk-only-admin-show> | <span style="cursor:pointer;">打开控制台</span></span>')
36
+ errEl.appendChild(adminBtn)
37
+ if (!User.data.isAdmin) adminBtn.classList.add('atk-hide')
38
+
39
+ let sidebarView = ''
40
+
41
+ // 找不到站点错误,打开侧边栏并填入创建站点表单
42
+ if (errData?.err_no_site) {
43
+ const viewLoadParam = {
44
+ create_name: list.ctx.conf.site,
45
+ create_urls: `${window.location.protocol}//${window.location.host}`
46
+ }
47
+ sidebarView = `sites|${JSON.stringify(viewLoadParam)}`
48
+ }
49
+
50
+ adminBtn.onclick = () => list.ctx.showSidebar({
51
+ view: sidebarView as any
52
+ })
53
+
54
+ return errEl
55
+ }
56
+
57
+ /** 版本检测弹窗 */
58
+ export function versionCheckDialog(list: ListLite, feVer: string, beVer: string): boolean {
59
+ const comp = Utils.versionCompare(feVer, beVer)
60
+ const notSameVer = (comp !== 0)
61
+ if (notSameVer) {
62
+ const errEl = Utils.createElement(
63
+ `<div>请更新 Artalk ${comp < 0 ? $t('frontend') : $t('backend')}以获得完整体验 ` +
64
+ `(<a href="https://artalk.js.org/" target="_blank">帮助文档</a>)` +
65
+ `<br/><br/><span style="color: var(--at-color-meta);">` +
66
+ `当前版本:${$t('frontend')} ${feVer} / ${$t('backend')} ${beVer}` +
67
+ `</span><br/><br/></div>`)
68
+ const ignoreBtn = Utils.createElement('<span style="cursor:pointer">忽略</span>')
69
+ ignoreBtn.onclick = () => {
70
+ Ui.setError(list.$el.parentElement!, null)
71
+ list.ctx.conf.versionCheck = false
72
+ list.fetchComments(0)
73
+ }
74
+ errEl.append(ignoreBtn)
75
+ Ui.setError(list.$el.parentElement!, errEl, '<span class="atk-warn-title">Artalk Warn</span>')
76
+ return true
77
+ }
78
+
79
+ return false
80
+ }
81
+
82
+ /** 评论排序方式选择下拉菜单 */
83
+ export function renderDropdown(conf: {
84
+ $dropdownWrap: HTMLElement,
85
+ dropdownList: [string, () => void][]
86
+ }) {
87
+ const { $dropdownWrap, dropdownList } = conf
88
+ if ($dropdownWrap.querySelector('.atk-dropdown')) return
89
+
90
+ // 修改 class
91
+ $dropdownWrap.classList.add('atk-dropdown-wrap')
92
+
93
+ // 插入图标
94
+ $dropdownWrap.append(Utils.createElement(`<span class="atk-arrow-down-icon"></span>`))
95
+
96
+ // 列表项点击事件
97
+ let curtActive = 0 // 当前选中
98
+ const onItemClick = (i: number, $item: HTMLElement, name: string, action: Function) => {
99
+ action()
100
+
101
+ // set active
102
+ curtActive = i
103
+ $dropdown.querySelectorAll('.active').forEach((e) => { e.classList.remove('active') })
104
+ $item.classList.add('active')
105
+
106
+ // 关闭层 (临时消失,取消 :hover)
107
+ $dropdown.style.display = 'none'
108
+ setTimeout(() => { $dropdown.style.display = '' }, 80)
109
+ }
110
+
111
+ // 生成列表元素
112
+ const $dropdown = Utils.createElement(`<ul class="atk-dropdown atk-fade-in"></ul>`)
113
+ dropdownList.forEach((item, i) => {
114
+ const name = item[0] as string
115
+ const action = item[1] as Function
116
+
117
+ const $item = Utils.createElement(`<li class="atk-dropdown-item"><span></span></li>`)
118
+ const $link = $item.querySelector<HTMLElement>('span')!
119
+ $link.innerText = name
120
+ $link.onclick = () => { onItemClick(i, $item, name, action) }
121
+ $dropdown.append($item)
122
+
123
+ if (i === curtActive) $item.classList.add('active') // 默认选中项
124
+ })
125
+
126
+ $dropdownWrap.append($dropdown)
127
+ }
128
+
129
+ /** 删除评论排序方式选择下拉菜单 */
130
+ export function removeDropdown(conf: {
131
+ $dropdownWrap: HTMLElement
132
+ }) {
133
+ const { $dropdownWrap } = conf
134
+ $dropdownWrap.classList.remove('atk-dropdown-wrap')
135
+ $dropdownWrap.querySelector('.atk-arrow-down-icon')?.remove()
136
+ $dropdownWrap.querySelector('.atk-dropdown')?.remove()
137
+ }
package/src/list/list.ts CHANGED
@@ -3,8 +3,10 @@ import Context from '~/types/context'
3
3
  import { version as ARTALK_VERSION } from '../../package.json'
4
4
  import * as Utils from '../lib/utils'
5
5
  import * as Ui from '../lib/ui'
6
+ import User from '../lib/user'
6
7
  import ListHTML from './list.html?raw'
7
8
  import ListLite from './list-lite'
9
+ import * as ListUi from './list-ui'
8
10
 
9
11
  export default class List extends ListLite {
10
12
  private $closeCommentBtn!: HTMLElement
@@ -29,16 +31,26 @@ export default class List extends ListLite {
29
31
  // 操作按钮
30
32
  this.initListActionBtn()
31
33
 
34
+ const countNumHTML = '<span class="atk-comment-count-num">0</span>'
32
35
  this.$commentCount = this.$el.querySelector('.atk-comment-count')!
33
- this.$commentCount.innerHTML = this.$t('counter', {
34
- count: '<span class="atk-comment-count-num">0</span>',
35
- })
36
- this.$commentCountNum = this.$commentCount.querySelector('.atk-comment-count-num')!
36
+
37
+ const refreshCountNumEl = () => {
38
+ this.$commentCount.innerHTML = this.$t('counter', {
39
+ count: countNumHTML,
40
+ })
41
+ this.$commentCountNum = this.$commentCount.querySelector('.atk-comment-count-num')!
42
+ }
43
+ refreshCountNumEl()
37
44
 
38
45
  // copyright
39
46
  this.$el.querySelector<HTMLElement>('.atk-copyright')!.innerHTML = `Powered By <a href="https://artalk.js.org" target="_blank" title="Artalk v${ARTALK_VERSION}">Artalk</a>`
40
47
 
41
48
  // event listen
49
+ this.ctx.on('conf-loaded', () => {
50
+ // i18n support when locale changed
51
+ refreshCountNumEl()
52
+ this.refreshUI()
53
+ })
42
54
  }
43
55
 
44
56
  private initListActionBtn() {
@@ -67,16 +79,14 @@ export default class List extends ListLite {
67
79
  this.$commentCountNum.innerText = String(Number(this.data?.total) || 0)
68
80
 
69
81
  // 已输入个人信息
70
- if (!!this.ctx.user.data.nick && !!this.ctx.user.data.email) {
82
+ if (!!User.data.nick && !!User.data.email) {
71
83
  this.$openSidebarBtn.classList.remove('atk-hide')
72
84
  } else {
73
85
  this.$openSidebarBtn.classList.add('atk-hide')
74
86
  }
75
87
 
76
- // 仅管理员显示控制
77
- this.ctx.checkAdminShowEl()
78
88
  this.$openSidebarBtn.querySelector<HTMLElement>('.atk-text')!
79
- .innerText = (!this.ctx.user.data.isAdmin) ? this.$t('msgCenter') : this.$t('ctrlCenter')
89
+ .innerText = (!User.data.isAdmin) ? this.$t('msgCenter') : this.$t('ctrlCenter')
80
90
 
81
91
  // 关闭评论
82
92
  if (!!this.data && !!this.data.page && this.data.page.admin_only === true) {
@@ -90,6 +100,10 @@ export default class List extends ListLite {
90
100
  // 评论列表排序 Dropdown 下拉选择层
91
101
  if (this.ctx.conf.listSort) {
92
102
  this.initDropdown()
103
+ } else {
104
+ ListUi.removeDropdown({
105
+ $dropdownWrap: this.$commentCount
106
+ })
93
107
  }
94
108
  }
95
109
 
@@ -121,8 +135,7 @@ export default class List extends ListLite {
121
135
  const comment = this.ctx.findComment(commentId)
122
136
  if (!comment) { // 若找不到评论
123
137
  // 自动翻页
124
- if (this.readMoreBtn) this.readMoreBtn.click()
125
- else if (this.pagination) this.pagination.next()
138
+ this.pgHolder?.next()
126
139
  return
127
140
  }
128
141
 
@@ -154,6 +167,7 @@ export default class List extends ListLite {
154
167
  else window.setTimeout(() => goTo(), 350)
155
168
 
156
169
  this.goToCommentFounded = true
170
+ this.goToCommentDelay = true // reset
157
171
  }
158
172
 
159
173
  /** 管理员设置页面信息 */
@@ -184,63 +198,22 @@ export default class List extends ListLite {
184
198
  }
185
199
  }
186
200
 
187
- private dropdownLoaded = false
188
-
189
201
  /** 初始化选择下拉层 */
190
202
  protected initDropdown() {
191
- if (this.dropdownLoaded) return
192
- this.dropdownLoaded = true
193
-
194
- this.$dropdownWrap = this.$commentCount
195
- this.$commentCount.classList.add('atk-dropdown-wrap')
196
-
197
- // 插入图标
198
- this.$dropdownWrap.append(Utils.createElement(`<span class="atk-arrow-down-icon"></span>`))
199
-
200
203
  const reloadUseParamsEditor = (func: (p: any) => void) => {
201
- this.paramsEditor = (p) => { func(p) }
204
+ this.paramsEditor = func
202
205
  this.fetchComments(0)
203
206
  }
204
207
 
205
- // 下拉列表
206
- const dropdownList = [
207
- [this.$t('sortLatest'), () => { reloadUseParamsEditor(p => { p.sort_by = 'date_desc' }) }],
208
- [this.$t('sortBest'), () => { reloadUseParamsEditor(p => { p.sort_by = 'vote' }) }],
209
- [this.$t('sortOldest'), () => { reloadUseParamsEditor(p => { p.sort_by = 'date_asc' }) }],
210
- [this.$t('sortAuthor'), () => { reloadUseParamsEditor(p => { p.view_only_admin = true }) }],
211
- ]
212
-
213
- // 列表项点击事件
214
- let curtActive = 0 // 当前选中
215
- const onItemClick = (i: number, $item: HTMLElement, name: string, action: Function) => {
216
- action()
217
-
218
- // set active
219
- curtActive = i
220
- $dropdown.querySelectorAll('.active').forEach((e) => { e.classList.remove('active') })
221
- $item.classList.add('active')
222
-
223
- // 关闭层 (临时消失,取消 :hover)
224
- $dropdown.style.display = 'none'
225
- setTimeout(() => { $dropdown.style.display = '' }, 80)
226
- }
227
-
228
- // 生成列表元素
229
- const $dropdown = Utils.createElement(`<ul class="atk-dropdown atk-fade-in"></ul>`)
230
- dropdownList.forEach((item, i) => {
231
- const name = item[0] as string
232
- const action = item[1] as Function
233
-
234
- const $item = Utils.createElement(`<li class="atk-dropdown-item"><span></span></li>`)
235
- const $link = $item.querySelector<HTMLElement>('span')!
236
- $link.innerText = name
237
- $link.onclick = () => { onItemClick(i, $item, name, action) }
238
- $dropdown.append($item)
239
-
240
- if (i === curtActive) $item.classList.add('active') // 默认选中项
208
+ ListUi.renderDropdown({
209
+ $dropdownWrap: this.$commentCount,
210
+ dropdownList: [
211
+ [this.$t('sortLatest'), () => { reloadUseParamsEditor(p => { p.sort_by = 'date_desc' }) }],
212
+ [this.$t('sortBest'), () => { reloadUseParamsEditor(p => { p.sort_by = 'vote' }) }],
213
+ [this.$t('sortOldest'), () => { reloadUseParamsEditor(p => { p.sort_by = 'date_asc' }) }],
214
+ [this.$t('sortAuthor'), () => { reloadUseParamsEditor(p => { p.view_only_admin = true }) }],
215
+ ]
241
216
  })
242
-
243
- this.$dropdownWrap.append($dropdown)
244
217
  }
245
218
 
246
219
  public updateUnread(notifies: NotifyData[]): void {
@@ -0,0 +1,21 @@
1
+ import { IPgHolderConf, TPgMode } from '../index'
2
+ import PaginationAdaptor from './pagination'
3
+ import ReadMoreAdaptor from './read-more'
4
+
5
+ // 分页方式适配器
6
+ const Adaptors: Record<TPgMode, IPgAdaptor<any>> = {
7
+ 'pagination': PaginationAdaptor,
8
+ 'read-more': ReadMoreAdaptor
9
+ }
10
+
11
+ export default Adaptors
12
+
13
+ export interface IPgAdaptor<T> {
14
+ instance: T
15
+ el: HTMLElement
16
+ createInstance(conf: IPgHolderConf): [T, HTMLElement]
17
+ setLoading(val: boolean): void
18
+ update(offset: number, total: number): void
19
+ next(): void
20
+ showErr?(msg: string): void
21
+ }
@@ -0,0 +1,42 @@
1
+ import Pagination from '@/components/pagination'
2
+ import * as Utils from '@/lib/utils'
3
+ import { IPgAdaptor } from '.'
4
+
5
+ interface IPaginationAdaptor extends IPgAdaptor<Pagination> {}
6
+
7
+ /**
8
+ * 翻页形式的分页
9
+ */
10
+ export default <IPaginationAdaptor>{
11
+ createInstance(conf) {
12
+ const instance = new Pagination(conf.total, {
13
+ pageSize: conf.pageSize,
14
+ onChange: async (o) => {
15
+ if (conf.list.conf.editorTravel === true)
16
+ conf.list.ctx.editorTravelBack() // 防止评论框被吞
17
+
18
+ await conf.list.fetchComments(o)
19
+
20
+ // 滚动到第一个评论的位置
21
+ if (conf.list.repositionAt) {
22
+ const at = conf.list.scrollListenerAt || window
23
+ at.scroll({
24
+ top: conf.list.repositionAt ? Utils.getOffset(conf.list.repositionAt).top : 0,
25
+ left: 0,
26
+ })
27
+ }
28
+ }
29
+ })
30
+
31
+ return [instance, instance.$el]
32
+ },
33
+ setLoading(val) {
34
+ this.instance.setLoading(val)
35
+ },
36
+ update(offset, total) {
37
+ this.instance.update(offset, total)
38
+ },
39
+ next() {
40
+ this.instance.next()
41
+ }
42
+ }
@@ -0,0 +1,56 @@
1
+ import ReadMoreBtn from '@/components/read-more-btn'
2
+ import $t from '@/i18n'
3
+ import * as Ui from '@/lib/ui'
4
+ import { IPgAdaptor } from '.'
5
+
6
+ interface IReadMoreAdaptor extends IPgAdaptor<ReadMoreBtn> {
7
+ autoLoadScrollEvent?: () => void
8
+ }
9
+
10
+ /**
11
+ * 阅读更多形式的分页
12
+ */
13
+ export default <IReadMoreAdaptor>{
14
+ createInstance(conf) {
15
+ const readMoreBtn = new ReadMoreBtn({
16
+ pageSize: conf.pageSize,
17
+ onClick: async (o) => {
18
+ await conf.list.fetchComments(o)
19
+ },
20
+ text: $t('loadMore'),
21
+ })
22
+
23
+ // 滚动到底部自动加载
24
+ if (conf.list.conf.pagination.autoLoad) {
25
+ // 添加滚动事件监听
26
+ const at = conf.list.scrollListenerAt || document
27
+ if (this.autoLoadScrollEvent) at.removeEventListener('scroll', this.autoLoadScrollEvent) // 解除原有
28
+ this.autoLoadScrollEvent = () => {
29
+ if (conf.mode !== 'read-more'
30
+ || !readMoreBtn?.hasMore
31
+ || conf.list.getLoading()
32
+ ) return
33
+
34
+ const $target = conf.list.$el.querySelector<HTMLElement>('.atk-list-comments-wrap > .atk-comment-wrap:nth-last-child(3)') // 获取倒数第3个评论元素
35
+ if ($target && Ui.isVisible($target, conf.list.scrollListenerAt)) {
36
+ readMoreBtn.click() // 自动点击加载更多按钮
37
+ }
38
+ }
39
+ at.addEventListener('scroll', this.autoLoadScrollEvent)
40
+ }
41
+
42
+ return [readMoreBtn, readMoreBtn.$el]
43
+ },
44
+ setLoading(val) {
45
+ this.instance.setLoading(val)
46
+ },
47
+ update(offset, total) {
48
+ this.instance.update(offset, total)
49
+ },
50
+ showErr(msg) {
51
+ this.instance.showErr(msg)
52
+ },
53
+ next() {
54
+ this.instance.click()
55
+ }
56
+ }
@@ -0,0 +1,56 @@
1
+ import { IPgHolderConf, TPgMode } from '.'
2
+ import Adaptors from './adaptors'
3
+
4
+ /**
5
+ * 分页方式持有者(调度器)
6
+ */
7
+ export default class PgHolder {
8
+ private conf: IPgHolderConf
9
+
10
+ constructor(conf: IPgHolderConf) {
11
+ this.conf = conf
12
+ this.init()
13
+ }
14
+
15
+ public getAdaptor() {
16
+ return Adaptors[this.conf.mode]
17
+ }
18
+
19
+ public init() {
20
+ const adaptor = this.getAdaptor()
21
+ const [instance, el] = adaptor.createInstance(this.conf)
22
+ adaptor.instance = instance
23
+ adaptor.el = el
24
+ this.conf.list.$el.append(adaptor.el)
25
+ }
26
+
27
+ public setLoading(val: boolean) {
28
+ this.getAdaptor().setLoading(val)
29
+ }
30
+
31
+ public update(offset: number, total: number) {
32
+ this.getAdaptor().update(offset, total)
33
+ }
34
+
35
+ public getEl() {
36
+ return this.getAdaptor().el
37
+ }
38
+
39
+ public showErr(msg: string) {
40
+ const that = this.getAdaptor()
41
+ const func = that.showErr
42
+ if (func) func.bind(that)(msg)
43
+ }
44
+
45
+ public setMode(val: TPgMode) {
46
+ if (val !== this.conf.mode) {
47
+ this.getEl().remove()
48
+ this.conf.mode = val
49
+ this.init()
50
+ }
51
+ }
52
+
53
+ public next() {
54
+ this.getAdaptor().next()
55
+ }
56
+ }
@@ -0,0 +1,13 @@
1
+ import ListLite from '../list-lite'
2
+ import PgHolder from './holder'
3
+
4
+ export type TPgMode = 'pagination'|'read-more'
5
+
6
+ export interface IPgHolderConf {
7
+ list: ListLite
8
+ mode: TPgMode
9
+ total: number
10
+ pageSize: number
11
+ }
12
+
13
+ export default PgHolder
package/src/service.ts ADDED
@@ -0,0 +1,118 @@
1
+ import Context from '~/types/context'
2
+ import CheckerLauncher from './lib/checker'
3
+ import Api from './api'
4
+ import Editor from './editor'
5
+ import List from './list'
6
+ import Layer from './layer'
7
+ import SidebarLayer from './layer/sidebar-layer'
8
+ import { initMarked } from './lib/marked'
9
+ import User from './lib/user'
10
+ import ListLite from './list/list-lite'
11
+ import * as DarkMode from './lib/dark-mode'
12
+ import * as I18n from './i18n'
13
+
14
+ /**
15
+ * Services
16
+ *
17
+ * @description
18
+ * 当函数有返回值时,该值将自动添加到 Context 类作为一个 private 成员的值,
19
+ * 这个成员的名称与函数名对应。可在 Context 类中访问该对象(同事类)。
20
+ */
21
+ const services = {
22
+ // I18n
23
+ i18n(ctx: Context) {
24
+ I18n.setLocale(ctx.conf.locale)
25
+
26
+ ctx.on('conf-loaded', () => {
27
+ I18n.setLocale(ctx.conf.locale)
28
+ })
29
+ },
30
+
31
+ // Markdown 组件
32
+ markdown() {
33
+ initMarked()
34
+ },
35
+
36
+ // User Store
37
+ user(ctx: Context) {
38
+ User.setContext(ctx)
39
+ return User
40
+ },
41
+
42
+ // HTTP API client
43
+ api(ctx: Context) {
44
+ const api = new Api(ctx)
45
+ return api
46
+ },
47
+
48
+ // CheckerLauncher
49
+ checkerLauncher(ctx: Context) {
50
+ const checkerLauncher = new CheckerLauncher(ctx)
51
+ return checkerLauncher
52
+ },
53
+
54
+ // 编辑器
55
+ editor(ctx: Context) {
56
+ const editor = new Editor(ctx)
57
+ ctx.$root.appendChild(editor.$el)
58
+ return editor
59
+ },
60
+
61
+ // 评论列表
62
+ list(ctx: Context): ListLite|undefined {
63
+ // 评论列表
64
+ const list = new List(ctx)
65
+ ctx.$root.appendChild(list.$el)
66
+
67
+ // 评论获取
68
+ list.fetchComments(0)
69
+
70
+ return list
71
+ },
72
+
73
+ // 弹出层
74
+ layer(ctx: Context) {
75
+ // 记录页面原始 CSS 属性
76
+ Layer.BodyOrgOverflow = document.body.style.overflow
77
+ Layer.BodyOrgPaddingRight = document.body.style.paddingRight
78
+ },
79
+
80
+ // 侧边栏 Layer
81
+ sidebarLayer(ctx: Context) {
82
+ const sidebarLayer = new SidebarLayer(ctx)
83
+ return sidebarLayer
84
+ },
85
+
86
+ // 默认事件绑定
87
+ eventsDefault(ctx: Context) {
88
+ // 锚点快速跳转评论
89
+ window.addEventListener('hashchange', () => {
90
+ ctx.listHashGotoCheck()
91
+ })
92
+
93
+ // 本地用户数据变更
94
+ ctx.on('user-changed', () => {
95
+ ctx.checkAdminShowEl()
96
+ ctx.listRefreshUI()
97
+ })
98
+ },
99
+
100
+ // 夜间模式
101
+ darkMode(ctx: Context) {
102
+ DarkMode.syncDarkModeConf(ctx)
103
+
104
+ ctx.on('conf-loaded', () => {
105
+ DarkMode.syncDarkModeConf(ctx)
106
+ })
107
+ }
108
+ }
109
+
110
+ export default services
111
+
112
+ // type tricks for dependency injection
113
+ type TServiceImps = typeof services
114
+ type TObjectWithFuncs = {[k: string]: (...args: any) => any}
115
+ type TKeysOnlyReturn<T extends TObjectWithFuncs, V> = {[K in keyof T]: ReturnType<T[K]> extends V ? K : never}[keyof T]
116
+ type TOmitConditions = TKeysOnlyReturn<TServiceImps, void>
117
+ type TServiceInjectors = Omit<TServiceImps, TOmitConditions>
118
+ export type TInjectedServices = {[K in keyof TServiceInjectors]: ReturnType<TServiceInjectors[K]>}
@@ -8,7 +8,7 @@
8
8
  --at-color-light: #4285f4;
9
9
  --at-color-bg: #FFF;
10
10
  --at-color-bg-transl: rgba(255, 255, 255, 0.94);
11
- --at-color-bg-grey: #F4F4F4;
11
+ --at-color-bg-grey: #f3f4f5;
12
12
  --at-color-bg-grey-transl: rgba(244, 244, 244, 0.75);
13
13
  --at-color-bg-light: rgba(29, 161, 242, 0.1);
14
14
  --at-color-main: #0083ff;
@@ -16,7 +16,7 @@
16
16
  --at-color-pink: #fa5a57;
17
17
  --at-color-yellow: #ff7c37;
18
18
  --at-color-green: #4caf50;
19
- --at-color-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0), rgba(255, 255, 255));
19
+ --at-color-gradient: linear-gradient(180deg, transparent, rgba(255, 255, 255));
20
20
 
21
21
  &.atk-dark-mode {
22
22
  --at-color-font: #fff;
@@ -36,6 +36,6 @@
36
36
  --at-color-pink: #fa5a57;
37
37
  --at-color-yellow: #ff7c37;
38
38
  --at-color-green: #4caf50;
39
- --at-color-gradient: linear-gradient(180deg, rgba(30, 34, 36, 0), rgba(30, 34, 36, 1));
39
+ --at-color-gradient: linear-gradient(180deg, transparent, rgba(30, 34, 36, 1));
40
40
  }
41
41
  }
@@ -1,4 +1,4 @@
1
- .atk-slim-scrollbar() {
1
+ .atk-slim-scrollbar {
2
2
  &::-webkit-scrollbar {
3
3
  width: 4px;
4
4
  height: 4px;
@@ -0,0 +1,3 @@
1
+ $font-size: 14px;
2
+ $line-height: 1.6em;
3
+ $block-radius: 6px;