artalk 2.3.4 → 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 (99) hide show
  1. package/dist/Artalk.css +1 -1
  2. package/dist/Artalk.es.js +3171 -3008
  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 +3124 -2980
  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 -366
  71. package/src/api/page.ts +85 -0
  72. package/src/api/site.ts +51 -0
  73. package/src/api/system.ts +39 -0
  74. package/src/api/upload.ts +29 -0
  75. package/src/api/user.ts +75 -0
  76. package/src/artalk.ts +50 -46
  77. package/src/comment/actions.ts +3 -3
  78. package/src/comment/comment.ts +6 -9
  79. package/src/comment/render.ts +76 -40
  80. package/src/components/pagination.ts +0 -1
  81. package/src/context.ts +31 -31
  82. package/src/editor/editor.ts +2 -4
  83. package/src/editor/plugs/header-input-plug.ts +1 -1
  84. package/src/editor/plugs/upload-plug.ts +1 -1
  85. package/src/i18n/index.ts +117 -2
  86. package/src/layer/sidebar-layer.ts +1 -3
  87. package/src/lib/checker/admin-checker.ts +1 -1
  88. package/src/lib/checker/captcha-checker.ts +3 -3
  89. package/src/lib/stat.ts +2 -2
  90. package/src/lib/ui.ts +6 -0
  91. package/src/list/list-lite.ts +25 -25
  92. package/src/list/list.ts +3 -4
  93. package/src/main.ts +3 -0
  94. package/src/style/main.less +6 -0
  95. package/types/context.d.ts +4 -1
  96. package/types/global.d.ts +0 -2
  97. package/vite-base.config.ts +9 -5
  98. package/vite.config.ts +5 -1
  99. 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 // 关闭投票功能
226
+
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
+ })
216
232
 
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
- }
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 // 不允许回复
245
+
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
+ }
230
257
 
231
- // 折叠按钮
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,18 +277,22 @@ 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() {
252
284
  const pinnedBtn = new ActionBtn(this.ctx, {
253
285
  text: () => (this.data.is_pinned ? this.ctx.$t('unpin') : this.ctx.$t('pin')),
254
286
  adminOnly: true
255
287
  })
256
288
  pinnedBtn.appendTo(this.$actions)
257
289
  pinnedBtn.setClick(() => {
258
- this.comment.getActions().adminEdit('pinned', pendingBtn)
290
+ this.comment.getActions().adminEdit('pinned', pinnedBtn)
259
291
  })
292
+ }
260
293
 
