artalk 2.2.11 → 2.3.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.
Files changed (78) hide show
  1. package/dist/Artalk.css +1 -1
  2. package/dist/Artalk.es.js +1872 -1143
  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 +5 -3
  10. package/package.json +7 -5
  11. package/src/api/index.ts +24 -4
  12. package/src/api/request.ts +50 -81
  13. package/src/artalk.ts +131 -127
  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 +15 -4
  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 +3 -10
  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 +83 -111
  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 -18
  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 -604
  78. package/src/components/html/editor.html +0 -18
@@ -1,604 +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
- .finally(() => {
157
- this.queryUserInfo.abortFunc = null // 清理
158
- })
159
- }, 400) // 延迟执行,减少请求次数
160
- }
161
-
162
- this.saveUser()
163
- }
164
-
165
- showLoginDialog () {
166
- this.ctx.trigger('checker-admin', {
167
- onSuccess: () => {
168
- }
169
- })
170
- }
171
-
172
- saveUser () {
173
- this.user.save()
174
- this.ctx.trigger('user-changed', this.ctx.user.data)
175
- }
176
-
177
- saveContent () {
178
- window.localStorage.setItem('ArtalkContent', this.getContentOriginal().trim())
179
- }
180
-
181
- initTextarea () {
182
- // 占位符
183
- this.$textarea.placeholder = this.ctx.conf.placeholder || ''
184
-
185
- // 修复按下 Tab 输入的内容
186
- this.$textarea.addEventListener('keydown', (e) => {
187
- const keyCode = e.keyCode || e.which
188
-
189
- if (keyCode === 9) {
190
- e.preventDefault()
191
- this.insertContent('\t')
192
- }
193
- })
194
-
195
- // 输入框高度随内容而变化
196
- this.$textarea.addEventListener('input', (evt) => {
197
- this.adjustTextareaHeight()
198
- })
199
- }
200
-
201
- adjustTextareaHeight () {
202
- const diff = this.$textarea.offsetHeight - this.$textarea.clientHeight
203
- this.$textarea.style.height = '0px' // it's a magic. 若不加此行,内容减少,高度回不去
204
- this.$textarea.style.height = `${this.$textarea.scrollHeight + diff}px`
205
- }
206
-
207
- openedPlugName: string|null = null
208
-
209
- initEditorPlug () {
210
- this.plugList = {}
211
- this.$plugWrap.innerHTML = ''
212
- this.$plugWrap.style.display = 'none'
213
- this.openedPlugName = null
214
- this.$plugBtnWrap.innerHTML = ''
215
-
216
- // 依次实例化 plug
217
- this.LOADABLE_PLUG_LIST.forEach((PlugObj) => {
218
- // 切换按钮
219
- const btnElem = Utils.createElement(`<span class="atk-plug-btn" data-plug-name="${PlugObj.Name}">${PlugObj.BtnHTML}</span>`)
220
- this.$plugBtnWrap.appendChild(btnElem)
221
-
222
- // 表情包插件预加载
223
- if (PlugObj.Name === 'emoticons') {
224
- const emoPlug = new PlugObj(this) as EmoticonsPlug
225
- this.plugList[PlugObj.Name] = emoPlug
226
-
227
- window.setTimeout(() => {
228
- emoPlug.loadEmoticonsData()
229
- }, 1000) // 延迟 1s 加载
230
- }
231
-
232
- btnElem.addEventListener('click', () => {
233
- let plug = this.plugList[PlugObj.Name]
234
- if (!plug) {
235
- plug = new PlugObj(this)
236
- this.plugList[PlugObj.Name] = plug
237
- }
238
-
239
- this.$plugBtnWrap.querySelectorAll('.active').forEach(item => item.classList.remove('active'))
240
-
241
- // 若点击已打开的,则收起
242
- if (PlugObj.Name === this.openedPlugName) {
243
- plug.onHide()
244
- this.$plugWrap.style.display = 'none'
245
- this.openedPlugName = null
246
- return
247
- }
248
-
249
- if (this.$plugWrap.querySelector(`[data-plug-name="${PlugObj.Name}"]`) === null) {
250
- // 需要初始化
251
- const plugEl = plug.getEl()
252
- plugEl.setAttribute('data-plug-name', PlugObj.Name)
253
- plugEl.style.display = 'none'
254
- this.$plugWrap.appendChild(plugEl)
255
- }
256
-
257
- (Array.from(this.$plugWrap.children) as HTMLElement[]).forEach((plugItemEl: HTMLElement) => {
258
- const plugItemName = plugItemEl.getAttribute('data-plug-name')!
259
- if (plugItemName === PlugObj.Name) {
260
- plugItemEl.style.display = ''
261
- this.plugList[plugItemName].onShow()
262
- } else {
263
- plugItemEl.style.display = 'none'
264
- this.plugList[plugItemName].onHide()
265
- }
266
- })
267
-
268
- this.$plugWrap.style.display = ''
269
- this.openedPlugName = PlugObj.Name
270
-
271
- btnElem.classList.add('active')
272
- })
273
- })
274
-
275
- this.initImgUpload()
276
- }
277
-
278
- /** 关闭编辑器插件 */
279
- closePlug () {
280
- this.$plugWrap.innerHTML = ''
281
- this.$plugWrap.style.display = 'none'
282
- this.openedPlugName = null
283
- }
284
-
285
- /** 允许的图片格式 */
286
- allowImgExts = ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'svg', 'webp']
287
-
288
- /** 初始化图片上传功能 */
289
- initImgUpload() {
290
- this.$imgUploadBtn = Utils.createElement(`<span class="atk-plug-btn">图片</span>`)
291
- this.$plugBtnWrap.querySelector('[data-plug-name="preview"]')!.before(this.$imgUploadBtn) // 显示在预览图标之前
292
-
293
- this.$imgUploadInput = document.createElement('input')
294
- this.$imgUploadInput.type = 'file'
295
- this.$imgUploadInput.style.display = 'none'
296
- this.$imgUploadInput.accept = this.allowImgExts.map(o => `.${o}`).join(',')
297
- this.$imgUploadBtn.after(this.$imgUploadInput)
298
-
299
- // 按钮点击
300
- this.$imgUploadBtn.onclick = () => {
301
- // 选择图片
302
- const $input = this.$imgUploadInput!
303
- $input.onchange = () => {
304
- (async () => { // 解决阻塞 UI 问题
305
- if (!$input.files || $input.files.length === 0) return
306
- const file = $input.files[0]
307
- this.uploadImg(file)
308
- })()
309
- }
310
- $input.click() // 显示选择图片对话框
311
- }
312
-
313
- // 统一从 FileList 获取文件并上传图片方法
314
- const uploadFromFileList = (files?: FileList) => {
315
- if (!files) return
316
-
317
- for (let i = 0; i < files.length; i++) {
318
- const file = files[i]
319
- this.uploadImg(file)
320
- }
321
- }
322
-
323
- // 拖拽图片
324
- // @link https://developer.mozilla.org/zh-CN/docs/Web/API/HTML_Drag_and_Drop_API/File_drag_and_drop
325
- // 阻止浏览器的默认释放行为
326
- this.$textarea.addEventListener('dragover', (evt) => {
327
- evt.stopPropagation()
328
- evt.preventDefault()
329
- })
330
-
331
- this.$textarea.addEventListener('drop', (evt) => {
332
- const files = evt.dataTransfer?.files
333
- if (files?.length) {
334
- evt.preventDefault()
335
- uploadFromFileList(files)
336
- }
337
- })
338
-
339
- // 粘贴图片
340
- this.$textarea.addEventListener('paste', (evt) => {
341
- const files = evt.clipboardData?.files
342
- if (files?.length) {
343
- evt.preventDefault()
344
- uploadFromFileList(files)
345
- }
346
- })
347
- }
348
-
349
- /** 刷新图片上传按钮 */
350
- refreshUploadBtn() {
351
- if (!this.$imgUploadBtn) return
352
-
353
- if (!this.ctx.conf.imgUpload) {
354
- this.$imgUploadBtn.setAttribute('atk-only-admin-show', '')
355
- this.ctx.trigger('check-admin-show-el')
356
- }
357
- }
358
-
359
- async uploadImg(file: File) {
360
- const fileExt = /[^.]+$/.exec(file.name)
361
- if (!fileExt || !this.allowImgExts.includes(fileExt[0])) return
362
-
363
- // 未登录提示
364
- if (!this.ctx.user.checkHasBasicUserInfo()) {
365
- this.showNotify('填入你的名字邮箱才能上传哦', 'w')
366
- return
367
- }
368
-
369
- // 插入图片前换一行
370
- let insertPrefix = '\n'
371
- if (this.$textarea.value.trim() === '') insertPrefix = ''
372
-
373
- // 插入占位加载文字
374
- const uploadPlaceholderTxt = `${insertPrefix}![](Uploading ${file.name}...)`
375
- this.insertContent(uploadPlaceholderTxt)
376
-
377
- // 上传图片
378
- let resp: any
379
- try {
380
- resp = await new Api(this.ctx).imgUpload(file)
381
- } catch (err: any) {
382
- console.error(err)
383
- this.showNotify(`图片上传失败,${err.msg}`, 'e')
384
- }
385
- if (!!resp && resp.img_url) {
386
- let imgURL = resp.img_url as string
387
-
388
- // 若为相对路径,加上 artalk server
389
- if (!Utils.isValidURL(imgURL)) imgURL = Utils.getURLBasedOnApi(this.ctx, imgURL)
390
-
391
- // 上传成功插入图片
392
- this.setContent(this.$textarea.value.replace(uploadPlaceholderTxt, `${insertPrefix}![](${imgURL})`))
393
- } else {
394
- // 上传失败删除加载文字
395
- this.setContent(this.$textarea.value.replace(uploadPlaceholderTxt, ''))
396
- }
397
- }
398
-
399
- insertContent (val: string) {
400
- if ((document as any).selection) {
401
- this.$textarea.focus();
402
- (document as any).selection.createRange().text = val
403
- this.$textarea.focus()
404
- } else if (this.$textarea.selectionStart || this.$textarea.selectionStart === 0) {
405
- const sStart = this.$textarea.selectionStart
406
- const sEnd = this.$textarea.selectionEnd
407
- const sT = this.$textarea.scrollTop
408
- this.setContent(this.$textarea.value.substring(0, sStart) + val + this.$textarea.value.substring(sEnd, this.$textarea.value.length))
409
- this.$textarea.focus()
410
- this.$textarea.selectionStart = sStart + val.length
411
- this.$textarea.selectionEnd = sStart + val.length
412
- this.$textarea.scrollTop = sT
413
- } else {
414
- this.$textarea.focus()
415
- this.$textarea.value += val
416
- }
417
- }
418
-
419
- setContent (val: string) {
420
- this.$textarea.value = val
421
- this.saveContent()
422
- if (!!this.plugList && !!this.plugList.preview) {
423
- this.plugList.preview.updateContent()
424
- }
425
-
426
- // 延迟执行防止无效
427
- window.setTimeout(() => {
428
- this.adjustTextareaHeight()
429
- }, 80)
430
- }
431
-
432
- clearEditor () {
433
- this.setContent('')
434
- this.cancelReply()
435
- }
436
-
437
- /** 获取最终用于 submit 的数据 */
438
- getFinalContent () {
439
- let content = this.getContentOriginal()
440
-
441
- // 表情包处理
442
- if (this.plugList && this.plugList.emoticons) {
443
- const emoticonsPlug = this.plugList.emoticons as EmoticonsPlug
444
- content = emoticonsPlug.transEmoticonImageText(content)
445
- }
446
-
447
- return content
448
- }
449
-
450
- getContentOriginal () {
451
- return this.$textarea.value || '' // Tip: !!"0" === true
452
- }
453
-
454
- getContentMarked () {
455
- return Utils.marked(this.ctx, this.getFinalContent())
456
- }
457
-
458
- initBottomPart () {
459
- this.initReply()
460
- this.initSubmit()
461
- }
462
-
463
- initReply () {
464
- this.replyComment = null
465
- this.$sendReply = null
466
- }
467
-
468
- setReply (commentData: CommentData, $comment: HTMLElement, scroll = true) {
469
- if (this.replyComment !== null) {
470
- this.cancelReply()
471
- }
472
-
473
- if (this.$sendReply === null) {
474
- this.$sendReply = Utils.createElement('<div class="atk-send-reply">回复 <span class="atk-text"></span><span class="atk-cancel" title="取消 AT">×</span></div>');
475
- this.$sendReply.querySelector<HTMLElement>('.atk-text')!.innerText = `@${commentData.nick}`
476
- this.$sendReply.addEventListener('click', () => {
477
- this.cancelReply()
478
- })
479
- this.$textareaWrap.append(this.$sendReply)
480
- }
481
- this.replyComment = commentData
482
-
483
- if (this.ctx.conf.editorTravel === true) {
484
- this.travel($comment)
485
- }
486
-
487
- if (scroll) Ui.scrollIntoView(this.$el)
488
-
489
- this.$textarea.focus()
490
- }
491
-
492
- cancelReply () {
493
- if (this.$sendReply !== null) {
494
- this.$sendReply.remove()
495
- this.$sendReply = null
496
- }
497
- this.replyComment = null
498
-
499
- if (this.ctx.conf.editorTravel === true) {
500
- this.travelBack()
501
- }
502
- }
503
-
504
- initSubmit () {
505
- this.$submitBtn.innerText = this.ctx.conf.sendBtn || 'Send'
506
-
507
- this.$submitBtn.addEventListener('click', (evt) => {
508
- const btnEl = evt.currentTarget
509
- this.submit()
510
- })
511
- }
512
-
513
- async submit () {
514
- if (this.getFinalContent().trim() === '') {
515
- this.$textarea.focus()
516
- return
517
- }
518
-
519
- this.ctx.trigger('editor-submit')
520
-
521
- Ui.showLoading(this.$el)
522
-
523
- try {
524
- const nComment = await new Api(this.ctx).add({
525
- content: this.getFinalContent(),
526
- nick: this.user.data.nick,
527
- email: this.user.data.email,
528
- link: this.user.data.link,
529
- rid: (this.replyComment === null) ? 0 : this.replyComment.id,
530
- page_key: (this.replyComment === null) ? this.ctx.conf.pageKey : this.replyComment.page_key,
531
- page_title: (this.replyComment === null) ? this.ctx.conf.pageTitle : undefined,
532
- site_name: (this.replyComment === null) ? this.ctx.conf.site : this.replyComment.site_name
533
- })
534
-
535
- // 回复不同页面的评论
536
- if (this.replyComment !== null && this.replyComment.page_key !== this.ctx.conf.pageKey) {
537
- window.open(`${this.replyComment.page_url}#atk-comment-${nComment.id}`)
538
- }
539
-
540
- this.ctx.trigger('list-insert', nComment)
541
- this.clearEditor() // 清空编辑器
542
- this.ctx.trigger('editor-submitted')
543
- } catch (err: any) {
544
- console.error(err)
545
- this.showNotify(`评论失败,${err.msg || String(err)}`, 'e')
546
- return
547
- } finally {
548
- Ui.hideLoading(this.$el)
549
- }
550
- }
551
-
552
- showNotify (msg: string, type) {
553
- Ui.showNotify(this.$notifyWrap, msg, type)
554
- }
555
-
556
- /** 关闭评论 */
557
- close () {
558
- if (!this.$textareaWrap.querySelector('.atk-comment-closed'))
559
- this.$textareaWrap.prepend(Utils.createElement('<div class="atk-comment-closed">仅管理员可评论</div>'))
560
-
561
- if (!this.user.data.isAdmin) {
562
- this.$textarea.style.display = 'none'
563
- this.closePlug()
564
- this.$bottom.style.display = 'none'
565
- } else {
566
- // 管理员一直打开评论
567
- this.$textarea.style.display = ''
568
- this.$bottom.style.display = ''
569
- }
570
- }
571
-
572
- /** 打开评论 */
573
- open () {
574
- this.$textareaWrap.querySelector('.atk-comment-closed')?.remove()
575
- this.$textarea.style.display = ''
576
- this.$bottom.style.display = ''
577
- }
578
-
579
- travel ($afterEl: HTMLElement) {
580
- if (this.isTraveling) return
581
- this.isTraveling = true
582
- this.$el.after(Utils.createElement('<div class="atk-editor-travel-placeholder"></div>'))
583
-
584
- const $travelPlace = Utils.createElement('<div></div>')
585
- $afterEl.after($travelPlace)
586
- $travelPlace.replaceWith(this.$el)
587
-
588
- this.$el.classList.add('atk-fade-in') // 添加渐入动画
589
- }
590
-
591
- travelBack () {
592
- if (!this.isTraveling) return
593
- this.isTraveling = false
594
- this.ctx.$root.querySelector('.atk-editor-travel-placeholder')?.replaceWith(this.$el)
595
-
596
- // 取消回复
597
- if (this.replyComment !== null) this.cancelReply()
598
- }
599
-
600
- initRemoteEmoticons () {
601
-
602
- }
603
- }
604
-
@@ -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>