artalk 2.1.0 → 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.
Files changed (45) hide show
  1. package/dist/Artalk.css +1 -1
  2. package/dist/Artalk.es.js +3895 -3106
  3. package/dist/Artalk.iife.js +1 -266
  4. package/dist/Artalk.js +1 -266
  5. package/package.json +19 -21
  6. package/src/api/index.ts +6 -7
  7. package/src/artalk.ts +11 -4
  8. package/src/components/action-btn.ts +1 -1
  9. package/src/components/comment.ts +102 -27
  10. package/src/components/editor-plugs/_example-plug.ts.txt +2 -7
  11. package/src/components/editor-plugs/editor-plug.ts +3 -3
  12. package/src/components/editor-plugs/emoticons-plug.less +21 -11
  13. package/src/components/editor-plugs/emoticons-plug.ts +199 -79
  14. package/src/components/editor-plugs/preview-plug.less +1 -1
  15. package/src/components/editor-plugs/preview-plug.ts +2 -7
  16. package/src/components/editor.ts +75 -34
  17. package/src/components/html/comment.html +6 -13
  18. package/src/components/html/editor.html +9 -10
  19. package/src/components/html/list.html +1 -1
  20. package/src/components/layer.ts +33 -14
  21. package/src/components/list-lite.ts +218 -235
  22. package/src/components/list.ts +11 -9
  23. package/src/components/pagination.ts +2 -2
  24. package/src/components/read-more-btn.ts +27 -10
  25. package/src/components/sidebar-views/comments-view.ts +12 -5
  26. package/src/components/sidebar-views/sites-view.ts +0 -6
  27. package/src/components/sidebar.ts +7 -2
  28. package/src/{default-conf.ts → defaults.ts} +7 -7
  29. package/src/lib/utils.ts +41 -12
  30. package/src/style/_color.less +19 -23
  31. package/src/style/_extend.less +3 -3
  32. package/src/style/_variables.less +0 -24
  33. package/src/style/comment.less +191 -197
  34. package/src/style/editor.less +78 -103
  35. package/src/style/item-text-editor.less +2 -2
  36. package/src/style/list.less +14 -13
  37. package/src/style/main.less +35 -31
  38. package/src/style/page-list.less +4 -4
  39. package/src/style/pagination.less +6 -6
  40. package/src/style/sidebar.less +35 -35
  41. package/src/style/site-list.less +13 -13
  42. package/types/artalk-config.d.ts +13 -10
  43. package/types/artalk-data.d.ts +10 -5
  44. package/types/event.d.ts +10 -1
  45. package/src/assets/emoticons.json +0 -100
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "artalk",
3
- "version": "2.1.0",
3
+ "version": "2.1.4",
4
4
  "description": "A Selfhosted Comment System",
5
5
  "keywords": [
6
6
  "comment-system",
@@ -38,29 +38,27 @@
38
38
  "lint": "eslint --ext .js,.ts src"
39
39
  },
40
40
  "devDependencies": {
41
- "@rollup/plugin-dynamic-import-vars": "^1.4.0",
42
- "@types/marked": "0.6.5",
43
- "@types/node": "^16.7.5",
44
- "@typescript-eslint/eslint-plugin": "^4.29.3",
45
- "@typescript-eslint/parser": "^4.31.1",
46
- "autoprefixer": "^10.3.3",
47
- "eslint": "7.2.0",
48
- "eslint-config-airbnb": "18.2.1",
49
- "eslint-config-airbnb-typescript": "^14.0.0",
41
+ "@rollup/plugin-dynamic-import-vars": "^1.4.1",
42
+ "@types/marked": "^4.0.1",
43
+ "@types/node": "^16.11.7",
44
+ "@typescript-eslint/eslint-plugin": "^5.4.0",
45
+ "@typescript-eslint/parser": "^5.4.0",
46
+ "autoprefixer": "^10.4.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",
56
- "less": "^4.1.1",
57
- "prettier": "^2.3.2",
58
- "typescript": "4.3.2",
59
- "vite": "^2.5.1",
60
- "vite-tsconfig-paths": "^3.3.14"
51
+ "eslint-import-resolver-typescript": "^2.5.0",
52
+ "eslint-plugin-import": "^2.25.2",
53
+ "less": "^4.1.2",
54
+ "prettier": "^2.4.1",
55
+ "typescript": "^4.5.2",
56
+ "vite": "^2.6.14",
57
+ "vite-tsconfig-paths": "^3.3.17"
61
58
  },
62
59
  "dependencies": {
63
60
  "hanabi": "^0.4.0",
64
- "marked": "0.7.0"
61
+ "insane": "^2.6.2",
62
+ "marked": "^4.0.3"
65
63
  }
66
64
  }
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
@@ -37,18 +36,18 @@ export default class Api {
37
36
  }
38
37
 
39
38
  /** 评论 · 创建 */
