artalk 2.1.5 → 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/index.html CHANGED
@@ -31,9 +31,9 @@
31
31
  </style>
32
32
  </head>
33
33
  <body>
34
- <div class="card billboard artalk-fade-in">
34
+ <div class="card billboard atk-fade-in">
35
35
  <div class="title"><a href="https://github.com/ArtalkJS/Artalk" target="_blank"><span>『 </span>Artalk<span> 』</span></a></div>
36
- <div class="desc">一款简洁的自托管评论系统</div>
36
+ <div class="desc">一款简洁的自托管评论系统 <span id="ArtalkPV"></span></div>
37
37
  <div class="social">
38
38
  <a href="https://www.npmjs.com/package/artalk" target="_blank"><img src="https://img.shields.io/npm/v/artalk.svg?style=flat-square"></a>
39
39
  <a href="https://www.npmjs.com/package/artalk" target="_blank"><img src="https://img.shields.io/npm/dt/artalk.svg?style=flat-square"></a>
@@ -42,11 +42,11 @@
42
42
  <a href="https://github.com/ArtalkJS/Artalk" target="_blank"><img src="https://img.shields.io/github/stars/ArtalkJS/Artalk?style=flat-square"></a>
43
43
  </div>
44
44
  </div>
45
- <div class="card artalk-fade-in">
45
+ <div class="card atk-fade-in">
46
46
  <div id="comments"></div>
47
47
  </div>
48
- <div class="float-btn to-night artalk-fade-in">Blink</div>
49
- <div class="float-btn to-top artalk-fade-in" style="display: none;">TOP</div>
48
+ <div class="float-btn to-night atk-fade-in">Blink</div>
49
+ <div class="float-btn to-top atk-fade-in" style="display: none;">TOP</div>
50
50
 
51
51
  <script type="module">
52
52
  import Artalk from './src/main'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "artalk",
3
- "version": "2.1.5",
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
@@ -252,6 +252,41 @@ export default class Api {
252
252
  return POST(this.ctx, `${this.baseURL}/mark-read`, params)
253
253
  }
254
254
 
255
+ /** PV */
256
+ public async pv() {
257
+ const params: any = {
258
+ site_name: this.ctx.conf.site || '',
259
+ page_key: this.ctx.conf.pageKey || '',
260
+ page_title: this.ctx.conf.pageTitle || ''
261
+ }
262
+
263
+ const p = await POST<any>(this.ctx, `${this.baseURL}/pv`, params)
264
+ return p.pv as number
265
+ }
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
+
255
290
  // ============================
256
291
  // 验证码
257
292
  // ============================
package/src/artalk.ts CHANGED
@@ -11,6 +11,7 @@ import SidebarLayer from './components/sidebar-layer'
11
11
 
12
12
  import { GetLayerWrap } from './components/layer'
13
13
  import { EventPayloadMap, Handler } from '~/types/event'
14
+ import Api from './api'
14
15
 
15
16
  /**
16
17
  * Artalk
@@ -76,6 +77,9 @@ export default class Artalk {
76
77
 
77
78
  // 事件绑定初始化
78
79
  this.initEventBind()
80
+
81
+ // 其他
82
+ this.initPV()
79
83
  }
80
84
 
81
85
  /** 事件绑定 · 初始化 */
