artalk 2.2.12 → 2.3.2

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 (78) hide show
  1. package/dist/Artalk.css +1 -1
  2. package/dist/Artalk.es.js +1535 -1175
  3. package/dist/Artalk.iife.js +1 -1
  4. package/dist/Artalk.js +1 -1
  5. package/dist/ArtalkLite.css +1 -0
  6. package/dist/ArtalkLite.es.js +5123 -0
  7. package/dist/ArtalkLite.iife.js +1 -0
  8. package/dist/ArtalkLite.js +1 -0
  9. package/index.html +2 -2
  10. package/package.json +6 -5
  11. package/src/api/index.ts +20 -1
  12. package/src/api/request.ts +7 -7
  13. package/src/artalk.ts +117 -121
  14. package/src/comment/actions.ts +86 -0
  15. package/src/{components/html → comment}/comment.html +1 -1
  16. package/src/comment/comment.ts +191 -0
  17. package/src/comment/index.ts +3 -0
  18. package/src/comment/render.ts +425 -0
  19. package/src/components/action-btn.ts +6 -3
  20. package/src/components/dialog.ts +7 -3
  21. package/src/components/read-more-btn.ts +6 -2
  22. package/src/context.ts +204 -13
  23. package/src/defaults.ts +16 -5
  24. package/src/editor/editor.html +18 -0
  25. package/src/editor/editor.ts +419 -0
  26. package/src/editor/index.ts +3 -0
  27. package/src/editor/plugs/editor-plug.ts +51 -0
  28. package/src/{components/editor-plugs → editor/plugs}/emoticons-plug.less +2 -1
  29. package/src/{components/editor-plugs → editor/plugs}/emoticons-plug.ts +65 -63
  30. package/src/editor/plugs/header-input-plug.ts +78 -0
  31. package/src/{components/editor-plugs → editor/plugs}/preview-plug.less +0 -0
  32. package/src/editor/plugs/preview-plug.ts +40 -0
  33. package/src/editor/plugs/upload-plug.ts +124 -0
  34. package/src/i18n/en.ts +78 -0
  35. package/src/i18n/index.ts +21 -0
  36. package/src/i18n/zh-CN.ts +82 -0
  37. package/src/layer/index.ts +4 -0
  38. package/src/{components → layer}/layer.ts +9 -9
  39. package/src/{components/html → layer}/sidebar-layer.html +0 -0
  40. package/src/{components → layer}/sidebar-layer.ts +17 -47
  41. package/src/lib/checker/admin-checker.ts +7 -7
  42. package/src/lib/checker/captcha-checker.ts +5 -6
  43. package/src/lib/checker/index.ts +22 -17
  44. package/src/lib/component.ts +7 -2
  45. package/src/lib/stat.ts +63 -0
  46. package/src/lib/ui.ts +6 -12
  47. package/src/lib/user.ts +20 -3
  48. package/src/lib/utils.ts +23 -9
  49. package/src/list/index.ts +3 -0
  50. package/src/{components → list}/list-lite.ts +80 -100
  51. package/src/{components → list}/list-nest.ts +0 -0
  52. package/src/{components/html → list}/list.html +3 -6
  53. package/src/{components → list}/list.ts +44 -42
  54. package/src/style/_variables.less +1 -2
  55. package/src/style/comment.less +26 -13
  56. package/src/style/editor.less +22 -14
  57. package/src/style/list.less +6 -9
  58. package/src/style/main.less +9 -8
  59. package/src/style/pagination.less +1 -1
  60. package/test/ArtalkCount.html +16 -0
  61. package/test/ArtalkI18n.html +15 -0
  62. package/test/ArtalkLite.html +15 -0
  63. package/types/artalk-config.d.ts +50 -32
  64. package/types/context.d.ts +128 -0
  65. package/types/event.d.ts +6 -66
  66. package/types/global.d.ts +1 -0
  67. package/types/plug.d.ts +4 -0
  68. package/{vite-common.config.ts → vite-base.config.ts} +6 -4
  69. package/vite-demo.config.ts +2 -4
  70. package/vite-lite.config.ts +22 -0
  71. package/vite.config.ts +9 -12
  72. package/src/components/_component.ts.txt +0 -10
  73. package/src/components/comment.ts +0 -605
  74. package/src/components/editor-plugs/_example-plug.ts.txt +0 -29
  75. package/src/components/editor-plugs/editor-plug.ts +0 -20
  76. package/src/components/editor-plugs/preview-plug.ts +0 -48
  77. package/src/components/editor.ts +0 -607
  78. package/src/components/html/editor.html +0 -18
