artalk 2.3.3 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/dist/Artalk.css +1 -1
  2. package/dist/Artalk.es.js +3184 -2888
  3. package/dist/Artalk.iife.js +1 -1
  4. package/dist/Artalk.js +1 -1
  5. package/dist/ArtalkLite.css +1 -1
  6. package/dist/ArtalkLite.es.js +3157 -2887
  7. package/dist/ArtalkLite.iife.js +1 -1
  8. package/dist/ArtalkLite.js +1 -1
  9. package/dist/src/api/admin.d.ts +10 -0
  10. package/dist/src/api/api-base.d.ts +14 -0
  11. package/dist/src/api/captcha.d.ts +14 -0
  12. package/dist/src/api/comment.d.ts +29 -0
  13. package/dist/src/api/index.d.ts +31 -0
  14. package/dist/src/api/page.d.ts +22 -0
  15. package/dist/src/api/request.d.ts +12 -0
  16. package/dist/src/api/site.d.ts +17 -0
  17. package/dist/src/api/system.d.ts +13 -0
  18. package/dist/src/api/upload.d.ts +11 -0
  19. package/dist/src/api/user.d.ts +28 -0
  20. package/dist/src/artalk.d.ts +56 -0
  21. package/dist/src/comment/actions.d.ts +17 -0
  22. package/dist/src/comment/comment.d.ts +67 -0
  23. package/dist/src/comment/index.d.ts +2 -0
  24. package/dist/src/comment/render.d.ts +76 -0
  25. package/dist/src/components/action-btn.d.ts +52 -0
  26. package/dist/src/components/dialog.d.ts +18 -0
  27. package/dist/src/components/pagination.d.ts +30 -0
  28. package/dist/src/components/read-more-btn.d.ts +33 -0
  29. package/dist/src/context.d.ts +72 -0
  30. package/dist/src/defaults.d.ts +3 -0
  31. package/dist/src/editor/editor.d.ts +84 -0
  32. package/dist/src/editor/index.d.ts +2 -0
  33. package/dist/src/editor/plugs/editor-plug.d.ts +26 -0
  34. package/dist/src/editor/plugs/emoticons-plug.d.ts +36 -0
  35. package/dist/src/editor/plugs/header-input-plug.d.ts +13 -0
  36. package/dist/src/editor/plugs/preview-plug.d.ts +10 -0
  37. package/dist/src/editor/plugs/upload-plug.d.ts +10 -0
  38. package/dist/src/i18n/en.d.ts +69 -0
  39. package/dist/src/i18n/index.d.ts +294 -0
  40. package/dist/src/i18n/zh-CN.d.ts +3 -0
  41. package/dist/src/layer/index.d.ts +3 -0
  42. package/dist/src/layer/layer.d.ts +32 -0
  43. package/dist/src/layer/sidebar-layer.d.ts +20 -0
  44. package/dist/src/lib/checker/admin-checker.d.ts +3 -0
  45. package/dist/src/lib/checker/captcha-checker.d.ts +3 -0
  46. package/dist/src/lib/checker/index.d.ts +42 -0
  47. package/dist/src/lib/component.d.ts +12 -0
  48. package/dist/src/lib/detect.d.ts +10 -0
  49. package/dist/src/lib/stat.d.ts +15 -0
  50. package/dist/src/lib/ui.d.ts +23 -0
  51. package/dist/src/lib/user.d.ts +13 -0
  52. package/dist/src/lib/utils.d.ts +39 -0
  53. package/dist/src/list/index.d.ts +2 -0
  54. package/dist/src/list/list-lite.d.ts +63 -0
  55. package/dist/src/list/list-nest.d.ts +10 -0
  56. package/dist/src/list/list.d.ts +26 -0
  57. package/dist/src/main.d.ts +4 -0
  58. package/dist/types/artalk-config.d.ts +132 -0
  59. package/dist/types/artalk-data.d.ts +204 -0
  60. package/dist/types/context.d.ts +146 -0
  61. package/dist/types/event.d.ts +28 -0
  62. package/dist/types/global.d.ts +0 -0
  63. package/dist/types/plug.d.ts +4 -0
  64. package/index.html +3 -3
  65. package/package.json +5 -5
  66. package/src/api/admin.ts +18 -0
  67. package/src/api/api-base.ts +27 -0
  68. package/src/api/captcha.ts +24 -0
  69. package/src/api/comment.ts +84 -0
  70. package/src/api/index.ts +25 -361
  71. package/src/api/page.ts +85 -0
  72. package/src/api/request.ts +12 -8
  73. package/src/api/site.ts +51 -0
  74. package/src/api/system.ts +39 -0
  75. package/src/api/upload.ts +29 -0
  76. package/src/api/user.ts +75 -0
  77. package/src/artalk.ts +75 -76
  78. package/src/comment/actions.ts +3 -3
  79. package/src/comment/comment.ts +6 -9
  80. package/src/comment/render.ts +93 -47
  81. package/src/components/pagination.ts +0 -1
  82. package/src/context.ts +57 -18
  83. package/src/editor/editor.ts +111 -18
  84. package/src/editor/plugs/header-input-plug.ts +1 -1
  85. package/src/editor/plugs/upload-plug.ts +1 -1
  86. package/src/i18n/en.ts +3 -0
  87. package/src/i18n/index.ts +117 -2
  88. package/src/i18n/zh-CN.ts +4 -1
  89. package/src/layer/sidebar-layer.ts +1 -3
  90. package/src/lib/checker/admin-checker.ts +1 -1
  91. package/src/lib/checker/captcha-checker.ts +3 -3
  92. package/src/lib/stat.ts +2 -2
  93. package/src/lib/ui.ts +6 -0
  94. package/src/list/list-lite.ts +33 -25
  95. package/src/list/list.ts +3 -4
  96. package/src/main.ts +3 -0
  97. package/src/style/comment.less +2 -3
  98. package/src/style/main.less +6 -0
  99. package/test/CSRF.html +26 -0
  100. package/types/artalk-data.d.ts +3 -0
  101. package/types/context.d.ts +19 -1
  102. package/types/global.d.ts +0 -2
  103. package/vite-base.config.ts +9 -5
  104. package/vite.config.ts +5 -1
  105. package/types/index.d.ts +0 -3
