artalk 2.2.8 → 2.2.11

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "artalk",
3
- "version": "2.2.8",
3
+ "version": "2.2.11",
4
4
  "backendMinVersion": "2.1.4",
5
5
  "license": "LGPL-3.0",
6
6
  "description": "A Selfhosted Comment System",
package/src/artalk.ts CHANGED
@@ -47,6 +47,11 @@ export default class Artalk {
47
47
  this.conf.pageTitle = `${document.title}`
48
48
  }
49
49
 
50
+ // 列表显示模式
51
+ if (this.conf.nestMax && this.conf.nestMax <= 1) {
52
+ this.conf.flatMode = true
53
+ }
54
+
50
55
  // 装载元素
51
56
  if (!!this.conf.el && this.conf.el instanceof HTMLElement) {
52
57
  this.$root = this.conf.el
@@ -133,6 +138,7 @@ export default class Artalk {
133
138
  private initEventBind() {
134
139
  // 锚点快速跳转评论
135
140
  window.addEventListener('hashchange', () => {
141
+ this.list.goToCommentDelay = false
136
142
  this.list.checkGoToCommentByUrlHash()
137
143
  })
138
144
 
@@ -15,6 +15,8 @@ export default class Comment extends Component {
15
15
 
16
16
  public $main!: HTMLElement
17
17
  public $header!: HTMLElement
18
+ public $headerNick!: HTMLElement
19
+ public $headerBadge?: HTMLElement
18
20
  public $body!: HTMLElement
19
21
  public $content!: HTMLElement
20
22
  public $children!: HTMLElement|null
@@ -23,12 +25,15 @@ export default class Comment extends Component {
23
25
  public voteBtnDown?: ActionBtn
24
26
 
25
27
  public parent: Comment|null
26
- public nestedNum: number
27
- private maxNestingNum: number // 最多嵌套层数
28
- public children: Comment[] = []
28
+ public nestCurt: number
29
+ private nestMax: number // 最大嵌套层数
30
+ private children: Comment[] = []
29
31
 
30
- public replyTo?: CommentData // 回复对象(flatMode 用)
31
- public $replyTo?: HTMLElement
32
+ public flatMode: boolean = false
33
+ public replyTo?: CommentData // 回复对象
34
+
35
+ public $replyTo?: HTMLElement // 回复评论内容 (平铺下显示)
36
+ public $replyAt?: HTMLElement // 回复 AT(层级嵌套下显示)
32
37
 
33
38
  public afterRender?: () => void
34
39
 
@@ -43,13 +48,13 @@ export default class Comment extends Component {
43
48
  super(ctx)
44
49
 
45
50
  // 最大嵌套数
46
- this.maxNestingNum = ctx.conf.maxNesting || 3
51
+ this.nestMax = ctx.conf.nestMax || 3
47
52
 
48
53
  this.data = { ...data }
49
54
  this.data.date = this.data.date.replace(/-/g, '/') // 解决 Safari 日期解析 NaN 问题
50
55
 
51
56
  this.parent = null
52
- this.nestedNum = 1 // 现在已嵌套 n 层
57
+ this.nestCurt = 1 // 现在已嵌套 n 层
53
58
  }
54
59
 
55
60
  /** 渲染 UI */
@@ -70,6 +75,7 @@ export default class Comment extends Component {
70
75
  this.renderAvatar()
71
76
  this.renderHeader()
72
77
  this.renderContent()
78
+ this.renderReplyAt()
73
79
  this.renderReplyTo()
74
80
  this.renderPending()
75
81
  this.renderActionBtn()
@@ -117,28 +123,29 @@ export default class Comment extends Component {
117
123
  }
118
124
 
119
125
  private renderHeader() {
120
- const $nick = this.$el.querySelector<HTMLElement>('.atk-nick')!
126
+ this.$headerNick = this.$el.querySelector<HTMLElement>('.atk-nick')!
121
127
  if (this.data.link) {
122
128
  const $nickA = Utils.createElement<HTMLLinkElement>('<a target="_blank" rel="noreferrer noopener nofollow"></a>')
123
129
  $nickA.innerText = this.data.nick
124
130
  $nickA.href = this.data.link
125
- $nick.append($nickA)
131
+ this.$headerNick.append($nickA)
126
132
  } else {
127
- $nick.innerText = this.data.nick
133
+ this.$headerNick.innerText = this.data.nick
128
134
  }
129
135
 
130
- const $badge = this.$el.querySelector<HTMLElement>('.atk-badge')!
136
+ this.$headerBadge = this.$el.querySelector<HTMLElement>('.atk-badge')!
131
137
  if (this.data.badge_name) {
132
- $badge.innerText = this.data.badge_name
138
+ this.$headerBadge.innerText = this.data.badge_name
133
139
  if (this.data.badge_color)
134
- $badge.style.backgroundColor = this.data.badge_color
140
+ this.$headerBadge.style.backgroundColor = this.data.badge_color
135
141
  } else {
136
- $badge.remove()
142
+ this.$headerBadge.remove()
143
+ this.$headerBadge = undefined
137
144
  }
138
145
 
139
146
  if (this.data.is_pinned) {
140
147
  const $pinnedBadge = Utils.createElement(`<span class="atk-item atk-pinned-badge">置顶</span>`) // 置顶徽章
141
- $nick.insertAdjacentElement('afterend', $pinnedBadge)
148
+ this.$headerNick.insertAdjacentElement('afterend', $pinnedBadge)
142
149
  }
143
150
 
144
151
  const $date = this.$el.querySelector<HTMLElement>('.atk-date')!
@@ -189,8 +196,23 @@ export default class Comment extends Component {
189
196
  })
190
197
  }
191
198
 
199
+ // 层级嵌套模式显示 At
200
+ private renderReplyAt() {
201
+ if (this.flatMode || this.data.rid === 0) return // not 平铺模式 或 根评论
202
+ if (this.$replyAt) return // not 关闭显示 或 已经显示
203
+ if (!this.replyTo) return
204
+
205
+ this.$replyAt = Utils.createElement(`<span class="atk-item atk-reply-at"><span class="atk-arrow"></span><span class="atk-nick"></span></span>`)
206
+ this.$replyAt.querySelector<HTMLElement>('.atk-nick')!.innerText = `${this.replyTo.nick}`
207
+ this.$replyAt.onclick = () => { this.goToReplyComment() }
208
+
209
+ const $after = this.$headerBadge || this.$headerNick
210
+ $after.insertAdjacentElement('afterend', this.$replyAt)
211
+ }
212
+
192
213
  // 回复的对象
193
214
  private renderReplyTo() {
215
+ if (!this.flatMode) return // 仅平铺模式显示
194
216
  if (!this.replyTo) return
195
217
 
196
218
  this.$replyTo = Utils.createElement(`
@@ -198,13 +220,23 @@ export default class Comment extends Component {
198
220
  <div class="atk-meta">回复 <span class="atk-nick"></span>:</div>
199
221
  <div class="atk-content"></div>
200
222
  </div>`)
201
- this.$replyTo.querySelector<HTMLElement>('.atk-nick')!.innerText = `@${this.replyTo.nick}`
223
+ const $nick = this.$replyTo.querySelector<HTMLElement>('.atk-nick')!
224
+ $nick.innerText = `@${this.replyTo.nick}`
225
+ $nick.onclick = () => { this.goToReplyComment() }
202
226
  let replyContent = Utils.marked(this.ctx, this.replyTo.content)
203
227
  if (this.replyTo.is_collapsed) replyContent = '[已折叠]'
204
228
  this.$replyTo.querySelector<HTMLElement>('.atk-content')!.innerHTML = replyContent
205
229
  this.$body.prepend(this.$replyTo)
206
230
  }
207
231
 
232
+ public goToReplyComment() {
233
+ const origHash = window.location.hash
234
+ const modifyHash = `#atk-comment-${this.data.rid}`
235
+
236
+ window.location.hash = modifyHash
237
+ if (modifyHash === origHash) window.dispatchEvent(new Event('hashchange')) // 强制触发事件
238
+ }
239
+
208
240
  // 待审核状态
209
241
  private renderPending() {
210
242
  if (!this.data.is_pending) return
@@ -324,30 +356,33 @@ export default class Comment extends Component {
324
356
  return this.children
325
357
  }
326
358
 
327
- putChild(childC: Comment) {
359
+ putChild(childC: Comment, insertMode: 'append'|'prepend' = 'append') {
328
360
  childC.parent = this
329
- childC.nestedNum = this.nestedNum + 1 // 嵌套层数 +1
361
+ childC.nestCurt = this.nestCurt + 1 // 嵌套层数 +1
330
362
  this.children.push(childC)
331
363
 
332
- this.getChildrenEl().appendChild(childC.getEl())
364
+ const $children = this.getChildrenEl()
365
+ if (insertMode === 'append') $children.append(childC.getEl())
366
+ else if (insertMode === 'prepend') $children.prepend(childC.getEl())
367
+
333
368
  childC.playFadeInAnim()
334
369
 
335
370
  // 内容限高
336
371
  childC.checkHeightLimitArea('content')
337
372
  }
338
373
 
339
- getChildrenEl() {
340
- if (this.$children === null) {
341
- // console.log(this.nestedNum)
342
- if (this.nestedNum < this.maxNestingNum) {
374
+ getChildrenEl(): HTMLElement {
375
+ if (!this.$children) {
376
+ // console.log(this.nestCurt)
377
+ if (this.nestCurt < this.nestMax) {
343
378
  this.$children = Utils.createElement('<div class="atk-comment-children"></div>')
344
- this.$main.appendChild(this.$children)
379
+ this.$main.append(this.$children)
345
380
  } else if (this.parent) {
346
381
  this.$children = this.parent.getChildrenEl()
347
382
  }
348
383
  }
349
384
 
350
- return this.$children
385
+ return this.$children!
351
386
  }
352
387
 
353
388
  getParent() {
@@ -85,9 +85,22 @@ export default class Editor extends Component {
85
85
  if (inputEl && inputEl instanceof HTMLInputElement) {
86
86
  inputEl.value = this.user.data[field] || ''
87
87
  // 绑定事件
88
- inputEl.addEventListener('input', () => this.onHeaderInputChanged(field, inputEl))
88
+ inputEl.addEventListener('input', () => this.onHeaderInput(field, inputEl))
89
89
  }
90
90
  })
91
+
92
+ // Link URL 自动补全协议
93
+ const $linkInput = this.getInputEl('link')
94
+ if ($linkInput) {
95
+ $linkInput.addEventListener('change', () => {
96
+ const link = $linkInput.value.trim()
97
+ if (!!link && !/^(http|https):\/\//.test(link)) {
98
+ $linkInput.value = `https://${link}`
99
+ this.user.data.link = $linkInput.value
100
+ this.saveUser()
101
+ }
102
+ })
103
+ }
91
104
  }
92
105
 
93
106
  getInputEl (field: string) {
@@ -101,7 +114,7 @@ export default class Editor extends Component {
101
114
  }
102
115
 
103
116
  /** header 输入框内容变化事件 */
104
- onHeaderInputChanged (field: string, inputEl: HTMLInputElement) {
117
+ onHeaderInput(field: string, inputEl: HTMLInputElement) {
105
118
  this.user.data[field] = inputEl.value.trim()
106
119
 
107
120
  // 若修改的是 nick or email
@@ -7,11 +7,14 @@ import Api from '../api'
7
7
  import Comment from './comment'
8
8
  import Pagination from './pagination'
9
9
  import ReadMoreBtn from './read-more-btn'
10
+ import * as ListNest from './list-nest'
10
11
  import { backendMinVersion } from '../../package.json'
11
12
 
12
13
  export default class ListLite extends Component {
13
14
  protected $commentsWrap: HTMLElement
14
- protected comments: Comment[] = []
15
+
16
+ protected commentList: Comment[] = [] // Note: 无层级结构 + 无须排列
17
+ protected get commentDataList() { return this.commentList.map(c => c.data) }
15
18
 
16
19
  protected data?: ListData
17
20
  protected isLoading: boolean = false
@@ -21,6 +24,9 @@ export default class ListLite extends Component {
21
24
  /** 平铺模式 */
22
25
  public flatMode = false
23
26
 
27
+ /** 嵌套模式 */
28
+ public nestSortBy!: ListNest.SortByType
29
+
24
30
  /** 分页 */
25
31
  public pageMode: 'pagination'|'read-more' = 'pagination'
26
32
  public pageSize: number = 20 // 每次请求获取量
@@ -52,6 +58,9 @@ export default class ListLite extends Component {
52
58
  // 评论为空时显示字符
53
59
  if (ctx.conf.noComment) this.noCommentText = ctx.conf.noComment
54
60
 
61
+ // 嵌套排序方式
62
+ this.nestSortBy = this.ctx.conf.nestSort || 'DATE_ASC'
63
+
55
64
  // 评论时间自动更新
56
65
  window.setInterval(() => {
57
66
  this.$el.querySelectorAll<HTMLElement>('[data-atk-comment-date]').forEach(el => {
@@ -265,7 +274,7 @@ export default class ListLite extends Component {
265
274
  /** 刷新界面 */
266
275
  public refreshUI() {
267
276
  // 无评论
268
- const isNoComment = this.comments.length <= 0
277
+ const isNoComment = this.commentList.length <= 0
269
278
  let $noComment = this.$commentsWrap.querySelector<HTMLElement>('.atk-list-no-comment')
270
279
 
271
280
  if (isNoComment) {
@@ -283,8 +292,11 @@ export default class ListLite extends Component {
283
292
  }
284
293
 
285
294
  /** 创建新评论 */
286
- protected createComment(data: CommentData) {
287
- const comment = new Comment(this.ctx, data)
295
+ protected createComment(cData: CommentData, ctxData?: CommentData[]) {
296
+ if (!ctxData) ctxData = this.commentDataList
297
+
298
+ const comment = new Comment(this.ctx, cData)
299
+ comment.flatMode = this.flatMode
288
300
  comment.afterRender = () => {
289
301
  if (this.renderComment) this.renderComment(comment)
290
302
  }
@@ -293,6 +305,9 @@ export default class ListLite extends Component {
293
305
  this.refreshUI()
294
306
  }
295
307
 
308
+ // 子评论查找回复对象
309
+ comment.replyTo = (cData.rid ? ctxData.find(c => c.id === cData.rid) : undefined)
310
+
296
311
  return comment
297
312
  }
298
313
 
@@ -303,31 +318,16 @@ export default class ListLite extends Component {
303
318
  this.putCommentFlatMode(commentData, srcData, 'append')
304
319
  })
305
320
  } else {
306
- this.importCommentsNesting(srcData)
321
+ this.importCommentsNest(srcData)
307
322
  }
308
323
  }
309
324
 
310
325
  // 导入评论 · 嵌套模式
311
- private importCommentsNesting(srcData: CommentData[]) {
312
- // 查找并导入所有子评论
313
- const loadChildren = (parentC: Comment) => {
314
- const children = srcData.filter(o => o.rid === parentC.data.id)
315
- children.forEach((childData: CommentData) => {
316
- const childC = this.createComment(childData)
317
- childC.render()
318
-
319
- // 插入到父评论中
320
- parentC.putChild(childC)
321
-
322
- // 递归加载子评论
323
- loadChildren(childC)
324
- })
325
- }
326
-
326
+ private importCommentsNest(srcData: CommentData[]) {
327
327
  // 遍历 root 评论
328
- const rootComments = srcData.filter((o) => o.rid === 0)
329
- rootComments.forEach((rootData: CommentData) => {
330
- const rootC = this.createComment(rootData)
328
+ const rootNodes = ListNest.makeNestCommentNodeList(srcData, this.nestSortBy, this.conf.nestMax)
329
+ rootNodes.forEach((rootNode: ListNest.CommentNode) => {
330
+ const rootC = this.createComment(rootNode.comment, srcData)
331
331
  rootC.render()
332
332
 
333
333
  // 显示并播放渐入动画
@@ -335,10 +335,25 @@ export default class ListLite extends Component {
335
335
  rootC.playFadeInAnim()
336
336
 
337
337
  // 评论放入 comments 总表中
338
- this.comments.push(rootC)
338
+ this.commentList.push(rootC)
339
339
 
340
340
  // 加载子评论
341
- loadChildren(rootC)
341
+ const that = this
342
+ ;(function loadChildren(parentC: Comment, parentNode: ListNest.CommentNode) {
343
+ parentNode.children.forEach((node: ListNest.CommentNode) => {
344
+ const childD = node.comment
345
+ const childC = that.createComment(childD, srcData)
346
+ childC.render()
347
+
348
+ // 插入到父评论中
349
+ parentC.putChild(childC)
350
+
351
+ that.commentList.push(childC)
352
+
353
+ // 递归加载子评论
354
+ loadChildren(childC, node)
355
+ })
356
+ })(rootC, rootNode)
342
357
 
343
358
  // 限高检测
344
359
  rootC.checkHeightLimit()
@@ -346,18 +361,13 @@ export default class ListLite extends Component {
346
361
  }
347
362
 
348
363
  /** 导入评论 · 平铺模式 */
349
- private putCommentFlatMode(cData: CommentData, srcData: CommentData[], insertMode: 'append'|'prepend') {
364
+ private putCommentFlatMode(cData: CommentData, ctxData: CommentData[], insertMode: 'append'|'prepend') {
350
365
  if (cData.is_collapsed) cData.is_allow_reply = false
351
- const comment = this.createComment(cData)
352
- if (cData.rid !== 0) {
353
- const rComment = srcData.find(o => o.id === cData.rid)
354
- if (rComment) comment.replyTo = rComment
355
- }
366
+ const comment = this.createComment(cData, ctxData)
356
367
  comment.render()
357
368
 
358
369
  // 将评论导入 comments 总表中
359
- if (insertMode === 'append') this.comments.push(comment)
360
- if (insertMode === 'prepend') this.comments.unshift(comment)
370
+ this.commentList.push(comment)
361
371
 
362
372
  // 可见评论添加到界面
363
373
  // 注:不可见评论用于显示 “引用内容”
@@ -378,17 +388,18 @@ export default class ListLite extends Component {
378
388
  if (!this.flatMode) {
379
389
  // 嵌套模式
380
390
  const comment = this.createComment(commentData)
381
- comment.render()
382
391
 
383
392
  if (commentData.rid === 0) {
384
393
  // root评论 新增
394
+ comment.render()
385
395
  this.$commentsWrap.prepend(comment.getEl())
386
- this.comments.unshift(comment)
396
+ this.commentList.push(comment)
387
397
  } else {
388
398
  // 子评论 新增
389
399
  const parent = this.findComment(commentData.rid)
390
400
  if (parent) {
391
- parent.putChild(comment)
401
+ comment.render()
402
+ parent.putChild(comment, (this.nestSortBy === 'DATE_ASC' ? 'append' : 'prepend'))
392
403
 
393
404
  // 若父评论存在 “子评论部分” 限高,取消限高
394
405
  comment.getParents().forEach((p) => {
@@ -403,7 +414,7 @@ export default class ListLite extends Component {
403
414
  comment.playFadeInAnim() // 播放评论渐出动画
404
415
  } else {
405
416
  // 平铺模式
406
- const comment = this.putCommentFlatMode(commentData, this.comments.map(c => c.data), 'prepend')
417
+ const comment = this.putCommentFlatMode(commentData, this.commentDataList, 'prepend')
407
418
  Ui.scrollIntoView(comment.getEl()) // 滚动到可见
408
419
  }
409
420
 
@@ -415,47 +426,22 @@ export default class ListLite extends Component {
415
426
  this.ctx.trigger('comments-loaded')
416
427
  }
417
428
 
418
- /** 遍历操作 Comment (包括子评论) */
419
- protected eachComment(commentList: Comment[], action: (comment: Comment, levelList: Comment[]) => boolean|void) {
420
- if (commentList.length === 0) return
421
- commentList.every((item) => {
422
- if (action(item, commentList) === false) return false
423
- this.eachComment(item.getChildren(), action)
424
- return true
425
- })
426
- }
427
-
428
429
  /** 查找评论项 */
429
- protected findComment(id: number, src?: Comment[]): Comment|null {
430
- if (!src) src = this.comments
431
- let comment: Comment|null = null
432
- this.eachComment(src, (item) => {
433
- if (item.data.id === id) {
434
- comment = item
435
- return false
436
- }
437
- return true
438
- })
439
-
440
- return comment
430
+ protected findComment(id: number): Comment|undefined {
431
+ return this.commentList.find(c => c.data.id === id)
441
432
  }
442
433
 
443
434
  /** 删除评论 */
444
- public deleteComment(comment: number|Comment) {
445
- let findComment: Comment|null
446
- if (typeof comment === 'number') {
447
- findComment = this.findComment(comment)
448
- if (!findComment) throw Error(`未找到评论 ${comment}`)
449
- } else findComment = comment
450
-
451
- findComment.getEl().remove()
452
- this.eachComment(this.comments, (item, levelList) => {
453
- if (item === findComment) {
454
- levelList.splice(levelList.indexOf(item), 1)
455
- return false
456
- }
457
- return true
458
- })
435
+ public deleteComment(_comment: number|Comment) {
436
+ let comment: Comment
437
+ if (typeof _comment === 'number') {
438
+ const findComment = this.findComment(_comment)
439
+ if (!findComment) throw Error(`未找到评论 ${_comment}`)
440
+ comment = findComment
441
+ } else comment = _comment
442
+
443
+ comment.getEl().remove()
444
+ this.commentList.splice(this.commentList.indexOf(comment), 1)
459
445
 
460
446
  if (this.data) this.data.total -= 1 // 评论数减 1
461
447
 
@@ -466,7 +452,7 @@ export default class ListLite extends Component {
466
452
  public clearAllComments() {
467
453
  this.$commentsWrap.innerHTML = ''
468
454
  this.data = undefined
469
- this.comments = []
455
+ this.commentList = []
470
456
  }
471
457
 
472
458
  /** 更新未读数据 */
@@ -475,7 +461,7 @@ export default class ListLite extends Component {
475
461
 
476
462
  // 高亮评论
477
463
  if (this.unreadHighlight === true) {
478
- this.eachComment(this.comments, (comment) => {
464
+ this.commentList.forEach((comment) => {
479
465
  const notify = this.unread.find(o => o.comment_id === comment.data.id)
480
466
  if (notify) {
481
467
  comment.setUnread(true)
@@ -0,0 +1,66 @@
1
+ import { CommentData } from '~/types/artalk-data'
2
+
3
+ export type CommentNode = {
4
+ id: number
5
+ comment: CommentData
6
+ children: CommentNode[]
7
+ parent?: CommentNode
8
+ level: number
9
+ }
10
+
11
+ export type SortByType = 'DATE_DESC'|'DATE_ASC'|'SRC_INDEX'|'VOTE_UP_DESC'
12
+
13
+ // 构建树状结构列表
14
+ export function makeNestCommentNodeList(srcData: CommentData[], sortBy: SortByType = 'DATE_DESC', nestMax = 2) {
15
+ const nodeList: CommentNode[] = []
16
+
17
+ const roots = srcData.filter((o) => o.rid === 0)
18
+ roots.forEach((root: CommentData) => {
19
+ const rootNode: CommentNode = {
20
+ id: root.id,
21
+ comment: root,
22
+ children: [],
23
+ level: 1,
24
+ }
25
+
26
+ rootNode.parent = rootNode
27
+ nodeList.push(rootNode)
28
+
29
+ ;(function loadChildren(parentNode: CommentNode) {
30
+ const children = srcData.filter((o) => o.rid === parentNode.id)
31
+ if (children.length === 0) return
32
+ if (parentNode.level >= nestMax) parentNode = parentNode.parent!
33
+ children.forEach((child: CommentData) => {
34
+ const childNode: CommentNode = {
35
+ id: child.id,
36
+ comment: child,
37
+ children: [],
38
+ parent: parentNode,
39
+ level: parentNode.level + 1,
40
+ }
41
+
42
+ parentNode.children.push(childNode)
43
+ loadChildren(childNode)
44
+ })
45
+ })(rootNode)
46
+ })
47
+
48
+ // 排序
49
+ const sortFunc = (a: CommentNode, b: CommentNode): number => {
50
+ let v = a.id - b.id
51
+ if (sortBy === 'DATE_ASC') v = +new Date(a.comment.date) - +new Date(b.comment.date)
52
+ else if (sortBy === 'DATE_DESC') v = +new Date(b.comment.date) - +new Date(a.comment.date)
53
+ else if (sortBy === 'SRC_INDEX') v = srcData.indexOf(a.comment) - srcData.indexOf(b.comment)
54
+ else if (sortBy === 'VOTE_UP_DESC') v = b.comment.vote_up - a.comment.vote_up
55
+ return v
56
+ }
57
+
58
+ (function sortLevels(nodes: CommentNode[]) {
59
+ nodes.forEach((node) => {
60
+ node.children = node.children.sort(sortFunc)
61
+ sortLevels(node.children)
62
+ })
63
+ })(nodeList)
64
+
65
+ return nodeList
66
+ }
@@ -112,11 +112,19 @@ export default class List extends ListLite {
112
112
  super.onLoad(data, offset)
113
113
 
114
114
  // 检测锚点跳转
115
- this.checkGoToCommentByUrlHash()
115
+ if (!this.goToCommentFounded) this.checkGoToCommentByUrlHash()
116
+
117
+ // 防止评论框被吞
118
+ if (this.ctx.conf.editorTravel === true) {
119
+ this.ctx.trigger('editor-travel-back')
120
+ }
116
121
  }
117
122
 
123
+ private goToCommentFounded = false
124
+ public goToCommentDelay = true
125
+
118
126
  /** 跳到评论项位置 - 根据 `location.hash` */
119
- public async checkGoToCommentByUrlHash () {
127
+ public checkGoToCommentByUrlHash() {
120
128
  let commentId = Number(Utils.getQueryParam('atk_comment')) // same as backend GetReplyLink()
121
129
  if (!commentId) {
122
130
  const match = window.location.hash.match(/#atk-comment-([0-9]+)/)
@@ -125,6 +133,14 @@ export default class List extends ListLite {
125
133
  }
126
134
  if (!commentId) return
127
135
 
136
+ const comment = this.findComment(commentId)
137
+ if (!comment) { // 若找不到评论
138
+ // 自动翻页
139
+ if (this.readMoreBtn) this.readMoreBtn.click()
140
+ else if (this.pagination) this.pagination.next()
141
+ return
142
+ }
143
+
128
144
  // 已阅 API
129
145
  const notifyKey = Utils.getQueryParam('atk_notify_key')
130
146
  if (notifyKey) {
@@ -137,20 +153,24 @@ export default class List extends ListLite {
137
153
  })
138
154
  }
139
155
 
140
- const comment = this.findComment(commentId)
141
- // TODO 若找不到评论的情况
142
- // if (!comment) { // 若找不到评论
143
- // if (this.hasMoreComments) { // 阅读更多,并重试
144
- // await this.fetchComments()
145
- // }
146
- // }
147
-
148
- if (!comment) return
149
-
150
- Ui.scrollIntoView(comment.getEl(), false)
151
- window.setTimeout(() => {
152
- comment.getEl().classList.add('atk-flash-once')
153
- }, 800)
156
+ // 若父评论存在 “子评论部分” 限高,取消限高
157
+ comment.getParents().forEach((p) => {
158
+ if (p.$children) p.heightLimitRemove(p.$children)
159
+ })
160
+
161
+ const goTo = () => {
162
+ Ui.scrollIntoView(comment.getEl(), false)
163
+
164
+ comment.getEl().classList.remove('atk-flash-once')
165
+ window.setTimeout(() => {
166
+ comment.getEl().classList.add('atk-flash-once')
167
+ }, 150)
168
+ }
169
+
170
+ if (!this.goToCommentDelay) goTo()
171
+ else window.setTimeout(() => goTo(), 350)
172
+
173
+ this.goToCommentFounded = true
154
174
  }
155
175
 
156
176
  /** 管理员设置页面信息 */
package/src/defaults.ts CHANGED
@@ -12,6 +12,10 @@ const defaults: ArtalkConfig = {
12
12
  darkMode: false,
13
13
  editorTravel: true,
14
14
 
15
+ flatMode: 'auto',
16
+ nestMax: 2,
17
+ nestSort: 'DATE_ASC',
18
+
15
19
  emoticons: "https://cdn.jsdelivr.net/gh/ArtalkJS/Emoticons/grps/default.json",
16
20
 
17
21
  vote: true,
@@ -19,8 +23,7 @@ const defaults: ArtalkConfig = {
19
23
  uaBadge: true,
20
24
  listSort: true,
21
25
  pvEl: '#ArtalkPV',
22
- flatMode: 'auto',
23
- maxNesting: 3,
26
+
24
27
  gravatar: {
25
28
  default: 'mp',
26
29
  mirror: 'https://sdn.geekzu.org/avatar/',