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
@@ -1,607 +0,0 @@
1
- import '../style/editor.less'
2
-
3
- import { CommentData } from '~/types/artalk-data'
4
- import Context from '../context'
5
- import Component from '../lib/component'
6
- import * as Utils from '../lib/utils'
7
- import * as Ui from '../lib/ui'
8
- import EditorHTML from './html/editor.html?raw'
9
-
10
- import EmoticonsPlug from './editor-plugs/emoticons-plug'
11
- import PreviewPlug from './editor-plugs/preview-plug'
12
- import Api from '../api'
13
-
14
- export default class Editor extends Component {
15
- private readonly LOADABLE_PLUG_LIST = [EmoticonsPlug, PreviewPlug]
16
- public plugList: { [name: string]: any } = {}
17
-
18
- public $header: HTMLElement
19
- public $textareaWrap: HTMLElement
20
- public $textarea: HTMLTextAreaElement
21
- public $plugWrap: HTMLElement
22
- public $bottom: HTMLElement
23
- public $plugBtnWrap: HTMLElement
24
- public $imgUploadBtn?: HTMLElement
25
- public $imgUploadInput?: HTMLInputElement
26
- public $submitBtn: HTMLButtonElement
27
- public $notifyWrap: HTMLElement
28
-
29
- private replyComment: CommentData|null = null
30
- private $sendReply: HTMLElement|null = null
31
-
32
- private isTraveling = false
33
-
34
- private get user () {
35
- return this.ctx.user
36
- }
37
-
38
- constructor (ctx: Context) {
39
- super(ctx)
40
-
41
- this.$el = Utils.createElement(EditorHTML)
42
-
43
- this.$header = this.$el.querySelector('.atk-header')!
44
- this.$textareaWrap = this.$el.querySelector('.atk-textarea-wrap')!
45
- this.$textarea = this.$el.querySelector('.atk-textarea')!
46
- this.$plugWrap = this.$el.querySelector('.atk-plug-wrap')!
47
- this.$bottom = this.$el.querySelector('.atk-bottom')!
48
- this.$plugBtnWrap = this.$el.querySelector('.atk-plug-btn-wrap')!
49
- this.$submitBtn = this.$el.querySelector('.atk-send-btn')!
50
- this.$notifyWrap = this.$el.querySelector('.atk-notify-wrap')!
51
-
52
- this.initLocalStorage()
53
- this.initHeader()
54
- this.initTextarea()
55
- this.initEditorPlug()
56
- this.initBottomPart()
57
-
58
- // 监听事件
59
- this.ctx.on('editor-open', () => (this.open()))
60
- this.ctx.on('editor-close', () => (this.close()))
61
- this.ctx.on('editor-reply', (p) => (this.setReply(p.data, p.$el, p.scroll)))
62
- this.ctx.on('editor-reply-cancel', () => (this.cancelReply()))
63
- this.ctx.on('editor-show-loading', () => (Ui.showLoading(this.$el)))
64
- this.ctx.on('editor-hide-loading', () => (Ui.hideLoading(this.$el)))
65
- this.ctx.on('editor-notify', (f) => (this.showNotify(f.msg, f.type)))
66
- this.ctx.on('editor-travel', ($el) => (this.travel($el)))
67
- this.ctx.on('editor-travel-back', () => (this.travelBack()))
68
- this.ctx.on('conf-updated', () => (this.refreshUploadBtn()))
69
- }
70
-
71
- initLocalStorage () {
72
- const localContent = window.localStorage.getItem('ArtalkContent') || ''
73
- if (localContent.trim() !== '') {
74
- this.showNotify('已自动恢复', 'i')
75
- this.setContent(localContent)
76
- }
77
- this.$textarea.addEventListener('input', () => {
78
- this.saveContent()
79
- })
80
- }
81
-
82
- initHeader () {
83
- Object.keys(this.user.data).forEach((field) => {
84
- const inputEl = this.getInputEl(field)
85
- if (inputEl && inputEl instanceof HTMLInputElement) {
86
- inputEl.value = this.user.data[field] || ''
87
- // 绑定事件
88
- inputEl.addEventListener('input', () => this.onHeaderInput(field, inputEl))
89
- }
90
- })
91
-
92
- // Link URL 自动补全协议
93
- const $linkInput = this.getInputEl('link')
94
- if ($linkInput) {
95
- $linkInput.addEventListener('change', () => {
96
- const link = $linkInput.value.trim()
97
- if (!!link && !/^(http|https):\/\//.test(link)) {
98
- $linkInput.value = `https://${link}`
99
- this.user.data.link = $linkInput.value
100
- this.saveUser()
101
- }
102
- })
103
- }
104
- }
105
-
106
- getInputEl (field: string) {
107
- const inputEl = this.$header.querySelector<HTMLInputElement>(`[name="${field}"]`)
108
- return inputEl
109
- }
110
-
111
- queryUserInfo = {
112
- timeout: <number|null>null,
113
- abortFunc: <(() => void)|null>null
114
- }
115
-
116
- /** header 输入框内容变化事件 */
117
- onHeaderInput(field: string, inputEl: HTMLInputElement) {
118
- this.user.data[field] = inputEl.value.trim()
119
-
120
- // 若修改的是 nick or email
121
- if (field === 'nick' || field === 'email') {
122
- this.user.data.token = '' // 清除 token 登陆状态
123
- this.user.data.isAdmin = false
124
-
125
- // 获取用户信息
126
- if (this.queryUserInfo.timeout !== null) window.clearTimeout(this.queryUserInfo.timeout) // 清除待发出的请求
127
- if (this.queryUserInfo.abortFunc !== null) this.queryUserInfo.abortFunc() // 之前发出未完成的请求立刻中止
128
-
129
- this.queryUserInfo.timeout = window.setTimeout(() => {
130
- this.queryUserInfo.timeout = null // 清理
131
-
132
- const {req, abort} = new Api(this.ctx).userGet(
133
- this.user.data.nick, this.user.data.email
134
- )
135
- this.queryUserInfo.abortFunc = abort
136
- req.then(data => {
137
- if (!data.is_login) {
138
- this.user.data.token = ''
139
- this.user.data.isAdmin = false
140
- }
141
-
142
- // 未读消息更新
143
- this.ctx.trigger('unread-update', { notifies: data.unread, })
144
-
145
- // 若用户为管理员,执行登陆操作
146
- if (this.user.checkHasBasicUserInfo() && !data.is_login && data.user && data.user.is_admin) {
147
- this.showLoginDialog()
148
- }
149
-
150
- // 自动填入 link
151
- if (data.user && data.user.link) {
152
- this.user.data.link = data.user.link
153
- this.getInputEl('link')!.value = data.user.link
154
- }
155
- })
156
- .catch(() => {})
157
- .finally(() => {
158
- this.queryUserInfo.abortFunc = null // 清理
159
- })
160
- }, 400) // 延迟执行,减少请求次数
161
- }
162
-
163
- this.saveUser()
164
- }
165
-
166
- showLoginDialog () {
167
- this.ctx.trigger('checker-admin', {
168
- onSuccess: () => {
169
- }
170
- })
171
- }
172
-
173
- saveUser () {
174
- this.user.save()
175
- this.ctx.trigger('user-changed', this.ctx.user.data)
176
- }
177
-
178
- saveContent () {
179
- window.localStorage.setItem('ArtalkContent', this.getContentOriginal().trim())
180
- }
181
-
182
- initTextarea () {
183
- // 占位符
184
- this.$textarea.placeholder = this.ctx.conf.placeholder || ''
185
-
186
- // 修复按下 Tab 输入的内容
187
- this.$textarea.addEventListener('keydown', (e) => {
188
- const keyCode = e.keyCode || e.which
189
-
190
- if (keyCode === 9) {
191
- e.preventDefault()
192
- this.insertContent('\t')
193
- }
194
- })
195
-
196
- // 输入框高度随内容而变化
197
- this.$textarea.addEventListener('input', (evt) => {
198
- this.adjustTextareaHeight()
199
- })
200
- }
201
-
202
- adjustTextareaHeight () {
203
- const diff = this.$textarea.offsetHeight - this.$textarea.clientHeight
204
- this.$textarea.style.height = '0px' // it's a magic. 若不加此行,内容减少,高度回不去
205
- this.$textarea.style.height = `${this.$textarea.scrollHeight + diff}px`
206
- }
207
-
208
- openedPlugName: string|null = null
209
-
210
- initEditorPlug () {
211
- this.plugList = {}
212
- this.$plugWrap.innerHTML = ''
213
- this.$plugWrap.style.display = 'none'
214
- this.openedPlugName = null
215
- this.$plugBtnWrap.innerHTML = ''
216
-
217
- // 初始化 plug 按钮
218
- this.LOADABLE_PLUG_LIST.forEach((PlugObj) => {
219
- if (PlugObj.Name === 'emoticons' && !this.conf.emoticons) return
220
-
221
- // 切换按钮
222
- const btnElem = Utils.createElement(`<span class="atk-plug-btn" data-plug-name="${PlugObj.Name}">${PlugObj.BtnHTML}</span>`)
223
- this.$plugBtnWrap.appendChild(btnElem)
224
-
225
- btnElem.addEventListener('click', () => {
226
- let plug = this.plugList[PlugObj.Name]
227
- if (!plug) {
228
- plug = new PlugObj(this)
229
- this.plugList[PlugObj.Name] = plug
230
- }
231
-
232
- this.$plugBtnWrap.querySelectorAll('.active').forEach(item => item.classList.remove('active'))
233
-
234
- // 若点击已打开的,则收起
235
- if (PlugObj.Name === this.openedPlugName) {
236
- plug.onHide()
237
- this.$plugWrap.style.display = 'none'
238
- this.openedPlugName = null
239
- return
240
- }
241
-
242
- if (this.$plugWrap.querySelector(`[data-plug-name="${PlugObj.Name}"]`) === null) {
243
- // 需要初始化
244
- const plugEl = plug.getEl()
245
- plugEl.setAttribute('data-plug-name', PlugObj.Name)
246
- plugEl.style.display = 'none'
247
- this.$plugWrap.appendChild(plugEl)
248
- }
249
-
250
- (Array.from(this.$plugWrap.children) as HTMLElement[]).forEach((plugItemEl: HTMLElement) => {
251
- const plugItemName = plugItemEl.getAttribute('data-plug-name')!
252
- if (plugItemName === PlugObj.Name) {
253
- plugItemEl.style.display = ''
254
- this.plugList[plugItemName].onShow()
255
- } else {
256
- plugItemEl.style.display = 'none'
257
- this.plugList[plugItemName].onHide()
258
- }
259
- })
260
-
261
- this.$plugWrap.style.display = ''
262
- this.openedPlugName = PlugObj.Name
263
-
264
- btnElem.classList.add('active')
265
- })
266
- })
267
-
268
- // 表情包插件预加载
269
- if (this.conf.emoticons) {
270
- const emoPlug = new EmoticonsPlug(this)
271
- this.plugList[EmoticonsPlug.Name] = emoPlug
272
-
273
- window.setTimeout(() => {
274
- emoPlug.loadEmoticonsData()
275
- }, 1000) // 延迟 1s 加载
276
- }
277
-
278
- this.initImgUpload()
279
- }
280
-
281
- /** 关闭编辑器插件 */
282
- closePlug () {
283
- this.$plugWrap.innerHTML = ''
284
- this.$plugWrap.style.display = 'none'
285
- this.openedPlugName = null
286
- }
287
-
288
- /** 允许的图片格式 */
289
- allowImgExts = ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'svg', 'webp']
290
-
291
- /** 初始化图片上传功能 */
292
- initImgUpload() {
293
- this.$imgUploadBtn = Utils.createElement(`<span class="atk-plug-btn">图片</span>`)
294
- this.$plugBtnWrap.querySelector('[data-plug-name="preview"]')!.before(this.$imgUploadBtn) // 显示在预览图标之前
295
-
296
- this.$imgUploadInput = document.createElement('input')
297
- this.$imgUploadInput.type = 'file'
298
- this.$imgUploadInput.style.display = 'none'
299
- this.$imgUploadInput.accept = this.allowImgExts.map(o => `.${o}`).join(',')
300
- this.$imgUploadBtn.after(this.$imgUploadInput)
301
-
302
- // 按钮点击
303
- this.$imgUploadBtn.onclick = () => {
304
- // 选择图片
305
- const $input = this.$imgUploadInput!
306
- $input.onchange = () => {
307
- (async () => { // 解决阻塞 UI 问题
308
- if (!$input.files || $input.files.length === 0) return
309
- const file = $input.files[0]
310
- this.uploadImg(file)
311
- })()
312
- }
313
- $input.click() // 显示选择图片对话框
314
- }
315
-
316
- // 统一从 FileList 获取文件并上传图片方法
317
- const uploadFromFileList = (files?: FileList) => {
318
- if (!files) return
319
-
320
- for (let i = 0; i < files.length; i++) {
321
- const file = files[i]
322
- this.uploadImg(file)
323
- }
324
- }
325
-
326
- // 拖拽图片
327
- // @link https://developer.mozilla.org/zh-CN/docs/Web/API/HTML_Drag_and_Drop_API/File_drag_and_drop
328
- // 阻止浏览器的默认释放行为
329
- this.$textarea.addEventListener('dragover', (evt) => {
330
- evt.stopPropagation()
331
- evt.preventDefault()
332
- })
333
-
334
- this.$textarea.addEventListener('drop', (evt) => {
335
- const files = evt.dataTransfer?.files
336
- if (files?.length) {
337
- evt.preventDefault()
338
- uploadFromFileList(files)
339
- }
340
- })
341
-
342
- // 粘贴图片
343
- this.$textarea.addEventListener('paste', (evt) => {
344
- const files = evt.clipboardData?.files
345
- if (files?.length) {
346
- evt.preventDefault()
347
- uploadFromFileList(files)
348
- }
349
- })
350
- }
351
-
352
- /** 刷新图片上传按钮 */
353
- refreshUploadBtn() {
354
- if (!this.$imgUploadBtn) return
355
-
356
- if (!this.ctx.conf.imgUpload) {
357
- this.$imgUploadBtn.setAttribute('atk-only-admin-show', '')
358
- this.ctx.trigger('check-admin-show-el')
359
- }
360
- }
361
-
362
- async uploadImg(file: File) {
363
- const fileExt = /[^.]+$/.exec(file.name)
364
- if (!fileExt || !this.allowImgExts.includes(fileExt[0])) return
365
-
366
- // 未登录提示
367
- if (!this.ctx.user.checkHasBasicUserInfo()) {
368
- this.showNotify('填入你的名字邮箱才能上传哦', 'w')
369
- return
370
- }
371
-
372
- // 插入图片前换一行
373
- let insertPrefix = '\n'
374
- if (this.$textarea.value.trim() === '') insertPrefix = ''
375
-
376
- // 插入占位加载文字
377
- const uploadPlaceholderTxt = `${insertPrefix}![](Uploading ${file.name}...)`
378
- this.insertContent(uploadPlaceholderTxt)
379
-
380
- // 上传图片
381
- let resp: any
382
- try {
383
- resp = await new Api(this.ctx).imgUpload(file)
384
- } catch (err: any) {
385
- console.error(err)
386
- this.showNotify(`图片上传失败,${err.msg}`, 'e')
387
- }
388
- if (!!resp && resp.img_url) {
389
- let imgURL = resp.img_url as string
390
-
391
- // 若为相对路径,加上 artalk server
392
- if (!Utils.isValidURL(imgURL)) imgURL = Utils.getURLBasedOnApi(this.ctx, imgURL)
393
-
394
- // 上传成功插入图片
395
- this.setContent(this.$textarea.value.replace(uploadPlaceholderTxt, `${insertPrefix}![](${imgURL})`))
396
- } else {
397
- // 上传失败删除加载文字
398
- this.setContent(this.$textarea.value.replace(uploadPlaceholderTxt, ''))
399
- }
400
- }
401
-
402
- insertContent (val: string) {
403
- if ((document as any).selection) {
404
- this.$textarea.focus();
405
- (document as any).selection.createRange().text = val
406
- this.$textarea.focus()
407
- } else if (this.$textarea.selectionStart || this.$textarea.selectionStart === 0) {
408
- const sStart = this.$textarea.selectionStart
409
- const sEnd = this.$textarea.selectionEnd
410
- const sT = this.$textarea.scrollTop
411
- this.setContent(this.$textarea.value.substring(0, sStart) + val + this.$textarea.value.substring(sEnd, this.$textarea.value.length))
412
- this.$textarea.focus()
413
- this.$textarea.selectionStart = sStart + val.length
414
- this.$textarea.selectionEnd = sStart + val.length
415
- this.$textarea.scrollTop = sT
416
- } else {
417
- this.$textarea.focus()
418
- this.$textarea.value += val
419
- }
420
- }
421
-
422
- setContent (val: string) {
423
- this.$textarea.value = val
424
- this.saveContent()
425
- if (!!this.plugList && !!this.plugList.preview) {
426
- this.plugList.preview.updateContent()
427
- }
428
-
429
- // 延迟执行防止无效
430
- window.setTimeout(() => {
431
- this.adjustTextareaHeight()
432
- }, 80)
433
- }
434
-
435
- clearEditor () {
436
- this.setContent('')
437
- this.cancelReply()
438
- }
439
-
440
- /** 获取最终用于 submit 的数据 */
441
- getFinalContent () {
442
- let content = this.getContentOriginal()
443
-
444
- // 表情包处理
445
- if (this.plugList && this.plugList.emoticons) {
446
- const emoticonsPlug = this.plugList.emoticons as EmoticonsPlug
447
- content = emoticonsPlug.transEmoticonImageText(content)
448
- }
449
-
450
- return content
451
- }
452
-
453
- getContentOriginal () {
454
- return this.$textarea.value || '' // Tip: !!"0" === true
455
- }
456
-
457
- getContentMarked () {
458
- return Utils.marked(this.ctx, this.getFinalContent())
459
- }
460
-
461
- initBottomPart () {
462
- this.initReply()
463
- this.initSubmit()
464
- }
465
-
466
- initReply () {
467
- this.replyComment = null
468
- this.$sendReply = null
469
- }
470
-
471
- setReply (commentData: CommentData, $comment: HTMLElement, scroll = true) {
472
- if (this.replyComment !== null) {
473
- this.cancelReply()
474
- }
475
-
476
- if (this.$sendReply === null) {
477
- this.$sendReply = Utils.createElement('<div class="atk-send-reply">回复 <span class="atk-text"></span><span class="atk-cancel" title="取消 AT">×</span></div>');
478
- this.$sendReply.querySelector<HTMLElement>('.atk-text')!.innerText = `@${commentData.nick}`
479
- this.$sendReply.addEventListener('click', () => {
480
- this.cancelReply()
481
- })
482
- this.$textareaWrap.append(this.$sendReply)
483
- }
484
- this.replyComment = commentData
485
-
486
- if (this.ctx.conf.editorTravel === true) {
487
- this.travel($comment)
488
- }
489
-
490
- if (scroll) Ui.scrollIntoView(this.$el)
491
-
492
- this.$textarea.focus()
493
- }
494
-
495
- cancelReply () {
496
- if (this.$sendReply !== null) {
497
- this.$sendReply.remove()
498
- this.$sendReply = null
499
- }
500
- this.replyComment = null
501
-
502
- if (this.ctx.conf.editorTravel === true) {
503
- this.travelBack()
504
- }
505
- }
506
-
507
- initSubmit () {
508
- this.$submitBtn.innerText = this.ctx.conf.sendBtn || 'Send'
509
-
510
- this.$submitBtn.addEventListener('click', (evt) => {
511
- const btnEl = evt.currentTarget
512
- this.submit()
513
- })
514
- }
515
-
516
- async submit () {
517
- if (this.getFinalContent().trim() === '') {
518
- this.$textarea.focus()
519
- return
520
- }
521
-
522
- this.ctx.trigger('editor-submit')
523
-
524
- Ui.showLoading(this.$el)
525
-
526
- try {
527
- const nComment = await new Api(this.ctx).add({
528
- content: this.getFinalContent(),
529
- nick: this.user.data.nick,
530
- email: this.user.data.email,
531
- link: this.user.data.link,
532
- rid: (this.replyComment === null) ? 0 : this.replyComment.id,
533
- page_key: (this.replyComment === null) ? this.ctx.conf.pageKey : this.replyComment.page_key,
534
- page_title: (this.replyComment === null) ? this.ctx.conf.pageTitle : undefined,
535
- site_name: (this.replyComment === null) ? this.ctx.conf.site : this.replyComment.site_name
536
- })
537
-
538
- // 回复不同页面的评论
539
- if (this.replyComment !== null && this.replyComment.page_key !== this.ctx.conf.pageKey) {
540
- window.open(`${this.replyComment.page_url}#atk-comment-${nComment.id}`)
541
- }
542
-
543
- this.ctx.trigger('list-insert', nComment)
544
- this.clearEditor() // 清空编辑器
545
- this.ctx.trigger('editor-submitted')
546
- } catch (err: any) {
547
- console.error(err)
548
- this.showNotify(`评论失败,${err.msg || String(err)}`, 'e')
549
- return
550
- } finally {
551
- Ui.hideLoading(this.$el)
552
- }
553
- }
554
-
555
- showNotify (msg: string, type) {
556
- Ui.showNotify(this.$notifyWrap, msg, type)
557
- }
558
-
559
- /** 关闭评论 */
560
- close () {
561
- if (!this.$textareaWrap.querySelector('.atk-comment-closed'))
562
- this.$textareaWrap.prepend(Utils.createElement('<div class="atk-comment-closed">仅管理员可评论</div>'))
563
-
564
- if (!this.user.data.isAdmin) {
565
- this.$textarea.style.display = 'none'
566
- this.closePlug()
567
- this.$bottom.style.display = 'none'
568
- } else {
569
- // 管理员一直打开评论
570
- this.$textarea.style.display = ''
571
- this.$bottom.style.display = ''
572
- }
573
- }
574
-
575
- /** 打开评论 */
576
- open () {
577
- this.$textareaWrap.querySelector('.atk-comment-closed')?.remove()
578
- this.$textarea.style.display = ''
579
- this.$bottom.style.display = ''
580
- }
581
-
582
- travel ($afterEl: HTMLElement) {
583
- if (this.isTraveling) return
584
- this.isTraveling = true
585
- this.$el.after(Utils.createElement('<div class="atk-editor-travel-placeholder"></div>'))
586
-
587
- const $travelPlace = Utils.createElement('<div></div>')
588
- $afterEl.after($travelPlace)
589
- $travelPlace.replaceWith(this.$el)
590
-
591
- this.$el.classList.add('atk-fade-in') // 添加渐入动画
592
- }
593
-
594
- travelBack () {
595
- if (!this.isTraveling) return
596
- this.isTraveling = false
597
- this.ctx.$root.querySelector('.atk-editor-travel-placeholder')?.replaceWith(this.$el)
598
-
599
- // 取消回复
600
- if (this.replyComment !== null) this.cancelReply()
601
- }
602
-
603
- initRemoteEmoticons () {
604
-
605
- }
606
- }
607
-
@@ -1,18 +0,0 @@
1
- <div class="atk-main-editor">
2
- <div class="atk-header">
3
- <input name="nick" placeholder="昵称" class="atk-nick" type="text" required="required">
4
- <input name="email" placeholder="邮箱" class="atk-email" type="email" required="required">
5
- <input name="link" placeholder="网址 (https://)" class="atk-link" type="url">
6
- </div>
7
- <div class="atk-textarea-wrap">
8
- <textarea class="atk-textarea" placeholder=""></textarea>
9
- </div>
10
- <div class="atk-plug-wrap" style="display: none;"></div>
11
- <div class="atk-bottom">
12
- <div class="atk-item atk-plug-btn-wrap"></div>
13
- <div class="atk-item">
14
- <button type="button" class="atk-send-btn"></button>
15
- </div>
16
- </div>
17
- <div class="atk-notify-wrap"></div>
18
- </div>