@@ -93,8 +97,12 @@ export default class Artalk {
93
97
 
94
98
  // for layer
95
99
  const { $wrap: $layerWrap } = GetLayerWrap(this.ctx)
96
- if ($layerWrap)
97
- $layerWrap.querySelectorAll<HTMLElement>(`[atk-only-admin-show]`).forEach(item => items.push(item))
100
+ if ($layerWrap) $layerWrap.querySelectorAll<HTMLElement>(`[atk-only-admin-show]`).forEach(item => items.push(item))
101
+
102
+ // for sidebar
103
+ // TODO: 这个其实应该写在 packages/artalk-sidebar 里面的
104
+ const $sidebarEl = document.querySelector<HTMLElement>('.atk-sidebar')
105
+ if ($sidebarEl) $sidebarEl.querySelectorAll<HTMLElement>(`[atk-only-admin-show]`).forEach(item => items.push(item))
98
106
 
99
107
  items.forEach(($item: HTMLElement) => {
100
108
  if (this.ctx.user.data.isAdmin)
@@ -140,9 +148,24 @@ export default class Artalk {
140
148
  /** 暗黑模式 · 设定 */
141
149
  public setDarkMode(darkMode: boolean) {
142
150
  this.ctx.conf.darkMode = darkMode
151
+ this.ctx.trigger('conf-updated')
143
152
  this.initDarkMode()
144
153
  }
145
154
 
155
+ /** PV */
156
+ public async initPV() {
157
+ if (!this.conf.pvEl || !document.querySelector(this.conf.pvEl))
158
+ return
159
+
160
+ const $pv = document.querySelector<HTMLElement>(this.conf.pvEl)!
161
+ // $pv.innerText = '-' // 默认占位符
162
+ const pvNum = await new Api(this.ctx).pv()
163
+ if (Number.isNaN(Number(pvNum)))
164
+ return
165
+
166
+ $pv.innerText = String(pvNum)
167
+ }
168
+
146
169
  /** 监听事件 */
147
170
  public on<K extends keyof EventPayloadMap>(name: K, handler: Handler<EventPayloadMap[K]>) {
148
171
  this.ctx.on(name, handler, 'external')
@@ -136,6 +136,11 @@ export default class Comment extends Component {
136
136
  $badge.remove()
137
137
  }
138
138
 
139
+ if (this.data.is_pinned) {
140
+ const $pinnedBadge = Utils.createElement(`<span class="atk-item atk-pinned-badge">置顶</span>`) // 置顶徽章
141
+ $nick.insertAdjacentElement('afterend', $pinnedBadge)
142
+ }
143
+
139
144
  const $date = this.$el.querySelector<HTMLElement>('.atk-date')!
140
145
  $date.innerText = this.getDateFormatted()
141
146
  $date.setAttribute('data-atk-comment-date', String(+new Date(this.data.date)))
@@ -272,6 +277,16 @@ export default class Comment extends Component {
272
277
  delBtn.setClick(() => {
273
278
  this.adminDelete(delBtn)
274
279
  })
280
+
281
+ // 绑定置顶按钮事件
282
+ const pinnedBtn = new ActionBtn({
283
+ text: () => (this.data.is_pinned ? '取消置顶' : '置顶'),
284
+ adminOnly: true
285
+ })
286
+ pinnedBtn.appendTo(this.$actions)
287
+ pinnedBtn.setClick(() => {
288
+ this.adminEdit('pinned', pendingBtn)
289
+ })
275
290
  }
276
291
  //#endregion
277
292
 
@@ -404,9 +419,9 @@ export default class Comment extends Component {
404
419
  })
405
420
  }
406
421
 
407
- /** 管理员 - 评论折叠 */
408
- adminEdit(type: 'collapsed'|'pending', btnElem: ActionBtn) {
409
- if (btnElem.isLoading) return // 若正在折叠中
422
+ /** 管理员 - 评论状态修改 */
423
+ adminEdit(type: 'collapsed'|'pending'|'pinned', btnElem: ActionBtn) {
424
+ if (btnElem.isLoading) return // 若正在修改中
410
425
 
411
426
  btnElem.setLoading(true, '修改中...')
412
427
 
@@ -416,6 +431,8 @@ export default class Comment extends Component {
416
431
  modify.is_collapsed = !modify.is_collapsed
417
432
  } else if (type === 'pending') {
418
433
  modify.is_pending = !modify.is_pending
434
+ } else if (type === 'pinned') {
435
+ modify.is_pinned = !modify.is_pinned
419
436
  }
420
437
 
421
438
  new Api(this.ctx).commentEdit(modify).then((comment) => {
@@ -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();
@@ -1,8 +1,10 @@
1
1
  <div class="atk-list">
2
2
  <div class="atk-list-header">
3
3
  <div class="atk-comment-count">
4
- <span class="atk-comment-count-num">0</span>
5
- 条评论
4
+ <div class="atk-text">
5
+ <span class="atk-comment-count-num">0</span>
6
+ 条评论
7
+ </div>
6
8
  </div>
7
9
  <div class="atk-right-action">
8
10
  <span data-action="admin-close-comment" class="atk-hide" atk-only-admin-show>关闭评论</span>
@@ -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)
@@ -12,6 +12,8 @@ export default class List extends ListLite {
12
12
  private $closeCommentBtn!: HTMLElement
13
13
  private $openSidebarBtn!: HTMLElement
14
14
  private $unreadBadge!: HTMLElement
15
+ private $commentCount!: HTMLElement
16
+ private $dropdownWrap?: HTMLElement
15
17
 
16
18
  constructor (ctx: Context) {
17
19
  const el = Utils.createElement(ListHTML)
@@ -40,6 +42,10 @@ export default class List extends ListLite {
40
42
  // 操作按钮
41
43
  this.initListActionBtn()
42
44
 
45
+ // 下拉选择层
46
+ this.$commentCount = this.$el.querySelector('.atk-comment-count')!
47
+ this.initDropdown()
48
+
43
49
  // copyright
44
50
  this.$el.querySelector<HTMLElement>('.atk-copyright')!.innerHTML = `Powered By <a href="https://artalk.js.org" target="_blank" title="Artalk v${ARTALK_VERSION}">Artalk</a>`
45
51
 
@@ -171,4 +177,58 @@ export default class List extends ListLite {
171
177
  this.$unreadBadge.style.display = 'none'
172
178
  }
173
179
  }
180
+
181
+ /** 初始化选择下拉层 */
182
+ protected initDropdown() {
183
+ this.$dropdownWrap = this.$commentCount
184
+ this.$commentCount.classList.add('atk-dropdown-wrap')
185
+
186
+ // 插入图标
187
+ this.$dropdownWrap.append(Utils.createElement(`<span class="atk-arrow-down-icon"></span>`))
188
+
189
+ const reloadUseParamsEditor = (func: (p: any) => void) => {
190
+ this.paramsEditor = (p) => { func(p) }
191
+ this.fetchComments(0)
192
+ }
193
+
194
+ // 下拉列表
195
+ const dropdownList = [
196
+ ['最新', () => { reloadUseParamsEditor(p => { p.sort_by = 'date_desc' }) }],
197
+ ['最热', () => { reloadUseParamsEditor(p => { p.sort_by = 'vote' }) }],
198
+ ['最早', () => { reloadUseParamsEditor(p => { p.sort_by = 'date_asc' }) }],
199
+ ['作者', () => { reloadUseParamsEditor(p => { p.view_only_admin = true }) }],
200
+ ]
201
+
202
+ // 列表项点击事件
203
+ let curtActive = 0 // 当前选中
204
+ const onItemClick = (i: number, $item: HTMLElement, name: string, action: Function) => {
205
+ action()
206
+
207
+ // set active
208
+ curtActive = i
209
+ $dropdown.querySelectorAll('.active').forEach((e) => { e.classList.remove('active') })
210
+ $item.classList.add('active')
211
+
212
+ // 关闭层 (临时消失,取消 :hover)
213
+ $dropdown.style.display = 'none'
214
+ setTimeout(() => { $dropdown.style.display = '' }, 80)
215
+ }
216
+
217
+ // 生成列表元素
218
+ const $dropdown = Utils.createElement(`<ul class="atk-dropdown atk-fade-in"></ul>`)
219
+ dropdownList.forEach((item, i) => {
220
+ const name = item[0] as string
221
+ const action = item[1] as Function
222
+
223
+ const $item = Utils.createElement(`<li class="atk-dropdown-item"><span></span></li>`)
224
+ const $link = $item.querySelector<HTMLElement>('span')!
225
+ $link.innerText = name
226
+ $link.onclick = () => { onItemClick(i, $item, name, action) }
227
+ $dropdown.append($item)
228
+
229
+ if (i === curtActive) $item.classList.add('active') // 默认选中项
230
+ })
231
+
232
+ this.$dropdownWrap.append($dropdown)
233
+ }
174
234
  }
@@ -106,7 +106,7 @@ export default class SidebarLayer extends Component {
106
106
  Ui.hideLoading(this.$iframeWrap)
107
107
  }
108
108
 
109
- this.checkReqStatus(src)
109
+ // this.checkReqStatus(src) // 判不准,删了,没啥用
110
110
  }
111
111
 
112
112
  loadingTimer: number|null = null
package/src/defaults.ts CHANGED
@@ -17,6 +17,7 @@ const defaults: ArtalkConfig = {
17
17
  vote: true,
18
18
  voteDown: false,
19
19
  uaBadge: true,
20
+ pvEl: '#ArtalkPV',
20
21
  flatMode: 'auto',
21
22
  maxNesting: 3,
22
23
  gravatar: {
@@ -35,6 +36,7 @@ const defaults: ArtalkConfig = {
35
36
  children: 400,
36
37
  },
37
38
 
39
+ imgUpload: true,
38
40
  reqTimeout: 15000,
39
41
  versionCheck: true,
40
42
  }
@@ -102,6 +102,12 @@
102
102
  color: #fff;
103
103
  }
104
104
 
105
+ .atk-pinned-badge {
106
+ .badge();
107
+ color: #fff;
108
+ background: #f44336;
109
+ }
110
+
105
111
  .atk-ua {
106
112
  .badge()
107
113
  }
@@ -7,6 +7,72 @@
7
7
  padding: 10px 10px 5px 10px;
8
8
  border-bottom: 1px solid var(--at-color-border);
9
9
 
10
+ .atk-text {
11
+ display: inline-block;
12
+ }
13
+
14
+ /* 下拉选择层 */
15
+ .atk-dropdown-wrap {
16
+ position: relative;
17
+ cursor: pointer;
18
+
19
+ .atk-arrow-down-icon {
20
+ cursor: pointer;
21
+ vertical-align: middle;
22
+ border-top: 5px solid var(--at-color-grey);
23
+ border-left: 3px solid transparent;
24
+ border-right: 3px solid transparent;
25
+ margin-top: -1px;
26
+ margin-left: 0.4rem;
27
+ display: inline-block;
28
+ }
29
+
30
+ &:hover {
31
+ .atk-dropdown {
32
+ display: block;
33
+ }
34
+ }
35
+
36
+ .atk-dropdown {
37
+ z-index: 888;
38
+ display: none;
39
+ height: auto !important;
40
+ max-height: calc(100vh - 2.7rem);
41
+ overflow-y: auto;
42
+ position: absolute;
43
+ top: 100%;
44
+ right: 0;
45
+ width: 100%;
46
+ background-color: var(--at-color-bg);
47
+ padding: 0.6rem 0;
48
+ border: 1px solid var(--at-color-border);
49
+ text-align: center;
50
+ border-radius: 0.25rem;
51
+ white-space: nowrap;
52
+ margin: 0;
53
+ list-style: none;
54
+ }
55
+
56
+ .atk-dropdown-item {
57
+ span, a {
58
+ display: block;
59
+ line-height: 2rem;
60
+ position: relative;
61
+ border-bottom: none;
62
+ font-weight: 400;
63
+ padding: 0 1.5rem;
64
+
65
+ &:hover {
66
+ color: var(--at-color-main);
67
+ }
68
+ }
69
+
70
+ &.active span, a {
71
+ color: var(--at-color-main);
72
+ }
73
+ }
74
+ }
75
+
10
76
  .atk-comment-count {
11
77
  font-size: 15px;
12
78
 
@@ -248,7 +248,7 @@ i.atk-icon {
248
248
  & > .atk-layer-dialog-content {
249
249
  .atk-captcha-img {
250
250
  cursor: pointer;
251
- width: 100px;
251
+ width: 170px;
252
252
  height: auto;
253
253
  margin-right: 10px;
254
254
  padding-right: 10px;
@@ -32,6 +32,7 @@
32
32
  display: flex;
33
33
  flex-direction: row;
34
34
  align-items: center;
35
+ z-index: 99999;
35
36
 
36
37
  .atk-sidebar-close {
37
38
  display: flex;
@@ -62,6 +62,9 @@ export default interface ArtalkConfig {
62
62
  /** 评论投票反对按钮 */
63
63
  voteDown?: boolean
64
64
 
65
+ /** PV 元素 Selector */
66
+ pvEl?: string
67
+
65
68
  /** 暗黑模式 */
66
69
  darkMode?: boolean
67
70
 
@@ -77,6 +80,9 @@ export default interface ArtalkConfig {
77
80
  /** 显示 UA 徽标 */
78
81
  uaBadge?: boolean
79
82
 
83
+ /** 图片上传功能 */
84
+ imgUpload?: boolean
85
+
80
86
  /** 版本检测 */
81
87
  versionCheck?: boolean
82
88
  }
@@ -29,6 +29,9 @@ export interface CommentData {
29
29
  /** 是否待审 */
30
30
  is_pending?: boolean
31
31
 
32
+ /** 是否置顶 */
33
+ is_pinned?: boolean
34
+
32
35
  /** 是否为管理员 */
33
36
  is_admin?: boolean
34
37
 
@@ -84,6 +87,9 @@ export interface ListData {
84
87
 
85
88
  /** API 版本 */
86
89
  api_version: ApiVersionData
90
+
91
+ /** 后端配置 */
92
+ conf: any // TODO
87
93
  }
88
94
 
89
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