artalk 2.4.4 → 2.5.0

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 (145) hide show
  1. package/README.md +27 -0
  2. package/dist/Artalk.css +1 -1
  3. package/dist/Artalk.es.js +4872 -4072
  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 +2468 -1779
  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 +15 -29
  30. package/dist/src/editor/edit-mode.d.ts +9 -0
  31. package/dist/src/editor/editor-ui.d.ts +25 -0
  32. package/dist/src/editor/editor.d.ts +37 -45
  33. package/dist/src/editor/funcs.d.ts +2 -0
  34. package/dist/src/editor/mover.d.ts +8 -0
  35. package/dist/src/editor/plug-manager.d.ts +17 -0
  36. package/dist/src/editor/plugs/editor-plug.d.ts +13 -1
  37. package/dist/src/editor/reply.d.ts +9 -0
  38. package/dist/src/editor/submit.d.ts +16 -0
  39. package/dist/src/editor/ui.d.ts +25 -0
  40. package/dist/src/i18n/index.d.ts +11 -7
  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 +18 -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/utils.d.ts +1 -5
  48. package/dist/src/list/list-lite.d.ts +15 -11
  49. package/dist/src/list/list-nest.d.ts +2 -2
  50. package/dist/src/list/list-ui.d.ts +7 -0
  51. package/dist/src/list/paginator/adaptors/index.d.ts +12 -0
  52. package/dist/src/list/paginator/adaptors/pagination.d.ts +9 -0
  53. package/dist/src/list/paginator/adaptors/read-more.d.ts +10 -0
  54. package/dist/src/list/paginator/holder.d.ts +16 -0
  55. package/dist/src/list/paginator/index.d.ts +10 -0
  56. package/dist/src/service.d.ts +40 -0
  57. package/dist/types/artalk-config.d.ts +5 -2
  58. package/dist/types/artalk-data.d.ts +3 -3
  59. package/dist/types/context.d.ts +11 -17
  60. package/dist/types/global.d.ts +1 -0
  61. package/package.json +6 -7
  62. package/src/api/system.ts +3 -12
  63. package/src/artalk.ts +101 -158
  64. package/src/comment/comment.ts +7 -6
  65. package/src/comment/height-limit.ts +100 -0
  66. package/src/comment/render-ctx.ts +30 -0
  67. package/src/comment/render.ts +31 -375
  68. package/src/comment/renders/actions.ts +114 -0
  69. package/src/comment/renders/avatar.ts +22 -0
  70. package/src/comment/renders/content.ts +39 -0
  71. package/src/comment/renders/header.ts +84 -0
  72. package/src/comment/renders/index.ts +19 -0
  73. package/src/comment/renders/pending.ts +12 -0
  74. package/src/comment/renders/reply-at.ts +16 -0
  75. package/src/comment/renders/reply-to.ts +24 -0
  76. package/src/config.ts +65 -0
  77. package/src/context.ts +31 -86
  78. package/src/defaults.ts +1 -0
  79. package/src/editor/edit-mode.ts +102 -0
  80. package/src/editor/editor.ts +100 -395
  81. package/src/editor/funcs.ts +96 -0
  82. package/src/editor/mover.ts +43 -0
  83. package/src/editor/plug-manager.ts +172 -0
  84. package/src/editor/plugs/editor-plug.ts +20 -2
  85. package/src/editor/plugs/{emoticons-plug.less → emoticons-plug.scss} +5 -4
  86. package/src/editor/plugs/emoticons-plug.ts +2 -1
  87. package/src/editor/plugs/header-input-plug.ts +5 -5
  88. package/src/editor/plugs/{preview-plug.less → preview-plug.scss} +1 -1
  89. package/src/editor/plugs/preview-plug.ts +8 -4
  90. package/src/editor/plugs/upload-plug.ts +10 -13
  91. package/src/editor/reply.ts +62 -0
  92. package/src/editor/submit.ts +98 -0
  93. package/src/editor/ui.ts +36 -0
  94. package/src/i18n/index.ts +21 -10
  95. package/src/layer/index.ts +2 -2
  96. package/src/layer/layer.ts +4 -4
  97. package/src/layer/sidebar-layer.ts +2 -1
  98. package/src/lib/checker/admin.ts +36 -0
  99. package/src/lib/checker/captcha-renders.ts +68 -0
  100. package/src/lib/checker/captcha.ts +26 -0
  101. package/src/lib/checker/index.ts +38 -20
  102. package/src/lib/dark-mode.ts +48 -0
  103. package/src/lib/marked.ts +133 -0
  104. package/src/lib/utils.ts +1 -123
  105. package/src/list/list-lite.ts +59 -162
  106. package/src/list/list-ui.ts +80 -0
  107. package/src/list/list.ts +16 -8
  108. package/src/list/paginator/adaptors/index.ts +21 -0
  109. package/src/list/paginator/adaptors/pagination.ts +42 -0
  110. package/src/list/paginator/adaptors/read-more.ts +56 -0
  111. package/src/list/paginator/holder.ts +55 -0
  112. package/src/list/paginator/index.ts +13 -0
  113. package/src/service.ts +118 -0
  114. package/src/style/{_color.less → _color.scss} +3 -3
  115. package/src/style/{_extend.less → _extend.scss} +1 -1
  116. package/src/style/_variables.scss +3 -0
  117. package/src/style/{comment.less → comment.scss} +36 -12
  118. package/src/style/{editor.less → editor.scss} +11 -11
  119. package/src/style/{list.less → list.scss} +8 -6
  120. package/src/style/{main.less → main.scss} +24 -23
  121. package/src/style/{sidebar-layer.less → sidebar-layer.scss} +8 -8
  122. package/types/artalk-config.d.ts +5 -2
  123. package/types/artalk-data.d.ts +3 -3
  124. package/types/context.d.ts +11 -17
  125. package/types/global.d.ts +1 -0
  126. package/CNAME +0 -1
  127. package/LICENSE +0 -165
  128. package/index.html +0 -157
  129. package/postcss.config.js +0 -5
  130. package/scripts/build-i18n.mjs +0 -46
  131. package/src/lib/checker/admin-checker.ts +0 -36
  132. package/src/lib/checker/captcha-checker.ts +0 -84
  133. package/src/style/_variables.less +0 -3
  134. package/test/ArtalkCount.html +0 -16
  135. package/test/ArtalkI18n.html +0 -16
  136. package/test/ArtalkLite.html +0 -15
  137. package/test/CSRF.html +0 -26
  138. package/tsconfig.json +0 -12
  139. package/vite-base.config.ts +0 -21
  140. package/vite-demo.config.ts +0 -12
  141. package/vite-lite.config.ts +0 -22
  142. package/vite.config.ts +0 -27
  143. /package/dist/src/lib/checker/{admin-checker.d.ts → admin.d.ts} +0 -0
  144. /package/dist/src/lib/checker/{captcha-checker.d.ts → captcha.d.ts} +0 -0
  145. /package/src/style/{pagination.less → pagination.scss} +0 -0
