artalk 2.2.12 → 2.3.2

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 (78) hide show
  1. package/dist/Artalk.css +1 -1
  2. package/dist/Artalk.es.js +1535 -1175
  3. package/dist/Artalk.iife.js +1 -1
  4. package/dist/Artalk.js +1 -1
  5. package/dist/ArtalkLite.css +1 -0
  6. package/dist/ArtalkLite.es.js +5123 -0
  7. package/dist/ArtalkLite.iife.js +1 -0
  8. package/dist/ArtalkLite.js +1 -0
  9. package/index.html +2 -2
  10. package/package.json +6 -5
  11. package/src/api/index.ts +20 -1
  12. package/src/api/request.ts +7 -7
  13. package/src/artalk.ts +117 -121
  14. package/src/comment/actions.ts +86 -0
  15. package/src/{components/html → comment}/comment.html +1 -1
  16. package/src/comment/comment.ts +191 -0
  17. package/src/comment/index.ts +3 -0
  18. package/src/comment/render.ts +425 -0
  19. package/src/components/action-btn.ts +6 -3
  20. package/src/components/dialog.ts +7 -3
  21. package/src/components/read-more-btn.ts +6 -2
  22. package/src/context.ts +204 -13
  23. package/src/defaults.ts +16 -5
  24. package/src/editor/editor.html +18 -0
  25. package/src/editor/editor.ts +419 -0
  26. package/src/editor/index.ts +3 -0
  27. package/src/editor/plugs/editor-plug.ts +51 -0
  28. package/src/{components/editor-plugs → editor/plugs}/emoticons-plug.less +2 -1
  29. package/src/{components/editor-plugs → editor/plugs}/emoticons-plug.ts +65 -63
  30. package/src/editor/plugs/header-input-plug.ts +78 -0
  31. package/src/{components/editor-plugs → editor/plugs}/preview-plug.less +0 -0
  32. package/src/editor/plugs/preview-plug.ts +40 -0
  33. package/src/editor/plugs/upload-plug.ts +124 -0
  34. package/src/i18n/en.ts +78 -0
  35. package/src/i18n/index.ts +21 -0
  36. package/src/i18n/zh-CN.ts +82 -0
  37. package/src/layer/index.ts +4 -0
  38. package/src/{components → layer}/layer.ts +9 -9
  39. package/src/{components/html → layer}/sidebar-layer.html +0 -0
  40. package/src/{components → layer}/sidebar-layer.ts +17 -47
  41. package/src/lib/checker/admin-checker.ts +7 -7
  42. package/src/lib/checker/captcha-checker.ts +5 -6
  43. package/src/lib/checker/index.ts +22 -17
  44. package/src/lib/component.ts +7 -2
  45. package/src/lib/stat.ts +63 -0
  46. package/src/lib/ui.ts +6 -12
  47. package/src/lib/user.ts +20 -3
  48. package/src/lib/utils.ts +23 -9
  49. package/src/list/index.ts +3 -0
  50. package/src/{components → list}/list-lite.ts +80 -100
  51. package/src/{components → list}/list-nest.ts +0 -0
  52. package/src/{components/html → list}/list.html +3 -6
  53. package/src/{components → list}/list.ts +44 -42
  54. package/src/style/_variables.less +1 -2
  55. package/src/style/comment.less +26 -13
  56. package/src/style/editor.less +22 -14
  57. package/src/style/list.less +6 -9
  58. package/src/style/main.less +9 -8
  59. package/src/style/pagination.less +1 -1
  60. package/test/ArtalkCount.html +16 -0
  61. package/test/ArtalkI18n.html +15 -0
  62. package/test/ArtalkLite.html +15 -0
  63. package/types/artalk-config.d.ts +50 -32
  64. package/types/context.d.ts +128 -0
  65. package/types/event.d.ts +6 -66
  66. package/types/global.d.ts +1 -0
  67. package/types/plug.d.ts +4 -0
  68. package/{vite-common.config.ts → vite-base.config.ts} +6 -4
  69. package/vite-demo.config.ts +2 -4
  70. package/vite-lite.config.ts +22 -0
  71. package/vite.config.ts +9 -12
  72. package/src/components/_component.ts.txt +0 -10
  73. package/src/components/comment.ts +0 -605
  74. package/src/components/editor-plugs/_example-plug.ts.txt +0 -29
  75. package/src/components/editor-plugs/editor-plug.ts +0 -20
  76. package/src/components/editor-plugs/preview-plug.ts +0 -48
  77. package/src/components/editor.ts +0 -607
  78. package/src/components/html/editor.html +0 -18
