artalk 2.0.4 → 2.1.1
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 +7 -60
- package/dist/Artalk.css +1 -1
- package/dist/Artalk.es.js +5996 -1
- package/dist/Artalk.iife.js +266 -1
- package/dist/Artalk.js +266 -1
- package/package.json +3 -4
- package/src/api/index.ts +269 -0
- package/src/api/request.ts +125 -0
- package/src/artalk.ts +153 -0
- package/src/components/_component.ts.txt +10 -0
- package/src/components/action-btn.ts +169 -0
- package/src/components/admin/page-list.ts +194 -0
- package/src/components/admin/site-list-floater.ts +98 -0
- package/src/components/admin/site-list.ts +289 -0
- package/src/components/comment.ts +478 -0
- package/src/components/dialog.ts +63 -0
- package/src/components/editor-plugs/{_ExamplePlug.ts.txt → _example-plug.ts.txt} +0 -0
- package/src/components/editor-plugs/{EditorPlug.ts → editor-plug.ts} +3 -3
- package/src/components/editor-plugs/{EmoticonsPlug.less → emoticons-plug.less} +0 -0
- package/src/components/editor-plugs/{EmoticonsPlug.ts → emoticons-plug.ts} +9 -9
- package/src/components/editor-plugs/{PreviewPlug.less → preview-plug.less} +0 -0
- package/src/components/editor-plugs/{PreviewPlug.ts → preview-plug.ts} +10 -10
- package/src/components/{Editor.ts → editor.ts} +104 -102
- package/src/components/html/comment.html +2 -6
- package/src/components/html/list.html +0 -1
- package/src/components/html/sidebar.html +18 -4
- package/src/components/item-text-editor.ts +136 -0
- package/src/components/layer.ts +135 -0
- package/src/components/list-lite.ts +507 -0
- package/src/components/list.ts +172 -0
- package/src/components/pagination.ts +150 -0
- package/src/components/read-more-btn.ts +71 -0
- package/src/components/sidebar-view.ts +26 -0
- package/src/components/sidebar-views/comments-view.ts +66 -0
- package/src/components/sidebar-views/pages-view.ts +61 -0
- package/src/components/sidebar-views/setting-view.ts +21 -0
- package/src/components/sidebar-views/sites-view.ts +42 -0
- package/src/components/sidebar-views/transfer-view.ts +218 -0
- package/src/components/sidebar.ts +288 -0
- package/src/context.ts +42 -0
- package/src/default-conf.ts +42 -0
- package/src/lib/checker/admin-checker.ts +33 -0
- package/src/lib/checker/captcha-checker.ts +38 -0
- package/src/lib/checker/index.ts +134 -0
- package/src/lib/component.ts +3 -3
- package/src/lib/md5.js +6 -0
- package/src/lib/ui.ts +64 -74
- package/src/lib/user.ts +2 -8
- package/src/lib/utils.ts +30 -11
- package/src/main.ts +1 -1
- package/src/style/_color.less +8 -2
- package/src/style/_extend.less +81 -0
- package/src/style/_variables.less +3 -0
- package/src/style/comment.less +60 -24
- package/src/style/editor.less +3 -0
- package/src/style/item-text-editor.less +35 -0
- package/src/style/list.less +51 -32
- package/src/style/main.less +75 -1
- package/src/style/page-list.less +50 -0
- package/src/style/pagination.less +58 -0
- package/src/style/sidebar.less +274 -199
- package/src/style/site-list.less +208 -0
- package/tsconfig.json +27 -0
- package/types/artalk-config.d.ts +68 -51
- package/types/artalk-data.d.ts +38 -1
- package/types/event.d.ts +73 -0
- package/types/index.d.ts +1 -6
- package/.github/workflows/npm.yml +0 -31
- package/.github/workflows/pages.yml +0 -30
- package/dist/src/Event.d.ts +0 -59
- package/dist/types/artalk-config.d.ts +0 -79
- package/dist/types/artalk-data.d.ts +0 -153
- package/dist/types/global.d.ts +0 -1
- package/dist/types/index.d.ts +0 -8
- package/dist/types/vite-env.d.ts +0 -1
- package/example/pjax/res1.html +0 -41
- package/example/pjax/res2.html +0 -41
- package/src/Artalk.ts +0 -160
- package/src/Constant.ts +0 -6
- package/src/Context.ts +0 -34
- package/src/Event.d.ts +0 -59
- package/src/components/Comment.ts +0 -402
- package/src/components/Layer.ts +0 -138
- package/src/components/List.ts +0 -162
- package/src/components/ListLite.ts +0 -407
- package/src/components/Sidebar.ts +0 -141
- package/src/components/sidebar-views/AdminView.ts +0 -523
- package/src/components/sidebar-views/MessageView.ts +0 -48
- package/src/components/sidebar-views/SidebarView.ts +0 -24
- package/src/lib/api.ts +0 -380
- package/src/lib/checker.ts +0 -164
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
import '../style/comment.less'
|
|
2
|
+
|
|
3
|
+
import Context from '../context'
|
|
4
|
+
import Component from '../lib/component'
|
|
5
|
+
import * as Utils from '../lib/utils'
|
|
6
|
+
import * as Ui from '../lib/ui'
|
|
7
|
+
import UADetect from '../lib/detect'
|
|
8
|
+
import { CommentData } from '~/types/artalk-data'
|
|
9
|
+
import CommentHTML from './html/comment.html?raw'
|
|
10
|
+
import Api from '../api'
|
|
11
|
+
import ActionBtn from './action-btn'
|
|
12
|
+
|
|
13
|
+
export default class Comment extends Component {
|
|
14
|
+
public data: CommentData
|
|
15
|
+
|
|
16
|
+
public $main!: HTMLElement
|
|
17
|
+
public $header!: HTMLElement
|
|
18
|
+
public $body!: HTMLElement
|
|
19
|
+
public $content!: HTMLElement
|
|
20
|
+
public $children!: HTMLElement|null
|
|
21
|
+
public $actions!: HTMLElement
|
|
22
|
+
public voteBtnUp?: ActionBtn
|
|
23
|
+
public voteBtnDown?: ActionBtn
|
|
24
|
+
|
|
25
|
+
public parent: Comment|null
|
|
26
|
+
public nestedNum: number
|
|
27
|
+
private maxNestingNum: number // 最多嵌套层数
|
|
28
|
+
public children: Comment[] = []
|
|
29
|
+
|
|
30
|
+
public replyTo?: CommentData // 回复对象(flatMode 用)
|
|
31
|
+
public $replyTo?: HTMLElement
|
|
32
|
+
|
|
33
|
+
public afterRender?: () => void
|
|
34
|
+
|
|
35
|
+
private unread = false
|
|
36
|
+
private openable = false
|
|
37
|
+
private openURL?: string
|
|
38
|
+
public openEvt?: () => void
|
|
39
|
+
|
|
40
|
+
constructor(ctx: Context, data: CommentData) {
|
|
41
|
+
super(ctx)
|
|
42
|
+
|
|
43
|
+
// 最大嵌套数
|
|
44
|
+
this.maxNestingNum = ctx.conf.maxNesting || 3
|
|
45
|
+
|
|
46
|
+
this.data = { ...data }
|
|
47
|
+
this.data.date = this.data.date.replace(/-/g, '/') // 解决 Safari 日期解析 NaN 问题
|
|
48
|
+
|
|
49
|
+
this.parent = null
|
|
50
|
+
this.nestedNum = 1 // 现在已嵌套 n 层
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** 渲染 UI */
|
|
54
|
+
public render() {
|
|
55
|
+
this.$el = Utils.createElement(CommentHTML)
|
|
56
|
+
this.$main = this.$el.querySelector('.atk-comment-main')!
|
|
57
|
+
this.$header = this.$el.querySelector('.atk-header')!
|
|
58
|
+
this.$body = this.$el.querySelector('.atk-body')!
|
|
59
|
+
this.$content = this.$body.querySelector('.atk-content')!
|
|
60
|
+
this.$actions = this.$el.querySelector('.atk-comment-actions')!
|
|
61
|
+
this.$children = null
|
|
62
|
+
|
|
63
|
+
this.$el.setAttribute('data-comment-id', `${this.data.id}`)
|
|
64
|
+
|
|
65
|
+
this.renderCheckUnread()
|
|
66
|
+
this.renderCheckClickable()
|
|
67
|
+
|
|
68
|
+
this.renderAvatar()
|
|
69
|
+
this.renderHeader()
|
|
70
|
+
this.renderContent()
|
|
71
|
+
this.renderReplyTo()
|
|
72
|
+
this.renderPending()
|
|
73
|
+
this.renderActionBtn()
|
|
74
|
+
|
|
75
|
+
if (this.afterRender) this.afterRender()
|
|
76
|
+
|
|
77
|
+
return this.$el
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
//#region Renders
|
|
81
|
+
private renderCheckUnread() {
|
|
82
|
+
if (this.unread) this.$el.classList.add('atk-unread')
|
|
83
|
+
else this.$el.classList.remove('atk-unread')
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private renderCheckClickable() {
|
|
87
|
+
if (this.openable) {
|
|
88
|
+
this.$el.classList.add('atk-openable')
|
|
89
|
+
} else {
|
|
90
|
+
this.$el.classList.remove('atk-openable')
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
this.$el.addEventListener('click', (evt) => {
|
|
94
|
+
if (this.openable && this.openURL) {
|
|
95
|
+
evt.preventDefault()
|
|
96
|
+
window.open(this.openURL)
|
|
97
|
+
}
|
|
98
|
+
if (this.openEvt)
|
|
99
|
+
this.openEvt()
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private renderAvatar() {
|
|
104
|
+
const $avatar = this.$el.querySelector<HTMLElement>('.atk-avatar')!
|
|
105
|
+
const $avatarImg = Utils.createElement<HTMLImageElement>('<img />')
|
|
106
|
+
$avatarImg.src = this.getGravatarUrl()
|
|
107
|
+
if (this.data.link) {
|
|
108
|
+
const $avatarA = Utils.createElement<HTMLLinkElement>('<a target="_blank"></a>')
|
|
109
|
+
$avatarA.href = this.data.link
|
|
110
|
+
$avatarA.append($avatarImg)
|
|
111
|
+
$avatar.append($avatarA)
|
|
112
|
+
} else {
|
|
113
|
+
$avatar.append($avatarImg)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private renderHeader() {
|
|
118
|
+
const $nick = this.$el.querySelector<HTMLElement>('.atk-nick')!
|
|
119
|
+
if (this.data.link) {
|
|
120
|
+
const $nickA = Utils.createElement<HTMLLinkElement>('<a target="_blank"></a>')
|
|
121
|
+
$nickA.innerText = this.data.nick
|
|
122
|
+
$nickA.href = this.data.link
|
|
123
|
+
$nick.append($nickA)
|
|
124
|
+
} else {
|
|
125
|
+
$nick.innerText = this.data.nick
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const $badge = this.$el.querySelector<HTMLElement>('.atk-badge')!
|
|
129
|
+
if (this.data.badge_name) {
|
|
130
|
+
$badge.innerText = this.data.badge_name
|
|
131
|
+
if (this.data.badge_color)
|
|
132
|
+
$badge.style.backgroundColor = this.data.badge_color
|
|
133
|
+
} else {
|
|
134
|
+
$badge.remove()
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const $date = this.$el.querySelector<HTMLElement>('.atk-date')!
|
|
138
|
+
$date.innerText = this.getDateFormatted()
|
|
139
|
+
$date.setAttribute('data-atk-comment-date', String(+new Date(this.data.date)))
|
|
140
|
+
|
|
141
|
+
if (this.conf.uaBadge) {
|
|
142
|
+
const $uaWrap = Utils.createElement(`<span class="atk-ua-wrap"></span>`)
|
|
143
|
+
const $uaBrowser = Utils.createElement(`<span class="atk-ua ua-browser"></span>`)
|
|
144
|
+
const $usOS = Utils.createElement(`<span class="atk-ua ua-os"></span>`)
|
|
145
|
+
$uaBrowser.innerText = this.getUserUaBrowser()
|
|
146
|
+
$usOS.innerText = this.getUserUaOS()
|
|
147
|
+
$uaWrap.append($uaBrowser)
|
|
148
|
+
$uaWrap.append($usOS)
|
|
149
|
+
this.$header.append($uaWrap)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private renderContent() {
|
|
154
|
+
// 内容 & 折叠
|
|
155
|
+
if (!this.data.is_collapsed) {
|
|
156
|
+
this.$content.innerHTML = this.getContentMarked()
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
this.$content.classList.add('atk-hide', 'atk-type-collapsed')
|
|
161
|
+
const collapsedInfoEl = Utils.createElement(`
|
|
162
|
+
<div class="atk-collapsed">
|
|
163
|
+
<span class="atk-text">该评论已被系统或管理员折叠</span>
|
|
164
|
+
<span class="atk-show-btn">查看内容</span>
|
|
165
|
+
</div>`)
|
|
166
|
+
this.$body.insertAdjacentElement('beforeend', collapsedInfoEl)
|
|
167
|
+
|
|
168
|
+
const contentShowBtn = collapsedInfoEl.querySelector('.atk-show-btn')!
|
|
169
|
+
contentShowBtn.addEventListener('click', (e) => {
|
|
170
|
+
e.stopPropagation() // 防止穿透
|
|
171
|
+
|
|
172
|
+
if (this.$content.classList.contains('atk-hide')) {
|
|
173
|
+
this.$content.innerHTML = this.getContentMarked()
|
|
174
|
+
this.$content.classList.remove('atk-hide')
|
|
175
|
+
Ui.playFadeInAnim(this.$content)
|
|
176
|
+
contentShowBtn.innerHTML = '收起内容'
|
|
177
|
+
} else {
|
|
178
|
+
this.$content.innerHTML = ''
|
|
179
|
+
this.$content.classList.add('atk-hide')
|
|
180
|
+
contentShowBtn.innerHTML = '查看内容'
|
|
181
|
+
}
|
|
182
|
+
})
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// 回复的对象
|
|
186
|
+
private renderReplyTo() {
|
|
187
|
+
if (!this.replyTo) return
|
|
188
|
+
|
|
189
|
+
this.$replyTo = Utils.createElement(`
|
|
190
|
+
<div class="atk-reply-to">
|
|
191
|
+
<div class="atk-meta">回复 <span class="atk-nick"></span>:</div>
|
|
192
|
+
<div class="atk-content"></div>
|
|
193
|
+
</div>`)
|
|
194
|
+
this.$replyTo.querySelector<HTMLElement>('.atk-nick')!.innerText = `@${this.replyTo.nick}`
|
|
195
|
+
let replyContent = Utils.marked(this.ctx, this.replyTo.content)
|
|
196
|
+
if (this.replyTo.is_collapsed) replyContent = '[已折叠]'
|
|
197
|
+
this.$replyTo.querySelector<HTMLElement>('.atk-content')!.innerHTML = replyContent
|
|
198
|
+
this.$body.prepend(this.$replyTo)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// 待审核状态
|
|
202
|
+
private renderPending() {
|
|
203
|
+
if (!this.data.is_pending) return
|
|
204
|
+
|
|
205
|
+
const pendingEl = Utils.createElement(`<div class="atk-pending">审核中,仅本人可见。</div>`)
|
|
206
|
+
this.$body.prepend(pendingEl)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** 初始化评论操作按钮 */
|
|
210
|
+
private renderActionBtn() {
|
|
211
|
+
// 投票功能
|
|
212
|
+
if (this.ctx.conf.vote) {
|
|
213
|
+
// 赞同按钮
|
|
214
|
+
this.voteBtnUp = new ActionBtn(() => `赞同 (${this.data.vote_up || 0})`).appendTo(this.$actions)
|
|
215
|
+
this.voteBtnUp.setClick(() => {
|
|
216
|
+
this.vote('up')
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
// 反对按钮
|
|
220
|
+
if (this.ctx.conf.voteDown) {
|
|
221
|
+
this.voteBtnDown = new ActionBtn(() => `反对 (${this.data.vote_down || 0})`).appendTo(this.$actions)
|
|
222
|
+
this.voteBtnDown.setClick(() => {
|
|
223
|
+
this.vote('down')
|
|
224
|
+
})
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// 绑定回复按钮事件
|
|
229
|
+
if (this.data.is_allow_reply) {
|
|
230
|
+
const replyBtn = Utils.createElement(`<span data-atk-action="comment-reply">回复</span>`)
|
|
231
|
+
this.$actions.append(replyBtn)
|
|
232
|
+
replyBtn.addEventListener('click', (e) => {
|
|
233
|
+
e.stopPropagation() // 防止穿透
|
|
234
|
+
this.ctx.trigger('editor-reply', this.data)
|
|
235
|
+
})
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// 绑定折叠按钮事件
|
|
239
|
+
const collapseBtn = new ActionBtn({
|
|
240
|
+
text: () => (this.data.is_collapsed ? '取消折叠' : '折叠'),
|
|
241
|
+
adminOnly: true
|
|
242
|
+
})
|
|
243
|
+
collapseBtn.appendTo(this.$actions)
|
|
244
|
+
collapseBtn.setClick(() => {
|
|
245
|
+
this.adminEdit('collapsed', collapseBtn)
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
// 绑定待审核按钮事件
|
|
249
|
+
const pendingBtn = new ActionBtn({
|
|
250
|
+
text: () => (this.data.is_pending ? '待审' : '已审'),
|
|
251
|
+
adminOnly: true
|
|
252
|
+
})
|
|
253
|
+
pendingBtn.appendTo(this.$actions)
|
|
254
|
+
pendingBtn.setClick(() => {
|
|
255
|
+
this.adminEdit('pending', pendingBtn)
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
// 绑定删除按钮事件
|
|
259
|
+
const delBtn = new ActionBtn({
|
|
260
|
+
text: '删除',
|
|
261
|
+
confirm: true,
|
|
262
|
+
confirmText: '确认删除',
|
|
263
|
+
adminOnly: true,
|
|
264
|
+
})
|
|
265
|
+
delBtn.appendTo(this.$actions)
|
|
266
|
+
delBtn.setClick(() => {
|
|
267
|
+
this.adminDelete(delBtn)
|
|
268
|
+
})
|
|
269
|
+
}
|
|
270
|
+
//#endregion
|
|
271
|
+
|
|
272
|
+
/** 刷新评论 UI */
|
|
273
|
+
public refreshUI() {
|
|
274
|
+
const originalEl = this.$el
|
|
275
|
+
const newEl = this.render()
|
|
276
|
+
originalEl.replaceWith(newEl) // 替换 document 中的的 elem
|
|
277
|
+
this.playFadeInAnim()
|
|
278
|
+
|
|
279
|
+
// 重建子评论元素
|
|
280
|
+
this.eachComment(this.children, (child) => {
|
|
281
|
+
child.parent?.getChildrenEl().appendChild(child.render())
|
|
282
|
+
child.playFadeInAnim()
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
this.ctx.trigger('comments-loaded')
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** 遍历评论 */
|
|
289
|
+
private eachComment(commentList: Comment[], action: (comment: Comment, levelList: Comment[]) => boolean|void) {
|
|
290
|
+
if (commentList.length === 0) return
|
|
291
|
+
commentList.every((item) => {
|
|
292
|
+
if (action(item, commentList) === false) return false
|
|
293
|
+
this.eachComment(item.getChildren(), action)
|
|
294
|
+
return true
|
|
295
|
+
})
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
getIsRoot() {
|
|
299
|
+
return this.parent === null
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
getChildren() {
|
|
303
|
+
return this.children
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
putChild(childC: Comment) {
|
|
307
|
+
childC.parent = this
|
|
308
|
+
childC.nestedNum = this.nestedNum + 1 // 嵌套层数 +1
|
|
309
|
+
this.children.push(childC)
|
|
310
|
+
|
|
311
|
+
this.getChildrenEl().appendChild(childC.getEl())
|
|
312
|
+
childC.playFadeInAnim()
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
getChildrenEl() {
|
|
316
|
+
if (this.$children === null) {
|
|
317
|
+
// console.log(this.nestedNum)
|
|
318
|
+
if (this.nestedNum < this.maxNestingNum) {
|
|
319
|
+
this.$children = Utils.createElement('<div class="atk-comment-children"></div>')
|
|
320
|
+
this.$main.appendChild(this.$children)
|
|
321
|
+
} else if (this.parent) {
|
|
322
|
+
this.$children = this.parent.getChildrenEl()
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return this.$children
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
getParent() {
|
|
330
|
+
return this.parent
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
getEl() {
|
|
334
|
+
return this.$el
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
getData() {
|
|
338
|
+
return this.data
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
getGravatarUrl() {
|
|
342
|
+
return Utils.getGravatarURL(this.ctx, this.data.email_encrypted)
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
getContentMarked() {
|
|
346
|
+
return Utils.marked(this.ctx, this.data.content)
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
getDateFormatted() {
|
|
350
|
+
return Utils.timeAgo(new Date(this.data.date))
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
getUserUaBrowser() {
|
|
354
|
+
const info = UADetect(this.data.ua)
|
|
355
|
+
return `${info.browser} ${info.version}`
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
getUserUaOS() {
|
|
359
|
+
const info = UADetect(this.data.ua)
|
|
360
|
+
return `${info.os} ${info.osVersion}`
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/** 渐出动画 */
|
|
364
|
+
playFadeInAnim() {
|
|
365
|
+
Ui.playFadeInAnim(this.$el)
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/** 投票操作 */
|
|
369
|
+
vote(type: 'up'|'down') {
|
|
370
|
+
const actionBtn = type === 'up' ? this.voteBtnUp : this.voteBtnDown
|
|
371
|
+
|
|
372
|
+
new Api(this.ctx).vote(this.data.id, `comment_${type}`)
|
|
373
|
+
.then((v) => {
|
|
374
|
+
this.data.vote_up = v.up
|
|
375
|
+
this.data.vote_down = v.down
|
|
376
|
+
this.voteBtnUp?.updateText()
|
|
377
|
+
this.voteBtnDown?.updateText()
|
|
378
|
+
})
|
|
379
|
+
.catch((err) => {
|
|
380
|
+
actionBtn?.setError(`投票失败`)
|
|
381
|
+
console.log(err)
|
|
382
|
+
})
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/** 管理员 - 评论折叠 */
|
|
386
|
+
adminEdit(type: 'collapsed'|'pending', btnElem: ActionBtn) {
|
|
387
|
+
if (btnElem.isLoading) return // 若正在折叠中
|
|
388
|
+
|
|
389
|
+
btnElem.setLoading(true, '修改中...')
|
|
390
|
+
|
|
391
|
+
// 克隆并修改当前数据
|
|
392
|
+
const modify = { ...this.data }
|
|
393
|
+
if (type === 'collapsed') {
|
|
394
|
+
modify.is_collapsed = !modify.is_collapsed
|
|
395
|
+
} else if (type === 'pending') {
|
|
396
|
+
modify.is_pending = !modify.is_pending
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
new Api(this.ctx).commentEdit(modify).then((comment) => {
|
|
400
|
+
btnElem.setLoading(false)
|
|
401
|
+
|
|
402
|
+
// 刷新当前 Comment UI
|
|
403
|
+
this.data = comment
|
|
404
|
+
this.refreshUI()
|
|
405
|
+
Ui.playFadeInAnim(this.$body)
|
|
406
|
+
|
|
407
|
+
// 刷新 List UI
|
|
408
|
+
this.ctx.trigger('list-refresh-ui')
|
|
409
|
+
}).catch((err) => {
|
|
410
|
+
console.error(err)
|
|
411
|
+
btnElem.setError('修改失败')
|
|
412
|
+
})
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
public onDelete?: (comment: Comment) => void
|
|
416
|
+
|
|
417
|
+
/** 管理员 - 评论删除 */
|
|
418
|
+
adminDelete(btnElem: ActionBtn) {
|
|
419
|
+
if (btnElem.isLoading) return // 若正在删除中
|
|
420
|
+
|
|
421
|
+
btnElem.setLoading(true, '删除中...')
|
|
422
|
+
new Api(this.ctx).commentDel(this.data.id, this.data.site_name)
|
|
423
|
+
.then(() => {
|
|
424
|
+
btnElem.setLoading(false)
|
|
425
|
+
if (this.onDelete) this.onDelete(this)
|
|
426
|
+
})
|
|
427
|
+
.catch((e) => {
|
|
428
|
+
console.error(e)
|
|
429
|
+
btnElem.setError('删除失败')
|
|
430
|
+
})
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
public setUnread(val: boolean) {
|
|
434
|
+
this.unread = val
|
|
435
|
+
if (this.unread) this.$el.classList.add('atk-unread')
|
|
436
|
+
else this.$el.classList.remove('atk-unread')
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
public setOpenURL(url: string) {
|
|
440
|
+
if (!url) {
|
|
441
|
+
this.openable = false
|
|
442
|
+
this.$el.classList.remove('atk-openable')
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
this.openable = true
|
|
446
|
+
this.openURL = url
|
|
447
|
+
this.$el.classList.add('atk-openable')
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/** 内容过多,折叠显示 */
|
|
451
|
+
public checkMoreHide($target: HTMLElement|null, allowHeight = 300) {
|
|
452
|
+
if (!$target) return
|
|
453
|
+
|
|
454
|
+
let $hideMoreOpenBtn = $target?.querySelector<HTMLElement>('.atk-more-hide-open-btn')
|
|
455
|
+
|
|
456
|
+
const removeHideMore = () => {
|
|
457
|
+
$target.classList.remove('atk-comment-more-hide')
|
|
458
|
+
if ($hideMoreOpenBtn) $hideMoreOpenBtn.remove()
|
|
459
|
+
$target.style.height = ''
|
|
460
|
+
$target.style.overflow = ''
|
|
461
|
+
}
|
|
462
|
+
|
|
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)
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import * as Utils from '../lib/utils'
|
|
2
|
+
|
|
3
|
+
type BtnClickHandler = (btnEl: HTMLElement, dialog: Dialog) => boolean|void
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 对话框
|
|
7
|
+
*/
|
|
8
|
+
export default class Dialog {
|
|
9
|
+
public $el: HTMLElement
|
|
10
|
+
public $content: HTMLElement
|
|
11
|
+
public $actions: HTMLElement
|
|
12
|
+
|
|
13
|
+
constructor (contentEl: HTMLElement) {
|
|
14
|
+
this.$el = Utils.createElement(
|
|
15
|
+
`<div class="atk-layer-dialog-wrap">
|
|
16
|
+
<div class="atk-layer-dialog">
|
|
17
|
+
<div class="atk-layer-dialog-content"></div>
|
|
18
|
+
<div class="atk-layer-dialog-actions"></div>
|
|
19
|
+
</div>
|
|
20
|
+
</div>`
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
// 按钮
|
|
24
|
+
this.$actions = this.$el.querySelector<HTMLElement>('.atk-layer-dialog-actions')!
|
|
25
|
+
|
|
26
|
+
// 内容
|
|
27
|
+
this.$content = this.$el.querySelector('.atk-layer-dialog-content')!
|
|
28
|
+
this.$content.appendChild(contentEl)
|
|
29
|
+
|
|
30
|
+
return this
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** 按钮 · 确定 */
|
|
34
|
+
public setYes(handler: BtnClickHandler) {
|
|
35
|
+
const btn = Utils.createElement<HTMLButtonElement>(
|
|
36
|
+
'<button data-action="confirm">确定</button>'
|
|
37
|
+
)
|
|
38
|
+
btn.onclick = this.onBtnClick(handler)
|
|
39
|
+
this.$actions.appendChild(btn)
|
|
40
|
+
|
|
41
|
+
return this
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** 按钮 · 取消 */
|
|
45
|
+
public setNo(handler: BtnClickHandler) {
|
|
46
|
+
const btn = Utils.createElement<HTMLButtonElement>(
|
|
47
|
+
'<button data-action="cancel">取消</button>'
|
|
48
|
+
)
|
|
49
|
+
btn.onclick = this.onBtnClick(handler)
|
|
50
|
+
this.$actions.appendChild(btn)
|
|
51
|
+
|
|
52
|
+
return this
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private onBtnClick(handler: BtnClickHandler) {
|
|
56
|
+
return (evt: Event) => {
|
|
57
|
+
const re = handler(evt.currentTarget as HTMLElement, this)
|
|
58
|
+
if (re === undefined || re === true) {
|
|
59
|
+
this.$el.remove()
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
File without changes
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import Context from "~/src/
|
|
2
|
-
import Editor from "../
|
|
1
|
+
import Context from "~/src/context"
|
|
2
|
+
import Editor from "../editor"
|
|
3
3
|
|
|
4
4
|
export default abstract class EditorPlug {
|
|
5
5
|
protected editor: Editor
|
|
6
6
|
protected ctx: Context
|
|
7
|
-
public abstract el: HTMLElement
|
|
7
|
+
public abstract $el: HTMLElement
|
|
8
8
|
|
|
9
9
|
constructor (editor: Editor) {
|
|
10
10
|
this.editor = editor
|
|
File without changes
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import './
|
|
1
|
+
import './emoticons-plug.less'
|
|
2
2
|
|
|
3
|
-
import Editor from '../
|
|
4
|
-
import EditorPlug from './
|
|
3
|
+
import Editor from '../editor'
|
|
4
|
+
import EditorPlug from './editor-plug'
|
|
5
5
|
import * as Utils from '~/src/lib/utils'
|
|
6
6
|
|
|
7
7
|
type EmoticonListType = {
|
|
@@ -12,7 +12,7 @@ type EmoticonListType = {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export default class EmoticonsPlug extends EditorPlug {
|
|
15
|
-
public el!: HTMLElement
|
|
15
|
+
public $el!: HTMLElement
|
|
16
16
|
public emoticons: EmoticonListType
|
|
17
17
|
|
|
18
18
|
public listWrapEl!: HTMLElement
|
|
@@ -26,11 +26,11 @@ export default class EmoticonsPlug extends EditorPlug {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
initEl () {
|
|
29
|
-
this
|
|
29
|
+
this.$el = Utils.createElement(`<div class="atk-editor-plug-emoticons"></div>`)
|
|
30
30
|
|
|
31
31
|
// 表情列表
|
|
32
32
|
this.listWrapEl = Utils.createElement(`<div class="atk-emoticons-list-wrap"></div>`)
|
|
33
|
-
this
|
|
33
|
+
this.$el.append(this.listWrapEl)
|
|
34
34
|
|
|
35
35
|
Object.entries(this.emoticons).forEach(([key, item]) => {
|
|
36
36
|
const emoticonsEl = Utils.createElement(`<div class="atk-emoticons-list" style="display: none;"></div>`)
|
|
@@ -55,7 +55,7 @@ export default class EmoticonsPlug extends EditorPlug {
|
|
|
55
55
|
|
|
56
56
|
// 表情分类
|
|
57
57
|
this.typesEl = Utils.createElement(`<div class="atk-emoticons-types"></div>`)
|
|
58
|
-
this
|
|
58
|
+
this.$el.append(this.typesEl)
|
|
59
59
|
Object.entries(this.emoticons).forEach(([key, item]) => {
|
|
60
60
|
const itemEl = Utils.createElement('<span />')
|
|
61
61
|
this.typesEl.append(itemEl)
|
|
@@ -118,7 +118,7 @@ export default class EmoticonsPlug extends EditorPlug {
|
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
getEl () {
|
|
121
|
-
return this
|
|
121
|
+
return this.$el
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
changeListHeight () {
|
|
@@ -134,7 +134,7 @@ export default class EmoticonsPlug extends EditorPlug {
|
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
onHide () {
|
|
137
|
-
this
|
|
137
|
+
this.$el.parentElement!.style.height = ''
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
public transEmoticonImageText (text: string) {
|
|
File without changes
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import './
|
|
1
|
+
import './preview-plug.less'
|
|
2
2
|
|
|
3
|
-
import Editor from '../
|
|
4
|
-
import EditorPlug from './
|
|
3
|
+
import Editor from '../editor'
|
|
4
|
+
import EditorPlug from './editor-plug'
|
|
5
5
|
import * as Utils from '~/src/lib/utils'
|
|
6
6
|
|
|
7
7
|
export default class PreviewPlug extends EditorPlug {
|
|
8
|
-
el!: HTMLElement
|
|
8
|
+
$el!: HTMLElement
|
|
9
9
|
binded: boolean = false
|
|
10
10
|
|
|
11
11
|
constructor (editor: Editor) {
|
|
@@ -15,7 +15,7 @@ export default class PreviewPlug extends EditorPlug {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
initEl () {
|
|
18
|
-
this
|
|
18
|
+
this.$el = Utils.createElement('<div class="atk-editor-plug-preview"></div>')
|
|
19
19
|
this.binded = false
|
|
20
20
|
}
|
|
21
21
|
|
|
@@ -28,7 +28,7 @@ export default class PreviewPlug extends EditorPlug {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
getEl () {
|
|
31
|
-
return this
|
|
31
|
+
return this.$el
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
onShow () {
|
|
@@ -37,8 +37,8 @@ export default class PreviewPlug extends EditorPlug {
|
|
|
37
37
|
const event = () => {
|
|
38
38
|
this.updateContent()
|
|
39
39
|
}
|
|
40
|
-
this.editor.
|
|
41
|
-
this.editor.
|
|
40
|
+
this.editor.$textarea.addEventListener('input', event)
|
|
41
|
+
this.editor.$textarea.addEventListener('change', event)
|
|
42
42
|
this.binded = true
|
|
43
43
|
}
|
|
44
44
|
}
|
|
@@ -46,8 +46,8 @@ export default class PreviewPlug extends EditorPlug {
|
|
|
46
46
|
onHide () {}
|
|
47
47
|
|
|
48
48
|
updateContent () {
|
|
49
|
-
if (this
|
|
50
|
-
this
|
|
49
|
+
if (this.$el.style.display !== 'none') {
|
|
50
|
+
this.$el.innerHTML = this.editor.getContentMarked()
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
}
|