artalk 2.4.4 → 2.5.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 (156) hide show
  1. package/README.md +27 -0
  2. package/dist/Artalk.css +1 -1
  3. package/dist/Artalk.es.js +7066 -6256
  4. package/dist/Artalk.iife.js +1 -1
  5. package/dist/Artalk.js +1 -1
  6. package/dist/ArtalkLite.css +1 -1
  7. package/dist/ArtalkLite.es.js +5095 -4396
  8. package/dist/ArtalkLite.iife.js +1 -1
  9. package/dist/ArtalkLite.js +1 -1
  10. package/dist/i18n/jp.js +1 -1
  11. package/dist/i18n/zh-TW.js +1 -1
  12. package/dist/src/api/index.d.ts +2 -2
  13. package/dist/src/api/system.d.ts +5 -2
  14. package/dist/src/artalk.d.ts +42 -34
  15. package/dist/src/comment/comment.d.ts +2 -2
  16. package/dist/src/comment/height-limit.d.ts +30 -0
  17. package/dist/src/comment/render-ctx.d.ts +23 -0
  18. package/dist/src/comment/render.d.ts +2 -50
  19. package/dist/src/comment/renders/actions.d.ts +5 -0
  20. package/dist/src/comment/renders/avatar.d.ts +5 -0
  21. package/dist/src/comment/renders/content.d.ts +5 -0
  22. package/dist/src/comment/renders/header.d.ts +5 -0
  23. package/dist/src/comment/renders/index.d.ts +2 -0
  24. package/dist/src/comment/renders/pending.d.ts +5 -0
  25. package/dist/src/comment/renders/reply-at.d.ts +5 -0
  26. package/dist/src/comment/renders/reply-to.d.ts +5 -0
  27. package/dist/src/components/dialog.d.ts +1 -1
  28. package/dist/src/config.d.ts +8 -0
  29. package/dist/src/context.d.ts +16 -29
  30. package/dist/src/editor/edit-mode.d.ts +9 -0
  31. package/dist/src/editor/editor.d.ts +37 -45
  32. package/dist/src/editor/funcs.d.ts +2 -0
  33. package/dist/src/editor/mover.d.ts +8 -0
  34. package/dist/src/editor/plug-manager.d.ts +17 -0
  35. package/dist/src/editor/plugs/editor-plug.d.ts +13 -1
  36. package/dist/src/editor/reply.d.ts +9 -0
  37. package/dist/src/editor/submit.d.ts +16 -0
  38. package/dist/src/editor/ui.d.ts +25 -0
  39. package/dist/src/i18n/external.d.ts +70 -0
  40. package/dist/src/i18n/index.d.ts +11 -75
  41. package/dist/src/layer/index.d.ts +2 -2
  42. package/dist/src/layer/layer.d.ts +1 -1
  43. package/dist/src/lib/checker/captcha-renders.d.ts +5 -0
  44. package/dist/src/lib/checker/index.d.ts +16 -10
  45. package/dist/src/lib/dark-mode.d.ts +4 -0
  46. package/dist/src/lib/marked.d.ts +9 -0
  47. package/dist/src/lib/user.d.ts +6 -3
  48. package/dist/src/lib/utils.d.ts +1 -5
  49. package/dist/src/list/list-lite.d.ts +15 -11
  50. package/dist/src/list/list-nest.d.ts +2 -2
  51. package/dist/src/list/list-ui.d.ts +16 -0
  52. package/dist/src/list/list.d.ts +0 -1
  53. package/dist/src/list/paginator/adaptors/index.d.ts +12 -0
  54. package/dist/src/list/paginator/adaptors/pagination.d.ts +9 -0
  55. package/dist/src/list/paginator/adaptors/read-more.d.ts +10 -0
  56. package/dist/src/list/paginator/holder.d.ts +16 -0
  57. package/dist/src/list/paginator/index.d.ts +10 -0
  58. package/dist/src/service.d.ts +46 -0
  59. package/dist/types/artalk-config.d.ts +5 -2
  60. package/dist/types/artalk-data.d.ts +3 -3
  61. package/dist/types/context.d.ts +13 -20
  62. package/dist/types/global.d.ts +1 -0
  63. package/package.json +6 -7
  64. package/src/api/comment.ts +7 -7
  65. package/src/api/page.ts +0 -2
  66. package/src/api/request.ts +6 -4
  67. package/src/api/system.ts +3 -12
  68. package/src/api/upload.ts +3 -4
  69. package/src/api/user.ts +6 -8
  70. package/src/artalk.ts +103 -158
  71. package/src/comment/comment.ts +7 -6
  72. package/src/comment/height-limit.ts +100 -0
  73. package/src/comment/render-ctx.ts +30 -0
  74. package/src/comment/render.ts +31 -375
  75. package/src/comment/renders/actions.ts +114 -0
  76. package/src/comment/renders/avatar.ts +22 -0
  77. package/src/comment/renders/content.ts +39 -0
  78. package/src/comment/renders/header.ts +84 -0
  79. package/src/comment/renders/index.ts +19 -0
  80. package/src/comment/renders/pending.ts +12 -0
  81. package/src/comment/renders/reply-at.ts +16 -0
  82. package/src/comment/renders/reply-to.ts +24 -0
  83. package/src/config.ts +65 -0
  84. package/src/context.ts +33 -86
  85. package/src/defaults.ts +1 -0
  86. package/src/editor/edit-mode.ts +103 -0
  87. package/src/editor/editor.ts +101 -395
  88. package/src/editor/funcs.ts +97 -0
  89. package/src/editor/mover.ts +43 -0
  90. package/src/editor/plug-manager.ts +172 -0
  91. package/src/editor/plugs/editor-plug.ts +20 -2
  92. package/src/editor/plugs/{emoticons-plug.less → emoticons-plug.scss} +5 -4
  93. package/src/editor/plugs/emoticons-plug.ts +2 -1
  94. package/src/editor/plugs/header-input-plug.ts +11 -10
  95. package/src/editor/plugs/{preview-plug.less → preview-plug.scss} +1 -1
  96. package/src/editor/plugs/preview-plug.ts +8 -4
  97. package/src/editor/plugs/upload-plug.ts +12 -14
  98. package/src/editor/reply.ts +62 -0
  99. package/src/editor/submit.ts +99 -0
  100. package/src/editor/ui.ts +36 -0
  101. package/src/i18n/external.ts +10 -0
  102. package/src/i18n/index.ts +22 -19
  103. package/src/i18n/jp.ts +1 -1
  104. package/src/i18n/zh-TW.ts +1 -1
  105. package/src/layer/index.ts +2 -2
  106. package/src/layer/layer.ts +4 -4
  107. package/src/layer/sidebar-layer.ts +4 -2
  108. package/src/lib/checker/admin.ts +37 -0
  109. package/src/lib/checker/captcha-renders.ts +68 -0
  110. package/src/lib/checker/captcha.ts +26 -0
  111. package/src/lib/checker/index.ts +36 -20
  112. package/src/lib/dark-mode.ts +48 -0
  113. package/src/lib/marked.ts +133 -0
  114. package/src/lib/user.ts +13 -7
  115. package/src/lib/utils.ts +1 -123
  116. package/src/list/list-lite.ts +59 -162
  117. package/src/list/list-ui.ts +137 -0
  118. package/src/list/list.ts +33 -60
  119. package/src/list/paginator/adaptors/index.ts +21 -0
  120. package/src/list/paginator/adaptors/pagination.ts +42 -0
  121. package/src/list/paginator/adaptors/read-more.ts +56 -0
  122. package/src/list/paginator/holder.ts +56 -0
  123. package/src/list/paginator/index.ts +13 -0
  124. package/src/service.ts +118 -0
  125. package/src/style/{_color.less → _color.scss} +3 -3
  126. package/src/style/{_extend.less → _extend.scss} +1 -1
  127. package/src/style/_variables.scss +3 -0
  128. package/src/style/{comment.less → comment.scss} +36 -12
  129. package/src/style/{editor.less → editor.scss} +11 -11
  130. package/src/style/{list.less → list.scss} +12 -10
  131. package/src/style/{main.less → main.scss} +24 -23
  132. package/src/style/{sidebar-layer.less → sidebar-layer.scss} +8 -8
  133. package/types/artalk-config.d.ts +5 -2
  134. package/types/artalk-data.d.ts +3 -3
  135. package/types/context.d.ts +13 -20
  136. package/types/global.d.ts +1 -0
  137. package/CNAME +0 -1
  138. package/LICENSE +0 -165
  139. package/index.html +0 -157
  140. package/postcss.config.js +0 -5
  141. package/scripts/build-i18n.mjs +0 -46
  142. package/src/lib/checker/admin-checker.ts +0 -36
  143. package/src/lib/checker/captcha-checker.ts +0 -84
  144. package/src/style/_variables.less +0 -3
  145. package/test/ArtalkCount.html +0 -16
  146. package/test/ArtalkI18n.html +0 -16
  147. package/test/ArtalkLite.html +0 -15
  148. package/test/CSRF.html +0 -26
  149. package/tsconfig.json +0 -12
  150. package/vite-base.config.ts +0 -21
  151. package/vite-demo.config.ts +0 -12
  152. package/vite-lite.config.ts +0 -22
  153. package/vite.config.ts +0 -27
  154. /package/dist/src/lib/checker/{admin-checker.d.ts → admin.d.ts} +0 -0
  155. /package/dist/src/lib/checker/{captcha-checker.d.ts → captcha.d.ts} +0 -0
  156. /package/src/style/{pagination.less → pagination.scss} +0 -0
