artalk 2.4.3 → 2.5.0
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/README.md +27 -0
- package/dist/Artalk.css +1 -1
- package/dist/Artalk.es.js +4895 -4067
- package/dist/Artalk.iife.js +1 -1
- package/dist/Artalk.js +1 -1
- package/dist/ArtalkLite.css +1 -1
- package/dist/ArtalkLite.es.js +2496 -1779
- package/dist/ArtalkLite.iife.js +1 -1
- package/dist/ArtalkLite.js +1 -1
- package/dist/i18n/jp.js +1 -1
- package/dist/i18n/zh-TW.js +1 -1
- package/dist/src/api/index.d.ts +2 -2
- package/dist/src/api/system.d.ts +5 -2
- package/dist/src/artalk.d.ts +42 -34
- package/dist/src/comment/comment.d.ts +2 -2
- package/dist/src/comment/height-limit.d.ts +30 -0
- package/dist/src/comment/render-ctx.d.ts +23 -0
- package/dist/src/comment/render.d.ts +2 -50
- package/dist/src/comment/renders/actions.d.ts +5 -0
- package/dist/src/comment/renders/avatar.d.ts +5 -0
- package/dist/src/comment/renders/content.d.ts +5 -0
- package/dist/src/comment/renders/header.d.ts +5 -0
- package/dist/src/comment/renders/index.d.ts +2 -0
- package/dist/src/comment/renders/pending.d.ts +5 -0
- package/dist/src/comment/renders/reply-at.d.ts +5 -0
- package/dist/src/comment/renders/reply-to.d.ts +5 -0
- package/dist/src/components/dialog.d.ts +1 -1
- package/dist/src/config.d.ts +8 -0
- package/dist/src/context.d.ts +15 -29
- package/dist/src/editor/edit-mode.d.ts +9 -0
- package/dist/src/editor/editor-ui.d.ts +25 -0
- package/dist/src/editor/editor.d.ts +37 -45
- package/dist/src/editor/funcs.d.ts +2 -0
- package/dist/src/editor/mover.d.ts +8 -0
- package/dist/src/editor/plug-manager.d.ts +17 -0
- package/dist/src/editor/plugs/editor-plug.d.ts +13 -1
- package/dist/src/editor/reply.d.ts +9 -0
- package/dist/src/editor/submit.d.ts +16 -0
- package/dist/src/editor/ui.d.ts +25 -0
- package/dist/src/i18n/index.d.ts +11 -7
- package/dist/src/layer/index.d.ts +2 -2
- package/dist/src/layer/layer.d.ts +1 -1
- package/dist/src/lib/checker/captcha-renders.d.ts +5 -0
- package/dist/src/lib/checker/index.d.ts +18 -10
- package/dist/src/lib/dark-mode.d.ts +4 -0
- package/dist/src/lib/marked.d.ts +9 -0
- package/dist/src/lib/utils.d.ts +1 -5
- package/dist/src/list/list-lite.d.ts +15 -11
- package/dist/src/list/list-nest.d.ts +2 -2
- package/dist/src/list/list-ui.d.ts +7 -0
- package/dist/src/list/paginator/adaptors/index.d.ts +12 -0
- package/dist/src/list/paginator/adaptors/pagination.d.ts +9 -0
- package/dist/src/list/paginator/adaptors/read-more.d.ts +10 -0
- package/dist/src/list/paginator/holder.d.ts +16 -0
- package/dist/src/list/paginator/index.d.ts +10 -0
- package/dist/src/service.d.ts +40 -0
- package/dist/types/artalk-config.d.ts +5 -2
- package/dist/types/artalk-data.d.ts +3 -3
- package/dist/types/context.d.ts +11 -17
- package/dist/types/global.d.ts +1 -0
- package/package.json +6 -7
- package/src/api/system.ts +3 -12
- package/src/artalk.ts +101 -158
- package/src/comment/comment.ts +7 -6
- package/src/comment/height-limit.ts +100 -0
- package/src/comment/render-ctx.ts +30 -0
- package/src/comment/render.ts +31 -375
- package/src/comment/renders/actions.ts +114 -0
- package/src/comment/renders/avatar.ts +22 -0
- package/src/comment/renders/content.ts +39 -0
- package/src/comment/renders/header.ts +84 -0
- package/src/comment/renders/index.ts +19 -0
- package/src/comment/renders/pending.ts +12 -0
- package/src/comment/renders/reply-at.ts +16 -0
- package/src/comment/renders/reply-to.ts +24 -0
- package/src/config.ts +65 -0
- package/src/context.ts +31 -88
- package/src/defaults.ts +1 -0
- package/src/editor/edit-mode.ts +102 -0
- package/src/editor/editor.ts +100 -394
- package/src/editor/funcs.ts +96 -0
- package/src/editor/mover.ts +43 -0
- package/src/editor/plug-manager.ts +172 -0
- package/src/editor/plugs/editor-plug.ts +20 -2
- package/src/editor/plugs/{emoticons-plug.less → emoticons-plug.scss} +5 -4
- package/src/editor/plugs/emoticons-plug.ts +2 -1
- package/src/editor/plugs/header-input-plug.ts +5 -5
- package/src/editor/plugs/{preview-plug.less → preview-plug.scss} +1 -1
- package/src/editor/plugs/preview-plug.ts +8 -4
- package/src/editor/plugs/upload-plug.ts +10 -13
- package/src/editor/reply.ts +62 -0
- package/src/editor/submit.ts +98 -0
- package/src/editor/ui.ts +36 -0
- package/src/i18n/index.ts +21 -10
- package/src/layer/index.ts +2 -2
- package/src/layer/layer.ts +4 -4
- package/src/layer/sidebar-layer.ts +2 -1
- package/src/lib/checker/admin.ts +36 -0
- package/src/lib/checker/captcha-renders.ts +68 -0
- package/src/lib/checker/captcha.ts +26 -0
- package/src/lib/checker/index.ts +38 -20
- package/src/lib/dark-mode.ts +48 -0
- package/src/lib/marked.ts +133 -0
- package/src/lib/utils.ts +1 -123
- package/src/list/list-lite.ts +59 -162
- package/src/list/list-ui.ts +80 -0
- package/src/list/list.ts +16 -8
- package/src/list/paginator/adaptors/index.ts +21 -0
- package/src/list/paginator/adaptors/pagination.ts +42 -0
- package/src/list/paginator/adaptors/read-more.ts +56 -0
- package/src/list/paginator/holder.ts +55 -0
- package/src/list/paginator/index.ts +13 -0
- package/src/service.ts +118 -0
- package/src/style/{_color.less → _color.scss} +3 -3
- package/src/style/{_extend.less → _extend.scss} +1 -1
- package/src/style/_variables.scss +3 -0
- package/src/style/{comment.less → comment.scss} +36 -12
- package/src/style/{editor.less → editor.scss} +11 -11
- package/src/style/{list.less → list.scss} +8 -6
- package/src/style/{main.less → main.scss} +24 -23
- package/src/style/{sidebar-layer.less → sidebar-layer.scss} +8 -8
- package/types/artalk-config.d.ts +5 -2
- package/types/artalk-data.d.ts +3 -3
- package/types/context.d.ts +11 -17
- package/types/global.d.ts +1 -0
- package/CNAME +0 -1
- package/LICENSE +0 -165
- package/index.html +0 -157
- package/postcss.config.js +0 -5
- package/scripts/build-i18n.mjs +0 -46
- package/src/lib/checker/admin-checker.ts +0 -36
- package/src/lib/checker/captcha-checker.ts +0 -84
- package/src/style/_variables.less +0 -3
- package/test/ArtalkCount.html +0 -16
- package/test/ArtalkI18n.html +0 -16
- package/test/ArtalkLite.html +0 -15
- package/test/CSRF.html +0 -26
- package/tsconfig.json +0 -12
- package/vite-base.config.ts +0 -21
- package/vite-demo.config.ts +0 -12
- package/vite-lite.config.ts +0 -22
- package/vite.config.ts +0 -27
- /package/dist/src/lib/checker/{admin-checker.d.ts → admin.d.ts} +0 -0
- /package/dist/src/lib/checker/{captcha-checker.d.ts → captcha.d.ts} +0 -0
- /package/src/style/{pagination.less → pagination.scss} +0 -0
package/src/comment/render.ts
CHANGED
|
@@ -1,36 +1,19 @@
|
|
|
1
1
|
import * as Utils from '../lib/utils'
|
|
2
2
|
import * as Ui from '../lib/ui'
|
|
3
|
-
|
|
3
|
+
|
|
4
4
|
import CommentHTML from './comment.html?raw'
|
|
5
5
|
import Comment from './comment'
|
|
6
|
+
import RenderCtx from './render-ctx'
|
|
7
|
+
import loadRenders from './renders'
|
|
8
|
+
import * as HeightLimit from './height-limit'
|
|
6
9
|
|
|
7
|
-
export default class CommentRender {
|
|
8
|
-
private comment: Comment
|
|
9
|
-
|
|
10
|
-
private get ctx() { return this.comment.ctx }
|
|
11
|
-
private get data() { return this.comment.getData() }
|
|
12
|
-
private get cConf() { return this.comment.getConf() }
|
|
13
|
-
|
|
14
|
-
public $el!: HTMLElement
|
|
15
|
-
public $main!: HTMLElement
|
|
16
|
-
public $header!: HTMLElement
|
|
17
|
-
public $headerNick!: HTMLElement
|
|
18
|
-
public $headerBadgeWrap!: HTMLElement
|
|
19
|
-
public $body!: HTMLElement
|
|
20
|
-
public $content!: HTMLElement
|
|
21
|
-
private $childrenWrap!: HTMLElement|null
|
|
22
|
-
public $actions!: HTMLElement
|
|
23
|
-
public voteBtnUp?: ActionBtn
|
|
24
|
-
public voteBtnDown?: ActionBtn
|
|
25
|
-
|
|
26
|
-
public $replyTo?: HTMLElement // 回复评论内容 (平铺下显示)
|
|
27
|
-
public $replyAt?: HTMLElement // 回复 AT(层级嵌套下显示)
|
|
28
|
-
|
|
10
|
+
export default class CommentRender extends RenderCtx {
|
|
29
11
|
public constructor(comment: Comment) {
|
|
30
|
-
|
|
12
|
+
super(comment)
|
|
31
13
|
}
|
|
32
14
|
|
|
33
15
|
public render() {
|
|
16
|
+
// init ui elements
|
|
34
17
|
this.$el = Utils.createElement(CommentHTML)
|
|
35
18
|
|
|
36
19
|
this.$main = this.$el.querySelector('.atk-main')!
|
|
@@ -39,371 +22,44 @@ export default class CommentRender {
|
|
|
39
22
|
this.$content = this.$body.querySelector('.atk-content')!
|
|
40
23
|
this.$actions = this.$el.querySelector('.atk-actions')!
|
|
41
24
|
|
|
42
|
-
this.$el.setAttribute('
|
|
25
|
+
this.$el.setAttribute('id', `atk-comment-${this.data.id}`)
|
|
43
26
|
|
|
44
|
-
|
|
45
|
-
this
|
|
46
|
-
this.renderContent()
|
|
47
|
-
this.renderReplyAt()
|
|
48
|
-
this.renderReplyTo()
|
|
49
|
-
this.renderPending()
|
|
50
|
-
this.renderActions()
|
|
27
|
+
// call all renders
|
|
28
|
+
loadRenders(this)
|
|
51
29
|
|
|
52
30
|
this.recoveryChildrenWrap()
|
|
53
31
|
|
|
54
32
|
return this.$el
|
|
55
33
|
}
|
|
56
34
|
|
|
57
|
-
/** 初始化 - 评论头像 */
|
|
58
|
-
private renderAvatar() {
|
|
59
|
-
const $avatar = this.$el.querySelector<HTMLElement>('.atk-avatar')!
|
|
60
|
-
const $avatarImg = Utils.createElement<HTMLImageElement>('<img />')
|
|
61
|
-
const avatarURLBuilder = this.ctx.conf.avatarURLBuilder
|
|
62
|
-
$avatarImg.src = avatarURLBuilder ? avatarURLBuilder(this.data) : this.comment.getGravatarURL()
|
|
63
|
-
if (this.data.link) {
|
|
64
|
-
const $avatarA = Utils.createElement<HTMLLinkElement>('<a target="_blank" rel="noreferrer noopener nofollow"></a>')
|
|
65
|
-
$avatarA.href = Utils.isValidURL(this.data.link) ? this.data.link : `https://${this.data.link}`
|
|
66
|
-
$avatarA.append($avatarImg)
|
|
67
|
-
$avatar.append($avatarA)
|
|
68
|
-
} else {
|
|
69
|
-
$avatar.append($avatarImg)
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/** 初始化 - 评论信息 */
|
|
74
|
-
private renderHeader() {
|
|
75
|
-
this.renderHeader_Nick()
|
|
76
|
-
this.renderHeader_VerifyBadge()
|
|
77
|
-
this.renderHeader_Date()
|
|
78
|
-
this.renderHeader_UABadge()
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
private renderHeader_Nick() {
|
|
82
|
-
this.$headerNick = this.$el.querySelector<HTMLElement>('.atk-nick')!
|
|
83
|
-
|
|
84
|
-
if (this.data.link) {
|
|
85
|
-
const $nickA = Utils.createElement<HTMLLinkElement>('<a target="_blank" rel="noreferrer noopener nofollow"></a>')
|
|
86
|
-
$nickA.innerText = this.data.nick
|
|
87
|
-
$nickA.href = Utils.isValidURL(this.data.link) ? this.data.link : `https://${this.data.link}`
|
|
88
|
-
this.$headerNick.append($nickA)
|
|
89
|
-
} else {
|
|
90
|
-
this.$headerNick.innerText = this.data.nick
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
private renderHeader_VerifyBadge() {
|
|
95
|
-
this.$headerBadgeWrap = this.$el.querySelector<HTMLElement>('.atk-badge-wrap')!
|
|
96
|
-
this.$headerBadgeWrap.innerHTML = ''
|
|
97
|
-
|
|
98
|
-
const badgeText = this.data.badge_name
|
|
99
|
-
const badgeColor = this.data.badge_color
|
|
100
|
-
if (badgeText) {
|
|
101
|
-
const $badge = Utils.createElement(`<span class="atk-badge"></span>`)
|
|
102
|
-
$badge.innerText = badgeText.replace('管理员', this.ctx.$t('admin')) // i18n patch
|
|
103
|
-
$badge.style.backgroundColor = badgeColor || ''
|
|
104
|
-
this.$headerBadgeWrap.append($badge)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (this.data.is_pinned) {
|
|
108
|
-
const $pinnedBadge = Utils.createElement(`<span class="atk-pinned-badge">${this.ctx.$t('pin')}</span>`) // 置顶徽章
|
|
109
|
-
this.$headerBadgeWrap.append($pinnedBadge)
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
private renderHeader_Date() {
|
|
114
|
-
const $date = this.$el.querySelector<HTMLElement>('.atk-date')!
|
|
115
|
-
$date.innerText = this.comment.getDateFormatted()
|
|
116
|
-
$date.setAttribute('data-atk-comment-date', String(+new Date(this.data.date)))
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
private renderHeader_UABadge() {
|
|
120
|
-
if (this.ctx.conf.uaBadge) {
|
|
121
|
-
let $uaWrap = this.$header.querySelector('atk-ua-wrap')
|
|
122
|
-
if (!$uaWrap) {
|
|
123
|
-
$uaWrap = Utils.createElement(`<span class="atk-ua-wrap"></span>`)
|
|
124
|
-
this.$header.append($uaWrap)
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
$uaWrap.innerHTML = ''
|
|
128
|
-
const $uaBrowser = Utils.createElement(`<span class="atk-ua ua-browser"></span>`)
|
|
129
|
-
const $usOS = Utils.createElement(`<span class="atk-ua ua-os"></span>`)
|
|
130
|
-
const uaInfo = this.comment.getUserUA()
|
|
131
|
-
$uaBrowser.innerText = uaInfo.browser
|
|
132
|
-
$usOS.innerText = uaInfo.os
|
|
133
|
-
$uaWrap.append($uaBrowser)
|
|
134
|
-
$uaWrap.append($usOS)
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/** 初始化 - 评论内容 */
|
|
139
|
-
private renderContent() {
|
|
140
|
-
if (!this.data.is_collapsed) {
|
|
141
|
-
this.$content.innerHTML = this.comment.getContentMarked()
|
|
142
|
-
this.$content.classList.remove('atk-hide', 'atk-collapsed')
|
|
143
|
-
return
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// 内容 & 折叠
|
|
147
|
-
this.$content.classList.add('atk-hide', 'atk-type-collapsed')
|
|
148
|
-
const collapsedInfoEl = Utils.createElement(`
|
|
149
|
-
<div class="atk-collapsed">
|
|
150
|
-
<span class="atk-text">${this.ctx.$t('collapsedMsg')}</span>
|
|
151
|
-
<span class="atk-show-btn">${this.ctx.$t('expand')}</span>
|
|
152
|
-
</div>`)
|
|
153
|
-
this.$body.insertAdjacentElement('beforeend', collapsedInfoEl)
|
|
154
|
-
|
|
155
|
-
const contentShowBtn = collapsedInfoEl.querySelector('.atk-show-btn')!
|
|
156
|
-
contentShowBtn.addEventListener('click', (e) => {
|
|
157
|
-
e.stopPropagation() // 防止穿透
|
|
158
|
-
|
|
159
|
-
if (this.$content.classList.contains('atk-hide')) {
|
|
160
|
-
this.$content.innerHTML = this.comment.getContentMarked()
|
|
161
|
-
this.$content.classList.remove('atk-hide')
|
|
162
|
-
Ui.playFadeInAnim(this.$content)
|
|
163
|
-
contentShowBtn.innerHTML = this.ctx.$t('collapse')
|
|
164
|
-
} else {
|
|
165
|
-
this.$content.innerHTML = ''
|
|
166
|
-
this.$content.classList.add('atk-hide')
|
|
167
|
-
contentShowBtn.innerHTML = this.ctx.$t('expand')
|
|
168
|
-
}
|
|
169
|
-
})
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/** 初始化 - 层级嵌套模式显示 At */
|
|
173
|
-
private renderReplyAt() {
|
|
174
|
-
if (this.cConf.isFlatMode || this.data.rid === 0) return // not 平铺模式 或 根评论
|
|
175
|
-
if (!this.cConf.replyTo) return
|
|
176
|
-
|
|
177
|
-
this.$replyAt = Utils.createElement(`<span class="atk-item atk-reply-at"><span class="atk-arrow"></span><span class="atk-nick"></span></span>`)
|
|
178
|
-
this.$replyAt.querySelector<HTMLElement>('.atk-nick')!.innerText = `${this.cConf.replyTo.nick}`
|
|
179
|
-
this.$replyAt.onclick = () => { this.comment.getActions().goToReplyComment() }
|
|
180
|
-
|
|
181
|
-
this.$headerBadgeWrap.insertAdjacentElement('afterend', this.$replyAt)
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/** 初始化 - 回复的对象 */
|
|
185
|
-
private renderReplyTo() {
|
|
186
|
-
if (!this.cConf.isFlatMode) return // 仅平铺模式显示
|
|
187
|
-
if (!this.cConf.replyTo) return
|
|
188
|
-
|
|
189
|
-
this.$replyTo = Utils.createElement(`
|
|
190
|
-
<div class="atk-reply-to">
|
|
191
|
-
<div class="atk-meta">${this.ctx.$t('reply')} <span class="atk-nick"></span>:</div>
|
|
192
|
-
<div class="atk-content"></div>
|
|
193
|
-
</div>`)
|
|
194
|
-
const $nick = this.$replyTo.querySelector<HTMLElement>('.atk-nick')!
|
|
195
|
-
$nick.innerText = `@${this.cConf.replyTo.nick}`
|
|
196
|
-
$nick.onclick = () => { this.comment.getActions().goToReplyComment() }
|
|
197
|
-
let replyContent = Utils.marked(this.ctx, this.cConf.replyTo.content)
|
|
198
|
-
if (this.cConf.replyTo.is_collapsed) replyContent = `[${this.ctx.$t('collapsed')}]`
|
|
199
|
-
this.$replyTo.querySelector<HTMLElement>('.atk-content')!.innerHTML = replyContent
|
|
200
|
-
this.$body.prepend(this.$replyTo)
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/** 初始化 - 待审核状态 */
|
|
204
|
-
private renderPending() {
|
|
205
|
-
if (!this.data.is_pending) return
|
|
206
|
-
|
|
207
|
-
const pendingEl = Utils.createElement(`<div class="atk-pending">${this.ctx.$t('pendingMsg')}</div>`)
|
|
208
|
-
this.$body.prepend(pendingEl)
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/** 初始化 - 评论操作按钮 */
|
|
212
|
-
private renderActions() {
|
|
213
|
-
this.renderActions_Vote()
|
|
214
|
-
this.renderActions_Reply()
|
|
215
|
-
|
|
216
|
-
// 管理员操作
|
|
217
|
-
this.renderActions_Collapse()
|
|
218
|
-
this.renderActions_Moderator()
|
|
219
|
-
this.renderActions_Pin()
|
|
220
|
-
this.renderActions_Edit()
|
|
221
|
-
this.renderActions_Del()
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// 操作按钮 - 投票
|
|
225
|
-
private renderActions_Vote() {
|
|
226
|
-
if (!this.ctx.conf.vote) return // 关闭投票功能
|
|
227
|
-
|
|
228
|
-
// 赞同按钮
|
|
229
|
-
this.voteBtnUp = new ActionBtn(this.ctx, () => `${this.ctx.$t('voteUp')} (${this.data.vote_up || 0})`).appendTo(this.$actions)
|
|
230
|
-
this.voteBtnUp.setClick(() => {
|
|
231
|
-
this.comment.getActions().vote('up')
|
|
232
|
-
})
|
|
233
|
-
|
|
234
|
-
// 反对按钮
|
|
235
|
-
if (this.ctx.conf.voteDown) {
|
|
236
|
-
this.voteBtnDown = new ActionBtn(this.ctx, () => `${this.ctx.$t('voteDown')} (${this.data.vote_down || 0})`).appendTo(this.$actions)
|
|
237
|
-
this.voteBtnDown.setClick(() => {
|
|
238
|
-
this.comment.getActions().vote('down')
|
|
239
|
-
})
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// 操作按钮 - 回复
|
|
244
|
-
private renderActions_Reply() {
|
|
245
|
-
if (!this.data.is_allow_reply) return // 不允许回复
|
|
246
|
-
|
|
247
|
-
const replyBtn = Utils.createElement(`<span>${this.ctx.$t('reply')}</span>`)
|
|
248
|
-
this.$actions.append(replyBtn)
|
|
249
|
-
replyBtn.addEventListener('click', (e) => {
|
|
250
|
-
e.stopPropagation() // 防止穿透
|
|
251
|
-
if (!this.cConf.onReplyBtnClick) {
|
|
252
|
-
this.ctx.replyComment(this.data, this.$el)
|
|
253
|
-
} else {
|
|
254
|
-
this.cConf.onReplyBtnClick()
|
|
255
|
-
}
|
|
256
|
-
})
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// 操作按钮 - 折叠
|
|
260
|
-
private renderActions_Collapse() {
|
|
261
|
-
const collapseBtn = new ActionBtn(this.ctx, {
|
|
262
|
-
text: () => (this.data.is_collapsed ? this.ctx.$t('expand') : this.ctx.$t('collapse')),
|
|
263
|
-
adminOnly: true
|
|
264
|
-
})
|
|
265
|
-
collapseBtn.appendTo(this.$actions)
|
|
266
|
-
collapseBtn.setClick(() => {
|
|
267
|
-
this.comment.getActions().adminEdit('collapsed', collapseBtn)
|
|
268
|
-
})
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// 操作按钮 - 审核
|
|
272
|
-
private renderActions_Moderator() {
|
|
273
|
-
const pendingBtn = new ActionBtn(this.ctx, {
|
|
274
|
-
text: () => (this.data.is_pending ? this.ctx.$t('pending') : this.ctx.$t('approved')),
|
|
275
|
-
adminOnly: true
|
|
276
|
-
})
|
|
277
|
-
pendingBtn.appendTo(this.$actions)
|
|
278
|
-
pendingBtn.setClick(() => {
|
|
279
|
-
this.comment.getActions().adminEdit('pending', pendingBtn)
|
|
280
|
-
})
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// 操作按钮 - 置顶
|
|
284
|
-
private renderActions_Pin() {
|
|
285
|
-
const pinnedBtn = new ActionBtn(this.ctx, {
|
|
286
|
-
text: () => (this.data.is_pinned ? this.ctx.$t('unpin') : this.ctx.$t('pin')),
|
|
287
|
-
adminOnly: true
|
|
288
|
-
})
|
|
289
|
-
pinnedBtn.appendTo(this.$actions)
|
|
290
|
-
pinnedBtn.setClick(() => {
|
|
291
|
-
this.comment.getActions().adminEdit('pinned', pinnedBtn)
|
|
292
|
-
})
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
// 操作按钮 - 编辑
|
|
296
|
-
private renderActions_Edit() {
|
|
297
|
-
const editBtn = new ActionBtn(this.ctx, {
|
|
298
|
-
text: this.ctx.$t('edit'),
|
|
299
|
-
adminOnly: true
|
|
300
|
-
})
|
|
301
|
-
editBtn.appendTo(this.$actions)
|
|
302
|
-
editBtn.setClick(() => {
|
|
303
|
-
this.ctx.editComment(this.data, this.$el)
|
|
304
|
-
})
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// 操作按钮 - 删除
|
|
308
|
-
private renderActions_Del() {
|
|
309
|
-
const delBtn = new ActionBtn(this.ctx, {
|
|
310
|
-
text: this.ctx.$t('delete'),
|
|
311
|
-
confirm: true,
|
|
312
|
-
confirmText: this.ctx.$t('deleteConfirm'),
|
|
313
|
-
adminOnly: true,
|
|
314
|
-
})
|
|
315
|
-
delBtn.appendTo(this.$actions)
|
|
316
|
-
delBtn.setClick(() => {
|
|
317
|
-
this.comment.getActions().adminDelete(delBtn)
|
|
318
|
-
})
|
|
319
|
-
}
|
|
320
|
-
|
|
321
35
|
/** 内容限高检测 */
|
|
322
36
|
public checkHeightLimit() {
|
|
323
|
-
this.
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
if (!$el) return
|
|
344
|
-
|
|
345
|
-
// 是否超过高度
|
|
346
|
-
if (Utils.getHeight($el) > maxHeight) {
|
|
347
|
-
this.heightLimitAdd($el, maxHeight)
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
// 执行限高检测
|
|
352
|
-
if (area === 'children') {
|
|
353
|
-
checkEl(this.$childrenWrap)
|
|
354
|
-
} else if (area === 'content') {
|
|
355
|
-
checkEl(this.$content)
|
|
356
|
-
checkEl(this.$replyTo)
|
|
357
|
-
|
|
358
|
-
// 若有图片 · 图片加载完后再检测一次
|
|
359
|
-
Utils.onImagesLoaded(this.$content, () => {
|
|
360
|
-
checkEl(this.$content)
|
|
361
|
-
})
|
|
362
|
-
if (this.$replyTo) {
|
|
363
|
-
Utils.onImagesLoaded(this.$replyTo, () => {
|
|
364
|
-
checkEl(this.$replyTo)
|
|
365
|
-
})
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
/** 移除限高 */
|
|
371
|
-
private heightLimitRemove($el: HTMLElement) {
|
|
372
|
-
if (!$el) return
|
|
373
|
-
if (!$el.classList.contains('atk-height-limit')) return
|
|
374
|
-
|
|
375
|
-
$el.classList.remove('atk-height-limit')
|
|
376
|
-
Array.from($el.children).forEach((e) => {
|
|
377
|
-
if (e.classList.contains('atk-height-limit-btn')) e.remove()
|
|
378
|
-
})
|
|
379
|
-
$el.style.height = ''
|
|
380
|
-
$el.style.overflow = ''
|
|
37
|
+
const conf = this.ctx.conf.heightLimit
|
|
38
|
+
if (!conf || !conf.content || !conf.children) return // 关闭限高
|
|
39
|
+
|
|
40
|
+
const contentMaxH = conf.content
|
|
41
|
+
const childrenMaxH = conf.children
|
|
42
|
+
|
|
43
|
+
HeightLimit.check({
|
|
44
|
+
postExpandBtnClick: () => {
|
|
45
|
+
// 子评论数仅有 1,直接取消限高
|
|
46
|
+
const children = this.comment.getChildren()
|
|
47
|
+
if (children.length === 1) HeightLimit.disposeHeightLimit(children[0].getRender().$content)
|
|
48
|
+
},
|
|
49
|
+
scrollable: conf.scrollable
|
|
50
|
+
}, [
|
|
51
|
+
// 评论内容限高
|
|
52
|
+
{ el: this.$content, max: contentMaxH, imgContains: true },
|
|
53
|
+
{ el: this.$replyTo, max: contentMaxH, imgContains: true },
|
|
54
|
+
// 子评论区域限高(仅嵌套模式)
|
|
55
|
+
{ el: this.$childrenWrap, max: childrenMaxH, imgContains: false }
|
|
56
|
+
])
|
|
381
57
|
}
|
|
382
58
|
|
|
383
59
|
/** 子评论区域移除限高 */
|
|
384
60
|
public heightLimitRemoveForChildren() {
|
|
385
61
|
if (!this.$childrenWrap) return
|
|
386
|
-
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
/** 内容限高区域新增 */
|
|
390
|
-
private heightLimitAdd($el: HTMLElement, maxHeight: number) {
|
|
391
|
-
if (!$el) return
|
|
392
|
-
if ($el.classList.contains('atk-height-limit')) return
|
|
393
|
-
|
|
394
|
-
$el.classList.add('atk-height-limit')
|
|
395
|
-
$el.style.height = `${maxHeight}px`
|
|
396
|
-
$el.style.overflow = 'hidden'
|
|
397
|
-
const $hideMoreOpenBtn = Utils.createElement(`<div class="atk-height-limit-btn">${this.ctx.$t('readMore')}</span>`)
|
|
398
|
-
$hideMoreOpenBtn.onclick = (e) => {
|
|
399
|
-
e.stopPropagation()
|
|
400
|
-
this.heightLimitRemove($el)
|
|
401
|
-
|
|
402
|
-
// 子评论数等于 1,直接取消限高
|
|
403
|
-
const children = this.comment.getChildren()
|
|
404
|
-
if (children.length === 1) children[0].getRender().heightLimitRemove(children[0].getRender().$content)
|
|
405
|
-
}
|
|
406
|
-
$el.append($hideMoreOpenBtn)
|
|
62
|
+
HeightLimit.disposeHeightLimit(this.$childrenWrap)
|
|
407
63
|
}
|
|
408
64
|
|
|
409
65
|
/** 渐出动画 */
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import * as Utils from '../../lib/utils'
|
|
2
|
+
import ActionBtn from '../../components/action-btn'
|
|
3
|
+
import RenderCtx from '../render-ctx'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 评论操作按钮界面
|
|
7
|
+
*/
|
|
8
|
+
export default function renderActions(ctx: RenderCtx) {
|
|
9
|
+
Object.entries({
|
|
10
|
+
renderVote, renderReply,
|
|
11
|
+
// 管理员操作
|
|
12
|
+
renderCollapse, renderModerator, renderPin, renderEdit, renderDel
|
|
13
|
+
}).forEach(([name, render]) => {
|
|
14
|
+
render(ctx)
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
// 操作按钮 - 投票
|
|
20
|
+
function renderVote(ctx: RenderCtx) {
|
|
21
|
+
if (!ctx.ctx.conf.vote) return // 关闭投票功能
|
|
22
|
+
|
|
23
|
+
// 赞同按钮
|
|
24
|
+
ctx.voteBtnUp = new ActionBtn(ctx.ctx, () => `${ctx.ctx.$t('voteUp')} (${ctx.data.vote_up || 0})`).appendTo(ctx.$actions)
|
|
25
|
+
ctx.voteBtnUp.setClick(() => {
|
|
26
|
+
ctx.comment.getActions().vote('up')
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
// 反对按钮
|
|
30
|
+
if (ctx.ctx.conf.voteDown) {
|
|
31
|
+
ctx.voteBtnDown = new ActionBtn(ctx.ctx, () => `${ctx.ctx.$t('voteDown')} (${ctx.data.vote_down || 0})`).appendTo(ctx.$actions)
|
|
32
|
+
ctx.voteBtnDown.setClick(() => {
|
|
33
|
+
ctx.comment.getActions().vote('down')
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 操作按钮 - 回复
|
|
39
|
+
function renderReply(ctx: RenderCtx) {
|
|
40
|
+
if (!ctx.data.is_allow_reply) return // 不允许回复
|
|
41
|
+
|
|
42
|
+
const replyBtn = Utils.createElement(`<span>${ctx.ctx.$t('reply')}</span>`)
|
|
43
|
+
ctx.$actions.append(replyBtn)
|
|
44
|
+
replyBtn.addEventListener('click', (e) => {
|
|
45
|
+
e.stopPropagation() // 防止穿透
|
|
46
|
+
if (!ctx.cConf.onReplyBtnClick) {
|
|
47
|
+
ctx.ctx.replyComment(ctx.data, ctx.$el)
|
|
48
|
+
} else {
|
|
49
|
+
ctx.cConf.onReplyBtnClick()
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 操作按钮 - 折叠
|
|
55
|
+
function renderCollapse(ctx: RenderCtx) {
|
|
56
|
+
const collapseBtn = new ActionBtn(ctx.ctx, {
|
|
57
|
+
text: () => (ctx.data.is_collapsed ? ctx.ctx.$t('expand') : ctx.ctx.$t('collapse')),
|
|
58
|
+
adminOnly: true
|
|
59
|
+
})
|
|
60
|
+
collapseBtn.appendTo(ctx.$actions)
|
|
61
|
+
collapseBtn.setClick(() => {
|
|
62
|
+
ctx.comment.getActions().adminEdit('collapsed', collapseBtn)
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 操作按钮 - 审核
|
|
67
|
+
function renderModerator(ctx: RenderCtx) {
|
|
68
|
+
const pendingBtn = new ActionBtn(ctx.ctx, {
|
|
69
|
+
text: () => (ctx.data.is_pending ? ctx.ctx.$t('pending') : ctx.ctx.$t('approved')),
|
|
70
|
+
adminOnly: true
|
|
71
|
+
})
|
|
72
|
+
pendingBtn.appendTo(ctx.$actions)
|
|
73
|
+
pendingBtn.setClick(() => {
|
|
74
|
+
ctx.comment.getActions().adminEdit('pending', pendingBtn)
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// 操作按钮 - 置顶
|
|
79
|
+
function renderPin(ctx: RenderCtx) {
|
|
80
|
+
const pinnedBtn = new ActionBtn(ctx.ctx, {
|
|
81
|
+
text: () => (ctx.data.is_pinned ? ctx.ctx.$t('unpin') : ctx.ctx.$t('pin')),
|
|
82
|
+
adminOnly: true
|
|
83
|
+
})
|
|
84
|
+
pinnedBtn.appendTo(ctx.$actions)
|
|
85
|
+
pinnedBtn.setClick(() => {
|
|
86
|
+
ctx.comment.getActions().adminEdit('pinned', pinnedBtn)
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 操作按钮 - 编辑
|
|
91
|
+
function renderEdit(ctx: RenderCtx) {
|
|
92
|
+
const editBtn = new ActionBtn(ctx.ctx, {
|
|
93
|
+
text: ctx.ctx.$t('edit'),
|
|
94
|
+
adminOnly: true
|
|
95
|
+
})
|
|
96
|
+
editBtn.appendTo(ctx.$actions)
|
|
97
|
+
editBtn.setClick(() => {
|
|
98
|
+
ctx.ctx.editComment(ctx.data, ctx.$el)
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 操作按钮 - 删除
|
|
103
|
+
function renderDel(ctx: RenderCtx) {
|
|
104
|
+
const delBtn = new ActionBtn(ctx.ctx, {
|
|
105
|
+
text: ctx.ctx.$t('delete'),
|
|
106
|
+
confirm: true,
|
|
107
|
+
confirmText: ctx.ctx.$t('deleteConfirm'),
|
|
108
|
+
adminOnly: true,
|
|
109
|
+
})
|
|
110
|
+
delBtn.appendTo(ctx.$actions)
|
|
111
|
+
delBtn.setClick(() => {
|
|
112
|
+
ctx.comment.getActions().adminDelete(delBtn)
|
|
113
|
+
})
|
|
114
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as Utils from '../../lib/utils'
|
|
2
|
+
import RenderCtx from '../render-ctx'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 评论头像界面
|
|
6
|
+
*/
|
|
7
|
+
export default function renderAvatar(ctx: RenderCtx) {
|
|
8
|
+
const $avatar = ctx.$el.querySelector<HTMLElement>('.atk-avatar')!
|
|
9
|
+
const $avatarImg = Utils.createElement<HTMLImageElement>('<img />')
|
|
10
|
+
|
|
11
|
+
const avatarURLBuilder = ctx.conf.avatarURLBuilder
|
|
12
|
+
$avatarImg.src = avatarURLBuilder ? avatarURLBuilder(ctx.data) : ctx.comment.getGravatarURL()
|
|
13
|
+
|
|
14
|
+
if (ctx.data.link) {
|
|
15
|
+
const $avatarA = Utils.createElement<HTMLLinkElement>('<a target="_blank" rel="noreferrer noopener nofollow"></a>')
|
|
16
|
+
$avatarA.href = Utils.isValidURL(ctx.data.link) ? ctx.data.link : `https://${ctx.data.link}`
|
|
17
|
+
$avatarA.append($avatarImg)
|
|
18
|
+
$avatar.append($avatarA)
|
|
19
|
+
} else {
|
|
20
|
+
$avatar.append($avatarImg)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as Utils from '../../lib/utils'
|
|
2
|
+
import * as Ui from '../../lib/ui'
|
|
3
|
+
import RenderCtx from '../render-ctx'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 评论内容界面
|
|
7
|
+
*/
|
|
8
|
+
export default function renderContent(ctx: RenderCtx) {
|
|
9
|
+
if (!ctx.data.is_collapsed) {
|
|
10
|
+
ctx.$content.innerHTML = ctx.comment.getContentMarked()
|
|
11
|
+
ctx.$content.classList.remove('atk-hide', 'atk-collapsed')
|
|
12
|
+
return
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// 内容 & 折叠
|
|
16
|
+
ctx.$content.classList.add('atk-hide', 'atk-type-collapsed')
|
|
17
|
+
const collapsedInfoEl = Utils.createElement(`
|
|
18
|
+
<div class="atk-collapsed">
|
|
19
|
+
<span class="atk-text">${ctx.ctx.$t('collapsedMsg')}</span>
|
|
20
|
+
<span class="atk-show-btn">${ctx.ctx.$t('expand')}</span>
|
|
21
|
+
</div>`)
|
|
22
|
+
ctx.$body.insertAdjacentElement('beforeend', collapsedInfoEl)
|
|
23
|
+
|
|
24
|
+
const contentShowBtn = collapsedInfoEl.querySelector('.atk-show-btn')!
|
|
25
|
+
contentShowBtn.addEventListener('click', (e) => {
|
|
26
|
+
e.stopPropagation() // 防止穿透
|
|
27
|
+
|
|
28
|
+
if (ctx.$content.classList.contains('atk-hide')) {
|
|
29
|
+
ctx.$content.innerHTML = ctx.comment.getContentMarked()
|
|
30
|
+
ctx.$content.classList.remove('atk-hide')
|
|
31
|
+
Ui.playFadeInAnim(ctx.$content)
|
|
32
|
+
contentShowBtn.innerHTML = ctx.ctx.$t('collapse')
|
|
33
|
+
} else {
|
|
34
|
+
ctx.$content.innerHTML = ''
|
|
35
|
+
ctx.$content.classList.add('atk-hide')
|
|
36
|
+
contentShowBtn.innerHTML = ctx.ctx.$t('expand')
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
}
|