@@ -14,24 +14,53 @@ type OwOFormatType = {
14
14
  }
15
15
 
16
16
  export default class EmoticonsPlug extends EditorPlug {
17
- public $el!: HTMLElement
17
+ public static Name = 'emoticons'
18
+
19
+ declare protected $panel: HTMLElement
20
+
18
21
  public emoticons: EmoticonListData = []
22
+ public loadingTask: Promise<void>|null = null
19
23
 
20
24
  public $grpWrap!: HTMLElement
21
25
  public $grpSwitcher!: HTMLElement
22
26
 
23
- public loadingTask: Promise<void>|null = null
24
-
25
- constructor (public editor: Editor) {
27
+ public constructor(public editor: Editor) {
26
28
  super(editor)
27
29
 
28
- this.$el = Utils.createElement(`<div class="atk-editor-plug-emoticons"></div>`)
30
+ this.registerPanel(`<div class="atk-editor-plug-emoticons"></div>`)
31
+ this.registerBtn(this.ctx.$t('emoticon'))
32
+
33
+ // 表情包预加载
34
+ window.setTimeout(() => {
35
+ this.loadEmoticonsData()
36
+ }, 1000) // 延迟 1s 加载
37
+ }
38
+
39
+ public onPanelShow() {
40
+ ;(async () => {
41
+ await this.loadEmoticonsData()
42
+
43
+ // 初始化元素
44
+ if (!this.isImgLoaded) {
45
+ this.initEmoticonsList()
46
+ this.isImgLoaded = true
47
+ }
48
+
49
+ // 延迟执行,防止无法读取高度
50
+ setTimeout(() => {
51
+ this.changeListHeight()
52
+ }, 30)
53
+ })()
54
+ }
55
+
56
+ public onPanelHide() {
57
+ this.$panel.parentElement!.style.height = ''
29
58
  }
30
59
 
31
60
  isListLoaded = false
32
61
  isImgLoaded = false
33
62
 
34
- async loadEmoticonsData() {
63
+ public async loadEmoticonsData() {
35
64
  if (this.isListLoaded) return
36
65
  if (this.loadingTask !== null) {
37
66
  await this.loadingTask
@@ -40,23 +69,23 @@ export default class EmoticonsPlug extends EditorPlug {
40
69
 
41
70
  // 数据处理
42
71
  this.loadingTask = (async () => {
43
- Ui.showLoading(this.$el)
72
+ Ui.showLoading(this.$panel)
44
73
  this.emoticons = await this.handleData(this.ctx.conf.emoticons)
45
- Ui.hideLoading(this.$el)
74
+ Ui.hideLoading(this.$panel)
46
75
  this.loadingTask = null
47
76
  this.isListLoaded = true
48
77
  })()
49
78
  await this.loadingTask
50
79
  }
51
80
 
52
- async handleData(data: any): Promise<any[]> {
81
+ private async handleData(data: any): Promise<any[]> {
53
82
  if (!Array.isArray(data) && ['object', 'string'].includes(typeof data)) {
54
83
  data = [data]
55
84
  }
56
85
 
57
86
  if (!Array.isArray(data)) {
58
- Ui.setError(this.$el, "表情包数据必须为 Array/Object/String 类型")
59
- Ui.hideLoading(this.$el)
87
+ Ui.setError(this.$panel, "表情包数据必须为 Array/Object/String 类型")
88
+ Ui.hideLoading(this.$panel)
60
89
  return []
61
90
  }
62
91
 
@@ -105,7 +134,7 @@ export default class EmoticonsPlug extends EditorPlug {
105
134
  }
106
135
 
107
136
  /** 远程加载 */
108
- async remoteLoad(url: string): Promise<any> {
137
+ private async remoteLoad(url: string): Promise<any> {
109
138
  if (!url) return []
110
139
 
111
140
  try {
@@ -113,14 +142,14 @@ export default class EmoticonsPlug extends EditorPlug {
113
142
  const json = await resp.json()
114
143
  return json
115
144
  } catch (err) {
116
- Ui.hideLoading(this.$el)
117
- Ui.setError(this.$el, `表情加载失败 ${String(err)}`)
145
+ Ui.hideLoading(this.$panel)
146
+ Ui.setError(this.$panel, `表情加载失败 ${String(err)}`)
118
147
  return []
119
148
  }
120
149
  }
121
150
 
122
151
  /** 避免表情 item.key 为 null 的情况 */
123
- solveNullKey(data: EmoticonGrpData[]) {
152
+ private solveNullKey(data: EmoticonGrpData[]) {
124
153
  data.forEach((grp) => {
125
154
  grp.items.forEach((item, index) => {
126
155
  if (!item.key) item.key = `${grp.name} ${index+1}`
@@ -129,7 +158,7 @@ export default class EmoticonsPlug extends EditorPlug {
129
158
  }
130
159
 
131
160
  /** 避免相同 item.key */
132
- solveSameKey(data: EmoticonGrpData[]) {
161
+ private solveSameKey(data: EmoticonGrpData[]) {
133
162
  const tmp: {[key: string]: number} = {}
134
163
  data.forEach((grp) => {
135
164
  grp.items.forEach(item => {
@@ -143,7 +172,7 @@ export default class EmoticonsPlug extends EditorPlug {
143
172
  }
144
173
 
145
174
  /** 判断是否为 OwO 格式 */
146
- isOwOFormat(data: any) {
175
+ private isOwOFormat(data: any) {
147
176
  try {
148
177
  return (typeof data === 'object') && !!Object.values(data).length
149
178
  && Array.isArray(Object.keys(Object.values<any>(data)[0].container))
@@ -152,7 +181,7 @@ export default class EmoticonsPlug extends EditorPlug {
152
181
  }
153
182
 
154
183
  /** 转换 OwO 格式数据 */
155
- convertOwO(owoData: OwOFormatType): EmoticonListData {
184
+ private convertOwO(owoData: OwOFormatType): EmoticonListData {
156
185
  const dest: EmoticonListData = []
157
186
  Object.entries(owoData).forEach(([grpName, grp]) => {
158
187
  const nGrp: EmoticonGrpData = { name: grpName, type: grp.type, items: [] }
@@ -171,10 +200,10 @@ export default class EmoticonsPlug extends EditorPlug {
171
200
  }
172
201
 
173
202
  /** 初始化表情列表界面 */
174
- initEmoticonsList () {
203
+ private initEmoticonsList() {
175
204
  // 表情列表
176
205
  this.$grpWrap = Utils.createElement(`<div class="atk-grp-wrap"></div>`)
177
- this.$el.append(this.$grpWrap)
206
+ this.$panel.append(this.$grpWrap)
178
207
 
179
208
  this.emoticons.forEach((grp, index) => {
180
209
  const $grp = Utils.createElement(`<div class="atk-grp" style="display: none;"></div>`)
@@ -208,24 +237,25 @@ export default class EmoticonsPlug extends EditorPlug {
208
237
  })
209
238
  })
210
239
 
211
- // 表情分类
212
- this.$grpSwitcher = Utils.createElement(`<div class="atk-grp-switcher"></div>`)
213
- this.$el.append(this.$grpSwitcher)
214
- this.emoticons.forEach((grp, index) => {
215
- const $item = Utils.createElement('<span />')
216
- $item.innerText = grp.name
217
- $item.setAttribute('data-index', String(index))
218
- $item.onclick = () => (this.openGrp(index))
219
- this.$grpSwitcher.append($item)
220
- })
240
+ // 表情分类切换 bar
241
+ if (this.emoticons.length > 1) {
242
+ this.$grpSwitcher = Utils.createElement(`<div class="atk-grp-switcher"></div>`)
243
+ this.$panel.append(this.$grpSwitcher)
244
+ this.emoticons.forEach((grp, index) => {
245
+ const $item = Utils.createElement('<span />')
246
+ $item.innerText = grp.name
247
+ $item.setAttribute('data-index', String(index))
248
+ $item.onclick = () => (this.openGrp(index))
249
+ this.$grpSwitcher.append($item)
250
+ })
251
+ }
221
252
 
222
253
  // 默认打开第一个分类
223
- if (this.emoticons.length > 0)
224
- this.openGrp(0)
254
+ if (this.emoticons.length > 0) this.openGrp(0)
225
255
  }
226
256
 
227
257
  /** 打开一个表情组 */
228
- openGrp (index: number) {
258
+ public openGrp(index: number) {
229
259
  Array.from(this.$grpWrap.children).forEach((item) => {
230
260
  const el = item as HTMLElement
231
261
  if (el.getAttribute('data-index') !== String(index)) {
@@ -241,41 +271,13 @@ export default class EmoticonsPlug extends EditorPlug {
241
271
  this.changeListHeight()
242
272
  }
243
273
 
244
- static Name = 'emoticons'
245
- static BtnHTML = '表情'
246
-
247
- getEl () {
248
- return this.$el
249
- }
250
-
251
- changeListHeight () {
274
+ private changeListHeight() {
252
275
  /* const listWrapHeight = Utils.getHeight(this.$grpWrapem)
253
276
  this.editor.plugWrapEl.style.height = `${listWrapHeight > 150 ? listWrapHeight : 150}px` */
254
277
  }
255
278
 
256
- onShow () {
257
- ;(async () => {
258
- await this.loadEmoticonsData()
259
-
260
- // 初始化元素
261
- if (!this.isImgLoaded) {
262
- this.initEmoticonsList()
263
- this.isImgLoaded = true
264
- }
265
-
266
- // 延迟执行,防止无法读取高度
267
- setTimeout(() => {
268
- this.changeListHeight()
269
- }, 30)
270
- })()
271
- }
272
-
273
- onHide () {
274
- this.$el.parentElement!.style.height = ''
275
- }
276
-
277
279
  /** 处理评论 content 中的表情内容 */
278
- public transEmoticonImageText (text: string) {
280
+ public transEmoticonImageText(text: string) {
279
281
  if (!this.emoticons || !Array.isArray(this.emoticons))
280
282
  return text
281
283
 
@@ -0,0 +1,78 @@
1
+ import Editor from '../editor'
2
+ import EditorPlug from './editor-plug'
3
+
4
+ export default class HeaderInputPlug extends EditorPlug {
5
+ public static Name = 'headerInput'
6
+
7
+ public constructor(editor: Editor) {
8
+ super(editor)
9
+
10
+ this.registerHeaderInputEvt((key, $input) => {
11
+ if (key === 'nick' || key === 'email') {
12
+ this.fetchUserInfo()
13
+ }
14
+ })
15
+
16
+ // Link URL 自动补全协议
17
+ this.editor.$link.addEventListener('change', () => {
18
+ const link = this.editor.$link.value.trim()
19
+ if (!!link && !/^(http|https):\/\//.test(link)) {
20
+ this.editor.$link.value = `https://${link}`
21
+ this.ctx.user.update({ link: this.editor.$link.value })
22
+ }
23
+ })
24
+ }
25
+
26
+ queryUserInfo = {
27
+ timeout: <number|null>null,
28
+ abortFunc: <(() => void)|null>null
29
+ }
30
+
31
+ /** 远程获取用户数据 */
32
+ fetchUserInfo() {
33
+ this.ctx.user.logout()
34
+
35
+ // 获取用户信息
36
+ if (this.queryUserInfo.timeout) window.clearTimeout(this.queryUserInfo.timeout) // 清除待发出的请求
37
+ if (this.queryUserInfo.abortFunc) this.queryUserInfo.abortFunc() // 之前发出未完成的请求立刻中止
38
+
39
+ this.queryUserInfo.timeout = window.setTimeout(() => {
40
+ this.queryUserInfo.timeout = null // 清理
41
+
42
+ const {req, abort} = this.ctx.getApi().userGet(
43
+ this.ctx.user.data.nick, this.ctx.user.data.email
44
+ )
45
+ this.queryUserInfo.abortFunc = abort
46
+ req.then(data => {
47
+ if (!data.is_login) {
48
+ this.ctx.user.logout()
49
+ }
50
+
51
+ // 未读消息更新
52
+ this.ctx.updateNotifies(data.unread)
53
+
54
+ // 若用户为管理员,执行登陆操作
55
+ if (this.ctx.user.checkHasBasicUserInfo() && !data.is_login && data.user?.is_admin) {
56
+ this.showLoginDialog()
57
+ }
58
+
59
+ // 自动填入 link
60
+ if (data.user && data.user.link) {
61
+ this.editor.$link.value = data.user.link
62
+ this.ctx.user.update({ link: data.user.link })
63
+ }
64
+ })
65
+ .catch(() => {})
66
+ .finally(() => {
67
+ this.queryUserInfo.abortFunc = null // 清理
68
+ })
69
+ }, 400) // 延迟执行,减少请求次数
70
+ }
71
+
72
+ showLoginDialog() {
73
+ this.ctx.checkAdmin({
74
+ onSuccess: () => {
75
+ }
76
+ })
77
+ }
78
+ }
@@ -0,0 +1,40 @@
1
+ import './preview-plug.less'
2
+
3
+ import Editor from '../editor'
4
+ import EditorPlug from './editor-plug'
5
+
6
+ export default class PreviewPlug extends EditorPlug {
7
+ public static Name = 'preview'
8
+ declare protected $panel: HTMLElement
9
+
10
+ private isBind = false
11
+
12
+ public constructor(editor: Editor) {
13
+ super(editor)
14
+
15
+ this.registerPanel(`<div class="atk-editor-plug-preview"></div>`)
16
+
17
+ let btnText = this.editor.$t('preview')
18
+ if (this.ctx.markedInstance) btnText += ` <i title="Markdown is supported"><svg class="markdown" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M14.85 3H1.15C.52 3 0 3.52 0 4.15v7.69C0 12.48.52 13 1.15 13h13.69c.64 0 1.15-.52 1.15-1.15v-7.7C16 3.52 15.48 3 14.85 3zM9 11H7V8L5.5 9.92 4 8v3H2V5h2l1.5 2L7 5h2v6zm2.99.5L9.5 8H11V5h2v3h1.5l-2.51 3.5z"></path></svg></i>`
19
+ this.registerBtn(btnText)
20
+ }
21
+
22
+ public onPanelShow() {
23
+ this.updateContent()
24
+
25
+ if (!this.isBind) {
26
+ const event = () => {
27
+ this.updateContent()
28
+ }
29
+ this.editor.$textarea.addEventListener('input', event)
30
+ this.editor.$textarea.addEventListener('change', event)
31
+ this.isBind = true
32
+ }
33
+ }
34
+
35
+ public updateContent() {
36
+ if (this.$panel.style.display !== 'none') {
37
+ this.$panel.innerHTML = this.editor.getContentMarked()
38
+ }
39
+ }
40
+ }
@@ -0,0 +1,124 @@
1
+ import * as Utils from '@/lib/utils'
2
+ import Editor from '../editor'
3
+ import EditorPlug from './editor-plug'
4
+
5
+ export default class UploadPlug extends EditorPlug {
6
+ public static Name = 'upload'
7
+
8
+ public $imgUploadInput?: HTMLInputElement
9
+
10
+ /** 允许的图片格式 */
11
+ allowImgExts = ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'svg', 'webp']
12
+
13
+ public constructor(editor: Editor) {
14
+ super(editor)
15
+
16
+ this.$imgUploadInput = document.createElement('input')
17
+ this.$imgUploadInput.type = 'file'
18
+ this.$imgUploadInput.style.display = 'none'
19
+ this.$imgUploadInput.accept = this.allowImgExts.map(o => `.${o}`).join(',')
20
+
21
+ const $btn = this.registerBtn(`${this.ctx.$t('image')}`)
22
+ $btn.after(this.$imgUploadInput)
23
+ $btn.onclick = () => {
24
+ // 选择图片
25
+ const $input = this.$imgUploadInput!
26
+ $input.onchange = () => {
27
+ (async () => { // 解决阻塞 UI 问题
28
+ if (!$input.files || $input.files.length === 0) return
29
+ const file = $input.files[0]
30
+ this.uploadImg(file)
31
+ })()
32
+ }
33
+ $input.click() // 显示选择图片对话框
34
+ }
35
+
36
+ this.ctx.on('conf-updated', () => {
37
+ if (!this.ctx.conf.imgUpload) {
38
+ this.getBtn()!.setAttribute('atk-only-admin-show', '')
39
+ this.ctx.checkAdminShowEl()
40
+ }
41
+ })
42
+
43
+ // 统一从 FileList 获取文件并上传图片方法
44
+ const uploadFromFileList = (files?: FileList) => {
45
+ if (!files) return
46
+
47
+ for (let i = 0; i < files.length; i++) {
48
+ const file = files[i]
49
+ this.uploadImg(file)
50
+ }
51
+ }
52
+
53
+ // 拖拽图片
54
+ // @link https://developer.mozilla.org/zh-CN/docs/Web/API/HTML_Drag_and_Drop_API/File_drag_and_drop
55
+ // 阻止浏览器的默认释放行为
56
+ this.editor.$textarea.addEventListener('dragover', (evt) => {
57
+ evt.stopPropagation()
58
+ evt.preventDefault()
59
+ })
60
+
61
+ this.editor.$textarea.addEventListener('drop', (evt) => {
62
+ const files = evt.dataTransfer?.files
63
+ if (files?.length) {
64
+ evt.preventDefault()
65
+ uploadFromFileList(files)
66
+ }
67
+ })
68
+
69
+ // 粘贴图片
70
+ this.editor.$textarea.addEventListener('paste', (evt) => {
71
+ const files = evt.clipboardData?.files
72
+ if (files?.length) {
73
+ evt.preventDefault()
74
+ uploadFromFileList(files)
75
+ }
76
+ })
77
+ }
78
+
79
+ public async uploadImg(file: File) {
80
+ const fileExt = /[^.]+$/.exec(file.name)
81
+ if (!fileExt || !this.allowImgExts.includes(fileExt[0])) return
82
+
83
+ // 未登录提示
84
+ if (!this.ctx.user.checkHasBasicUserInfo()) {
85
+ this.editor.showNotify(this.ctx.$t('uploadLoginMsg'), 'w')
86
+ return
87
+ }
88
+
89
+ // 插入图片前换一行
90
+ let insertPrefix = '\n'
91
+ if (this.editor.$textarea.value.trim() === '') insertPrefix = ''
92
+
93
+ // 插入占位加载文字
94
+ const uploadPlaceholderTxt = `${insertPrefix}![](Uploading ${file.name}...)`
95
+ this.editor.insertContent(uploadPlaceholderTxt)
96
+
97
+ // 上传图片
98
+ let resp: any
99
+ try {
100
+ if (!this.ctx.conf.imgUploader) {
101
+ // 使用 ArtalkGo 进行图片上传
102
+ resp = await this.ctx.getApi().imgUpload(file)
103
+ } else {
104
+ // 使用自定义的图片上传器
105
+ resp = {img_url: await this.ctx.conf.imgUploader(file)}
106
+ }
107
+ } catch (err: any) {
108
+ console.error(err)
109
+ this.editor.showNotify(`${this.ctx.$t('uploadFail')},${err.msg}`, 'e')
110
+ }
111
+ if (!!resp && resp.img_url) {
112
+ let imgURL = resp.img_url as string
113
+
114
+ // 若为相对路径,加上 artalk server
115
+ if (!Utils.isValidURL(imgURL)) imgURL = Utils.getURLBasedOnApi(this.ctx, imgURL)
116
+
117
+ // 上传成功插入图片
118
+ this.editor.setContent(this.editor.$textarea.value.replace(uploadPlaceholderTxt, `${insertPrefix}![](${imgURL})`))
119
+ } else {
120
+ // 上传失败删除加载文字
121
+ this.editor.setContent(this.editor.$textarea.value.replace(uploadPlaceholderTxt, ''))
122
+ }
123
+ }
124
+ }
package/src/i18n/en.ts ADDED
@@ -0,0 +1,78 @@
1
+ export default {
2
+ /* Editor */
3
+ placeholder: 'Leave a comment',
4
+ noComment: 'No Comment',
5
+ send: 'Send',
6
+ nick: 'Nickname',
7
+ email: 'Email',
8
+ link: 'Website',
9
+ emoticon: 'Emoji',
10
+ preview: 'Preview',
11
+ image: 'Image',
12
+ uploadFail: 'Upload Failed',
13
+ commentFail: 'Failed to comment',
14
+ restoredMsg: 'Content has been restored',
15
+ onlyAdminCanReply: 'Only admin can reply',
16
+ uploadLoginMsg: 'Please fill in your name and email to upload',
17
+
18
+ /* List */
19
+ counter: '{count} Comments',
20
+ sortLatest: 'Latest',
21
+ sortOldest: 'Oldest',
22
+ sortBest: 'Best',
23
+ sortAuthor: 'Author',
24
+ openComment: 'Open Comment',
25
+ closeComment: 'Close Comment',
26
+ listLoadFailMsg: 'Failed to load comments',
27
+ listRetry: 'Click to retry',
28
+ loadMore: 'Load More',
29
+
30
+ /* Comment */
31
+ admin: 'Admin',
32
+ reply: 'Reply',
33
+ voteUp: 'Up',
34
+ voteDown: 'Down',
35
+ voteFail: 'Vote Failed',
36
+ readMore: 'Read More',
37
+ actionConfirm: 'Confirm',
38
+ collapse: 'Collapse',
39
+ collapsed: 'Collapsed',
40
+ collapsedMsg: 'This comment has been collapsed',
41
+ expand: 'Expand',
42
+ approved: 'Approved',
43
+ pending: 'Pending',
44
+ pendingMsg: 'Pending, visible only to commenter.',
45
+ delete: 'Delete',
46
+ deleteConfirm: 'Confirm',
47
+ pin: 'Pin',
48
+ unpin: 'Unpin',
49
+
50
+ /* Time */
51
+ seconds: 'seconds ago',
52
+ minutes: 'minutes ago',
53
+ hours: 'hours ago',
54
+ days: 'days ago',
55
+ now: 'just now',
56
+
57
+ /* Checker */
58
+ adminCheck: 'Enter admin password:',
59
+ captchaCheck: 'Enter the CAPTCHA to continue:',
60
+ confirm: 'Confirm',
61
+ cancel: 'Cancel',
62
+
63
+ /* Sidebar */
64
+ msgCenter: 'Messages',
65
+ ctrlCenter: 'Admin',
66
+
67
+ /* General */
68
+ frontend: 'Frontend',
69
+ backend: 'Backend',
70
+ loading: 'Loading',
71
+ loadFail: 'Load Failed',
72
+ editing: 'Editing',
73
+ editFail: 'Edit Failed',
74
+ deleting: 'Deleting',
75
+ deleteFail: 'Delete Failed',
76
+ reqGot: 'Request got',
77
+ reqAborted: 'Request timed out or terminated unexpectedly',
78
+ }
@@ -0,0 +1,21 @@
1
+ import en from './en'
2
+ import zhCN from './zh-CN'
3
+
4
+ export type I18n = typeof en
5
+
6
+ // @note the Key name is followed by `ISO 639`
7
+ // https://en.wikipedia.org/wiki/ISO_639
8
+ // https://datatracker.ietf.org/doc/html/rfc5646#section-2.1
9
+ export const internal = {
10
+ 'en': en,
11
+ 'en-US': en,
12
+ 'zh-CN': zhCN,
13
+ }
14
+
15
+ export const external = {
16
+ 'jp-JP': {},
17
+ 'zh-TW': {},
18
+ 'fr-FR': {},
19
+ 'de-DE': {},
20
+ 'bn-IN': {},
21
+ }
@@ -0,0 +1,82 @@
1
+ import { I18n } from '.'
2
+
3
+ const zhCN: I18n = {
4
+ /* Editor */
5
+ placeholder: '键入内容...',
6
+ noComment: '「此时无声胜有声」',
7
+ send: '发送评论',
8
+ nick: '昵称',
9
+ email: '邮件',
10
+ link: '网址',
11
+ emoticon: '表情',
12
+ preview: '预览',
13
+ image: '图片',
14
+ uploadFail: '上传失败',
15
+ commentFail: '评论失败',
16
+ restoredMsg: '内容已自动恢复',
17
+ onlyAdminCanReply: '仅管理员可评论',
18
+ uploadLoginMsg: '填入你的名字邮箱才能上传哦',
19
+
20
+ /* List */
21
+ counter: '{count} 条评论',
22
+ sortLatest: '最新',
23
+ sortOldest: '最早',
24
+ sortBest: '最热',
25
+ sortAuthor: '作者',
26
+ openComment: '打开评论',
27
+ closeComment: '关闭评论',
28
+ listLoadFailMsg: '无法获取评论列表数据',
29
+ listRetry: '点击重新获取',
30
+ loadMore: '加载更多',
31
+
32
+ /* Comment */
33
+ admin: '管理员',
34
+ reply: '回复',
35
+ voteUp: '赞同',
36
+ voteDown: '反对',
37
+ voteFail: '投票失败',
38
+ readMore: '阅读更多',
39
+ actionConfirm: '确认操作',
40
+ collapse: '折叠',
41
+ collapsed: '已折叠',
42
+ collapsedMsg: '该评论已被系统或管理员折叠',
43
+ expand: '取消折叠',
44
+ approved: '已审',
45
+ pending: '待审',
46
+ pendingMsg: '审核中,仅本人可见。',
47
+ delete: '删除',
48
+ deleteConfirm: '确认删除',
49
+ pin: '置顶',
50
+ unpin: '取消置顶',
51
+
52
+ /* Time */
53
+ seconds: '秒前',
54
+ minutes: '分钟前',
55
+ hours: '小时前',
56
+ days: '天前',
57
+ now: '刚刚',
58
+
59
+ /* Checker */
60
+ adminCheck: '键入密码来验证管理员身份:',
61
+ captchaCheck: '键入验证码继续:',
62
+ confirm: '确认',
63
+ cancel: '取消',
64
+
65
+ /* Sidebar */
66
+ msgCenter: '通知中心',
67
+ ctrlCenter: '控制中心',
68
+
69
+ /* General */
70
+ frontend: '前端',
71
+ backend: '后端',
72
+ loading: '加载中',
73
+ loadFail: '加载失败',
74
+ editing: '修改中',
75
+ editFail: '修改失败',
76
+ deleting: '删除中',
77
+ deleteFail: '删除失败',
78
+ reqGot: '请求响应',
79
+ reqAborted: '请求超时或意外终止',
80
+ }
81
+
82
+ export default zhCN
@@ -0,0 +1,4 @@
1
+ import Layer, { GetLayerWrap } from './layer'
2
+
3
+ export default Layer
4
+ export { GetLayerWrap }