40
- public async add(comment: { nick: string, email: string, link: string, content: string, rid: number }) {
39
+ public async add(comment: { nick: string, email: string, link: string, content: string, rid: number, page_key: string, page_title?: string, site_name?: string }) {
41
40
  const params: any = {
42
41
  name: comment.nick,
43
42
  email: comment.email,
44
43
  link: comment.link,
45
44
  content: comment.content,
46
45
  rid: comment.rid,
47
- page_key: this.ctx.conf.pageKey,
48
- page_title: this.ctx.conf.pageTitle || '',
46
+ page_key: comment.page_key,
49
47
  }
50
48
 
51
- if (this.ctx.conf.site) params.site_name = this.ctx.conf.site
49
+ if (comment.page_title) params.page_title = comment.page_title
50
+ if (comment.site_name) params.site_name = comment.site_name
52
51
 
53
52
  const data = await POST<any>(this.ctx, `${this.baseURL}/add`, params)
54
53
  return (data.comment as CommentData)
package/src/artalk.ts CHANGED
@@ -2,7 +2,7 @@ import './style/main.less'
2
2
 
3
3
  import Context from './context'
4
4
  import ArtalkConfig from '~/types/artalk-config'
5
- import defaultConf from './default-conf'
5
+ import defaults from './defaults'
6
6
 
7
7
  import CheckerLauncher from './lib/checker'
8
8
  import Editor from './components/editor'
@@ -17,6 +17,8 @@ import { EventPayloadMap, Handler } from '~/types/event'
17
17
  * @link https://artalk.js.org
18
18
  */
19
19
  export default class Artalk {
20
+ public static readonly defaults: ArtalkConfig = defaults
21
+
20
22
  public ctx: Context
21
23
  public conf: ArtalkConfig
22
24
  public $root: HTMLElement
@@ -28,12 +30,12 @@ export default class Artalk {
28
30
 
29
31
  constructor (customConf: ArtalkConfig) {
30
32
  // 配置
31
- this.conf = { ...defaultConf, ...customConf }
33
+ this.conf = { ...Artalk.defaults, ...customConf }
32
34
  this.conf.server = this.conf.server.replace(/\/$/, '')
33
35
 
34
36
  // 默认 pageKey
35
37
  if (!this.conf.pageKey) {
36
- this.conf.pageKey = `${window.location.protocol}//'${window.location.host}/${window.location.pathname}`
38
+ this.conf.pageKey = `${window.location.protocol}//${window.location.host}${window.location.pathname}`
37
39
  }
38
40
 
39
41
  // 装载元素
@@ -70,7 +72,7 @@ export default class Artalk {
70
72
  this.$root.appendChild(this.sidebar.$el)
71
73
 
72
74
  // 评论获取
73
- this.list.reqComments()
75
+ this.list.fetchComments(0)
74
76
 
75
77
  // 事件绑定初始化
76
78
  this.initEventBind()
@@ -109,6 +111,11 @@ export default class Artalk {
109
111
  })
110
112
  }
111
113
 
114
+ /** 重新加载 */
115
+ public reload() {
116
+ this.list.fetchComments(0)
117
+ }
118
+
112
119
  /** 暗黑模式 · 初始化 */
113
120
  public initDarkMode() {
114
121
  const darkModeClassName = 'atk-dark-mode'
@@ -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) {
@@ -37,6 +37,8 @@ export default class Comment extends Component {
37
37
  private openURL?: string
38
38
  public openEvt?: () => void
39
39
 
40
+ public onReplyBtnClick?: Function
41
+
40
42
  constructor(ctx: Context, data: CommentData) {
41
43
  super(ctx)
42
44
 
@@ -53,11 +55,11 @@ export default class Comment extends Component {
53
55
  /** 渲染 UI */
54
56
  public render() {
55
57
  this.$el = Utils.createElement(CommentHTML)
56
- this.$main = this.$el.querySelector('.atk-comment-main')!
58
+ this.$main = this.$el.querySelector('.atk-main')!
57
59
  this.$header = this.$el.querySelector('.atk-header')!
58
60
  this.$body = this.$el.querySelector('.atk-body')!
59
61
  this.$content = this.$body.querySelector('.atk-content')!
60
- this.$actions = this.$el.querySelector('.atk-comment-actions')!
62
+ this.$actions = this.$el.querySelector('.atk-actions')!
61
63
  this.$children = null
62
64
 
63
65
  this.$el.setAttribute('data-comment-id', `${this.data.id}`)
@@ -105,7 +107,7 @@ export default class Comment extends Component {
105
107
  const $avatarImg = Utils.createElement<HTMLImageElement>('<img />')
106
108
  $avatarImg.src = this.getGravatarUrl()
107
109
  if (this.data.link) {
108
- const $avatarA = Utils.createElement<HTMLLinkElement>('<a target="_blank"></a>')
110
+ const $avatarA = Utils.createElement<HTMLLinkElement>('<a target="_blank" rel="noreferrer noopener nofollow"></a>')
109
111
  $avatarA.href = this.data.link
110
112
  $avatarA.append($avatarImg)
111
113
  $avatar.append($avatarA)
@@ -117,7 +119,7 @@ export default class Comment extends Component {
117
119
  private renderHeader() {
118
120
  const $nick = this.$el.querySelector<HTMLElement>('.atk-nick')!
119
121
  if (this.data.link) {
120
- const $nickA = Utils.createElement<HTMLLinkElement>('<a target="_blank"></a>')
122
+ const $nickA = Utils.createElement<HTMLLinkElement>('<a target="_blank" rel="noreferrer noopener nofollow"></a>')
121
123
  $nickA.innerText = this.data.nick
122
124
  $nickA.href = this.data.link
123
125
  $nick.append($nickA)
@@ -227,11 +229,15 @@ export default class Comment extends Component {
227
229
 
228
230
  // 绑定回复按钮事件
229
231
  if (this.data.is_allow_reply) {
230
- const replyBtn = Utils.createElement(`<span data-atk-action="comment-reply">回复</span>`)
232
+ const replyBtn = Utils.createElement(`<span>回复</span>`)
231
233
  this.$actions.append(replyBtn)
232
234
  replyBtn.addEventListener('click', (e) => {
233
235
  e.stopPropagation() // 防止穿透
234
- this.ctx.trigger('editor-reply', this.data)
236
+ if (!this.onReplyBtnClick) {
237
+ this.ctx.trigger('editor-reply', {data: this.data, $el: this.$el})
238
+ } else {
239
+ this.onReplyBtnClick()
240
+ }
235
241
  })
236
242
  }
237
243
 
@@ -310,6 +316,9 @@ export default class Comment extends Component {
310
316
 
311
317
  this.getChildrenEl().appendChild(childC.getEl())
312
318
  childC.playFadeInAnim()
319
+
320
+ // 内容限高
321
+ childC.checkHeightLimitArea('content')
313
322
  }
314
323
 
315
324
  getChildrenEl() {
@@ -330,6 +339,19 @@ export default class Comment extends Component {
330
339
  return this.parent
331
340
  }
332
341
 
342
+ getParents() {
343
+ const parents: Comment[] = []
344
+ const once = (c: Comment) => {
345
+ if (c.parent) {
346
+ parents.push(c.parent)
347
+ once(c.parent)
348
+ }
349
+ }
350
+
351
+ once(this)
352
+ return parents
353
+ }
354
+
333
355
  getEl() {
334
356
  return this.$el
335
357
  }
@@ -447,32 +469,85 @@ export default class Comment extends Component {
447
469
  this.$el.classList.add('atk-openable')
448
470
  }
449
471
 
450
- /** 内容过多,折叠显示 */
451
- public checkMoreHide($target: HTMLElement|null, allowHeight = 300) {
452
- if (!$target) return
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!
453
491
 
454
- let $hideMoreOpenBtn = $target?.querySelector<HTMLElement>('.atk-more-hide-open-btn')
492
+ // 检测指定元素
493
+ const checkEl = ($el?: HTMLElement|null) => {
494
+ if (!$el) return
455
495
 
456
- const removeHideMore = () => {
457
- $target.classList.remove('atk-comment-more-hide')
458
- if ($hideMoreOpenBtn) $hideMoreOpenBtn.remove()
459
- $target.style.height = ''
460
- $target.style.overflow = ''
496
+ // 是否超过高度
497
+ if (Utils.getHeight($el) > maxHeight) {
498
+ this.heightLimitAdd($el, maxHeight)
499
+ }
461
500
  }
462
501
 
463
- if (Utils.getHeight($target) > allowHeight) {
464
- //console.log('内容过多,需要折叠', $target)
465
- $target.classList.add('atk-comment-more-hide')
466
- $target.style.height = `${allowHeight}px`
467
- $target.style.overflow = 'hidden'
468
- if (!$hideMoreOpenBtn) {
469
- $hideMoreOpenBtn = Utils.createElement(`<div class="atk-more-hide-open-btn">阅读更多</span>`)
470
- $hideMoreOpenBtn.onclick = (e) => {
471
- e.stopPropagation()
472
- removeHideMore()
473
- }
474
- $target.append($hideMoreOpenBtn)
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
+ })
475
517
  }
476
518
  }
477
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
+ }
478
553
  }
@@ -16,13 +16,8 @@ export default class PreviewPlug extends ArtalkContext {
16
16
  this.elem = Utils.createElement('<div class="atk-editor-plug-example"></div>')
17
17
  }
18
18
 
19
- getName () {
20
- return 'example'
21
- }
22
-
23
- getBtnHtml () {
24
- return '栗子'
25
- }
19
+ static Name = 'example'
20
+ static BtnHTML = '栗子'
26
21
 
27
22
  getEl () {
28
23
  return this.elem
@@ -11,10 +11,10 @@ export default abstract class EditorPlug {
11
11
  this.ctx = editor.ctx
12
12
  }
13
13
 
14
- public abstract initEl(): void
14
+ public static Name: string
15
+ public static BtnHTML: string
16
+
15
17
  public abstract getEl(): HTMLElement
16
- public abstract getName(): string
17
- public abstract getBtnHtml(): string
18
18
  public abstract onShow(): void
19
19
  public abstract onHide(): void
20
20
  }
@@ -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
  }