261
- // 编辑按钮
294
+ // 操作按钮 - 编辑
295
+ private renderActions_Edit() {
262
296
  const editBtn = new ActionBtn(this.ctx, {
263
297
  text: this.ctx.$t('edit'),
264
298
  adminOnly: true
@@ -267,8 +301,10 @@ export default class CommentRender {
267
301
  editBtn.setClick(() => {
268
302
  this.ctx.editComment(this.data, this.$el)
269
303
  })
304
+ }
270
305
 
271
- // 删除按钮
306
+ // 操作按钮 - 删除
307
+ private renderActions_Del() {
272
308
  const delBtn = new ActionBtn(this.ctx, {
273
309
  text: this.ctx.$t('delete'),
274
310
  confirm: true,
@@ -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,24 +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)
116
122
  }
117
123
 
118
124
  public updateComment(commentData: CommentData): void {
119
- this.list.updateComment(commentData)
125
+ this.list?.updateComment(commentData)
120
126
  }
121
127
 
122
128
  public replyComment(commentData: CommentData, $comment: HTMLElement, scroll?: boolean): void {
@@ -136,12 +142,12 @@ export default class Context implements ContextApi {
136
142
  }
137
143
 
138
144
  public updateNotifies(notifies: NotifyData[]): void {
139
- this.list.updateUnread(notifies)
145
+ this.list?.updateUnread(notifies)
140
146
  }
141
147
 
142
148
  /* 评论列表 */
143
149
  public listReload(): void {
144
- this.list.reload()
150
+ this.list?.reload()
145
151
  }
146
152
 
147
153
  public reload(): void {
@@ -149,7 +155,15 @@ export default class Context implements ContextApi {
149
155
  }
150
156
 
151
157
  public listRefreshUI(): void {
152
- 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()
153
167
  }
154
168
 
155
169
  /* 编辑器 */
@@ -240,15 +254,7 @@ export default class Context implements ContextApi {
240
254
 
241
255
  /* i18n */
242
256
  public $t(key: keyof I18n, args: {[key: string]: string} = {}): string {
243
- let locales = this.conf.locale
244
- if (typeof locales === 'string') {
245
- locales = internalLocales[locales]
246
- }
247
-
248
- let str = locales?.[key] || key
249
- str = str.replace(/\{\s*(\w+?)\s*\}/g, (_, token) => args[token] || '')
250
-
251
- return str
257
+ return getI18n(this.conf.locale, key, args)
252
258
  }
253
259
 
254
260
  public setDarkMode(darkMode: boolean): void {
@@ -257,20 +263,14 @@ export default class Context implements ContextApi {
257
263
  this.conf.darkMode = darkMode
258
264
  this.trigger('conf-updated')
259
265
 
260
- if (this.conf.darkMode) {
261
- this.$root.classList.add(darkModeClassName)
262
- } else {
263
- this.$root.classList.remove(darkModeClassName)
264
- }
266
+ if (this.conf.darkMode) this.$root.classList.add(darkModeClassName)
267
+ else this.$root.classList.remove(darkModeClassName)
265
268
 
266
269
  // for Layer
267
270
  const { $wrap: $layerWrap } = GetLayerWrap(this)
268
271
  if ($layerWrap) {
269
- if (this.conf.darkMode) {
270
- $layerWrap.classList.add(darkModeClassName)
271
- } else {
272
- $layerWrap.classList.remove(darkModeClassName)
273
- }
272
+ if (this.conf.darkMode) $layerWrap.classList.add(darkModeClassName)
273
+ else $layerWrap.classList.remove(darkModeClassName)
274
274
  }
275
275
  }
276
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'
@@ -326,7 +324,7 @@ export default class Editor extends Component {
326
324
  /** 创建评论 */
327
325
  public async submitAdd() {
328
326
  try {
329
- const nComment = await this.ctx.getApi().add({
327
+ const nComment = await this.ctx.getApi().comment.add({
330
328
  content: this.getFinalContent(),
331
329
  nick: this.user.data.nick,
332
330
  email: this.user.data.email,
@@ -364,7 +362,7 @@ export default class Editor extends Component {
364
362
  email: this.$email.value,
365
363
  link: this.$link.value,
366
364
  }
367
- const nComment = await this.ctx.getApi().commentEdit({
365
+ const nComment = await this.ctx.getApi().comment.commentEdit({
368
366
  ...this.editComment, ...saveData
369
367
  })
370
368
  this.ctx.updateComment(nComment)
@@ -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/index.ts CHANGED
@@ -3,7 +3,7 @@ import zhCN from './zh-CN'
3
3
 
4
4
  export type I18n = typeof en
5
5
 
6
- // @note the Key name is followed by `ISO 639`
6
+ // @note the key of language is followed by `ISO 639`
7
7
  // https://en.wikipedia.org/wiki/ISO_639
8
8
  // https://datatracker.ietf.org/doc/html/rfc5646#section-2.1
9
9
  export const internal = {
@@ -14,8 +14,123 @@ export const internal = {
14
14
 
15
15
  export const external = {
16
16
  'jp-JP': {},
17
- 'zh-TW': {},
17
+ 'zh-TW': {
18
+ /* Editor */
19
+ placeholder: '鍵入內容...',
20
+ noComment: '「此時無聲勝有聲」',
21
+ send: '發送評論',
22
+ save: '保存評論',
23
+ nick: '昵稱',
24
+ email: '郵箱',
25
+ link: '網址',
26
+ emoticon: '表情',
27
+ preview: '預覽',
28
+ image: '圖片',
29
+ uploadFail: '上傳失敗',
30
+ commentFail: '評論失敗',
31
+ restoredMsg: '內容已自動恢復',
32
+ onlyAdminCanReply: '僅管理員可評論',
33
+ uploadLoginMsg: '填入你的名字郵箱才能上傳哦',
34
+
35
+ /* List */
36
+ counter: '{count} 條評論',
37
+ sortLatest: '最新',
38
+ sortOldest: '最早',
39
+ sortBest: '最熱',
40
+ sortAuthor: '作者',
41
+ openComment: '打開評論',
42
+ closeComment: '關閉評論',
43
+ listLoadFailMsg: '無法獲取評論列表數據',
44
+ listRetry: '點擊重新獲取',
45
+ loadMore: '加載更多',
46
+
47
+ /* Comment */
48
+ admin: '管理員',
49
+ reply: '回覆',
50
+ voteUp: '贊同',
51
+ voteDown: '反對',
52
+ voteFail: '投票失敗',
53
+ readMore: '閱讀更多',
54
+ actionConfirm: '確認操作',
55
+ collapse: '摺疊',
56
+ collapsed: '已摺疊',
57
+ collapsedMsg: '該評論已被系統或管理員摺疊',
58
+ expand: '展開',
59
+ approved: '已審',
60
+ pending: '待審',
61
+ pendingMsg: '審核中,僅本人可見。',
62
+ edit: '編輯',
63
+ editCancel: '取消編輯',
64
+ delete: '刪除',
65
+ deleteConfirm: '確認刪除',
66
+ pin: '置頂',
67
+ unpin: '取消置頂',
68
+
69
+ /* Time */
70
+ seconds: '秒前',
71
+ minutes: '分鐘前',
72
+ hours: '小時前',
73
+ days: '天前',
74
+ now: '剛剛',
75
+
76
+ /* Checker */
77
+ adminCheck: '鍵入密碼來驗證管理員身份:',
78
+ captchaCheck: '鍵入驗證碼繼續:',
79
+ confirm: '確認',
80
+ cancel: '取消',
81
+
82
+ /* Sidebar */
83
+ msgCenter: '通知中心',
84
+ ctrlCenter: '控制中心',
85
+
86
+ /* General */
87
+ frontend: '前端',
88
+ backend: '後端',
89
+ loading: '加載中',
90
+ loadFail: '加載失敗',
91
+ editing: '修改中',
92
+ editFail: '修改失敗',
93
+ deleting: '刪除中',
94
+ deleteFail: '刪除失敗',
95
+ reqGot: '請求響應',
96
+ reqAborted: '請求超時或意外終止',
97
+ },
18
98
  'fr-FR': {},
19
99
  'de-DE': {},
20
100
  'bn-IN': {},
21
101
  }
102
+
103
+ /**
104
+ * get a locale object by language name
105
+ */
106
+ function getLocaleSet(lang: string): I18n {
107
+ // normalize a key of language to `ISO 639`
108
+ lang = lang.replace(
109
+ /^([a-zA-Z]+)(-[a-zA-Z]+)?$/,
110
+ (_, p1: string, p2: string) => (p1.toLowerCase() + (p2 || '').toUpperCase())
111
+ )
112
+
113
+ // case not found
114
+ if (!internal[lang]) {
115
+ return internal.en // use `en` by default
116
+ }
117
+
118
+ return internal[lang]
119
+ }
120
+
121
+ /**
122
+ * get a i18n message by key
123
+ */
124
+ function getI18n(locale: I18n|string, key: keyof I18n, args: {[key: string]: string} = {}) {
125
+ if (typeof locale === 'string') {
126
+ locale = getLocaleSet(locale)
127
+ }
128
+
129
+ let str = locale?.[key] || key
130
+ str = str.replace(/\{\s*(\w+?)\s*\}/g, (_, token) => args[token] || '')
131
+
132
+ return str
133
+ }
134
+
135
+ export { getLocaleSet, getI18n }
136
+ export default getI18n
@@ -1,5 +1,3 @@
1
- import '../style/sidebar-layer.less'
2
-
3
1
  import Context from '~/types/context'
4
2
  import Component from '@/lib/component'
5
3
  import * as Utils from '@/lib/utils'
@@ -62,7 +60,7 @@ export default class SidebarLayer extends Component {
62
60
 
63
61
  // 管理员身份验证 (若身份失效,弹出验证窗口)
64
62
  ;(async () => {
65
- const resp = await this.ctx.getApi().loginStatus()
63
+ const resp = await this.ctx.getApi().user.loginStatus()
66
64
  if (resp.is_admin && !resp.is_login) {
67
65
  this.layer?.hide()
68
66
  this.firstShow = true
@@ -11,7 +11,7 @@ const AdminChecker: Checker = {
11
11
  password: inputVal
12
12
  }
13
13
 
14
- return that.ctx.getApi().login(data.name, data.email, data.password)
14
+ return that.ctx.getApi().user.login(data.name, data.email, data.password)
15
15
  },
16
16
 
17
17
  body(that, ctx) {
@@ -4,7 +4,7 @@ import { Checker } from '.'
4
4
 
5
5
  const CaptchaChecker: Checker = {
6
6
  request(that, ctx, inputVal) {
7
- return that.ctx.getApi().captchaCheck(inputVal)
7
+ return that.ctx.getApi().captcha.captchaCheck(inputVal)
8
8
  },
9
9
 
10
10
  body(that, ctx) {
@@ -34,7 +34,7 @@ const CaptchaChecker: Checker = {
34
34
  if (stop) return
35
35
  let isPass = false
36
36
  try {
37
- const resp = await that.ctx.getApi().captchaStatus()
37
+ const resp = await that.ctx.getApi().captcha.captchaStatus()
38
38
  isPass = resp.is_pass
39
39
  } catch { isPass = false }
40
40
  if (isPass) {
@@ -60,7 +60,7 @@ const CaptchaChecker: Checker = {
60
60
  // 刷新验证码
61
61
  elem.querySelector<HTMLElement>('.atk-captcha-img')!.onclick = () => {
62
62
  const imgEl = elem.querySelector('.atk-captcha-img')
63
- that.ctx.getApi().captchaGet()
63
+ that.ctx.getApi().captcha.captchaGet()
64
64
  .then((imgData) => {
65
65
  imgEl!.setAttribute('src', imgData)
66
66
  })
package/src/lib/stat.ts CHANGED
@@ -21,7 +21,7 @@ export async function initCountWidget(p: CountConf) {
21
21
  }
22
22
 
23
23
  // PV
24
- const curtPagePvNum = p.pvAdd ? await p.ctx.getApi().pv() : undefined
24
+ const curtPagePvNum = p.pvAdd ? await p.ctx.getApi().page.pv() : undefined
25
25
  const pvEl = p.ctx.conf.pvEl
26
26
  if (pvEl && document.querySelector(pvEl)) {
27
27
  handleStatCount(p, {
@@ -52,7 +52,7 @@ export async function handleStatCount(
52
52
  .filter((pageKey) => pageCounts[pageKey] === undefined)
53
53
  queryPageKeys = [...new Set(queryPageKeys)] // 去重
54
54
  if (queryPageKeys.length > 0) {
55
- const counts: any = await p.ctx.getApi().stat(args.api, queryPageKeys)
55
+ const counts: any = await p.ctx.getApi().page.stat(args.api, queryPageKeys)
56
56
  pageCounts = { ...pageCounts, ...counts }
57
57
  }
58
58
 
package/src/lib/ui.ts CHANGED
@@ -32,6 +32,12 @@ export function hideLoading(parentElem: HTMLElement) {
32
32
  if ($loading) $loading.style.display = 'none'
33
33
  }
34
34
 
35
+ /** 元素加载动画 */
36
+ export function setLoading(val: boolean, parentElem: HTMLElement) {
37
+ if (val) showLoading(parentElem)
38
+ else hideLoading(parentElem)
39
+ }
40
+
35
41
  /** 元素是否用户可见 */
36
42
  export function isVisible(el: HTMLElement, viewport: HTMLElement = document.documentElement) {
37
43
  const viewportHeight = viewport.clientHeight