@@ -1,5 +1,3 @@
1
- import '../style/comment.less'
2
-
3
1
  import * as Utils from '../lib/utils'
4
2
  import * as Ui from '../lib/ui'
5
3
  import ActionBtn from '../components/action-btn'
@@ -49,7 +47,7 @@ export default class CommentRender {
49
47
  this.renderReplyAt()
50
48
  this.renderReplyTo()
51
49
  this.renderPending()
52
- this.renderActionBtn()
50
+ this.renderActions()
53
51
 
54
52
  this.recoveryChildrenWrap()
55
53
 
@@ -63,7 +61,7 @@ export default class CommentRender {
63
61
  $avatarImg.src = this.comment.getGravatarURL()
64
62
  if (this.data.link) {
65
63
  const $avatarA = Utils.createElement<HTMLLinkElement>('<a target="_blank" rel="noreferrer noopener nofollow"></a>')
66
- $avatarA.href = this.data.link
64
+ $avatarA.href = Utils.isValidURL(this.data.link) ? this.data.link : `https://${this.data.link}`
67
65
  $avatarA.append($avatarImg)
68
66
  $avatar.append($avatarA)
69
67
  } else {
@@ -73,17 +71,26 @@ export default class CommentRender {
73
71
 
74
72
  /** 初始化 - 评论信息 */
75
73
  private renderHeader() {
74
+ this.renderHeader_Nick()
75
+ this.renderHeader_VerifyBadge()
76
+ this.renderHeader_Date()
77
+ this.renderHeader_UABadge()
78
+ }
79
+
80
+ private renderHeader_Nick() {
76
81
  this.$headerNick = this.$el.querySelector<HTMLElement>('.atk-nick')!
77
82
 
78
83
  if (this.data.link) {
79
84
  const $nickA = Utils.createElement<HTMLLinkElement>('<a target="_blank" rel="noreferrer noopener nofollow"></a>')
80
85
  $nickA.innerText = this.data.nick
81
- $nickA.href = this.data.link
86
+ $nickA.href = Utils.isValidURL(this.data.link) ? this.data.link : `https://${this.data.link}`
82
87
  this.$headerNick.append($nickA)
83
88
  } else {
84
89
  this.$headerNick.innerText = this.data.nick
85
90
  }
91
+ }
86
92
 
93
+ private renderHeader_VerifyBadge() {
87
94
  this.$headerBadgeWrap = this.$el.querySelector<HTMLElement>('.atk-badge-wrap')!
88
95
  this.$headerBadgeWrap.innerHTML = ''
89
96
 
@@ -100,11 +107,15 @@ export default class CommentRender {
100
107
  const $pinnedBadge = Utils.createElement(`<span class="atk-pinned-badge">${this.ctx.$t('pin')}</span>`) // 置顶徽章
101
108
  this.$headerBadgeWrap.append($pinnedBadge)
102
109
  }
110
+ }
103
111
 
112
+ private renderHeader_Date() {
104
113
  const $date = this.$el.querySelector<HTMLElement>('.atk-date')!
105
114
  $date.innerText = this.comment.getDateFormatted()
106
115
  $date.setAttribute('data-atk-comment-date', String(+new Date(this.data.date)))
116
+ }
107
117
 
118
+ private renderHeader_UABadge() {
108
119
  if (this.ctx.conf.uaBadge) {
109
120
  let $uaWrap = this.$header.querySelector('atk-ua-wrap')
110
121
  if (!$uaWrap) {
@@ -115,8 +126,9 @@ export default class CommentRender {
115
126
  $uaWrap.innerHTML = ''
116
127
  const $uaBrowser = Utils.createElement(`<span class="atk-ua ua-browser"></span>`)
117
128
  const $usOS = Utils.createElement(`<span class="atk-ua ua-os"></span>`)
118
- $uaBrowser.innerText = this.comment.getUserUaBrowser()
119
- $usOS.innerText = this.comment.getUserUaOS()
129
+ const uaInfo = this.comment.getUserUA()
130
+ $uaBrowser.innerText = uaInfo.browser
131
+ $usOS.innerText = uaInfo.os
120
132
  $uaWrap.append($uaBrowser)
121
133
  $uaWrap.append($usOS)
122
134
  }
@@ -196,39 +208,55 @@ export default class CommentRender {
196
208
  }
197
209
 
198
210
  /** 初始化 - 评论操作按钮 */
199
- private renderActionBtn() {
200
- // 投票功能
201
- if (this.ctx.conf.vote) {
202
- // 赞同按钮
203
- this.voteBtnUp = new ActionBtn(this.ctx, () => `${this.ctx.$t('voteUp')} (${this.data.vote_up || 0})`).appendTo(this.$actions)
204
- this.voteBtnUp.setClick(() => {
205
- this.comment.getActions().vote('up')
206
- })
211
+ private renderActions() {
212
+ this.renderActions_Vote()
213
+ this.renderActions_Reply()
214
+
215
+ // 管理员操作
216
+ this.renderActions_Collapse()
217
+ this.renderActions_Moderator()
218
+ this.renderActions_Pin()
219
+ this.renderActions_Edit()
220
+ this.renderActions_Del()
221
+ }
207
222
 
208
- // 反对按钮
209
- if (this.ctx.conf.voteDown) {
210
- this.voteBtnDown = new ActionBtn(this.ctx, () => `${this.ctx.$t('voteDown')} (${this.data.vote_down || 0})`).appendTo(this.$actions)
211
- this.voteBtnDown.setClick(() => {
212
- this.comment.getActions().vote('down')
213
- })
214
- }
215
- }
223
+ // 操作按钮 - 投票
224
+ private renderActions_Vote() {
225
+ if (!this.ctx.conf.vote) return // 关闭投票功能
216
226
 
217
- // 绑定回复按钮事件
218
- if (this.data.is_allow_reply) {
219
- const replyBtn = Utils.createElement(`<span>${this.ctx.$t('reply')}</span>`)
220
- this.$actions.append(replyBtn)
221
- replyBtn.addEventListener('click', (e) => {
222
- e.stopPropagation() // 防止穿透
223
- if (!this.cConf.onReplyBtnClick) {
224
- this.ctx.replyComment(this.data, this.$el)
225
- } else {
226
- this.cConf.onReplyBtnClick()
227
- }
227
+ // 赞同按钮
228
+ this.voteBtnUp = new ActionBtn(this.ctx, () => `${this.ctx.$t('voteUp')} (${this.data.vote_up || 0})`).appendTo(this.$actions)
229
+ this.voteBtnUp.setClick(() => {
230
+ this.comment.getActions().vote('up')
231
+ })
232
+
233
+ // 反对按钮
234
+ if (this.ctx.conf.voteDown) {
235
+ this.voteBtnDown = new ActionBtn(this.ctx, () => `${this.ctx.$t('voteDown')} (${this.data.vote_down || 0})`).appendTo(this.$actions)
236
+ this.voteBtnDown.setClick(() => {
237
+ this.comment.getActions().vote('down')
228
238
  })
229
239
  }
240
+ }
241
+
242
+ // 操作按钮 - 回复
243
+ private renderActions_Reply() {
244
+ if (!this.data.is_allow_reply) return // 不允许回复
230
245
 
231
- // 绑定折叠按钮事件
246
+ const replyBtn = Utils.createElement(`<span>${this.ctx.$t('reply')}</span>`)
247
+ this.$actions.append(replyBtn)
248
+ replyBtn.addEventListener('click', (e) => {
249
+ e.stopPropagation() // 防止穿透
250
+ if (!this.cConf.onReplyBtnClick) {
251
+ this.ctx.replyComment(this.data, this.$el)
252
+ } else {
253
+ this.cConf.onReplyBtnClick()
254
+ }
255
+ })
256
+ }
257
+
258
+ // 操作按钮 - 折叠
259
+ private renderActions_Collapse() {
232
260
  const collapseBtn = new ActionBtn(this.ctx, {
233
261
  text: () => (this.data.is_collapsed ? this.ctx.$t('expand') : this.ctx.$t('collapse')),
234
262
  adminOnly: true
@@ -237,8 +265,10 @@ export default class CommentRender {
237
265
  collapseBtn.setClick(() => {
238
266
  this.comment.getActions().adminEdit('collapsed', collapseBtn)
239
267
  })
268
+ }
240
269
 
241
- // 绑定待审核按钮事件
270
+ // 操作按钮 - 审核
271
+ private renderActions_Moderator() {
242
272
  const pendingBtn = new ActionBtn(this.ctx, {
243
273
  text: () => (this.data.is_pending ? this.ctx.$t('pending') : this.ctx.$t('approved')),
244
274
  adminOnly: true
@@ -247,8 +277,34 @@ export default class CommentRender {
247
277
  pendingBtn.setClick(() => {
248
278
  this.comment.getActions().adminEdit('pending', pendingBtn)
249
279
  })
280
+ }
250
281
 
251
- // 绑定删除按钮事件
282
+ // 操作按钮 - 置顶
283
+ private renderActions_Pin() {
284
+ const pinnedBtn = new ActionBtn(this.ctx, {
285
+ text: () => (this.data.is_pinned ? this.ctx.$t('unpin') : this.ctx.$t('pin')),
286
+ adminOnly: true
287
+ })
288
+ pinnedBtn.appendTo(this.$actions)
289
+ pinnedBtn.setClick(() => {
290
+ this.comment.getActions().adminEdit('pinned', pinnedBtn)
291
+ })
292
+ }
293
+
294
+ // 操作按钮 - 编辑
295
+ private renderActions_Edit() {
296
+ const editBtn = new ActionBtn(this.ctx, {
297
+ text: this.ctx.$t('edit'),
298
+ adminOnly: true
299
+ })
300
+ editBtn.appendTo(this.$actions)
301
+ editBtn.setClick(() => {
302
+ this.ctx.editComment(this.data, this.$el)
303
+ })
304
+ }
305
+
306
+ // 操作按钮 - 删除
307
+ private renderActions_Del() {
252
308
  const delBtn = new ActionBtn(this.ctx, {
253
309
  text: this.ctx.$t('delete'),
254
310
  confirm: true,
@@ -259,16 +315,6 @@ export default class CommentRender {
259
315
  delBtn.setClick(() => {
260
316
  this.comment.getActions().adminDelete(delBtn)
261
317
  })
262
-
263
- // 绑定置顶按钮事件
264
- const pinnedBtn = new ActionBtn(this.ctx, {
265
- text: () => (this.data.is_pinned ? this.ctx.$t('unpin') : this.ctx.$t('pin')),
266
- adminOnly: true
267
- })
268
- pinnedBtn.appendTo(this.$actions)
269
- pinnedBtn.setClick(() => {
270
- this.comment.getActions().adminEdit('pinned', pendingBtn)
271
- })
272
318
  }
273
319
 
274
320
  /** 内容限高检测 */
@@ -1,4 +1,3 @@
1
- import '../style/pagination.less'
2
1
  import * as Utils from '../lib/utils'
3
2
  import * as Ui from '../lib/ui'
4
3
 
package/src/context.ts CHANGED
@@ -2,7 +2,7 @@ import { marked as libMarked } from 'marked'
2
2
  import ArtalkConfig from '~/types/artalk-config'
3
3
  import { CommentData, NotifyData } from '~/types/artalk-data'
4
4
  import { Event } from '~/types/event'
5
- import { internal as internalLocales, I18n } from './i18n'
5
+ import getI18n, { I18n } from './i18n'
6
6
  import User from './lib/user'
7
7
  import ContextApi from '../types/context'
8
8
  import Editor from './editor'
@@ -12,6 +12,7 @@ import SidebarLayer, { SidebarShowPayload } from './layer/sidebar-layer'
12
12
  import CheckerLauncher, { CheckerCaptchaPayload, CheckerPayload } from './lib/checker'
13
13
  import Layer, { GetLayerWrap } from './layer'
14
14
  import Api from './api'
15
+ import List from './list'
15
16
 
16
17
  /**
17
18
  * Artalk Context
@@ -20,7 +21,7 @@ export default class Context implements ContextApi {
20
21
  /* 持有同事类 */
21
22
  private api!: Api
22
23
  private editor!: Editor
23
- private list!: ListLite
24
+ private list?: ListLite
24
25
  private sidebarLayer!: SidebarLayer
25
26
  private checkerLauncher!: CheckerLauncher
26
27
 
@@ -99,20 +100,29 @@ export default class Context implements ContextApi {
99
100
  comment.getEl().remove()
100
101
  this.commentList.splice(this.commentList.indexOf(comment), 1)
101
102
 
102
- const listData = this.list.getData()
103
- if (listData) listData.total -= 1 // 评论数减 1
103
+ if (this.list) {
104
+ const listData = this.list.getData()
105
+ if (listData) listData.total -= 1 // 评论数减 1
104
106
 
105
- this.list.refreshUI()
107
+ this.list.refreshUI()
108
+ }
106
109
  }
107
110
 
108
111
  public clearAllComments() {
109
- this.list.getCommentsWrapEl().innerHTML = ''
110
- this.list.clearData()
112
+ if (this.list) {
113
+ this.list.getCommentsWrapEl().innerHTML = ''
114
+ this.list.clearData()
115
+ }
116
+
111
117
  this.commentList = []
112
118
  }
113
119
 
114
120
  public insertComment(commentData: CommentData) {
115
- this.list.insertComment(commentData)
121
+ this.list?.insertComment(commentData)
122
+ }
123
+
124
+ public updateComment(commentData: CommentData): void {
125
+ this.list?.updateComment(commentData)
116
126
  }
117
127
 
118
128
  public replyComment(commentData: CommentData, $comment: HTMLElement, scroll?: boolean): void {
@@ -123,17 +133,37 @@ export default class Context implements ContextApi {
123
133
  this.editor.cancelReply()
124
134
  }
125
135
 
136
+ public editComment(commentData: CommentData, $comment: HTMLElement): void {
137
+ this.editor.setEditComment(commentData, $comment)
138
+ }
139
+
140
+ public cancelEditComment(): void {
141
+ this.editor.cancelEditComment()
142
+ }
143
+
126
144
  public updateNotifies(notifies: NotifyData[]): void {
127
- this.list.updateUnread(notifies)
145
+ this.list?.updateUnread(notifies)
128
146
  }
129
147
 
130
148
  /* 评论列表 */
131
149
  public listReload(): void {
132
- this.list.reload()
150
+ this.list?.reload()
151
+ }
152
+
153
+ public reload(): void {
154
+ this.listReload()
133
155
  }
134
156
 
135
157
  public listRefreshUI(): void {
136
- this.list.refreshUI()
158
+ this.list?.refreshUI()
159
+ }
160
+
161
+ public listHashGotoCheck(): void {
162
+ if (!this.list || this.list !instanceof List) return
163
+ const list = this.list as List
164
+
165
+ list.goToCommentDelay = false
166
+ list.checkGoToCommentByUrlHash()
137
167
  }
138
168
 
139
169
  /* 编辑器 */
@@ -224,14 +254,23 @@ export default class Context implements ContextApi {
224
254
 
225
255
  /* i18n */
226
256
  public $t(key: keyof I18n, args: {[key: string]: string} = {}): string {
227
- let locales = this.conf.locale
228
- if (typeof locales === 'string') {
229
- locales = internalLocales[locales]
230
- }
257
+ return getI18n(this.conf.locale, key, args)
258
+ }
259
+
260
+ public setDarkMode(darkMode: boolean): void {
261
+ const darkModeClassName = 'atk-dark-mode'
262
+
263
+ this.conf.darkMode = darkMode
264
+ this.trigger('conf-updated')
231
265
 
232
- let str = locales?.[key] || key
233
- str = str.replace(/\{\s*(\w+?)\s*\}/g, (_, token) => args[token] || '')
266
+ if (this.conf.darkMode) this.$root.classList.add(darkModeClassName)
267
+ else this.$root.classList.remove(darkModeClassName)
234
268
 
235
- return str
269
+ // for Layer
270
+ const { $wrap: $layerWrap } = GetLayerWrap(this)
271
+ if ($layerWrap) {
272
+ if (this.conf.darkMode) $layerWrap.classList.add(darkModeClassName)
273
+ else $layerWrap.classList.remove(darkModeClassName)
274
+ }
236
275
  }
237
276
  }
@@ -1,5 +1,3 @@
1
- import '@/style/editor.less'
2
-
3
1
  import { CommentData } from '~/types/artalk-data'
4
2
  import Context from '~/types/context'
5
3
  import Component from '../lib/component'
@@ -31,10 +29,18 @@ export default class Editor extends Component {
31
29
  public $submitBtn: HTMLButtonElement
32
30
  public $notifyWrap: HTMLElement
33
31
 
32
+ // Editor 的不同状态
33
+ private isTraveling = false
34
+
35
+ /** 回复评论 */
34
36
  private replyComment: CommentData|null = null
35
37
  private $sendReply: HTMLElement|null = null
38
+ public get isReplyMode() { return this.replyComment !== null }
36
39
 
37
- private isTraveling = false
40
+ /** 编辑评论 */
41
+ private editComment: CommentData|null = null
42
+ private $editCancelBtn: HTMLElement|null = null
43
+ public get isEditMode() { return this.editComment !== null }
38
44
 
39
45
  /** 启用的插件 */
40
46
  private readonly ENABLED_PLUGS = [ EmoticonsPlug, UploadPlug, PreviewPlug, HeaderInputPlug ]
@@ -98,6 +104,8 @@ export default class Editor extends Component {
98
104
  }
99
105
 
100
106
  private onHeaderInput(key: string, $input: HTMLInputElement) {
107
+ if (this.isEditMode) return // 评论编辑模式,不修改个人信息
108
+
101
109
  this.user.update({
102
110
  [key]: $input.value.trim()
103
111
  })
@@ -128,8 +136,13 @@ export default class Editor extends Component {
128
136
  })
129
137
  }
130
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
+
131
144
  private initSubmitBtn() {
132
- this.$submitBtn.innerText = this.ctx.conf.sendBtn || this.$t('send')
145
+ this.refreshSendBtnText()
133
146
  this.$submitBtn.addEventListener('click', () => (this.submit()))
134
147
  }
135
148
 
@@ -186,21 +199,16 @@ export default class Editor extends Component {
186
199
  }
187
200
  }
188
201
 
189
- public clearEditor() {
190
- this.setContent('')
191
- this.cancelReply()
192
- }
193
-
194
202
  private adjustTextareaHeight() {
195
203
  const diff = this.$textarea.offsetHeight - this.$textarea.clientHeight
196
204
  this.$textarea.style.height = '0px' // it's a magic. 若不加此行,内容减少,高度回不去
197
205
  this.$textarea.style.height = `${this.$textarea.scrollHeight + diff}px`
198
206
  }
199
207
 
208
+ /** 设置回复评论 */
200
209
  public setReply(commentData: CommentData, $comment: HTMLElement, scroll = true) {
201
- if (this.replyComment !== null) {
202
- this.cancelReply()
203
- }
210
+ if (this.isEditMode) this.cancelEditComment()
211
+ if (this.isReplyMode) this.cancelReply()
204
212
 
205
213
  if (this.$sendReply === null) {
206
214
  this.$sendReply = Utils.createElement(`<div class="atk-send-reply">${this.$t('reply')} <span class="atk-text"></span><span class="atk-cancel">×</span></div>`);
@@ -221,6 +229,7 @@ export default class Editor extends Component {
221
229
  this.$textarea.focus()
222
230
  }
223
231
 
232
+ /** 取消回复评论 */
224
233
  public cancelReply() {
225
234
  if (this.$sendReply !== null) {
226
235
  this.$sendReply.remove()
@@ -233,6 +242,53 @@ export default class Editor extends Component {
233
242
  }
234
243
  }
235
244
 
245
+ /** 设置编辑评论 */
246
+ 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()
271
+ }
272
+
273
+ /** 取消编辑评论 */
274
+ 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
290
+ }
291
+
236
292
  public showNotify(msg: string, type: "i"|"s"|"w"|"e") {
237
293
  Ui.showNotify(this.$notifyWrap, msg, type)
238
294
  }
@@ -245,7 +301,7 @@ export default class Editor extends Component {
245
301
  Ui.hideLoading(this.$el)
246
302
  }
247
303
 
248
- /** 提交评论 */
304
+ /** 点击评论提交按钮事件 */
249
305
  public async submit() {
250
306
  if (this.getFinalContent().trim() === '') {
251
307
  this.$textarea.focus()
@@ -256,8 +312,19 @@ export default class Editor extends Component {
256
312
 
257
313
  this.showLoading()
258
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() {
259
326
  try {
260
- const nComment = await this.ctx.getApi().add({
327
+ const nComment = await this.ctx.getApi().comment.add({
261
328
  content: this.getFinalContent(),
262
329
  nick: this.user.data.nick,
263
330
  email: this.user.data.email,
@@ -274,14 +341,40 @@ export default class Editor extends Component {
274
341
  }
275
342
 
276
343
  this.ctx.insertComment(nComment)
277
- this.clearEditor() // 清空编辑器
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
+
278
374
  this.ctx.trigger('editor-submitted')
279
375
  } catch (err: any) {
280
376
  console.error(err)
281
377
  this.showNotify(`${this.$t('commentFail')},${err.msg || String(err)}`, 'e')
282
- return
283
- } finally {
284
- this.hideLoading()
285
378
  }
286
379
  }
287
380
 
@@ -39,7 +39,7 @@ export default class HeaderInputPlug extends EditorPlug {
39
39
  this.queryUserInfo.timeout = window.setTimeout(() => {
40
40
  this.queryUserInfo.timeout = null // 清理
41
41
 
42
- const {req, abort} = this.ctx.getApi().userGet(
42
+ const {req, abort} = this.ctx.getApi().user.userGet(
43
43
  this.ctx.user.data.nick, this.ctx.user.data.email
44
44
  )
45
45
  this.queryUserInfo.abortFunc = abort
@@ -99,7 +99,7 @@ export default class UploadPlug extends EditorPlug {
99
99
  try {
100
100
  if (!this.ctx.conf.imgUploader) {
101
101
  // 使用 ArtalkGo 进行图片上传
102
- resp = await this.ctx.getApi().imgUpload(file)
102
+ resp = await this.ctx.getApi().upload.imgUpload(file)
103
103
  } else {
104
104
  // 使用自定义的图片上传器
105
105
  resp = {img_url: await this.ctx.conf.imgUploader(file)}
package/src/i18n/en.ts CHANGED
@@ -3,6 +3,7 @@ export default {
3
3
  placeholder: 'Leave a comment',
4
4
  noComment: 'No Comment',
5
5
  send: 'Send',
6
+ save: 'Save',
6
7
  nick: 'Nickname',
7
8
  email: 'Email',
8
9
  link: 'Website',
@@ -42,6 +43,8 @@ export default {
42
43
  approved: 'Approved',
43
44
  pending: 'Pending',
44
45
  pendingMsg: 'Pending, visible only to commenter.',
46
+ edit: 'Edit',
47
+ editCancel: 'Cancel Edit',
45
48
  delete: 'Delete',
46
49
  deleteConfirm: 'Confirm',
47
50
  pin: 'Pin',