@@ -0,0 +1,191 @@
1
+ import { CommentData } from '~/types/artalk-data'
2
+ import Context from '~/types/context'
3
+ import Component from '../lib/component'
4
+ import * as Utils from '../lib/utils'
5
+ import UADetect from '../lib/detect'
6
+ import CommentRender from './render'
7
+ import CommentActions from './actions'
8
+
9
+ export interface CommentConf {
10
+ isUnread?: boolean
11
+ openURL?: string
12
+ isFlatMode: boolean
13
+ replyTo?: CommentData
14
+ afterRender?: () => void
15
+ openEvt?: () => void
16
+ onReplyBtnClick?: Function
17
+ onDelete?: Function
18
+ }
19
+
20
+ export default class Comment extends Component {
21
+ private renderInstance: CommentRender
22
+ private actionInstance: CommentActions
23
+
24
+ private data: CommentData
25
+ private cConf: CommentConf
26
+
27
+ private parent: Comment|null
28
+ private children: Comment[] = []
29
+
30
+ private nestCurt: number // 当前嵌套层数
31
+ private nestMax: number // 最大嵌套层数
32
+
33
+ constructor(ctx: Context, data: CommentData, conf: CommentConf) {
34
+ super(ctx)
35
+
36
+ // 最大嵌套数
37
+ this.nestMax = ctx.conf.nestMax || 3
38
+
39
+ this.cConf = conf
40
+ this.data = { ...data }
41
+ this.data.date = this.data.date.replace(/-/g, '/') // 解决 Safari 日期解析 NaN 问题
42
+
43
+ this.parent = null
44
+ this.nestCurt = 1 // 现在已嵌套 n 层
45
+
46
+ this.actionInstance = new CommentActions(this)
47
+ this.renderInstance = new CommentRender(this)
48
+ }
49
+
50
+ /** 渲染 UI */
51
+ public render() {
52
+ const newEl = this.renderInstance.render()
53
+
54
+ if (this.$el) this.$el.replaceWith(newEl)
55
+ this.$el = newEl
56
+
57
+ if (this.cConf.afterRender) this.cConf.afterRender()
58
+ }
59
+
60
+ /** 获取评论操作实例对象 */
61
+ public getActions() {
62
+ return this.actionInstance
63
+ }
64
+
65
+ /** 获取评论渲染器实例对象 */
66
+ public getRender() {
67
+ return this.renderInstance
68
+ }
69
+
70
+ /** 获取评论数据 */
71
+ public getData() {
72
+ return this.data
73
+ }
74
+
75
+ /** 设置数据 */
76
+ public setData(data: CommentData) {
77
+ this.data = data
78
+
79
+ this.render()
80
+ this.getRender().playFadeAnimForBody()
81
+ }
82
+
83
+ /** 获取父评论 */
84
+ public getParent() {
85
+ return this.parent
86
+ }
87
+
88
+ /** 获取所有子评论 */
89
+ public getChildren() {
90
+ return this.children
91
+ }
92
+
93
+ /** 获取当前嵌套层数 */
94
+ public getNestCurt() {
95
+ return this.nestCurt
96
+ }
97
+
98
+ /** 判断是否为根评论 */
99
+ public getIsRoot() {
100
+ return this.data.rid === 0
101
+ }
102
+
103
+ /** 获取评论 ID */
104
+ public getID() {
105
+ return this.data.id
106
+ }
107
+
108
+ /** 置入子评论 */
109
+ public putChild(childC: Comment, insertMode: 'append'|'prepend' = 'append') {
110
+ childC.parent = this
111
+ childC.nestCurt = this.nestCurt + 1 // 嵌套层数 +1
112
+
113
+ this.children.push(childC)
114
+
115
+ const $children = this.getChildrenEl()
116
+ if (insertMode === 'append') $children.append(childC.getEl())
117
+ else if (insertMode === 'prepend') $children.prepend(childC.getEl())
118
+
119
+ childC.getRender().playFadeAnim()
120
+
121
+ // 内容限高
122
+ childC.getRender().checkHeightLimitArea('content')
123
+ }
124
+
125
+ /** 获取存放子评论的元素对象 */
126
+ public getChildrenEl(): HTMLElement {
127
+ let $children = this.getRender().getChildrenWrap()
128
+
129
+ if (!$children) {
130
+ // console.log(this.nestCurt)
131
+ if (this.nestCurt < this.nestMax) {
132
+ $children = this.getRender().renderChildrenWrap()
133
+ } else {
134
+ $children = this.parent!.getChildrenEl()
135
+ }
136
+ }
137
+
138
+ return $children
139
+ }
140
+
141
+ /** 获取所有父评论 */
142
+ public getParents() {
143
+ const parents: Comment[] = []
144
+ const once = (c: Comment) => {
145
+ if (c.parent) {
146
+ parents.push(c.parent)
147
+ once(c.parent)
148
+ }
149
+ }
150
+
151
+ once(this)
152
+ return parents
153
+ }
154
+
155
+ /** 获取评论元素对象 */
156
+ public getEl() {
157
+ return this.$el
158
+ }
159
+
160
+ /** 获取 Gravatar 头像 URL */
161
+ public getGravatarURL() {
162
+ return Utils.getGravatarURL(this.ctx, this.data.email_encrypted)
163
+ }
164
+
165
+ /** 获取评论 markdown 解析后的内容 */
166
+ public getContentMarked() {
167
+ return Utils.marked(this.ctx, this.data.content)
168
+ }
169
+
170
+ /** 获取格式化后的日期 */
171
+ public getDateFormatted() {
172
+ return Utils.timeAgo(new Date(this.data.date), this.ctx)
173
+ }
174
+
175
+ /** 获取用户 UserAgent 浏览器 */
176
+ public getUserUaBrowser() {
177
+ const info = UADetect(this.data.ua)
178
+ return `${info.browser} ${info.version}`
179
+ }
180
+
181
+ /** 获取用户 UserAgent 系统 */
182
+ public getUserUaOS() {
183
+ const info = UADetect(this.data.ua)
184
+ return `${info.os} ${info.osVersion}`
185
+ }
186
+
187
+ /** 获取配置 */
188
+ public getConf() {
189
+ return this.cConf
190
+ }
191
+ }
@@ -0,0 +1,3 @@
1
+ import Comment from './comment'
2
+
3
+ export default Comment
@@ -0,0 +1,425 @@
1
+ import '../style/comment.less'
2
+
3
+ import * as Utils from '../lib/utils'
4
+ import * as Ui from '../lib/ui'
5
+ import ActionBtn from '../components/action-btn'
6
+ import CommentHTML from './comment.html?raw'
7
+ import Comment from './comment'
8
+
9
+ export default class CommentRender {
10
+ private comment: Comment
11
+
12
+ private get ctx() { return this.comment.ctx }
13
+ private get data() { return this.comment.getData() }
14
+ private get cConf() { return this.comment.getConf() }
15
+
16
+ public $el!: HTMLElement
17
+ public $main!: HTMLElement
18
+ public $header!: HTMLElement
19
+ public $headerNick!: HTMLElement
20
+ public $headerBadgeWrap!: HTMLElement
21
+ public $body!: HTMLElement
22
+ public $content!: HTMLElement
23
+ private $childrenWrap!: HTMLElement|null
24
+ public $actions!: HTMLElement
25
+ public voteBtnUp?: ActionBtn
26
+ public voteBtnDown?: ActionBtn
27
+
28
+ public $replyTo?: HTMLElement // 回复评论内容 (平铺下显示)
29
+ public $replyAt?: HTMLElement // 回复 AT(层级嵌套下显示)
30
+
31
+ public constructor(comment: Comment) {
32
+ this.comment = comment
33
+ }
34
+
35
+ public render() {
36
+ this.$el = Utils.createElement(CommentHTML)
37
+
38
+ this.$main = this.$el.querySelector('.atk-main')!
39
+ this.$header = this.$el.querySelector('.atk-header')!
40
+ this.$body = this.$el.querySelector('.atk-body')!
41
+ this.$content = this.$body.querySelector('.atk-content')!
42
+ this.$actions = this.$el.querySelector('.atk-actions')!
43
+
44
+ this.$el.setAttribute('data-comment-id', `${this.data.id}`)
45
+
46
+ this.renderAvatar()
47
+ this.renderHeader()
48
+ this.renderContent()
49
+ this.renderReplyAt()
50
+ this.renderReplyTo()
51
+ this.renderPending()
52
+ this.renderActionBtn()
53
+
54
+ this.recoveryChildrenWrap()
55
+
56
+ return this.$el
57
+ }
58
+
59
+ /** 初始化 - 评论头像 */
60
+ private renderAvatar() {
61
+ const $avatar = this.$el.querySelector<HTMLElement>('.atk-avatar')!
62
+ const $avatarImg = Utils.createElement<HTMLImageElement>('<img />')
63
+ $avatarImg.src = this.comment.getGravatarURL()
64
+ if (this.data.link) {
65
+ const $avatarA = Utils.createElement<HTMLLinkElement>('<a target="_blank" rel="noreferrer noopener nofollow"></a>')
66
+ $avatarA.href = this.data.link
67
+ $avatarA.append($avatarImg)
68
+ $avatar.append($avatarA)
69
+ } else {
70
+ $avatar.append($avatarImg)
71
+ }
72
+ }
73
+
74
+ /** 初始化 - 评论信息 */
75
+ private renderHeader() {
76
+ this.$headerNick = this.$el.querySelector<HTMLElement>('.atk-nick')!
77
+
78
+ if (this.data.link) {
79
+ const $nickA = Utils.createElement<HTMLLinkElement>('<a target="_blank" rel="noreferrer noopener nofollow"></a>')
80
+ $nickA.innerText = this.data.nick
81
+ $nickA.href = this.data.link
82
+ this.$headerNick.append($nickA)
83
+ } else {
84
+ this.$headerNick.innerText = this.data.nick
85
+ }
86
+
87
+ this.$headerBadgeWrap = this.$el.querySelector<HTMLElement>('.atk-badge-wrap')!
88
+ this.$headerBadgeWrap.innerHTML = ''
89
+
90
+ const badgeText = this.data.badge_name
91
+ const badgeColor = this.data.badge_color
92
+ if (badgeText) {
93
+ const $badge = Utils.createElement(`<span class="atk-badge"></span>`)
94
+ $badge.innerText = badgeText.replace('管理员', this.ctx.$t('admin')) // i18n patch
95
+ $badge.style.backgroundColor = badgeColor || ''
96
+ this.$headerBadgeWrap.append($badge)
97
+ }
98
+
99
+ if (this.data.is_pinned) {
100
+ const $pinnedBadge = Utils.createElement(`<span class="atk-pinned-badge">${this.ctx.$t('pin')}</span>`) // 置顶徽章
101
+ this.$headerBadgeWrap.append($pinnedBadge)
102
+ }
103
+
104
+ const $date = this.$el.querySelector<HTMLElement>('.atk-date')!
105
+ $date.innerText = this.comment.getDateFormatted()
106
+ $date.setAttribute('data-atk-comment-date', String(+new Date(this.data.date)))
107
+
108
+ if (this.ctx.conf.uaBadge) {
109
+ let $uaWrap = this.$header.querySelector('atk-ua-wrap')
110
+ if (!$uaWrap) {
111
+ $uaWrap = Utils.createElement(`<span class="atk-ua-wrap"></span>`)
112
+ this.$header.append($uaWrap)
113
+ }
114
+
115
+ $uaWrap.innerHTML = ''
116
+ const $uaBrowser = Utils.createElement(`<span class="atk-ua ua-browser"></span>`)
117
+ const $usOS = Utils.createElement(`<span class="atk-ua ua-os"></span>`)
118
+ $uaBrowser.innerText = this.comment.getUserUaBrowser()
119
+ $usOS.innerText = this.comment.getUserUaOS()
120
+ $uaWrap.append($uaBrowser)
121
+ $uaWrap.append($usOS)
122
+ }
123
+ }
124
+
125
+ /** 初始化 - 评论内容 */
126
+ private renderContent() {
127
+ if (!this.data.is_collapsed) {
128
+ this.$content.innerHTML = this.comment.getContentMarked()
129
+ this.$content.classList.remove('atk-hide', 'atk-collapsed')
130
+ return
131
+ }
132
+
133
+ // 内容 & 折叠
134
+ this.$content.classList.add('atk-hide', 'atk-type-collapsed')
135
+ const collapsedInfoEl = Utils.createElement(`
136
+ <div class="atk-collapsed">
137
+ <span class="atk-text">${this.ctx.$t('collapsedMsg')}</span>
138
+ <span class="atk-show-btn">${this.ctx.$t('expand')}</span>
139
+ </div>`)
140
+ this.$body.insertAdjacentElement('beforeend', collapsedInfoEl)
141
+
142
+ const contentShowBtn = collapsedInfoEl.querySelector('.atk-show-btn')!
143
+ contentShowBtn.addEventListener('click', (e) => {
144
+ e.stopPropagation() // 防止穿透
145
+
146
+ if (this.$content.classList.contains('atk-hide')) {
147
+ this.$content.innerHTML = this.comment.getContentMarked()
148
+ this.$content.classList.remove('atk-hide')
149
+ Ui.playFadeInAnim(this.$content)
150
+ contentShowBtn.innerHTML = this.ctx.$t('collapse')
151
+ } else {
152
+ this.$content.innerHTML = ''
153
+ this.$content.classList.add('atk-hide')
154
+ contentShowBtn.innerHTML = this.ctx.$t('expand')
155
+ }
156
+ })
157
+ }
158
+
159
+ /** 初始化 - 层级嵌套模式显示 At */
160
+ private renderReplyAt() {
161
+ if (this.cConf.isFlatMode || this.data.rid === 0) return // not 平铺模式 或 根评论
162
+ if (!this.cConf.replyTo) return
163
+
164
+ this.$replyAt = Utils.createElement(`<span class="atk-item atk-reply-at"><span class="atk-arrow"></span><span class="atk-nick"></span></span>`)
165
+ this.$replyAt.querySelector<HTMLElement>('.atk-nick')!.innerText = `${this.cConf.replyTo.nick}`
166
+ this.$replyAt.onclick = () => { this.comment.getActions().goToReplyComment() }
167
+
168
+ this.$headerBadgeWrap.insertAdjacentElement('afterend', this.$replyAt)
169
+ }
170
+
171
+ /** 初始化 - 回复的对象 */
172
+ private renderReplyTo() {
173
+ if (!this.cConf.isFlatMode) return // 仅平铺模式显示
174
+ if (!this.cConf.replyTo) return
175
+
176
+ this.$replyTo = Utils.createElement(`
177
+ <div class="atk-reply-to">
178
+ <div class="atk-meta">${this.ctx.$t('reply')} <span class="atk-nick"></span>:</div>
179
+ <div class="atk-content"></div>
180
+ </div>`)
181
+ const $nick = this.$replyTo.querySelector<HTMLElement>('.atk-nick')!
182
+ $nick.innerText = `@${this.cConf.replyTo.nick}`
183
+ $nick.onclick = () => { this.comment.getActions().goToReplyComment() }
184
+ let replyContent = Utils.marked(this.ctx, this.cConf.replyTo.content)
185
+ if (this.cConf.replyTo.is_collapsed) replyContent = `[${this.ctx.$t('collapsed')}]`
186
+ this.$replyTo.querySelector<HTMLElement>('.atk-content')!.innerHTML = replyContent
187
+ this.$body.prepend(this.$replyTo)
188
+ }
189
+
190
+ /** 初始化 - 待审核状态 */
191
+ private renderPending() {
192
+ if (!this.data.is_pending) return
193
+
194
+ const pendingEl = Utils.createElement(`<div class="atk-pending">${this.ctx.$t('pendingMsg')}</div>`)
195
+ this.$body.prepend(pendingEl)
196
+ }
197
+
198
+ /** 初始化 - 评论操作按钮 */
199
+ private renderActionBtn() {
200
+ // 投票功能
201
+ if (this.ctx.conf.vote) {
202
+ // 赞同按钮
203
+ this.voteBtnUp = new ActionBtn(this.ctx, () => `${this.ctx.$t('voteUp')} (${this.data.vote_up || 0})`).appendTo(this.$actions)
204
+ this.voteBtnUp.setClick(() => {
205
+ this.comment.getActions().vote('up')
206
+ })
207
+
208
+ // 反对按钮
209
+ if (this.ctx.conf.voteDown) {
210
+ this.voteBtnDown = new ActionBtn(this.ctx, () => `${this.ctx.$t('voteDown')} (${this.data.vote_down || 0})`).appendTo(this.$actions)
211
+ this.voteBtnDown.setClick(() => {
212
+ this.comment.getActions().vote('down')
213
+ })
214
+ }
215
+ }
216
+
217
+ // 绑定回复按钮事件
218
+ if (this.data.is_allow_reply) {
219
+ const replyBtn = Utils.createElement(`<span>${this.ctx.$t('reply')}</span>`)
220
+ this.$actions.append(replyBtn)
221
+ replyBtn.addEventListener('click', (e) => {
222
+ e.stopPropagation() // 防止穿透
223
+ if (!this.cConf.onReplyBtnClick) {
224
+ this.ctx.replyComment(this.data, this.$el)
225
+ } else {
226
+ this.cConf.onReplyBtnClick()
227
+ }
228
+ })
229
+ }
230
+
231
+ // 绑定折叠按钮事件
232
+ const collapseBtn = new ActionBtn(this.ctx, {
233
+ text: () => (this.data.is_collapsed ? this.ctx.$t('expand') : this.ctx.$t('collapse')),
234
+ adminOnly: true
235
+ })
236
+ collapseBtn.appendTo(this.$actions)
237
+ collapseBtn.setClick(() => {
238
+ this.comment.getActions().adminEdit('collapsed', collapseBtn)
239
+ })
240
+
241
+ // 绑定待审核按钮事件
242
+ const pendingBtn = new ActionBtn(this.ctx, {
243
+ text: () => (this.data.is_pending ? this.ctx.$t('pending') : this.ctx.$t('approved')),
244
+ adminOnly: true
245
+ })
246
+ pendingBtn.appendTo(this.$actions)
247
+ pendingBtn.setClick(() => {
248
+ this.comment.getActions().adminEdit('pending', pendingBtn)
249
+ })
250
+
251
+ // 绑定删除按钮事件
252
+ const delBtn = new ActionBtn(this.ctx, {
253
+ text: this.ctx.$t('delete'),
254
+ confirm: true,
255
+ confirmText: this.ctx.$t('deleteConfirm'),
256
+ adminOnly: true,
257
+ })
258
+ delBtn.appendTo(this.$actions)
259
+ delBtn.setClick(() => {
260
+ this.comment.getActions().adminDelete(delBtn)
261
+ })
262
+
263
+ // 绑定置顶按钮事件
264
+ const pinnedBtn = new ActionBtn(this.ctx, {
265
+ text: () => (this.data.is_pinned ? this.ctx.$t('unpin') : this.ctx.$t('pin')),
266
+ adminOnly: true
267
+ })
268
+ pinnedBtn.appendTo(this.$actions)
269
+ pinnedBtn.setClick(() => {
270
+ this.comment.getActions().adminEdit('pinned', pendingBtn)
271
+ })
272
+ }
273
+
274
+ /** 内容限高检测 */
275
+ public checkHeightLimit() {
276
+ this.checkHeightLimitArea('content') // 评论内容限高
277
+ this.checkHeightLimitArea('children') // 子评论部分限高(嵌套模式)
278
+ }
279
+
280
+ /** 目标内容限高检测 */
281
+ public checkHeightLimitArea(area: 'children'|'content') {
282
+ // 参数准备
283
+ const childrenMaxH = this.ctx.conf.heightLimit.children
284
+ const contentMaxH = this.ctx.conf.heightLimit.content
285
+
286
+ if (area === 'children' && !childrenMaxH) return
287
+ if (area === 'content' && !contentMaxH) return
288
+
289
+ // 限高
290
+ let maxHeight: number
291
+ if (area === 'children') maxHeight = childrenMaxH!
292
+ if (area === 'content') maxHeight = contentMaxH!
293
+
294
+ // 检测指定元素
295
+ const checkEl = ($el?: HTMLElement|null) => {
296
+ if (!$el) return
297
+
298
+ // 是否超过高度
299
+ if (Utils.getHeight($el) > maxHeight) {
300
+ this.heightLimitAdd($el, maxHeight)
301
+ }
302
+ }
303
+
304
+ // 执行限高检测
305
+ if (area === 'children') {
306
+ checkEl(this.$childrenWrap)
307
+ } else if (area === 'content') {
308
+ checkEl(this.$content)
309
+ checkEl(this.$replyTo)
310
+
311
+ // 若有图片 · 图片加载完后再检测一次
312
+ Utils.onImagesLoaded(this.$content, () => {
313
+ checkEl(this.$content)
314
+ })
315
+ if (this.$replyTo) {
316
+ Utils.onImagesLoaded(this.$replyTo, () => {
317
+ checkEl(this.$replyTo)
318
+ })
319
+ }
320
+ }
321
+ }
322
+
323
+ /** 移除限高 */
324
+ private heightLimitRemove($el: HTMLElement) {
325
+ if (!$el) return
326
+ if (!$el.classList.contains('atk-height-limit')) return
327
+
328
+ $el.classList.remove('atk-height-limit')
329
+ Array.from($el.children).forEach((e) => {
330
+ if (e.classList.contains('atk-height-limit-btn')) e.remove()
331
+ })
332
+ $el.style.height = ''
333
+ $el.style.overflow = ''
334
+ }
335
+
336
+ /** 子评论区域移除限高 */
337
+ public heightLimitRemoveForChildren() {
338
+ if (!this.$childrenWrap) return
339
+ this.heightLimitRemove(this.$childrenWrap)
340
+ }
341
+
342
+ /** 内容限高区域新增 */
343
+ private heightLimitAdd($el: HTMLElement, maxHeight: number) {
344
+ if (!$el) return
345
+ if ($el.classList.contains('atk-height-limit')) return
346
+
347
+ $el.classList.add('atk-height-limit')
348
+ $el.style.height = `${maxHeight}px`
349
+ $el.style.overflow = 'hidden'
350
+ const $hideMoreOpenBtn = Utils.createElement(`<div class="atk-height-limit-btn">${this.ctx.$t('readMore')}</span>`)
351
+ $hideMoreOpenBtn.onclick = (e) => {
352
+ e.stopPropagation()
353
+ this.heightLimitRemove($el)
354
+
355
+ // 子评论数等于 1,直接取消限高
356
+ const children = this.comment.getChildren()
357
+ if (children.length === 1) children[0].getRender().heightLimitRemove(children[0].getRender().$content)
358
+ }
359
+ $el.append($hideMoreOpenBtn)
360
+ }
361
+
362
+ /** 渐出动画 */
363
+ playFadeAnim() {
364
+ Ui.playFadeInAnim(this.comment.getRender().$el)
365
+ }
366
+
367
+ /** 渐出动画 · 评论内容区域 */
368
+ playFadeAnimForBody() {
369
+ Ui.playFadeInAnim(this.comment.getRender().$body)
370
+ }
371
+
372
+ /** 获取子评论 Wrap */
373
+ public getChildrenWrap() {
374
+ return this.$childrenWrap
375
+ }
376
+
377
+ /** 初始化子评论区域 Wrap */
378
+ public renderChildrenWrap() {
379
+ if (!this.$childrenWrap) {
380
+ this.$childrenWrap = Utils.createElement('<div class="atk-comment-children"></div>')
381
+ this.$main.append(this.$childrenWrap)
382
+ }
383
+
384
+ return this.$childrenWrap
385
+ }
386
+
387
+ /** 恢复原有的子评论区域 Wrap */
388
+ public recoveryChildrenWrap() {
389
+ if (this.$childrenWrap) {
390
+ this.$main.append(this.$childrenWrap)
391
+ }
392
+ }
393
+
394
+ /** 设置已读 */
395
+ public setUnread(val: boolean) {
396
+ if (val) this.$el.classList.add('atk-unread')
397
+ else this.$el.classList.remove('atk-unread')
398
+ }
399
+
400
+ /** 设置为可点击的评论 */
401
+ public setOpenable(val: boolean) {
402
+ if (val) this.$el.classList.add('atk-openable')
403
+ else this.$el.classList.remove('atk-openable')
404
+ }
405
+
406
+ /** 设置点击评论打开置顶 URL */
407
+ public setOpenURL(url: string) {
408
+ this.setOpenable(true)
409
+ this.$el.onclick = (evt) => {
410
+ evt.preventDefault()
411
+ window.open(url)
412
+
413
+ if (this.cConf.openEvt) this.cConf.openEvt()
414
+ }
415
+ }
416
+
417
+ /** 设置点击评论时的操作 */
418
+ public setOpenAction(action: () => void) {
419
+ this.setOpenable(true)
420
+ this.$el.onclick = (evt) => {
421
+ evt.preventDefault()
422
+ action()
423
+ }
424
+ }
425
+ }
@@ -1,3 +1,4 @@
1
+ import Context from '~/types/context'
1
2
  import * as Utils from '../lib/utils'
