artalk 2.1.7 → 2.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "artalk",
3
- "version": "2.1.7",
3
+ "version": "2.1.8",
4
4
  "license": "GPL-3.0",
5
5
  "description": "A Selfhosted Comment System",
6
6
  "keywords": [
package/src/api/index.ts CHANGED
@@ -264,6 +264,29 @@ export default class Api {
264
264
  return p.pv as number
265
265
  }
266
266
 
267
+ /** 图片上传 */
268
+ public async imgUpload(file: File) {
269
+ const params: any = {
270
+ name: this.ctx.user.data.nick,
271
+ email: this.ctx.user.data.email,
272
+ page_key: this.ctx.conf.pageKey,
273
+ }
274
+
275
+ if (this.ctx.conf.site) params.site_name = this.ctx.conf.site
276
+
277
+ const form = ToFormData(params)
278
+ form.set('file', file)
279
+
280
+ const init: RequestInit = {
281
+ method: 'POST',
282
+ body: form
283
+ }
284
+
285
+ const json = await Fetch(this.ctx, `${this.baseURL}/img-upload`, init)
286
+ return ((json.data || {}) as any) as { img_file: string, img_url: string }
287
+
288
+ }
289
+
267
290
  // ============================
268
291
  // 验证码
269
292
  // ============================
package/src/artalk.ts CHANGED
@@ -148,6 +148,7 @@ export default class Artalk {
148
148
  /** 暗黑模式 · 设定 */
149
149
  public setDarkMode(darkMode: boolean) {
150
150
  this.ctx.conf.darkMode = darkMode
151
+ this.ctx.trigger('conf-updated')
151
152
  this.initDarkMode()
152
153
  }
153
154
 
@@ -21,6 +21,7 @@ export default class Editor extends Component {
21
21
  public $plugWrap: HTMLElement
22
22
  public $bottom: HTMLElement
23
23
  public $plugBtnWrap: HTMLElement
24
+ public $imgUploadBtn?: HTMLElement
24
25
  public $submitBtn: HTMLButtonElement
25
26
  public $notifyWrap: HTMLElement
26
27
 
@@ -63,6 +64,7 @@ export default class Editor extends Component {
63
64
  this.ctx.on('editor-notify', (f) => (this.showNotify(f.msg, f.type)))
64
65
  this.ctx.on('editor-travel', ($el) => (this.travel($el)))
65
66
  this.ctx.on('editor-travel-back', () => (this.travelBack()))
67
+ this.ctx.on('conf-updated', () => (this.refreshUploadBtn()))
66
68
  }
67
69
 
68
70
  initLocalStorage () {
@@ -200,7 +202,7 @@ export default class Editor extends Component {
200
202
  // 依次实例化 plug
201
203
  this.LOADABLE_PLUG_LIST.forEach((PlugObj) => {
202
204
  // 切换按钮
203
- const btnElem = Utils.createElement(`<span class="atk-plug-btn">${PlugObj.BtnHTML}</span>`)
205
+ const btnElem = Utils.createElement(`<span class="atk-plug-btn" data-plug-name="${PlugObj.Name}">${PlugObj.BtnHTML}</span>`)
204
206
  this.$plugBtnWrap.appendChild(btnElem)
205
207
  btnElem.addEventListener('click', () => {
206
208
  let plug = this.plugList[PlugObj.Name]
@@ -244,6 +246,8 @@ export default class Editor extends Component {
244
246
  btnElem.classList.add('active')
245
247
  })
246
248
  })
249
+
250
+ this.initImgUploadBtn()
247
251
  }
248
252
 
249
253
  /** 关闭编辑器插件 */
@@ -253,6 +257,91 @@ export default class Editor extends Component {
253
257
  this.openedPlugName = null
254
258
  }
255
259
 
260
+ /** 允许的图片格式 */
261
+ allowImgExts = ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'svg', 'webp']
262
+
263
+ /** 初始化图片上传按钮 */
264
+ initImgUploadBtn() {
265
+ this.$imgUploadBtn = Utils.createElement(`<span class="atk-plug-btn">图片</span>`)
266
+ this.$plugBtnWrap.querySelector('[data-plug-name="preview"]')!.before(this.$imgUploadBtn) // 显示在预览图标之前
267
+
268
+ this.$imgUploadBtn.onclick = () => {
269
+ // 选择图片
270
+ const $input = document.createElement('input')
271
+ $input.type = 'file'
272
+ $input.accept = this.allowImgExts.map(o => `.${o}`).join(',')
273
+ $input.onchange = () => {
274
+ (async () => { // 解决阻塞 UI 问题
275
+ if (!$input.files || $input.files.length === 0) return
276
+ const file = $input.files[0]
277
+ this.uploadImg(file)
278
+ })()
279
+ }
280
+ $input.click() // 显示选择图片对话框
281
+ }
282
+
283
+ // @link https://developer.mozilla.org/zh-CN/docs/Web/API/HTML_Drag_and_Drop_API/File_drag_and_drop
284
+ // 阻止浏览器的默认释放行为
285
+ this.$textarea.addEventListener('dragover', (evt) => {
286
+ evt.stopPropagation()
287
+ evt.preventDefault()
288
+ })
289
+
290
+ this.$textarea.addEventListener('drop', (evt) => {
291
+ if (evt.dataTransfer?.files) {
292
+ evt.preventDefault()
293
+ for (let i = 0; i < evt.dataTransfer.files.length; i++) {
294
+ const file = evt.dataTransfer.files[i]
295
+ this.uploadImg(file)
296
+ }
297
+ }
298
+ })
299
+ }
300
+
301
+ /** 刷新图片上传按钮 */
302
+ refreshUploadBtn() {
303
+ if (!this.$imgUploadBtn) return
304
+
305
+ if (!this.ctx.conf.imgUpload) {
306
+ this.$imgUploadBtn.setAttribute('atk-only-admin-show', '')
307
+ this.ctx.trigger('check-admin-show-el')
308
+ }
309
+ }
310
+
311
+ async uploadImg(file: File) {
312
+ const fileExt = /[^.]+$/.exec(file.name)
313
+ if (!fileExt || !this.allowImgExts.includes(fileExt[0])) return
314
+
315
+ // 插入占位加载文字
316
+ const uploadPlaceholderTxt = `\n![](Uploading ${file.name}...)`
317
+ this.insertContent(uploadPlaceholderTxt)
318
+
319
+ // 上传图片
320
+ let resp: any
321
+ try {
322
+ resp = await new Api(this.ctx).imgUpload(file)
323
+ } catch (err: any) {
324
+ console.error(err)
325
+ this.showNotify(`图片上传失败,${err.msg}`, 'e')
326
+ }
327
+ if (!!resp && resp.img_url) {
328
+ let imgURL = resp.img_url as string
329
+ if (imgURL.startsWith('.') || (imgURL.startsWith('/') && !imgURL.startsWith('//'))) {
330
+ // 若为相对路径,加上 artalk server
331
+ imgURL = `${this.ctx.conf.server.replace(/\/api\/?$/, '')}/${imgURL.replace(/^\//, '')}`
332
+ }
333
+
334
+ // 上传成功插入图片
335
+ this.$textarea.value = this.$textarea.value.replace(uploadPlaceholderTxt, `\n![](${imgURL})`)
336
+ } else {
337
+ // 上传失败删除加载文字
338
+ this.$textarea.value = this.$textarea.value.replace(uploadPlaceholderTxt, '')
339
+ }
340
+
341
+ // 更新内容保存到 localStorage
342
+ this.saveContent()
343
+ }
344
+
256
345
  insertContent (val: string) {
257
346
  if ((document as any).selection) {
258
347
  this.$textarea.focus();
@@ -119,6 +119,11 @@ export default class ListLite extends Component {
119
119
  // 版本检测
120
120
  if (this.ctx.conf.versionCheck && this.versionCheck(data.api_version)) return
121
121
 
122
+ // 图片上传功能
123
+ if (data.conf && typeof data.conf.img_upload === "boolean") {
124
+ this.ctx.conf.imgUpload = data.conf.img_upload
125
+ }
126
+
122
127
  // 导入数据
123
128
  this.importComments(data.comments)
124
129
 
@@ -130,6 +135,7 @@ export default class ListLite extends Component {
130
135
 
131
136
  this.ctx.trigger('unread-update', { notifies: data.unread || [] })
132
137
  this.ctx.trigger('comments-loaded')
138
+ this.ctx.trigger('conf-updated')
133
139
 
134
140
  if (this.onAfterLoad) this.onAfterLoad(data)
135
141
  }
@@ -481,6 +487,7 @@ export default class ListLite extends Component {
481
487
  ignoreBtn.onclick = () => {
482
488
  Ui.setError(this.ctx, null)
483
489
  this.ctx.conf.versionCheck = false
490
+ this.ctx.trigger('conf-updated')
484
491
  this.fetchComments(0)
485
492
  }
486
493
  errEl.append(ignoreBtn)
@@ -184,7 +184,7 @@ export default class List extends ListLite {
184
184
  this.$commentCount.classList.add('atk-dropdown-wrap')
185
185
 
186
186
  // 插入图标
187
- this.$commentsWrap.append(Utils.createElement(`<span class="atk-arrow-down-icon"></span>`))
187
+ this.$dropdownWrap.append(Utils.createElement(`<span class="atk-arrow-down-icon"></span>`))
188
188
 
189
189
  const reloadUseParamsEditor = (func: (p: any) => void) => {
190
190
  this.paramsEditor = (p) => { func(p) }
package/src/defaults.ts CHANGED
@@ -36,6 +36,7 @@ const defaults: ArtalkConfig = {
36
36
  children: 400,
37
37
  },
38
38
 
39
+ imgUpload: true,
39
40
  reqTimeout: 15000,
40
41
  versionCheck: true,
41
42
  }
@@ -19,9 +19,9 @@
19
19
  .atk-arrow-down-icon {
20
20
  cursor: pointer;
21
21
  vertical-align: middle;
22
- border-top: 6px solid var(--at-color-grey);
23
- border-left: 4px solid transparent;
24
- border-right: 4px solid transparent;
22
+ border-top: 5px solid var(--at-color-grey);
23
+ border-left: 3px solid transparent;
24
+ border-right: 3px solid transparent;
25
25
  margin-top: -1px;
26
26
  margin-left: 0.4rem;
27
27
  display: inline-block;
@@ -80,6 +80,9 @@ export default interface ArtalkConfig {
80
80
  /** 显示 UA 徽标 */
81
81
  uaBadge?: boolean
82
82
 
83
+ /** 图片上传功能 */
84
+ imgUpload?: boolean
85
+
83
86
  /** 版本检测 */
84
87
  versionCheck?: boolean
85
88
  }
@@ -87,6 +87,9 @@ export interface ListData {
87
87
 
88
88
  /** API 版本 */
89
89
  api_version: ApiVersionData
90
+
91
+ /** 后端配置 */
92
+ conf: any // TODO
90
93
  }
91
94
 
92
95
  export interface PageData {
package/types/event.d.ts CHANGED
@@ -8,6 +8,7 @@ export interface EventPayloadMap {
8
8
  'editor-submit': undefined // 编辑器提交时
9
9
  'editor-submitted': undefined // 编辑器提交后
10
10
  'user-changed': LocalUser // 本地用户数据变更时
11
+ 'conf-updated': undefined // Artalk 配置变更时
11
12
 
12
13
  // List 操作(外部:不建议 listen,仅 trigger)
13
14
  'list-reload': undefined // 重新加载 List