artalk 2.1.3 → 2.1.4

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.1.3",
3
+ "version": "2.1.4",
4
4
  "description": "A Selfhosted Comment System",
5
5
  "keywords": [
6
6
  "comment-system",
@@ -44,15 +44,12 @@
44
44
  "@typescript-eslint/eslint-plugin": "^5.4.0",
45
45
  "@typescript-eslint/parser": "^5.4.0",
46
46
  "autoprefixer": "^10.4.0",
47
- "eslint": "7.2.0",
48
- "eslint-config-airbnb": "18.2.1",
49
- "eslint-config-airbnb-typescript": "^14.0.0",
47
+ "eslint": "^8.2.0",
48
+ "eslint-config-airbnb-base": "15.0.0",
49
+ "eslint-config-airbnb-typescript": "^16.0.0",
50
50
  "eslint-config-prettier": "^8.3.0",
51
- "eslint-import-resolver-typescript": "^2.4.0",
52
- "eslint-plugin-import": "2.22.1",
53
- "eslint-plugin-jsx-a11y": "^6.4.1",
54
- "eslint-plugin-react": "^7.25.1",
55
- "eslint-plugin-react-hooks": "^4.2.0",
51
+ "eslint-import-resolver-typescript": "^2.5.0",
52
+ "eslint-plugin-import": "^2.25.2",
56
53
  "less": "^4.1.2",
57
54
  "prettier": "^2.4.1",
58
55
  "typescript": "^4.5.2",
package/src/api/index.ts CHANGED
@@ -16,15 +16,14 @@ export default class Api {
16
16
  // ============================
17
17
 
18
18
  /** 评论 · 获取 */
19
- public get(offset: number, type?: string, flatMode?: boolean, paramsEditor?: (params: any) => void) {
19
+ public get(offset: number, pageSize: number, flatMode?: boolean, paramsEditor?: (params: any) => void) {
20
20
  const params: any = {
21
21
  page_key: this.ctx.conf.pageKey,
22
22
  site_name: this.ctx.conf.site || '',
23
- limit: this.ctx.conf.pagination?.pageSize || 15,
23
+ limit: pageSize,
24
24
  offset,
25
25
  }
26
26
 
27
- if (type) params.type = type
28
27
  if (flatMode) params.flat_mode = flatMode // 平铺模式
29
28
  if (this.ctx.user.checkHasBasicUserInfo()) {
30
29
  params.name = this.ctx.user.data.nick
package/src/artalk.ts CHANGED
@@ -72,7 +72,7 @@ export default class Artalk {
72
72
  this.$root.appendChild(this.sidebar.$el)
73
73
 
74
74
  // 评论获取
75
- this.list.reqComments()
75
+ this.list.fetchComments(0)
76
76
 
77
77
  // 事件绑定初始化
78
78
  this.initEventBind()
@@ -113,7 +113,7 @@ export default class Artalk {
113
113
 
114
114
  /** 重新加载 */
115
115
  public reload() {
116
- this.list.reqComments()
116
+ this.list.fetchComments(0)
117
117
  }
118
118
 
119
119
  /** 暗黑模式 · 初始化 */
@@ -102,7 +102,7 @@ export default class ActionBtn {
102
102
  }
103
103
 
104
104
  /** 设置加载状态 */
105
- public setLoading(value = true, loadingText?: string) {
105
+ public setLoading(value: boolean, loadingText?: string) {
106
106
  if (this.isLoading === value) return
107
107
  this.isLoading = value
108
108
  if (value) {
@@ -55,11 +55,11 @@ export default class Comment extends Component {
55
55
  /** 渲染 UI */
56
56
  public render() {
57
57
  this.$el = Utils.createElement(CommentHTML)
58
- this.$main = this.$el.querySelector('.atk-comment-main')!
58
+ this.$main = this.$el.querySelector('.atk-main')!
59
59
  this.$header = this.$el.querySelector('.atk-header')!
60
60
  this.$body = this.$el.querySelector('.atk-body')!
61
61
  this.$content = this.$body.querySelector('.atk-content')!
62
- this.$actions = this.$el.querySelector('.atk-comment-actions')!
62
+ this.$actions = this.$el.querySelector('.atk-actions')!
63
63
  this.$children = null
64
64
 
65
65
  this.$el.setAttribute('data-comment-id', `${this.data.id}`)
@@ -229,7 +229,7 @@ export default class Comment extends Component {
229
229
 
230
230
  // 绑定回复按钮事件
231
231
  if (this.data.is_allow_reply) {
232
- const replyBtn = Utils.createElement(`<span data-atk-action="comment-reply">回复</span>`)
232
+ const replyBtn = Utils.createElement(`<span>回复</span>`)
233
233
  this.$actions.append(replyBtn)
234
234
  replyBtn.addEventListener('click', (e) => {
235
235
  e.stopPropagation() // 防止穿透
@@ -316,6 +316,9 @@ export default class Comment extends Component {
316
316
 
317
317
  this.getChildrenEl().appendChild(childC.getEl())
318
318
  childC.playFadeInAnim()
319
+
320
+ // 内容限高
321
+ childC.checkHeightLimitArea('content')
319
322
  }
320
323
 
321
324
  getChildrenEl() {
@@ -465,4 +468,86 @@ export default class Comment extends Component {
465
468
  this.openURL = url
466
469
  this.$el.classList.add('atk-openable')
467
470
  }
471
+
472
+ /** 内容限高检测 */
473
+ checkHeightLimit() {
474
+ this.checkHeightLimitArea('content') // 评论内容限高
475
+ this.checkHeightLimitArea('children') // 子评论部分限高(嵌套模式)
476
+ }
477
+
478
+ /** 目标内容限高检测 */
479
+ checkHeightLimitArea(area: 'children'|'content') {
480
+ // 参数准备
481
+ const childrenMaxH = this.ctx.conf.heightLimit?.children
482
+ const contentMaxH = this.ctx.conf.heightLimit?.content
483
+
484
+ if (area === 'children' && !childrenMaxH) return
485
+ if (area === 'content' && !contentMaxH) return
486
+
487
+ // 限高
488
+ let maxHeight: number
489
+ if (area === 'children') maxHeight = childrenMaxH!
490
+ if (area === 'content') maxHeight = contentMaxH!
491
+
492
+ // 检测指定元素
493
+ const checkEl = ($el?: HTMLElement|null) => {
494
+ if (!$el) return
495
+
496
+ // 是否超过高度
497
+ if (Utils.getHeight($el) > maxHeight) {
498
+ this.heightLimitAdd($el, maxHeight)
499
+ }
500
+ }
501
+
502
+ // 执行限高检测
503
+ if (area === 'children') {
504
+ checkEl(this.$children)
505
+ } else if (area === 'content') {
506
+ checkEl(this.$content)
507
+ checkEl(this.$replyTo)
508
+
509
+ // 若有图片 · 图片加载完后再检测一次
510
+ Utils.onImagesLoaded(this.$content, () => {
511
+ checkEl(this.$content)
512
+ })
513
+ if (this.$replyTo) {
514
+ Utils.onImagesLoaded(this.$replyTo, () => {
515
+ checkEl(this.$replyTo)
516
+ })
517
+ }
518
+ }
519
+ }
520
+
521
+ // 操作 · 取消限高
522
+ heightLimitRemove($el: HTMLElement) {
523
+ if (!$el) return
524
+ if (!$el.classList.contains('atk-height-limit')) return
525
+
526
+ $el.classList.remove('atk-height-limit')
527
+ Array.from($el.children).forEach((e) => {
528
+ if (e.classList.contains('atk-height-limit-btn')) e.remove()
529
+ })
530
+ $el.style.height = ''
531
+ $el.style.overflow = ''
532
+ }
533
+
534
+ // 操作 · 内容限高
535
+ heightLimitAdd($el: HTMLElement, maxHeight: number) {
536
+ if (!$el) return
537
+ if ($el.classList.contains('atk-height-limit')) return
538
+
539
+ $el.classList.add('atk-height-limit')
540
+ $el.style.height = `${maxHeight}px`
541
+ $el.style.overflow = 'hidden'
542
+ const $hideMoreOpenBtn = Utils.createElement(`<div class="atk-height-limit-btn">阅读更多</span>`)
543
+ $hideMoreOpenBtn.onclick = (e) => {
544
+ e.stopPropagation()
545
+ this.heightLimitRemove($el)
546
+
547
+ // 子评论数等于 1,直接取消限高
548
+ const children = this.getChildren()
549
+ if (children.length === 1) children[0].heightLimitRemove(children[0].$content)
550
+ }
551
+ $el.append($hideMoreOpenBtn)
552
+ }
468
553
  }
@@ -2,44 +2,55 @@
2
2
  height: 100%;
3
3
  width: 100%;
4
4
 
5
- .atk-emoticons-list-wrap {
5
+ & > .atk-grp-wrap {
6
6
  .atk-slim-scrollbar();
7
7
  overflow-y: scroll;
8
8
  overflow-x: hidden;
9
9
  height: 100%; // calc(100% - 30px);
10
10
  width: 100%;
11
11
 
12
- .atk-emoticons-list {
12
+ & > .atk-grp {
13
+ display: flex;
14
+ flex-wrap: wrap;
15
+ flex-direction: row;
13
16
  padding: 5px 10px 35px 10px;
14
17
 
15
- .atk-emoticons-item {
16
- display: inline-block;
18
+ &[data-type="image"] > .atk-item {
19
+ height: 63px;
20
+ width: 63px;
21
+ }
22
+
23
+ & > .atk-item {
24
+ display: flex;
25
+ align-items: center;
26
+ justify-content: center;
17
27
  padding: 5px;
18
28
  cursor: pointer;
19
29
  user-select: none;
20
30
  border-radius: 2px;
21
31
  font-size: 16px;
32
+ min-width: 35px;
22
33
 
23
34
  & > img {
24
- max-height: 60px;
35
+ max-height: 100%;
25
36
  width: auto;
26
37
  }
27
38
 
28
39
  &:hover {
29
- background: @color-bg-grey;
40
+ background: var(--at-color-bg-grey);
30
41
  }
31
42
  }
32
43
  }
33
44
  }
34
45
 
35
- .atk-emoticons-types {
46
+ & > .atk-grp-switcher {
36
47
  position: absolute;
37
48
  bottom: 0;
38
49
  left: 0;
39
50
  width: 100%;
40
- background: @color-bg-transl;
51
+ background: var(--at-color-bg-transl);
41
52
  height: 30px;
42
- border-top: 1px solid @color-border;
53
+ border-top: 1px solid var(--at-color-border);
43
54
 
44
55
  & > span {
45
56
  user-select: none;
@@ -52,9 +63,8 @@
52
63
 
53
64
  &:hover,
54
65
  &.active {
55
- background: @color-bg-grey;
66
+ background: var(--at-color-bg-grey);
56
67
  }
57
68
  }
58
69
  }
59
-
60
70
  }
@@ -17,8 +17,8 @@ export default class EmoticonsPlug extends EditorPlug {
17
17
  public $el!: HTMLElement
18
18
  public emoticons: EmoticonListData = []
19
19
 
20
- public $listWrap!: HTMLElement
21
- public $types!: HTMLElement
20
+ public $grpWrap!: HTMLElement
21
+ public $grpSwitcher!: HTMLElement
22
22
 
23
23
  constructor (public editor: Editor) {
24
24
  super(editor)
@@ -162,18 +162,18 @@ export default class EmoticonsPlug extends EditorPlug {
162
162
  /** 初始化表情列表界面 */
163
163
  initEmoticonsList () {
164
164
  // 表情列表
165
- this.$listWrap = Utils.createElement(`<div class="atk-emoticons-list-wrap"></div>`)
166
- this.$el.append(this.$listWrap)
165
+ this.$grpWrap = Utils.createElement(`<div class="atk-grp-wrap"></div>`)
166
+ this.$el.append(this.$grpWrap)
167
167
 
168
168
  this.emoticons.forEach((grp, index) => {
169
- const emoticonsEl = Utils.createElement(`<div class="atk-emoticons-list" style="display: none;"></div>`)
170
- this.$listWrap.append(emoticonsEl)
171
- emoticonsEl.setAttribute('data-index', String(index))
172
- emoticonsEl.setAttribute('data-grp-name', grp.name)
173
- emoticonsEl.setAttribute('data-type', grp.type)
169
+ const $grp = Utils.createElement(`<div class="atk-grp" style="display: none;"></div>`)
170
+ this.$grpWrap.append($grp)
171
+ $grp.setAttribute('data-index', String(index))
172
+ $grp.setAttribute('data-grp-name', grp.name)
173
+ $grp.setAttribute('data-type', grp.type)
174
174
  grp.items.forEach((item) => {
175
- const $item = Utils.createElement(`<span class="atk-emoticons-item"></span>`)
176
- emoticonsEl.append($item)
175
+ const $item = Utils.createElement(`<span class="atk-item"></span>`)
176
+ $grp.append($item)
177
177
 
178
178
  if (!!item.key && !(new RegExp(`^(${grp.name})?\\s?[0-9]+$`).test(item.key)))
179
179
  $item.setAttribute('title', item.key)
@@ -198,14 +198,14 @@ export default class EmoticonsPlug extends EditorPlug {
198
198
  })
199
199
 
200
200
  // 表情分类
201
- this.$types = Utils.createElement(`<div class="atk-emoticons-types"></div>`)
202
- this.$el.append(this.$types)
201
+ this.$grpSwitcher = Utils.createElement(`<div class="atk-grp-switcher"></div>`)
202
+ this.$el.append(this.$grpSwitcher)
203
203
  this.emoticons.forEach((grp, index) => {
204
204
  const $item = Utils.createElement('<span />')
205
205
  $item.innerText = grp.name
206
206
  $item.setAttribute('data-index', String(index))
207
207
  $item.onclick = () => (this.openGrp(index))
208
- this.$types.append($item)
208
+ this.$grpSwitcher.append($item)
209
209
  })
210
210
 
211
211
  // 默认打开第一个分类
@@ -215,7 +215,7 @@ export default class EmoticonsPlug extends EditorPlug {
215
215
 
216
216
  /** 打开一个表情组 */
217
217
  openGrp (index: number) {
218
- Array.from(this.$listWrap.children).forEach((item) => {
218
+ Array.from(this.$grpWrap.children).forEach((item) => {
219
219
  const el = item as HTMLElement
220
220
  if (el.getAttribute('data-index') !== String(index)) {
221
221
  el.style.display = 'none'
@@ -224,8 +224,8 @@ export default class EmoticonsPlug extends EditorPlug {
224
224
  }
225
225
  })
226
226
 
227
- this.$types.querySelectorAll('span.active').forEach(item => item.classList.remove('active'))
228
- this.$types.querySelector(`span[data-index="${index}"]`)?.classList.add('active')
227
+ this.$grpSwitcher.querySelectorAll('span.active').forEach(item => item.classList.remove('active'))
228
+ this.$grpSwitcher.querySelector(`span[data-index="${index}"]`)?.classList.add('active')
229
229
 
230
230
  this.changeListHeight()
231
231
  }
@@ -238,7 +238,7 @@ export default class EmoticonsPlug extends EditorPlug {
238
238
  }
239
239
 
240
240
  changeListHeight () {
241
- /* const listWrapHeight = Utils.getHeight(this.$listWrapem)
241
+ /* const listWrapHeight = Utils.getHeight(this.$grpWrapem)
242
242
  this.editor.plugWrapEl.style.height = `${listWrapHeight > 150 ? listWrapHeight : 150}px` */
243
243
  }
244
244
 
@@ -3,5 +3,5 @@
3
3
  overflow-x: hidden;
4
4
  height: 100%;
5
5
  width: 100%;
6
- padding: @block-padding 10px + 5px;
6
+ padding: 10px (10px + 5px);
7
7
  }
@@ -18,12 +18,9 @@ export default class Editor extends Component {
18
18
  public $header: HTMLElement
19
19
  public $textareaWrap: HTMLElement
20
20
  public $textarea: HTMLTextAreaElement
21
- public $closeComment: HTMLTextAreaElement
22
21
  public $plugWrap: HTMLElement
23
22
  public $bottom: HTMLElement
24
- public $bottomPartLeft: HTMLElement
25
- public $plugSwitcherWrap: HTMLElement
26
- public $bottomPartRight: HTMLElement
23
+ public $plugBtnWrap: HTMLElement
27
24
  public $submitBtn: HTMLButtonElement
28
25
  public $notifyWrap: HTMLElement
29
26
 
@@ -41,17 +38,14 @@ export default class Editor extends Component {
41
38
 
42
39
  this.$el = Utils.createElement(EditorHTML)
43
40
 
44
- this.$header = this.$el.querySelector('.atk-editor-header')!
45
- this.$textareaWrap = this.$el.querySelector('.atk-editor-textarea-wrap')!
46
- this.$textarea = this.$el.querySelector('.atk-editor-textarea')!
47
- this.$closeComment = this.$el.querySelector('.atk-close-comment')!
48
- this.$plugWrap = this.$el.querySelector('.atk-editor-plug-wrap')!
49
- this.$bottom = this.$el.querySelector('.atk-editor-bottom')!
50
- this.$bottomPartLeft = this.$el.querySelector('.atk-editor-bottom-part.atk-left')!
51
- this.$plugSwitcherWrap = this.$el.querySelector('.atk-editor-plug-switcher-wrap')!
52
- this.$bottomPartRight = this.$el.querySelector('.atk-editor-bottom-part.atk-right')!
41
+ this.$header = this.$el.querySelector('.atk-header')!
42
+ this.$textareaWrap = this.$el.querySelector('.atk-textarea-wrap')!
43
+ this.$textarea = this.$el.querySelector('.atk-textarea')!
44
+ this.$plugWrap = this.$el.querySelector('.atk-plug-wrap')!
45
+ this.$bottom = this.$el.querySelector('.atk-bottom')!
46
+ this.$plugBtnWrap = this.$el.querySelector('.atk-plug-btn-wrap')!
53
47
  this.$submitBtn = this.$el.querySelector('.atk-send-btn')!
54
- this.$notifyWrap = this.$el.querySelector('.atk-editor-notify-wrap')!
48
+ this.$notifyWrap = this.$el.querySelector('.atk-notify-wrap')!
55
49
 
56
50
  this.initLocalStorage()
57
51
  this.initHeader()
@@ -201,13 +195,13 @@ export default class Editor extends Component {
201
195
  this.$plugWrap.innerHTML = ''
202
196
  this.$plugWrap.style.display = 'none'
203
197
  this.openedPlugName = null
204
- this.$plugSwitcherWrap.innerHTML = ''
198
+ this.$plugBtnWrap.innerHTML = ''
205
199
 
206
200
  // 依次实例化 plug
207
201
  this.LOADABLE_PLUG_LIST.forEach((PlugObj) => {
208
202
  // 切换按钮
209
- const btnElem = Utils.createElement(`<span class="atk-editor-action atk-editor-plug-switcher">${PlugObj.BtnHTML}</span>`)
210
- this.$plugSwitcherWrap.appendChild(btnElem)
203
+ const btnElem = Utils.createElement(`<span class="atk-plug-btn">${PlugObj.BtnHTML}</span>`)
204
+ this.$plugBtnWrap.appendChild(btnElem)
211
205
  btnElem.addEventListener('click', () => {
212
206
  let plug = this.plugList[PlugObj.Name]
213
207
  if (!plug) {
@@ -215,7 +209,7 @@ export default class Editor extends Component {
215
209
  this.plugList[PlugObj.Name] = plug
216
210
  }
217
211
 
218
- this.$plugSwitcherWrap.querySelectorAll('.active').forEach(item => item.classList.remove('active'))
212
+ this.$plugBtnWrap.querySelectorAll('.active').forEach(item => item.classList.remove('active'))
219
213
 
220
214
  // 若点击已打开的,则收起
221
215
  if (PlugObj.Name === this.openedPlugName) {
@@ -413,7 +407,8 @@ export default class Editor extends Component {
413
407
 
414
408
  /** 关闭评论 */
415
409
  close () {
416
- this.$closeComment.style.display = ''
410
+ if (!this.$textareaWrap.querySelector('.atk-comment-closed'))
411
+ this.$textareaWrap.prepend(Utils.createElement('<div class="atk-comment-closed">仅管理员可评论</div>'))
417
412
 
418
413
  if (!this.user.data.isAdmin) {
419
414
  this.$textarea.style.display = 'none'
@@ -428,7 +423,7 @@ export default class Editor extends Component {
428
423
 
429
424
  /** 打开评论 */
430
425
  open () {
431
- this.$closeComment.style.display = 'none'
426
+ this.$textareaWrap.querySelector('.atk-comment-closed')?.remove()
432
427
  this.$textarea.style.display = ''
433
428
  this.$bottom.style.display = ''
434
429
  }
@@ -1,25 +1,18 @@
1
- <div class="atk-comment-wrap" data-comment-id="">
1
+ <div class="atk-comment-wrap">
2
2
  <div class="atk-comment">
3
-
4
3
  <div class="atk-avatar"></div>
5
-
6
- <div class="atk-comment-main">
7
-
4
+ <div class="atk-main">
8
5
  <div class="atk-header">
9
- <span class="atk-nick"></span>
10
- <span class="atk-badge"></span>
11
- <span class="atk-date"></span>
6
+ <span class="atk-item atk-nick"></span>
7
+ <span class="atk-item atk-badge"></span>
8
+ <span class="atk-item atk-date"></span>
12
9
  </div>
13
-
14
10
  <div class="atk-body">
15
11
  <div class="atk-content"></div>
16
12
  </div>
17
-
18
13
  <div class="atk-footer">
19
- <div class="atk-comment-actions"></div>
14
+ <div class="atk-actions"></div>
20
15
  </div>
21
-
22
16
  </div>
23
-
24
17
  </div>
25
18
  </div>
@@ -1,19 +1,18 @@
1
1
  <div class="atk-main-editor">
2
- <div class="atk-editor-header">
2
+ <div class="atk-header">
3
3
  <input name="nick" placeholder="昵称" class="atk-nick" type="text" required="required">
4
4
  <input name="email" placeholder="邮箱" class="atk-email" type="email" required="required">
5
5
  <input name="link" placeholder="网址 (https://)" class="atk-link" type="url">
6
6
  </div>
7
- <div class="atk-editor-textarea-wrap">
8
- <div class="atk-close-comment" style="display: none;"><span>仅管理员可评论</span></div>
9
- <textarea id="atk-editor-textarea" class="atk-editor-textarea" placeholder=""></textarea>
7
+ <div class="atk-textarea-wrap">
8
+ <textarea class="atk-textarea" placeholder=""></textarea>
10
9
  </div>
11
- <div class="atk-editor-plug-wrap" style="display: none;"></div>
12
- <div class="atk-editor-bottom">
13
- <div class="atk-editor-bottom-part atk-left atk-editor-plug-switcher-wrap"></div>
14
- <div class="atk-editor-bottom-part atk-right">
10
+ <div class="atk-plug-wrap" style="display: none;"></div>
11
+ <div class="atk-bottom">
12
+ <div class="atk-item atk-plug-btn-wrap"></div>
13
+ <div class="atk-item">
15
14
  <button type="button" class="atk-send-btn"></button>
16
15
  </div>
17
16
  </div>
18
- <div class="atk-editor-notify-wrap"></div>
17
+ <div class="atk-notify-wrap"></div>
19
18
  </div>
@@ -8,7 +8,7 @@
8
8
  <span data-action="admin-close-comment" class="atk-hide" atk-only-admin-show>关闭评论</span>
9
9
  <span data-action="open-sidebar" class="atk-hide atk-on">
10
10
  <span class="atk-unread-badge" style="display: none;"></span>
11
- 通知中心
11
+ <div class="atk-text">通知中心</div>
12
12
  </span>
13
13
  </div>
14
14
  </div>