2
3
 
3
4
  interface ActionBtnConf {
@@ -18,6 +19,7 @@ interface ActionBtnConf {
18
19
  * 通用操作按钮
19
20
  */
20
21
  export default class ActionBtn {
22
+ private ctx: Context
21
23
  private conf: ActionBtnConf
22
24
  public $el: HTMLElement
23
25
 
@@ -31,7 +33,8 @@ export default class ActionBtn {
31
33
  public confirmRecTimer?: number // 确认消息复原定时器
32
34
 
33
35
  /** 构造函数 */
34
- constructor(conf: ActionBtnConf|string|(() => string)) {
36
+ constructor(ctx: Context, conf: ActionBtnConf|string|(() => string)) {
37
+ this.ctx = ctx
35
38
  this.$el = Utils.createElement(`<span class="atk-common-action-btn"></span>`)
36
39
 
37
40
  this.conf = (typeof conf !== 'object') ? ({ text: conf }) : conf
@@ -73,7 +76,7 @@ export default class ActionBtn {
73
76
  if (!this.isConfirming) {
74
77
  this.isConfirming = true
75
78
  this.$el.classList.add('atk-btn-confirm')
76
- this.$el.innerText = this.conf.confirmText || '确认操作'
79
+ this.$el.innerText = this.conf.confirmText || this.ctx.$t('actionConfirm')
77
80
  this.confirmRecTimer = window.setTimeout(() => confirmRestore(), 5000)
78
81
  return
79
82
  }
@@ -107,7 +110,7 @@ export default class ActionBtn {
107
110
  this.isLoading = value
108
111
  if (value) {
109
112
  this.$el.classList.add('atk-btn-loading')
110
- this.$el.innerText = loadingText || '加载中...'
113
+ this.$el.innerText = loadingText || `${this.ctx.$t('loading')}...`
111
114
  } else {
112
115
  this.$el.classList.remove('atk-btn-loading')
113
116
  this.$el.innerText = this.getText()
@@ -1,3 +1,4 @@
1
+ import Context from '~/types/context'
1
2
  import * as Utils from '../lib/utils'
2
3
 
3
4
  type BtnClickHandler = (btnEl: HTMLElement, dialog: Dialog) => boolean|void
@@ -6,11 +7,14 @@ type BtnClickHandler = (btnEl: HTMLElement, dialog: Dialog) => boolean|void
6
7
  * 对话框
7
8
  */
8
9
  export default class Dialog {
10
+ public ctx: Context
9
11
  public $el: HTMLElement
10
12
  public $content: HTMLElement
11
13
  public $actions: HTMLElement
12
14
 
13
- constructor (contentEl: HTMLElement) {
15
+ constructor (ctx: Context, contentEl: HTMLElement) {
16
+ this.ctx = ctx
17
+
14
18
  this.$el = Utils.createElement(
15
19
  `<div class="atk-layer-dialog-wrap">
16
20
  <div class="atk-layer-dialog">
@@ -33,7 +37,7 @@ export default class Dialog {
33
37
  /** 按钮 · 确定 */
34
38
  public setYes(handler: BtnClickHandler) {
35
39
  const btn = Utils.createElement<HTMLButtonElement>(
36
- '<button data-action="confirm">确定</button>'
40
+ `<button data-action="confirm">${this.ctx.$t('confirm')}</button>`
37
41
  )
38
42
  btn.onclick = this.onBtnClick(handler)
39
43
  this.$actions.appendChild(btn)
@@ -44,7 +48,7 @@ export default class Dialog {
44
48
  /** 按钮 · 取消 */
45
49
  public setNo(handler: BtnClickHandler) {
46
50
  const btn = Utils.createElement<HTMLButtonElement>(
47
- '<button data-action="cancel">取消</button>'
51
+ `<button data-action="cancel">${this.ctx.$t('cancel')}</button>`
48
52
  )
49
53
  btn.onclick = this.onBtnClick(handler)
50
54
  this.$actions.appendChild(btn)