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
@@ -16,7 +16,7 @@ export default class List extends ListLite {
16
16
  constructor (ctx: Context) {
17
17
  const el = Utils.createElement(ListHTML)
18
18
 
19
- super(ctx, el)
19
+ super(ctx)
20
20
 
21
21
  // 把 listLite $el 变为子元素
22
22
  el.querySelector('.atk-list-body')!.append(this.$el)
@@ -34,6 +34,8 @@ export default class List extends ListLite {
34
34
 
35
35
  // 分页模式
36
36
  this.pageMode = this.conf.pagination?.readMore ? 'read-more' : 'pagination'
37
+ this.pageSize = this.conf.pagination?.pageSize || 20
38
+ this.repositionAt = this.$el
37
39
 
38
40
  // 操作按钮
39
41
  this.initListActionBtn()
@@ -42,7 +44,7 @@ export default class List extends ListLite {
42
44
  this.$el.querySelector<HTMLElement>('.atk-copyright')!.innerHTML = `Powered By <a href="https://artalk.js.org" target="_blank" title="Artalk v${ARTALK_VERSION}">Artalk</a>`
43
45
 
44
46
  // event listen
45
- this.ctx.on('list-reload', () => (this.reqComments(0))) // 刷新评论
47
+ this.ctx.on('list-reload', () => (this.fetchComments(0))) // 刷新评论
46
48
  this.ctx.on('list-refresh-ui', () => (this.refreshUI()))
47
49
  this.ctx.on('list-import', (data) => (this.importComments(data)))
48
50
  this.ctx.on('list-insert', (data) => (this.insertComment(data)))
@@ -51,7 +53,7 @@ export default class List extends ListLite {
51
53
  this.ctx.on('unread-update', (data) => (this.showUnreadBadge(data.notifies?.length || 0)))
52
54
  }
53
55
 
54
- private initListActionBtn () {
56
+ private initListActionBtn() {
55
57
  // 侧边栏呼出按钮
56
58
  this.$openSidebarBtn = this.$el.querySelector('[data-action="open-sidebar"]')!
57
59
  this.$closeCommentBtn = this.$el.querySelector('[data-action="admin-close-comment"]')!
@@ -71,10 +73,10 @@ export default class List extends ListLite {
71
73
  }
72
74
 
73
75
  /** 刷新界面 */
74
- public refreshUI () {
76
+ public refreshUI() {
75
77
  super.refreshUI()
76
78
 
77
- this.$el.querySelector<HTMLElement>('.atk-comment-count-num')!.innerText = String(this.commentsCount)
79
+ this.$el.querySelector<HTMLElement>('.atk-comment-count-num')!.innerText = String(Number(this.data?.total) || 0)
78
80
 
79
81
  // 已输入个人信息
80
82
  if (!!this.ctx.user.data.nick && !!this.ctx.user.data.email) {
@@ -85,7 +87,7 @@ export default class List extends ListLite {
85
87
 
86
88
  // 仅管理员显示控制
87
89
  this.ctx.trigger('check-admin-show-el')
88
- this.$openSidebarBtn.innerText = (!this.ctx.user.data.isAdmin) ? '通知中心' : '控制中心'
90
+ this.$openSidebarBtn.querySelector<HTMLElement>('.atk-text')!.innerText = (!this.ctx.user.data.isAdmin) ? '通知中心' : '控制中心'
89
91
 
90
92
  // 关闭评论
91
93
  if (!!this.data && !!this.data.page && this.data.page.admin_only === true) {
@@ -97,7 +99,7 @@ export default class List extends ListLite {
97
99
  }
98
100
  }
99
101
 
100
- public onLoad(data: ListData, offset: number) {
102
+ protected onLoad(data: ListData, offset: number) {
101
103
  super.onLoad(data, offset)
102
104
 
103
105
  // 检测锚点跳转
@@ -130,7 +132,7 @@ export default class List extends ListLite {
130
132
  // TODO 若找不到评论的情况
131
133
  // if (!comment) { // 若找不到评论
132
134
  // if (this.hasMoreComments) { // 阅读更多,并重试
133
- // await this.reqComments()
135
+ // await this.fetchComments()
134
136
  // }
135
137
  // }
136
138
 
@@ -161,7 +163,7 @@ export default class List extends ListLite {
161
163
  })
162
164
  }
163
165
 
164
- public showUnreadBadge (count: number) {
166
+ public showUnreadBadge(count: number) {
165
167
  if (count > 0) {
166
168
  this.$unreadBadge.innerText = `${Number(count || 0)}`
167
169
  this.$unreadBadge.style.display = 'block'
@@ -4,7 +4,7 @@ import * as Ui from '../lib/ui'
4
4
 
5
5
  export interface PaginationConf {
6
6
  /** 每页条数 */
7
- pageSize?: number
7
+ pageSize: number
8
8
 
9
9
  /** 回调函数 */
10
10
  onChange: (offset: number) => void
@@ -21,7 +21,7 @@ export default class Pagination {
21
21
 
22
22
  public page: number = 1
23
23
  get pageSize(): number {
24
- return this.conf.pageSize || 15
24
+ return this.conf.pageSize
25
25
  }
26
26
  get offset(): number {
27
27
  return this.pageSize * (this.page - 1)
@@ -1,14 +1,10 @@
1
1
  import * as Utils from '../lib/utils'
2
2
 
3
3
  export interface ReadMoreBtnConf {
4
- /** 每页条数 */
5
- pageSize?: number
6
-
7
- /** 数据总数 */
8
- total: number
9
-
10
4
  /** 回调函数 */
11
- onClick: () => void
5
+ onClick: (offset: number) => void
6
+
7
+ pageSize: number
12
8
  }
13
9
 
14
10
  /**
@@ -19,6 +15,13 @@ export default class ReadMoreBtn {
19
15
  public $el: HTMLElement
20
16
  private $loading: HTMLElement
21
17
  private $text: HTMLElement
18
+ private offset: number = 0
19
+ private total: number = 0
20
+
21
+ /** 是否有更多内容 */
22
+ get hasMore() {
23
+ return this.total > (this.offset + this.conf.pageSize)
24
+ }
22
25
 
23
26
  public constructor(conf: ReadMoreBtnConf) {
24
27
  this.conf = conf
@@ -27,18 +30,19 @@ export default class ReadMoreBtn {
27
30
  `<div class="atk-list-read-more" style="display: none;">
28
31
  <div class="atk-list-read-more-inner">
29
32
  <div class="atk-loading-icon" style="display: none;"></div>
30
- <span class="atk-text">查看更多</span>
33
+ <span class="atk-text">加载更多</span>
31
34
  </div>
32
35
  </div>`)
33
36
 
34
37
  this.$loading = this.$el.querySelector<HTMLElement>('.atk-loading-icon')!
35
38
  this.$text = this.$el.querySelector<HTMLElement>('.atk-text')!
36
39
 
37
- this.$el.onclick = () => this.click()
40
+ this.$el.onclick = () => { this.click() }
38
41
  }
39
42
 
40
43
  click() {
41
- this.conf.onClick()
44
+ if (this.hasMore) this.conf.onClick(this.offset + this.conf.pageSize)
45
+ this.checkDisabled()
42
46
  }
43
47
 
44
48
  /** 显示 */
@@ -68,4 +72,17 @@ export default class ReadMoreBtn {
68
72
  this.$el.classList.remove('atk-err')
69
73
  }, 2000) // 2s后错误提示复原
70
74
  }
75
+
76
+ /** 更新数据 */
77
+ public update(offset: number, total: number) {
78
+ this.offset = offset
79
+ this.total = total
80
+
81
+ this.checkDisabled()
82
+ }
83
+
84
+ public checkDisabled() {
85
+ if (this.hasMore) this.show()
86
+ else this.hide()
87
+ }
71
88
  }
@@ -31,14 +31,22 @@ export default class MessageView extends SidebarView {
31
31
  this.viewActiveTab = 'mentions'
32
32
  }
33
33
 
34
- this.list = new ListLite(this.ctx, this.$el)
34
+ this.list = new ListLite(this.ctx)
35
35
  this.list.flatMode = true
36
36
  this.list.unreadHighlight = true
37
- this.list.autoLoadListenerAt = this.$parent
37
+ this.list.scrollListenerAt = this.$parent
38
38
  this.list.pageMode = 'pagination'
39
39
  this.list.noCommentText = '<div class="atk-sidebar-no-content">无内容</div>'
40
40
  this.list.renderComment = (comment) => {
41
41
  comment.setOpenURL(`${comment.data.page_key}#atk-comment-${comment.data.id}`)
42
+ comment.onReplyBtnClick = () => {
43
+ if (this.ctx.conf.editorTravel === true) {
44
+ this.ctx.trigger('editor-reply', {data: comment.data, $el: comment.$el, scroll: false})
45
+ } else {
46
+ this.ctx.trigger('sidebar-hide')
47
+ this.ctx.trigger('editor-reply', {data: comment.data, $el: comment.$el, scroll: true})
48
+ }
49
+ }
42
50
  }
43
51
  this.list.paramsEditor = (params) => {
44
52
  params.site_name = siteName
@@ -53,12 +61,11 @@ export default class MessageView extends SidebarView {
53
61
  switchTab(tab: string, siteName: string): boolean|void {
54
62
  //console.log(tab, siteName)
55
63
  this.viewActiveTab = tab
56
- this.list.type = tab as any;
57
- this.list.isFirstLoad = true
58
64
  this.list.paramsEditor = (params) => {
65
+ params.type = tab as any
59
66
  params.site_name = siteName
60
67
  }
61
- this.list.reqComments()
68
+ this.list.fetchComments(0)
62
69
 
63
70
  return true
64
71
  }
@@ -16,12 +16,6 @@ export default class SitesView extends SidebarView {
16
16
 
17
17
  siteList!: SiteList
18
18
 
19
- constructor(ctx: Context) {
20
- super(ctx)
21
-
22
- this.$el = Utils.createElement(`<div class="atk-sidebar-view"></div>`)
23
- }
24
-
25
19
  mount(siteName: string) {
26
20
  if (!this.siteList) {
27
21
  this.siteList = new SiteList(this.ctx)
@@ -139,6 +139,12 @@ export default class Sidebar extends Component {
139
139
 
140
140
  // 获取 Layer
141
141
  this.layer = new Layer(this.ctx, 'sidebar', this.$el)
142
+ this.layer.afterHide = () => {
143
+ // 防止评论框被吞
144
+ if (this.ctx.conf.editorTravel === true) {
145
+ this.ctx.trigger('editor-travel-back')
146
+ }
147
+ }
142
148
  this.layer.show()
143
149
 
144
150
  // viewWrap 滚动条归位
@@ -217,8 +223,7 @@ export default class Sidebar extends Component {
217
223
  // 执行动画
218
224
  this.$el.style.transform = ''
219
225
 
220
- // 用完即销毁
221
- this.layer?.dispose()
226
+ this.layer?.hide()
222
227
  }
223
228
 
224
229
  /** 切换 View */
@@ -1,7 +1,6 @@
1
1
  import ArtalkConfig from "~/types/artalk-config";
2
- import emoticons from './assets/emoticons.json'
3
2
 
4
- const defaultConf: ArtalkConfig = {
3
+ const defaults: ArtalkConfig = {
5
4
  el: '',
6
5
  pageKey: '',
7
6
  server: '',
@@ -11,8 +10,9 @@ const defaultConf: ArtalkConfig = {
11
10
  noComment: '「此时无声胜有声」',
12
11
  sendBtn: '发送评论',
13
12
  darkMode: false,
13
+ editorTravel: true,
14
14
 
15
- emoticons,
15
+ emoticons: "https://cdn.jsdelivr.net/gh/ArtalkJS/Emoticons/grps/default.json",
16
16
 
17
17
  vote: true,
18
18
  voteDown: false,
@@ -25,18 +25,18 @@ const defaultConf: ArtalkConfig = {
25
25
  },
26
26
 
27
27
  pagination: {
28
- pageSize: 15,
28
+ pageSize: 20,
29
29
  readMore: true,
30
30
  autoLoad: true,
31
31
  },
32
32
 
33
33
  heightLimit: {
34
- content: 200,
35
- children: 300,
34
+ content: 300,
35
+ children: 400,
36
36
  },
37
37
 
38
38
  reqTimeout: 15000,
39
39
  versionCheck: true,
40
40
  }
41
41
 
42
- export default defaultConf
42
+ export default defaults
package/src/lib/utils.ts CHANGED
@@ -1,4 +1,5 @@
1
- import libMarked from 'marked'
1
+ import { marked as libMarked } from 'marked'
2
+ import insane from 'insane'
2
3
  import hanabi from 'hanabi'
3
4
  import Context from '../context'
4
5
 
@@ -95,12 +96,33 @@ export function timeAgo(date: Date) {
95
96
  }
96
97
  }
97
98
 
99
+ /** 所有图片加载完毕后执行 */
100
+ export function onImagesLoaded($container: HTMLElement, event: Function) {
101
+ if (!$container) return
102
+ const images = $container.getElementsByTagName('img')
103
+ if (!images.length) return
104
+ let loaded = images.length
105
+ for (let i = 0; i < images.length; i++) {
106
+ if (images[i].complete) {
107
+ loaded--
108
+ } else {
109
+ // eslint-disable-next-line @typescript-eslint/no-loop-func
110
+ images[i].addEventListener('load', () => {
111
+ loaded--
112
+ if (loaded === 0) event()
113
+ })
114
+ }
115
+
116
+ if (loaded === 0) event()
117
+ }
118
+ }
119
+
98
120
  export function getGravatarURL(ctx: Context, emailMD5: string) {
99
121
  return `${(ctx.conf.gravatar?.mirror || '').replace(/\/$/, '')}/${emailMD5}?d=${encodeURIComponent(ctx.conf.gravatar?.default || '')}&s=80`
100
122
  }
101
123
 
102
124
  export function sleep(ms: number) {
103
- return new Promise(resolve => setTimeout(resolve, ms));
125
+ return new Promise(resolve => { setTimeout(resolve, ms) });
104
126
  }
105
127
 
106
128
  /** 版本号比较(a < b :-1 | 0 | b < a :1) */
@@ -118,34 +140,41 @@ export function versionCompare(a: string, b: string) {
118
140
  return 0
119
141
  }
120
142
 
121
- let markedInstance: any = null
143
+ let markedInstance: typeof libMarked
122
144
  export function marked(ctx: Context, src: string): string {
123
145
  if (!markedInstance) {
124
146
  const renderer = new libMarked.Renderer()
125
147
  const linkRenderer = renderer.link
126
148
  renderer.link = (href, title, text) => {
127
- const html = linkRenderer.call(renderer, href, title, text)
128
- return html.replace(/^<a /, '<a target="_blank" rel="nofollow" ')
149
+ const localLink = href?.startsWith(`${window.location.protocol}//${window.location.hostname}`);
150
+ const html = linkRenderer.call(renderer as any, href, title, text);
151
+ return html.replace(/^<a /, `<a target="_blank" ${!localLink ? `rel="noreferrer noopener nofollow"` : ''} `);
129
152
  }
130
153
 
154
+ // @see https://github.com/markedjs/marked/blob/4afb228d956a415624c4e5554bb8f25d047676fe/src/Tokenizer.js#L329
131
155
  const nMarked = libMarked
132
- nMarked.setOptions({
156
+ libMarked.setOptions({
133
157
  renderer,
134
158
  highlight: (code) => hanabi(code),
135
159
  pedantic: false,
136
160
  gfm: true,
137
- tables: true,
138
161
  breaks: true,
139
- sanitize: true, // 净化
140
162
  smartLists: true,
141
163
  smartypants: true,
142
- xhtml: false
164
+ xhtml: false,
165
+ sanitize: true,
166
+ sanitizer: (html) => insane(html, {
167
+ ...insane.defaults,
168
+ allowedAttributes: {
169
+ ...insane.defaults.allowedAttributes,
170
+ img: ['src', 'atk-emoticon']
171
+ },
172
+ }),
173
+ silent: true,
143
174
  })
144
175
 
145
176
  markedInstance = nMarked
146
177
  }
147
178
 
148
- return markedInstance(src)
149
-
150
- return ''
179
+ return markedInstance.parse(src)
151
180
  }
@@ -1,27 +1,24 @@
1
1
  .artalk, .atk-layer-wrap {
2
- * {
3
- --at-color-font: #2a2e2e;
4
- --at-color-deep: #2a2e2e;
5
- --at-color-sub: #757575;
6
- --at-color-grey: #999;
7
- --at-color-meta: #757e91;
8
- --at-color-border: #eceff2;
9
- --at-color-light: #4285f4;
10
- --at-color-bg: #FFF;
11
- --at-color-bg-transl: rgba(255, 255, 255, 0.94);
12
- --at-color-bg-grey: #F4F4F4;
13
- --at-color-bg-grey-transl: rgba(244, 244, 244, 0.75);
14
- --at-color-bg-light: rgba(29, 161, 242, 0.1);
15
- --at-color-main: #0083ff;
16
- --at-color-red: #ff5652;
17
- --at-color-pink: #fa5a57;
18
- --at-color-yellow: #ff7c37;
19
- --at-color-green: #4caf50;
20
- --at-color-quote: #687a86;
21
- --at-color-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0), rgba(255, 255, 255));
22
- }
2
+ --at-color-font: #2a2e2e;
3
+ --at-color-deep: #2a2e2e;
4
+ --at-color-sub: #757575;
5
+ --at-color-grey: #999;
6
+ --at-color-meta: #757e91;
7
+ --at-color-border: #eceff2;
8
+ --at-color-light: #4285f4;
9
+ --at-color-bg: #FFF;
10
+ --at-color-bg-transl: rgba(255, 255, 255, 0.94);
11
+ --at-color-bg-grey: #F4F4F4;
12
+ --at-color-bg-grey-transl: rgba(244, 244, 244, 0.75);
13
+ --at-color-bg-light: rgba(29, 161, 242, 0.1);
14
+ --at-color-main: #0083ff;
15
+ --at-color-red: #ff5652;
16
+ --at-color-pink: #fa5a57;
17
+ --at-color-yellow: #ff7c37;
18
+ --at-color-green: #4caf50;
19
+ --at-color-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0), rgba(255, 255, 255));
23
20
 
24
- &.atk-dark-mode * {
21
+ &.atk-dark-mode {
25
22
  --at-color-font: #fff;
26
23
  --at-color-deep: #e7e7e7;
27
24
  --at-color-sub: #e7e7e7;
@@ -39,7 +36,6 @@
39
36
  --at-color-pink: #fa5a57;
40
37
  --at-color-yellow: #ff7c37;
41
38
  --at-color-green: #4caf50;
42
- --at-color-quote: #687a86;
43
39
  --at-color-gradient: linear-gradient(180deg, rgba(30, 34, 36, 0), rgba(30, 34, 36, 1));
44
40
  }
45
41
  }
@@ -69,7 +69,7 @@
69
69
 
70
70
  .atk-item {
71
71
  &:hover {
72
- background: @color-bg-grey;
72
+ background: var(--at-color-bg-grey);
73
73
  }
74
74
 
75
75
  &:not(:last-child) {
@@ -91,10 +91,10 @@
91
91
  }
92
92
 
93
93
  .atk-icon {
94
- background-color: @color-meta;
94
+ background-color: var(--at-color-meta);
95
95
  }
96
96
 
97
97
  .atk-icon-yes {
98
- background-color: @color-main;
98
+ background-color: var(--at-color-main);
99
99
  }
100
100
  }
@@ -1,28 +1,4 @@
1
- @color-font: var(--at-color-font);
2
- @color-deep: var(--at-color-deep);
3
- @color-sub: var(--at-color-sub);
4
- @color-grey: var(--at-color-grey);
5
- @color-meta: var(--at-color-meta);
6
- @color-light: var(--at-color-light);
7
- @color-border: var(--at-color-border);
8
- @color-bg: var(--at-color-bg);
9
- @color-bg-transl: var(--at-color-bg-transl);
10
- @color-bg-grey: var(--at-color-bg-grey);
11
- @color-bg-grey-transl: var(--at-color-bg-grey-transl);
12
- @color-bg-light: var(--at-color-bg-light);
13
- @color-main: var(--at-color-main);
14
- @color-red: var(--at-color-red);
15
- @color-pink: var(--at-color-pink);
16
- @color-yellow: var(--at-color-yellow);
17
- @color-green: var(--at-color-green);
18
- @color-quote: var(--at-color-quote);
19
- @color-gradient: var(--at-color-gradient);
20
-
21
1
  @font-family: "Helvetica Neue", "PingFangSC-Light", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;
22
2
  @font-size: 14px;
23
3
  @line-height: 1.6em;
24
-
25
- @block-margin: 15px;
26
- @block-margin-mobile: 7px;
27
- @block-padding: 10px;
28
4
  @block-radius: 4px;