@@ -1,177 +1,98 @@
1
- import { CommentData } from '~/types/artalk-data'
2
- import Context from '~/types/context'
1
+ import type { CommentData } from '~/types/artalk-data'
2
+ import type Context from '~/types/context'
3
3
  import Component from '../lib/component'
4
4
  import * as Utils from '../lib/utils'
5
5
  import * as Ui from '../lib/ui'
6
-
7
- import EditorHTML from './editor.html?raw'
8
-
9
- import EmoticonsPlug from './plugs/emoticons-plug'
10
- import UploadPlug from './plugs/upload-plug'
11
- import PreviewPlug from './plugs/preview-plug'
12
- import EditorPlug from './plugs/editor-plug'
13
- import HeaderInputPlug from './plugs/header-input-plug'
6
+ import marked from '../lib/marked'
7
+ import User from '../lib/user'
8
+ import initEditorFuncs from './funcs'
9
+ import { render, EditorUI } from './ui'
10
+ import { PlugManager } from './plug-manager'
11
+ import { Mover } from './mover'
12
+ import { ReplyManager } from './reply'
13
+ import { EditModeManager } from './edit-mode'
14
+ import { SubmitManager } from './submit'
14
15
 
15
16
  export default class Editor extends Component {
16
- private get user() { return this.ctx.user }
17
-
18
- public $header: HTMLElement
19
- public $nick: HTMLInputElement
20
- public $email: HTMLInputElement
21
- public $link: HTMLInputElement
22
- public get $inputs() {
23
- return { nick: this.$nick, email: this.$email, link: this.$link }
24
- }
17
+ /** 界面 */
18
+ private ui: EditorUI
19
+ public getUI() { return this.ui }
25
20
 
26
- public $textareaWrap: HTMLElement
27
- public $textarea: HTMLTextAreaElement
28
- public $bottom: HTMLElement
29
- public $submitBtn: HTMLButtonElement
30
- public $notifyWrap: HTMLElement
21
+ /** 插件管理器 */
22
+ private plugs?: PlugManager
23
+ public getPlugs() { return this.plugs }
24
+ public setPlugs(p: PlugManager) { this.plugs = p }
31
25
 
32
- // Editor 的不同状态
33
- private isTraveling = false
26
+ /** 评论框移动 */
27
+ private mover?: Mover
28
+ public setMover(m: Mover) { this.mover = m }
34
29
 
35
30
  /** 回复评论 */
36
- private replyComment: CommentData|null = null
37
- private $sendReply: HTMLElement|null = null
38
- public get isReplyMode() { return this.replyComment !== null }
31
+ private reply?: ReplyManager
32
+ public setReplyManager(m: ReplyManager) { this.reply = m }
33
+ public getReplyManager() { return this.reply }
34
+ public get isReplyMode() { return !!this.reply?.comment }
39
35
 
40
36
  /** 编辑评论 */
41
- private editComment: CommentData|null = null
42
- private $editCancelBtn: HTMLElement|null = null
43
- public get isEditMode() { return this.editComment !== null }
37
+ private editMode?: EditModeManager
38
+ public setEditModeManager(m: EditModeManager) { this.editMode = m }
39
+ public get isEditMode() { return !!this.editMode?.comment }
44
40
 
45
- /** 启用的插件 */
46
- private readonly ENABLED_PLUGS = [ EmoticonsPlug, UploadPlug, PreviewPlug, HeaderInputPlug ]
47
- public plugList: { [name: string]: EditorPlug } = {}
48
- private openedPlugName: string|null = null
49
- public $plugPanelWrap: HTMLElement
50
- public $plugBtnWrap: HTMLElement
41
+ /** 提交评论 */
42
+ private submitManager?: SubmitManager
43
+ public setSubmitManager(m: SubmitManager) { this.submitManager = m }
44
+ public getSubmitManager() { return this.submitManager }
51
45
 
52
46
  public constructor(ctx: Context) {
53
47
  super(ctx)
54
48
 
55
- this.$el = Utils.createElement(EditorHTML)
56
-
57
- this.$header = this.$el.querySelector('.atk-header')!
58
- this.$nick = this.$header.querySelector('[name="nick"]')!
59
- this.$email = this.$header.querySelector('[name="email"]')!
60
- this.$link = this.$header.querySelector('[name="link"]')!
61
-
62
- this.$textareaWrap = this.$el.querySelector('.atk-textarea-wrap')!
63
- this.$textarea = this.$el.querySelector('.atk-textarea')!
64
- this.$bottom = this.$el.querySelector('.atk-bottom')!
65
- this.$submitBtn = this.$el.querySelector('.atk-send-btn')!
66
- this.$notifyWrap = this.$el.querySelector('.atk-notify-wrap')!
49
+ // init editor ui
50
+ this.ui = render()
51
+ this.$el = this.ui.$el
67
52
 
68
- this.$plugBtnWrap = this.$el.querySelector('.atk-plug-btn-wrap')!
69
- this.$plugPanelWrap = this.$el.querySelector('.atk-plug-panel-wrap')!
70
-
71
- // 监听事件
53
+ // event listen
72
54
  this.ctx.on('conf-loaded', () => {
73
- // 执行初始化
74
- this.initLocalStorage()
75
- this.initHeader()
76
- this.initTextarea()
77
- this.initSubmitBtn()
78
- this.initPlugs()
55
+ initEditorFuncs(this) // init editor funcs
79
56
  })
80
57
  }
81
58
 
82
- private initLocalStorage() {
83
- const localContent = window.localStorage.getItem('ArtalkContent') || ''
84
- if (localContent.trim() !== '') {
85
- this.showNotify(this.$t('restoredMsg'), 'i')
86
- this.setContent(localContent)
87
- }
88
-
89
- this.$textarea.addEventListener('input', () => (this.saveToLocalStorage()))
59
+ public getHeaderInputEls() {
60
+ return { nick: this.ui.$nick, email: this.ui.$email, link: this.ui.$link }
90
61
  }
91
62
 
92
- private saveToLocalStorage() {
63
+ public saveToLocalStorage() {
93
64
  window.localStorage.setItem('ArtalkContent', this.getContentOriginal().trim())
94
65
  }
95
66
 
96
- private initHeader() {
97
- Object.entries(this.$inputs).forEach(([key, $input]) => {
98
- $input.value = this.user.data[key] || ''
99
- $input.addEventListener('input', () => this.onHeaderInput(key, $input))
100
-
101
- // 设置 Placeholder
102
- $input.placeholder = `${this.$t(key as any)}`
103
- })
104
- }
105
-
106
- private onHeaderInput(key: string, $input: HTMLInputElement) {
107
- if (this.isEditMode) return // 评论编辑模式,不修改个人信息
108
-
109
- this.user.update({
110
- [key]: $input.value.trim()
111
- })
112
-
113
- // 插件监听事件响应
114
- Object.entries(this.plugList).forEach(([plugName, plug]) => {
115
- if (plug.onHeaderInput) plug.onHeaderInput(key, $input)
116
- })
117
- }
118
-
119
- private initTextarea() {
120
- // 占位符
121
- this.$textarea.placeholder = this.ctx.conf.placeholder || this.$t('placeholder')
122
-
123
- // 修复按下 Tab 输入的内容
124
- this.$textarea.addEventListener('keydown', (e) => {
125
- const keyCode = e.keyCode || e.which
126
-
127
- if (keyCode === 9) {
128
- e.preventDefault()
129
- this.insertContent('\t')
130
- }
131
- })
132
-
133
- // 输入框高度随内容而变化
134
- this.$textarea.addEventListener('input', () => {
135
- this.adjustTextareaHeight()
136
- })
137
- }
138
-
139
- private refreshSendBtnText() {
140
- if (this.isEditMode) this.$submitBtn.innerText = this.$t('save')
141
- else this.$submitBtn.innerText = this.ctx.conf.sendBtn || this.$t('send')
142
- }
143
-
144
- private initSubmitBtn() {
145
- this.refreshSendBtnText()
146
- this.$submitBtn.addEventListener('click', () => (this.submit()))
67
+ public refreshSendBtnText() {
68
+ if (this.isEditMode) this.ui.$submitBtn.innerText = this.$t('save')
69
+ else this.ui.$submitBtn.innerText = this.ctx.conf.sendBtn || this.$t('send')
147
70
  }
148
71
 
149
72
  /** 最终用于 submit 的数据 */
150
73
  public getFinalContent() {
151
74
  let content = this.getContentOriginal()
152
75
 
153
- // 表情包处理
154
- if (this.plugList.emoticons) {
155
- content = (this.plugList.emoticons as EmoticonsPlug).transEmoticonImageText(content)
156
- }
76
+ // plug hook: final content transformer
77
+ if (this.plugs) content = this.plugs.getTransformedContent(content)
157
78
 
158
79
  return content
159
80
  }
160
81
 
161
82
  public getContentOriginal() {
162
- return this.$textarea.value || '' // Tip: !!"0" === true
83
+ return this.ui.$textarea.value || '' // Tip: !!"0" === true
163
84
  }
164
85
 
165
86
  public getContentMarked() {
166
- return Utils.marked(this.ctx, this.getFinalContent())
87
+ return marked(this.ctx, this.getFinalContent())
167
88
  }
168
89
 
169
90
  public setContent(val: string) {
170
- this.$textarea.value = val
91
+ this.ui.$textarea.value = val
171
92
  this.saveToLocalStorage()
172
- if (this.plugList.preview) {
173
- ;(this.plugList.preview as PreviewPlug).updateContent()
174
- }
93
+
94
+ // plug hook: content updated
95
+ if (this.plugs) this.plugs.triggerContentUpdatedEvt(val)
175
96
 
176
97
  // 延迟执行防止无效
177
98
  window.setTimeout(() => {
@@ -181,333 +102,118 @@ export default class Editor extends Component {
181
102
 
182
103
  public insertContent(val: string) {
183
104
  if ((document as any).selection) {
184
- this.$textarea.focus();
105
+ this.ui.$textarea.focus();
185
106
  (document as any).selection.createRange().text = val
186
- this.$textarea.focus()
187
- } else if (this.$textarea.selectionStart || this.$textarea.selectionStart === 0) {
188
- const sStart = this.$textarea.selectionStart
189
- const sEnd = this.$textarea.selectionEnd
190
- const sT = this.$textarea.scrollTop
191
- this.setContent(this.$textarea.value.substring(0, sStart) + val + this.$textarea.value.substring(sEnd, this.$textarea.value.length))
192
- this.$textarea.focus()
193
- this.$textarea.selectionStart = sStart + val.length
194
- this.$textarea.selectionEnd = sStart + val.length
195
- this.$textarea.scrollTop = sT
107
+ this.ui.$textarea.focus()
108
+ } else if (this.ui.$textarea.selectionStart || this.ui.$textarea.selectionStart === 0) {
109
+ const sStart = this.ui.$textarea.selectionStart
110
+ const sEnd = this.ui.$textarea.selectionEnd
111
+ const sT = this.ui.$textarea.scrollTop
112
+ this.setContent(this.ui.$textarea.value.substring(0, sStart) + val + this.ui.$textarea.value.substring(sEnd, this.ui.$textarea.value.length))
113
+ this.ui.$textarea.focus()
114
+ this.ui.$textarea.selectionStart = sStart + val.length
115
+ this.ui.$textarea.selectionEnd = sStart + val.length
116
+ this.ui.$textarea.scrollTop = sT
196
117
  } else {
197
- this.$textarea.focus()
198
- this.$textarea.value += val
118
+ this.ui.$textarea.focus()
119
+ this.ui.$textarea.value += val
199
120
  }
200
121
  }
201
122
 
202
- private 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`
123
+ public adjustTextareaHeight() {
124
+ const diff = this.ui.$textarea.offsetHeight - this.ui.$textarea.clientHeight
125
+ this.ui.$textarea.style.height = '0px' // it's a magic. 若不加此行,内容减少,高度回不去
126
+ this.ui.$textarea.style.height = `${this.ui.$textarea.scrollHeight + diff}px`
206
127
  }
207
128
 
208
- /** 设置回复评论 */
209
- public setReply(commentData: CommentData, $comment: HTMLElement, scroll = true) {
210
- if (this.isEditMode) this.cancelEditComment()
211
- if (this.isReplyMode) this.cancelReply()
212
-
213
- if (this.$sendReply === null) {
214
- this.$sendReply = Utils.createElement(`<div class="atk-send-reply">${this.$t('reply')} <span class="atk-text"></span><span class="atk-cancel">×</span></div>`);
215
- this.$sendReply.querySelector<HTMLElement>('.atk-text')!.innerText = `@${commentData.nick}`
216
- this.$sendReply.addEventListener('click', () => {
217
- this.cancelReply()
218
- })
219
- this.$textareaWrap.append(this.$sendReply)
220
- }
221
- this.replyComment = commentData
222
-
223
- if (this.ctx.conf.editorTravel === true) {
224
- this.travel($comment)
225
- }
129
+ public focus() {
130
+ this.ui.$textarea.focus()
131
+ }
226
132
 
227
- if (scroll) Ui.scrollIntoView(this.$el)
133
+ public reset() {
134
+ this.setContent('')
135
+ this.cancelReply()
136
+ this.cancelEditComment()
137
+ }
228
138
 
229
- this.$textarea.focus()
139
+ /** 设置回复评论 */
140
+ public setReply(commentData: CommentData, $comment: HTMLElement, scroll = true) {
141
+ this.reply?.setReply(commentData, $comment, scroll)
230
142
  }
231
143
 
232
144
  /** 取消回复评论 */
233
145
  public cancelReply() {
234
- if (this.$sendReply !== null) {
235
- this.$sendReply.remove()
236
- this.$sendReply = null
237
- }
238
- this.replyComment = null
239
-
240
- if (this.ctx.conf.editorTravel === true) {
241
- this.travelBack()
242
- }
146
+ this.reply?.cancelReply()
243
147
  }
244
148
 
245
149
  /** 设置编辑评论 */
246
150
  public setEditComment(commentData: CommentData, $comment: HTMLElement) {
247
- if (this.isEditMode) this.cancelEditComment()
248
- if (this.isReplyMode) this.cancelReply()
249
-
250
- if (this.$editCancelBtn === null) {
251
- this.$editCancelBtn = Utils.createElement(`<div class="atk-send-reply">${this.$t('editCancel')}<span class="atk-cancel">×</span></div>`);
252
- this.$editCancelBtn.onclick = () => {
253
- this.cancelEditComment()
254
- }
255
- this.$textareaWrap.append(this.$editCancelBtn)
256
- }
257
- this.editComment = commentData
258
-
259
- this.$header.style.display = 'none' // TODO 暂时隐藏
260
-
261
- this.travel($comment)
262
-
263
- this.$nick.value = commentData.nick
264
- this.$email.value = commentData.email || ''
265
- this.$link.value = commentData.link
266
-
267
- this.setContent(commentData.content)
268
- this.$textarea.focus()
269
-
270
- this.refreshSendBtnText()
151
+ this.editMode?.setEdit(commentData, $comment)
271
152
  }
272
153
 
273
154
  /** 取消编辑评论 */
274
155
  public cancelEditComment() {
275
- if (this.$editCancelBtn !== null) {
276
- this.$editCancelBtn.remove()
277
- this.$editCancelBtn = null
278
- }
279
-
280
- this.editComment = null
281
- this.travelBack()
282
-
283
- this.$nick.value = this.user.data.nick
284
- this.$email.value = this.user.data.email
285
- this.$link.value = this.user.data.link
286
-
287
- this.setContent('')
288
- this.refreshSendBtnText()
289
- this.$header.style.display = '' // TODO
156
+ this.editMode?.cancelEdit()
290
157
  }
291
158
 
292
159
  public showNotify(msg: string, type: "i"|"s"|"w"|"e") {
293
- Ui.showNotify(this.$notifyWrap, msg, type)
160
+ Ui.showNotify(this.ui.$notifyWrap, msg, type)
294
161
  }
295
162
 
296
163
  public showLoading() {
297
- Ui.showLoading(this.$el)
164
+ Ui.showLoading(this.ui.$el)
298
165
  }
299
166
 
300
167
  public hideLoading() {
301
- Ui.hideLoading(this.$el)
168
+ Ui.hideLoading(this.ui.$el)
302
169
  }
303
170
 
304
171
  /** 点击评论提交按钮事件 */
305
172
  public async submit() {
306
- if (this.getFinalContent().trim() === '') {
307
- this.$textarea.focus()
308
- return
309
- }
310
-
311
- this.ctx.trigger('editor-submit')
312
-
313
- this.showLoading()
314
-
315
- if (!this.isEditMode) {
316
- await this.submitAdd()
317
- } else {
318
- await this.submitEdit()
319
- }
320
-
321
- this.hideLoading()
322
- }
323
-
324
- /** 创建评论 */
325
- public async submitAdd() {
326
- try {
327
- const nComment = await this.ctx.getApi().comment.add({
328
- content: this.getFinalContent(),
329
- nick: this.user.data.nick,
330
- email: this.user.data.email,
331
- link: this.user.data.link,
332
- rid: (this.replyComment === null) ? 0 : this.replyComment.id,
333
- page_key: (this.replyComment === null) ? this.ctx.conf.pageKey : this.replyComment.page_key,
334
- page_title: (this.replyComment === null) ? this.ctx.conf.pageTitle : undefined,
335
- site_name: (this.replyComment === null) ? this.ctx.conf.site : this.replyComment.site_name
336
- })
337
-
338
- // 回复不同页面的评论
339
- if (this.replyComment !== null && this.replyComment.page_key !== this.ctx.conf.pageKey) {
340
- window.open(`${this.replyComment.page_url}#atk-comment-${nComment.id}`)
341
- }
342
-
343
- this.ctx.insertComment(nComment)
344
-
345
- // 复原编辑器
346
- this.setContent('')
347
- this.cancelReply()
348
-
349
- this.ctx.trigger('editor-submitted')
350
- } catch (err: any) {
351
- console.error(err)
352
- this.showNotify(`${this.$t('commentFail')},${err.msg || String(err)}`, 'e')
353
- }
354
- }
355
-
356
- /** 修改评论 */
357
- public async submitEdit() {
358
- try {
359
- const saveData = {
360
- content: this.getFinalContent(),
361
- nick: this.$nick.value,
362
- email: this.$email.value,
363
- link: this.$link.value,
364
- }
365
- const nComment = await this.ctx.getApi().comment.commentEdit({
366
- ...this.editComment, ...saveData
367
- })
368
- this.ctx.updateComment(nComment)
369
-
370
- // 复原编辑器
371
- this.setContent('')
372
- this.cancelEditComment()
373
-
374
- this.ctx.trigger('editor-submitted')
375
- } catch (err: any) {
376
- console.error(err)
377
- this.showNotify(`${this.$t('commentFail')},${err.msg || String(err)}`, 'e')
378
- }
173
+ if (!this.submitManager) return
174
+ await this.submitManager.do()
379
175
  }
380
176
 
381
177
  /** 关闭评论 */
382
178
  public close() {
383
- if (!this.$textareaWrap.querySelector('.atk-comment-closed'))
384
- this.$textareaWrap.prepend(Utils.createElement(`<div class="atk-comment-closed">${this.$t('onlyAdminCanReply')}</div>`))
179
+ if (!this.ui.$textareaWrap.querySelector('.atk-comment-closed'))
180
+ this.ui.$textareaWrap.prepend(Utils.createElement(`<div class="atk-comment-closed">${this.$t('onlyAdminCanReply')}</div>`))
385
181
 
386
- if (!this.user.data.isAdmin) {
387
- this.$textarea.style.display = 'none'
182
+ if (!User.data.isAdmin) {
183
+ this.ui.$textarea.style.display = 'none'
388
184
  this.closePlugPanel()
389
- this.$bottom.style.display = 'none'
185
+ this.ui.$bottom.style.display = 'none'
390
186
  } else {
391
187
  // 管理员一直打开评论
392
- this.$textarea.style.display = ''
393
- this.$bottom.style.display = ''
188
+ this.ui.$textarea.style.display = ''
189
+ this.ui.$bottom.style.display = ''
394
190
  }
395
191
  }
396
192
 
397
193
  /** 打开评论 */
398
194
  public open() {
399
- this.$textareaWrap.querySelector('.atk-comment-closed')?.remove()
400
- this.$textarea.style.display = ''
401
- this.$bottom.style.display = ''
195
+ this.ui.$textareaWrap.querySelector('.atk-comment-closed')?.remove()
196
+ this.ui.$textarea.style.display = ''
197
+ this.ui.$bottom.style.display = ''
402
198
  }
403
199
 
404
200
  /** 移动评论框到置顶元素之后 */
405
201
  public travel($afterEl: HTMLElement) {
406
- if (this.isTraveling) return
407
- this.isTraveling = true
408
- this.$el.after(Utils.createElement('<div class="atk-editor-travel-placeholder"></div>'))
409
-
410
- const $travelPlace = Utils.createElement('<div></div>')
411
- $afterEl.after($travelPlace)
412
- $travelPlace.replaceWith(this.$el)
413
-
414
- this.$el.classList.add('atk-fade-in') // 添加渐入动画
202
+ this.mover?.move($afterEl)
415
203
  }
416
204
 
417
205
  /** 评论框归位 */
418
206
  public travelBack() {
419
- if (!this.isTraveling) return
420
- this.isTraveling = false
421
- this.ctx.$root.querySelector('.atk-editor-travel-placeholder')?.replaceWith(this.$el)
422
-
423
- // 取消回复
424
- if (this.replyComment !== null) this.cancelReply()
425
- }
426
-
427
- /** 插件初始化 */
428
- private initPlugs() {
429
- this.plugList = {}
430
- this.$plugPanelWrap.innerHTML = ''
431
- this.$plugPanelWrap.style.display = 'none'
432
- this.openedPlugName = null
433
- this.$plugBtnWrap.innerHTML = ''
434
-
435
- const disabledPlugs: string[] = []
436
- if (!this.conf.imgUpload) disabledPlugs.push('upload')
437
- if (!this.conf.emoticons) disabledPlugs.push('emoticons')
438
- if (!this.conf.preview) disabledPlugs.push('preview')
439
-
440
- // 初始化 Editor 插件
441
- this.ENABLED_PLUGS.forEach((Plug) => {
442
- const plugName = Plug.Name
443
-
444
- // 禁用的插件
445
- if (disabledPlugs.includes(plugName)) return
446
-
447
- // 插件对象实例化
448
- const plug = new Plug(this)
449
- this.plugList[plugName] = plug
450
-
451
- // 插件按钮
452
- const $btn = plug.getBtn()
453
- if ($btn) {
454
- this.$plugBtnWrap.appendChild($btn)
455
- $btn.onclick = $btn.onclick || (() => {
456
- // 其他按钮去除 Active
457
- this.$plugBtnWrap.querySelectorAll('.active').forEach(item => item.classList.remove('active'))
458
-
459
- // 若点击已打开的,则关闭打开的面板
460
- if (plugName === this.openedPlugName) {
461
- this.closePlugPanel()
462
- return
463
- }
464
-
465
- this.openPlugPanel(plugName)
466
-
467
- // 当前按钮添加 Active
468
- $btn.classList.add('active')
469
- })
470
-
471
- // 插件面板初始化
472
- const $panel = plug.getPanel()
473
- if ($panel) {
474
- $panel.setAttribute('data-plug-name', plugName)
475
- $panel.style.display = 'none'
476
- this.$plugPanelWrap.appendChild($panel)
477
- }
478
- }
479
- })
207
+ this.mover?.back()
480
208
  }
481
209
 
482
210
  /** 展开插件面板 */
483
211
  public openPlugPanel(plugName: string) {
484
- Object.entries(this.plugList).forEach(([aPlugName, plug]) => {
485
- const plugPanel = plug.getPanel()
486
- if (!plugPanel) return
487
-
488
- if (aPlugName === plugName) {
489
- plugPanel.style.display = ''
490
- if (plug.onPanelShow) plug.onPanelShow()
491
- } else {
492
- plugPanel.style.display = 'none'
493
- if (plug.onPanelHide) plug.onPanelHide()
494
- }
495
- })
496
-
497
- this.$plugPanelWrap.style.display = ''
498
- this.openedPlugName = plugName
212
+ this.plugs?.openPlugPanel(plugName)
499
213
  }
500
214
 
501
215
  /** 收起插件面板 */
502
216
  public closePlugPanel() {
503
- if (!this.openedPlugName) return
504
-
505
- const plug = this.plugList[this.openedPlugName]
506
- if (!plug) return
507
-
508
- if (plug.onPanelHide) plug.onPanelHide()
509
-
510
- this.$plugPanelWrap.style.display = 'none'
511
- this.openedPlugName = null
217
+ this.plugs?.closePlugPanel()
512
218
  }
513
219
  }