@@ -1,36 +1,19 @@
1
1
  import * as Utils from '../lib/utils'
2
2
  import * as Ui from '../lib/ui'
3
- import ActionBtn from '../components/action-btn'
3
+
4
4
  import CommentHTML from './comment.html?raw'
5
5
  import Comment from './comment'
6
+ import RenderCtx from './render-ctx'
7
+ import loadRenders from './renders'
8
+ import * as HeightLimit from './height-limit'
6
9
 
7
- export default class CommentRender {
8
- private comment: Comment
9
-
10
- private get ctx() { return this.comment.ctx }
11
- private get data() { return this.comment.getData() }
12
- private get cConf() { return this.comment.getConf() }
13
-
14
- public $el!: HTMLElement
15
- public $main!: HTMLElement
16
- public $header!: HTMLElement
17
- public $headerNick!: HTMLElement
18
- public $headerBadgeWrap!: HTMLElement
19
- public $body!: HTMLElement
20
- public $content!: HTMLElement
21
- private $childrenWrap!: HTMLElement|null
22
- public $actions!: HTMLElement
23
- public voteBtnUp?: ActionBtn
24
- public voteBtnDown?: ActionBtn
25
-
26
- public $replyTo?: HTMLElement // 回复评论内容 (平铺下显示)
27
- public $replyAt?: HTMLElement // 回复 AT(层级嵌套下显示)
28
-
10
+ export default class CommentRender extends RenderCtx {
29
11
  public constructor(comment: Comment) {
30
- this.comment = comment
12
+ super(comment)
31
13
  }
32
14
 
33
15
  public render() {
16
+ // init ui elements
34
17
  this.$el = Utils.createElement(CommentHTML)
35
18
 
36
19
  this.$main = this.$el.querySelector('.atk-main')!
@@ -39,371 +22,44 @@ export default class CommentRender {
39
22
  this.$content = this.$body.querySelector('.atk-content')!
40
23
  this.$actions = this.$el.querySelector('.atk-actions')!
41
24
 
42
- this.$el.setAttribute('data-comment-id', `${this.data.id}`)
25
+ this.$el.setAttribute('id', `atk-comment-${this.data.id}`)
43
26
 
44
- this.renderAvatar()
45
- this.renderHeader()
46
- this.renderContent()
47
- this.renderReplyAt()
48
- this.renderReplyTo()
49
- this.renderPending()
50
- this.renderActions()
27
+ // call all renders
28
+ loadRenders(this)
51
29
 
52
30
  this.recoveryChildrenWrap()
53
31
 
54
32
  return this.$el
55
33
  }
56
34
 
57
- /** 初始化 - 评论头像 */
58
- private renderAvatar() {
59
- const $avatar = this.$el.querySelector<HTMLElement>('.atk-avatar')!
60
- const $avatarImg = Utils.createElement<HTMLImageElement>('<img />')
61
- const avatarURLBuilder = this.ctx.conf.avatarURLBuilder
62
- $avatarImg.src = avatarURLBuilder ? avatarURLBuilder(this.data) : this.comment.getGravatarURL()
63
- if (this.data.link) {
64
- const $avatarA = Utils.createElement<HTMLLinkElement>('<a target="_blank" rel="noreferrer noopener nofollow"></a>')
65
- $avatarA.href = Utils.isValidURL(this.data.link) ? this.data.link : `https://${this.data.link}`
66
- $avatarA.append($avatarImg)
67
- $avatar.append($avatarA)
68
- } else {
69
- $avatar.append($avatarImg)
70
- }
71
- }
72
-
73
- /** 初始化 - 评论信息 */
74
- private renderHeader() {
75
- this.renderHeader_Nick()
76
- this.renderHeader_VerifyBadge()
77
- this.renderHeader_Date()
78
- this.renderHeader_UABadge()
79
- }
80
-
81
- private renderHeader_Nick() {
82
- this.$headerNick = this.$el.querySelector<HTMLElement>('.atk-nick')!
83
-
84
- if (this.data.link) {
85
- const $nickA = Utils.createElement<HTMLLinkElement>('<a target="_blank" rel="noreferrer noopener nofollow"></a>')
86
- $nickA.innerText = this.data.nick
87
- $nickA.href = Utils.isValidURL(this.data.link) ? this.data.link : `https://${this.data.link}`
88
- this.$headerNick.append($nickA)
89
- } else {
90
- this.$headerNick.innerText = this.data.nick
91
- }
92
- }
93
-
94
- private renderHeader_VerifyBadge() {
95
- this.$headerBadgeWrap = this.$el.querySelector<HTMLElement>('.atk-badge-wrap')!
96
- this.$headerBadgeWrap.innerHTML = ''
97
-
98
- const badgeText = this.data.badge_name
99
- const badgeColor = this.data.badge_color
100
- if (badgeText) {
101
- const $badge = Utils.createElement(`<span class="atk-badge"></span>`)
102
- $badge.innerText = badgeText.replace('管理员', this.ctx.$t('admin')) // i18n patch
103
- $badge.style.backgroundColor = badgeColor || ''
104
- this.$headerBadgeWrap.append($badge)
105
- }
106
-
107
- if (this.data.is_pinned) {
108
- const $pinnedBadge = Utils.createElement(`<span class="atk-pinned-badge">${this.ctx.$t('pin')}</span>`) // 置顶徽章
109
- this.$headerBadgeWrap.append($pinnedBadge)
110
- }
111
- }
112
-
113
- private renderHeader_Date() {
114
- const $date = this.$el.querySelector<HTMLElement>('.atk-date')!
115
- $date.innerText = this.comment.getDateFormatted()
116
- $date.setAttribute('data-atk-comment-date', String(+new Date(this.data.date)))
117
- }
118
-
119
- private renderHeader_UABadge() {
120
- if (this.ctx.conf.uaBadge) {
121
- let $uaWrap = this.$header.querySelector('atk-ua-wrap')
122
- if (!$uaWrap) {
123
- $uaWrap = Utils.createElement(`<span class="atk-ua-wrap"></span>`)
124
- this.$header.append($uaWrap)
125
- }
126
-
127
- $uaWrap.innerHTML = ''
128
- const $uaBrowser = Utils.createElement(`<span class="atk-ua ua-browser"></span>`)
129
- const $usOS = Utils.createElement(`<span class="atk-ua ua-os"></span>`)
130
- const uaInfo = this.comment.getUserUA()
131
- $uaBrowser.innerText = uaInfo.browser
132
- $usOS.innerText = uaInfo.os
133
- $uaWrap.append($uaBrowser)
134
- $uaWrap.append($usOS)
135
- }
136
- }
137
-
138
- /** 初始化 - 评论内容 */
139
- private renderContent() {
140
- if (!this.data.is_collapsed) {
141
- this.$content.innerHTML = this.comment.getContentMarked()
142
- this.$content.classList.remove('atk-hide', 'atk-collapsed')
143
- return
144
- }
145
-
146
- // 内容 & 折叠
147
- this.$content.classList.add('atk-hide', 'atk-type-collapsed')
148
- const collapsedInfoEl = Utils.createElement(`
149
- <div class="atk-collapsed">
150
- <span class="atk-text">${this.ctx.$t('collapsedMsg')}</span>
151
- <span class="atk-show-btn">${this.ctx.$t('expand')}</span>
152
- </div>`)
153
- this.$body.insertAdjacentElement('beforeend', collapsedInfoEl)
154
-
155
- const contentShowBtn = collapsedInfoEl.querySelector('.atk-show-btn')!
156
- contentShowBtn.addEventListener('click', (e) => {
157
- e.stopPropagation() // 防止穿透
158
-
159
- if (this.$content.classList.contains('atk-hide')) {
160
- this.$content.innerHTML = this.comment.getContentMarked()
161
- this.$content.classList.remove('atk-hide')
162
- Ui.playFadeInAnim(this.$content)
163
- contentShowBtn.innerHTML = this.ctx.$t('collapse')
164
- } else {
165
- this.$content.innerHTML = ''
166
- this.$content.classList.add('atk-hide')
167
- contentShowBtn.innerHTML = this.ctx.$t('expand')
168
- }
169
- })
170
- }
171
-
172
- /** 初始化 - 层级嵌套模式显示 At */
173
- private renderReplyAt() {
174
- if (this.cConf.isFlatMode || this.data.rid === 0) return // not 平铺模式 或 根评论
175
- if (!this.cConf.replyTo) return
176
-
177
- this.$replyAt = Utils.createElement(`<span class="atk-item atk-reply-at"><span class="atk-arrow"></span><span class="atk-nick"></span></span>`)
178
- this.$replyAt.querySelector<HTMLElement>('.atk-nick')!.innerText = `${this.cConf.replyTo.nick}`
179
- this.$replyAt.onclick = () => { this.comment.getActions().goToReplyComment() }
180
-
181
- this.$headerBadgeWrap.insertAdjacentElement('afterend', this.$replyAt)
182
- }
183
-
184
- /** 初始化 - 回复的对象 */
185
- private renderReplyTo() {
186
- if (!this.cConf.isFlatMode) return // 仅平铺模式显示
187
- if (!this.cConf.replyTo) return
188
-
189
- this.$replyTo = Utils.createElement(`
190
- <div class="atk-reply-to">
191
- <div class="atk-meta">${this.ctx.$t('reply')} <span class="atk-nick"></span>:</div>
192
- <div class="atk-content"></div>
193
- </div>`)
194
- const $nick = this.$replyTo.querySelector<HTMLElement>('.atk-nick')!
195
- $nick.innerText = `@${this.cConf.replyTo.nick}`
196
- $nick.onclick = () => { this.comment.getActions().goToReplyComment() }
197
- let replyContent = Utils.marked(this.ctx, this.cConf.replyTo.content)
198
- if (this.cConf.replyTo.is_collapsed) replyContent = `[${this.ctx.$t('collapsed')}]`
199
- this.$replyTo.querySelector<HTMLElement>('.atk-content')!.innerHTML = replyContent
200
- this.$body.prepend(this.$replyTo)
201
- }
202
-
203
- /** 初始化 - 待审核状态 */
204
- private renderPending() {
205
- if (!this.data.is_pending) return
206
-
207
- const pendingEl = Utils.createElement(`<div class="atk-pending">${this.ctx.$t('pendingMsg')}</div>`)
208
- this.$body.prepend(pendingEl)
209
- }
210
-
211
- /** 初始化 - 评论操作按钮 */
212
- private renderActions() {
213
- this.renderActions_Vote()
214
- this.renderActions_Reply()
215
-
216
- // 管理员操作
217
- this.renderActions_Collapse()
218
- this.renderActions_Moderator()
219
- this.renderActions_Pin()
220
- this.renderActions_Edit()
221
- this.renderActions_Del()
222
- }
223
-
224
- // 操作按钮 - 投票
225
- private renderActions_Vote() {
226
- if (!this.ctx.conf.vote) return // 关闭投票功能
227
-
228
- // 赞同按钮
229
- this.voteBtnUp = new ActionBtn(this.ctx, () => `${this.ctx.$t('voteUp')} (${this.data.vote_up || 0})`).appendTo(this.$actions)
230
- this.voteBtnUp.setClick(() => {
231
- this.comment.getActions().vote('up')
232
- })
233
-
234
- // 反对按钮
235
- if (this.ctx.conf.voteDown) {
236
- this.voteBtnDown = new ActionBtn(this.ctx, () => `${this.ctx.$t('voteDown')} (${this.data.vote_down || 0})`).appendTo(this.$actions)
237
- this.voteBtnDown.setClick(() => {
238
- this.comment.getActions().vote('down')
239
- })
240
- }
241
- }
242
-
243
- // 操作按钮 - 回复
244
- private renderActions_Reply() {
245
- if (!this.data.is_allow_reply) return // 不允许回复
246
-
247
- const replyBtn = Utils.createElement(`<span>${this.ctx.$t('reply')}</span>`)
248
- this.$actions.append(replyBtn)
249
- replyBtn.addEventListener('click', (e) => {
250
- e.stopPropagation() // 防止穿透
251
- if (!this.cConf.onReplyBtnClick) {
252
- this.ctx.replyComment(this.data, this.$el)
253
- } else {
254
- this.cConf.onReplyBtnClick()
255
- }
256
- })
257
- }
258
-
259
- // 操作按钮 - 折叠
260
- private renderActions_Collapse() {
261
- const collapseBtn = new ActionBtn(this.ctx, {
262
- text: () => (this.data.is_collapsed ? this.ctx.$t('expand') : this.ctx.$t('collapse')),
263
- adminOnly: true
264
- })
265
- collapseBtn.appendTo(this.$actions)
266
- collapseBtn.setClick(() => {
267
- this.comment.getActions().adminEdit('collapsed', collapseBtn)
268
- })
269
- }
270
-
271
- // 操作按钮 - 审核
272
- private renderActions_Moderator() {
273
- const pendingBtn = new ActionBtn(this.ctx, {
274
- text: () => (this.data.is_pending ? this.ctx.$t('pending') : this.ctx.$t('approved')),
275
- adminOnly: true
276
- })
277
- pendingBtn.appendTo(this.$actions)
278
- pendingBtn.setClick(() => {
279
- this.comment.getActions().adminEdit('pending', pendingBtn)
280
- })
281
- }
282
-
283
- // 操作按钮 - 置顶
284
- private renderActions_Pin() {
285
- const pinnedBtn = new ActionBtn(this.ctx, {
286
- text: () => (this.data.is_pinned ? this.ctx.$t('unpin') : this.ctx.$t('pin')),
287
- adminOnly: true
288
- })
289
- pinnedBtn.appendTo(this.$actions)
290
- pinnedBtn.setClick(() => {
291
- this.comment.getActions().adminEdit('pinned', pinnedBtn)
292
- })
293
- }
294
-
295
- // 操作按钮 - 编辑
296
- private renderActions_Edit() {
297
- const editBtn = new ActionBtn(this.ctx, {
298
- text: this.ctx.$t('edit'),
299
- adminOnly: true
300
- })
301
- editBtn.appendTo(this.$actions)
302
- editBtn.setClick(() => {
303
- this.ctx.editComment(this.data, this.$el)
304
- })
305
- }
306
-
307
- // 操作按钮 - 删除
308
- private renderActions_Del() {
309
- const delBtn = new ActionBtn(this.ctx, {
310
- text: this.ctx.$t('delete'),
311
- confirm: true,
312
- confirmText: this.ctx.$t('deleteConfirm'),
313
- adminOnly: true,
314
- })
315
- delBtn.appendTo(this.$actions)
316
- delBtn.setClick(() => {
317
- this.comment.getActions().adminDelete(delBtn)
318
- })
319
- }
320
-
321
35
  /** 内容限高检测 */
322
36
  public checkHeightLimit() {
323
- this.checkHeightLimitArea('content') // 评论内容限高
324
- this.checkHeightLimitArea('children') // 子评论部分限高(嵌套模式)
325
- }
326
-
327
- /** 目标内容限高检测 */
328
- public checkHeightLimitArea(area: 'children'|'content') {
329
- // 参数准备
330
- const childrenMaxH = this.ctx.conf.heightLimit.children
331
- const contentMaxH = this.ctx.conf.heightLimit.content
332
-
333
- if (area === 'children' && !childrenMaxH) return
334
- if (area === 'content' && !contentMaxH) return
335
-
336
- // 限高
337
- let maxHeight: number
338
- if (area === 'children') maxHeight = childrenMaxH!
339
- if (area === 'content') maxHeight = contentMaxH!
340
-
341
- // 检测指定元素
342
- const checkEl = ($el?: HTMLElement|null) => {
343
- if (!$el) return
344
-
345
- // 是否超过高度
346
- if (Utils.getHeight($el) > maxHeight) {
347
- this.heightLimitAdd($el, maxHeight)
348
- }
349
- }
350
-
351
- // 执行限高检测
352
- if (area === 'children') {
353
- checkEl(this.$childrenWrap)
354
- } else if (area === 'content') {
355
- checkEl(this.$content)
356
- checkEl(this.$replyTo)
357
-
358
- // 若有图片 · 图片加载完后再检测一次
359
- Utils.onImagesLoaded(this.$content, () => {
360
- checkEl(this.$content)
361
- })
362
- if (this.$replyTo) {
363
- Utils.onImagesLoaded(this.$replyTo, () => {
364
- checkEl(this.$replyTo)
365
- })
366
- }
367
- }
368
- }
369
-
370
- /** 移除限高 */
371
- private heightLimitRemove($el: HTMLElement) {
372
- if (!$el) return
373
- if (!$el.classList.contains('atk-height-limit')) return
374
-
375
- $el.classList.remove('atk-height-limit')
376
- Array.from($el.children).forEach((e) => {
377
- if (e.classList.contains('atk-height-limit-btn')) e.remove()
378
- })
379
- $el.style.height = ''
380
- $el.style.overflow = ''
37
+ const conf = this.ctx.conf.heightLimit
38
+ if (!conf || !conf.content || !conf.children) return // 关闭限高
39
+
40
+ const contentMaxH = conf.content
41
+ const childrenMaxH = conf.children
42
+
43
+ HeightLimit.check({
44
+ postExpandBtnClick: () => {
45
+ // 子评论数仅有 1,直接取消限高
46
+ const children = this.comment.getChildren()
47
+ if (children.length === 1) HeightLimit.disposeHeightLimit(children[0].getRender().$content)
48
+ },
49
+ scrollable: conf.scrollable
50
+ }, [
51
+ // 评论内容限高
52
+ { el: this.$content, max: contentMaxH, imgContains: true },
53
+ { el: this.$replyTo, max: contentMaxH, imgContains: true },
54
+ // 子评论区域限高(仅嵌套模式)
55
+ { el: this.$childrenWrap, max: childrenMaxH, imgContains: false }
56
+ ])
381
57
  }
382
58
 
383
59
  /** 子评论区域移除限高 */
384
60
  public heightLimitRemoveForChildren() {
385
61
  if (!this.$childrenWrap) return
386
- this.heightLimitRemove(this.$childrenWrap)
387
- }
388
-
389
- /** 内容限高区域新增 */
390
- private heightLimitAdd($el: HTMLElement, maxHeight: number) {
391
- if (!$el) return
392
- if ($el.classList.contains('atk-height-limit')) return
393
-
394
- $el.classList.add('atk-height-limit')
395
- $el.style.height = `${maxHeight}px`
396
- $el.style.overflow = 'hidden'
397
- const $hideMoreOpenBtn = Utils.createElement(`<div class="atk-height-limit-btn">${this.ctx.$t('readMore')}</span>`)
398
- $hideMoreOpenBtn.onclick = (e) => {
399
- e.stopPropagation()
400
- this.heightLimitRemove($el)
401
-
402
- // 子评论数等于 1,直接取消限高
403
- const children = this.comment.getChildren()
404
- if (children.length === 1) children[0].getRender().heightLimitRemove(children[0].getRender().$content)
405
- }
406
- $el.append($hideMoreOpenBtn)
62
+ HeightLimit.disposeHeightLimit(this.$childrenWrap)
407
63
  }
408
64
 
409
65
  /** 渐出动画 */
@@ -0,0 +1,114 @@
1
+ import * as Utils from '../../lib/utils'
2
+ import ActionBtn from '../../components/action-btn'
3
+ import RenderCtx from '../render-ctx'
4
+
5
+ /**
6
+ * 评论操作按钮界面
7
+ */
8
+ export default function renderActions(ctx: RenderCtx) {
9
+ Object.entries({
10
+ renderVote, renderReply,
11
+ // 管理员操作
12
+ renderCollapse, renderModerator, renderPin, renderEdit, renderDel
13
+ }).forEach(([name, render]) => {
14
+ render(ctx)
15
+ })
16
+ }
17
+
18
+
19
+ // 操作按钮 - 投票
20
+ function renderVote(ctx: RenderCtx) {
21
+ if (!ctx.ctx.conf.vote) return // 关闭投票功能
22
+
23
+ // 赞同按钮
24
+ ctx.voteBtnUp = new ActionBtn(ctx.ctx, () => `${ctx.ctx.$t('voteUp')} (${ctx.data.vote_up || 0})`).appendTo(ctx.$actions)
25
+ ctx.voteBtnUp.setClick(() => {
26
+ ctx.comment.getActions().vote('up')
27
+ })
28
+
29
+ // 反对按钮
30
+ if (ctx.ctx.conf.voteDown) {
31
+ ctx.voteBtnDown = new ActionBtn(ctx.ctx, () => `${ctx.ctx.$t('voteDown')} (${ctx.data.vote_down || 0})`).appendTo(ctx.$actions)
32
+ ctx.voteBtnDown.setClick(() => {
33
+ ctx.comment.getActions().vote('down')
34
+ })
35
+ }
36
+ }
37
+
38
+ // 操作按钮 - 回复
39
+ function renderReply(ctx: RenderCtx) {
40
+ if (!ctx.data.is_allow_reply) return // 不允许回复
41
+
42
+ const replyBtn = Utils.createElement(`<span>${ctx.ctx.$t('reply')}</span>`)
43
+ ctx.$actions.append(replyBtn)
44
+ replyBtn.addEventListener('click', (e) => {
45
+ e.stopPropagation() // 防止穿透
46
+ if (!ctx.cConf.onReplyBtnClick) {
47
+ ctx.ctx.replyComment(ctx.data, ctx.$el)
48
+ } else {
49
+ ctx.cConf.onReplyBtnClick()
50
+ }
51
+ })
52
+ }
53
+
54
+ // 操作按钮 - 折叠
55
+ function renderCollapse(ctx: RenderCtx) {
56
+ const collapseBtn = new ActionBtn(ctx.ctx, {
57
+ text: () => (ctx.data.is_collapsed ? ctx.ctx.$t('expand') : ctx.ctx.$t('collapse')),
58
+ adminOnly: true
59
+ })
60
+ collapseBtn.appendTo(ctx.$actions)
61
+ collapseBtn.setClick(() => {
62
+ ctx.comment.getActions().adminEdit('collapsed', collapseBtn)
63
+ })
64
+ }
65
+
66
+ // 操作按钮 - 审核
67
+ function renderModerator(ctx: RenderCtx) {
68
+ const pendingBtn = new ActionBtn(ctx.ctx, {
69
+ text: () => (ctx.data.is_pending ? ctx.ctx.$t('pending') : ctx.ctx.$t('approved')),
70
+ adminOnly: true
71
+ })
72
+ pendingBtn.appendTo(ctx.$actions)
73
+ pendingBtn.setClick(() => {
74
+ ctx.comment.getActions().adminEdit('pending', pendingBtn)
75
+ })
76
+ }
77
+
78
+ // 操作按钮 - 置顶
79
+ function renderPin(ctx: RenderCtx) {
80
+ const pinnedBtn = new ActionBtn(ctx.ctx, {
81
+ text: () => (ctx.data.is_pinned ? ctx.ctx.$t('unpin') : ctx.ctx.$t('pin')),
82
+ adminOnly: true
83
+ })
84
+ pinnedBtn.appendTo(ctx.$actions)
85
+ pinnedBtn.setClick(() => {
86
+ ctx.comment.getActions().adminEdit('pinned', pinnedBtn)
87
+ })
88
+ }
89
+
90
+ // 操作按钮 - 编辑
91
+ function renderEdit(ctx: RenderCtx) {
92
+ const editBtn = new ActionBtn(ctx.ctx, {
93
+ text: ctx.ctx.$t('edit'),
94
+ adminOnly: true
95
+ })
96
+ editBtn.appendTo(ctx.$actions)
97
+ editBtn.setClick(() => {
98
+ ctx.ctx.editComment(ctx.data, ctx.$el)
99
+ })
100
+ }
101
+
102
+ // 操作按钮 - 删除
103
+ function renderDel(ctx: RenderCtx) {
104
+ const delBtn = new ActionBtn(ctx.ctx, {
105
+ text: ctx.ctx.$t('delete'),
106
+ confirm: true,
107
+ confirmText: ctx.ctx.$t('deleteConfirm'),
108
+ adminOnly: true,
109
+ })
110
+ delBtn.appendTo(ctx.$actions)
111
+ delBtn.setClick(() => {
112
+ ctx.comment.getActions().adminDelete(delBtn)
113
+ })
114
+ }
@@ -0,0 +1,22 @@
1
+ import * as Utils from '../../lib/utils'
2
+ import RenderCtx from '../render-ctx'
3
+
4
+ /**
5
+ * 评论头像界面
6
+ */
7
+ export default function renderAvatar(ctx: RenderCtx) {
8
+ const $avatar = ctx.$el.querySelector<HTMLElement>('.atk-avatar')!
9
+ const $avatarImg = Utils.createElement<HTMLImageElement>('<img />')
10
+
11
+ const avatarURLBuilder = ctx.conf.avatarURLBuilder
12
+ $avatarImg.src = avatarURLBuilder ? avatarURLBuilder(ctx.data) : ctx.comment.getGravatarURL()
13
+
14
+ if (ctx.data.link) {
15
+ const $avatarA = Utils.createElement<HTMLLinkElement>('<a target="_blank" rel="noreferrer noopener nofollow"></a>')
16
+ $avatarA.href = Utils.isValidURL(ctx.data.link) ? ctx.data.link : `https://${ctx.data.link}`
17
+ $avatarA.append($avatarImg)
18
+ $avatar.append($avatarA)
19
+ } else {
20
+ $avatar.append($avatarImg)
21
+ }
22
+ }
@@ -0,0 +1,39 @@
1
+ import * as Utils from '../../lib/utils'
2
+ import * as Ui from '../../lib/ui'
3
+ import RenderCtx from '../render-ctx'
4
+
5
+ /**
6
+ * 评论内容界面
7
+ */
8
+ export default function renderContent(ctx: RenderCtx) {
9
+ if (!ctx.data.is_collapsed) {
10
+ ctx.$content.innerHTML = ctx.comment.getContentMarked()
11
+ ctx.$content.classList.remove('atk-hide', 'atk-collapsed')
12
+ return
13
+ }
14
+
15
+ // 内容 & 折叠
16
+ ctx.$content.classList.add('atk-hide', 'atk-type-collapsed')
17
+ const collapsedInfoEl = Utils.createElement(`
18
+ <div class="atk-collapsed">
19
+ <span class="atk-text">${ctx.ctx.$t('collapsedMsg')}</span>
20
+ <span class="atk-show-btn">${ctx.ctx.$t('expand')}</span>
21
+ </div>`)
22
+ ctx.$body.insertAdjacentElement('beforeend', collapsedInfoEl)
23
+
24
+ const contentShowBtn = collapsedInfoEl.querySelector('.atk-show-btn')!
25
+ contentShowBtn.addEventListener('click', (e) => {
26
+ e.stopPropagation() // 防止穿透
27
+
28
+ if (ctx.$content.classList.contains('atk-hide')) {
29
+ ctx.$content.innerHTML = ctx.comment.getContentMarked()
30
+ ctx.$content.classList.remove('atk-hide')
31
+ Ui.playFadeInAnim(ctx.$content)
32
+ contentShowBtn.innerHTML = ctx.ctx.$t('collapse')
33
+ } else {
34
+ ctx.$content.innerHTML = ''
35
+ ctx.$content.classList.add('atk-hide')
36
+ contentShowBtn.innerHTML = ctx.ctx.$t('expand')
37
+ }
38
+ })
39
+ }