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
@@ -0,0 +1,85 @@
1
+ import { PageData, CommentData } from '~/types/artalk-data'
2
+ import ApiBase from './api-base'
3
+
4
+ /**
5
+ * 页面 API
6
+ */
7
+ export default class PageApi extends ApiBase {
8
+ /** 页面 · 获取 */
9
+ public async pageGet(siteName?: string, offset?: number, limit?: number) {
10
+ const params: any = {
11
+ site_name: siteName || '',
12
+ offset: offset || 0,
13
+ limit: limit || 15,
14
+ }
15
+
16
+ const d = await this.POST<any>('/admin/page-get', params)
17
+ return (d as { pages: PageData[], total: number })
18
+ }
19
+
20
+ /** 页面 · 修改 */
21
+ public async pageEdit(data: PageData) {
22
+ const params: any = {
23
+ id: data.id,
24
+ key: data.key,
25
+ title: data.title,
26
+ admin_only: data.admin_only,
27
+ site_name: data.site_name || this.ctx.conf.site,
28
+ }
29
+
30
+ const d = await this.POST<any>('/admin/page-edit', params)
31
+ return (d.page as PageData)
32
+ }
33
+
34
+ /** 页面 · 删除 */
35
+ public pageDel(pageKey: string, siteName?: string) {
36
+ const params: any = {
37
+ key: String(pageKey),
38
+ site_name: siteName || '',
39
+ }
40
+
41
+ return this.POST('/admin/page-del', params)
42
+ }
43
+
44
+ /** 页面 · 数据更新 */
45
+ public async pageFetch(id?: number, siteName?: string, getStatus?: boolean) {
46
+ const params: any = {}
47
+ if (id) params.id = id
48
+ if (siteName) params.site_name = siteName
49
+ if (getStatus) params.get_status = getStatus
50
+
51
+ const d = await this.POST<any>('/admin/page-fetch', params)
52
+ return (d as any)
53
+ }
54
+
55
+ /** PV */
56
+ public async pv() {
57
+ const params: any = {
58
+ site_name: this.ctx.conf.site || '',
59
+ page_key: this.ctx.conf.pageKey || '',
60
+ page_title: this.ctx.conf.pageTitle || ''
61
+ }
62
+
63
+ const p = await this.POST<any>('/pv', params)
64
+ return p.pv as number
65
+ }
66
+
67
+ /** 统计 */
68
+ public async stat(
69
+ type: 'latest_comments'|'latest_pages'|'pv_most_pages'|'comment_most_pages'|
70
+ 'page_pv'|'site_pv'|'page_comment'|'site_comment',
71
+ pageKeys?: string|string[],
72
+ limit?: number
73
+ ) {
74
+ const params: any = {
75
+ type,
76
+ site_name: this.ctx.conf.site || '',
77
+ }
78
+
79
+ if (pageKeys) params.page_keys = Array.isArray(pageKeys) ? pageKeys.join(',') : pageKeys
80
+ if (limit) params.limit = limit
81
+
82
+ const data = await this.POST<PageData[]|CommentData[]|object|number>(`/stat`, params)
83
+ return data
84
+ }
85
+ }
@@ -0,0 +1,51 @@
1
+ import { SiteData } from '~/types/artalk-data'
2
+ import ApiBase from './api-base'
3
+
4
+ /**
5
+ * 站点 API
6
+ */
7
+ export default class SiteApi extends ApiBase {
8
+ /** 站点 · 获取 */
9
+ public async siteGet() {
10
+ const params: any = {}
11
+
12
+ const d = await this.POST<any>('/admin/site-get', params)
13
+ return (d.sites as SiteData[])
14
+ }
15
+
16
+ /** 站点 · 创建 */
17
+ public async siteAdd(name: string, urls: string) {
18
+ const params: any = {
19
+ name, urls,
20
+ site_name: '' // 全局保留字段,当前站点名
21
+ }
22
+
23
+ const d = await this.POST<any>('/admin/site-add', params)
24
+ return (d.site as SiteData)
25
+ }
26
+
27
+ /** 站点 · 修改 */
28
+ public async siteEdit(data: SiteData) {
29
+ const params: any = {
30
+ id: data.id,
31
+ name: data.name || '',
32
+ urls: data.urls || '',
33
+ }
34
+
35
+ const d = await this.POST<any>('/admin/site-edit', params)
36
+ return (d.site as SiteData)
37
+ }
38
+
39
+ /** 站点 · 删除 */
40
+ public siteDel(id: number, delContent = false) {
41
+ const params: any = { id, del_content: delContent }
42
+
43
+ return this.POST('/admin/site-del', params)
44
+ }
45
+
46
+ /** 导出 */
47
+ public async export() {
48
+ const d = await this.Fetch(`/admin/export`, { method: 'POST' }, 0)
49
+ return (d.data?.data || '' as string)
50
+ }
51
+ }
@@ -0,0 +1,39 @@
1
+ import ArtalkConfig from '~/types/artalk-config'
2
+ import ApiBase from './api-base'
3
+
4
+ /**
5
+ * 系统 API
6
+ */
7
+ export default class SystemApi extends ApiBase {
8
+ /** 获取配置 */
9
+ public async conf() {
10
+ const data = await this.POST<any>(`/conf`)
11
+ const conf = (data.frontend_conf || {}) as ArtalkConfig
12
+
13
+ // Patch: `emoticons` config string to json
14
+ if (conf.emoticons && typeof conf.emoticons === "string") {
15
+ conf.emoticons = conf.emoticons.trim()
16
+ if (conf.emoticons.startsWith("[") || conf.emoticons.startsWith("{")) {
17
+ conf.emoticons = JSON.parse(conf.emoticons) // pase json
18
+ } else if (conf.emoticons === "false") {
19
+ conf.emoticons = false
20
+ }
21
+ }
22
+
23
+ return conf
24
+ }
25
+
26
+ /** 获取配置数据 */
27
+ public async getSettings() {
28
+ const data = await this.POST<string>('/admin/setting-get')
29
+ return data
30
+ }
31
+
32
+ /** 保存配置数据 */
33
+ public async saveSettings(yamlStr: string) {
34
+ const data = await this.POST<boolean>('/admin/setting-save', {
35
+ data: yamlStr,
36
+ })
37
+ return data
38
+ }
39
+ }
@@ -0,0 +1,29 @@
1
+ import { ToFormData } from './request'
2
+ import ApiBase from './api-base'
3
+
4
+ /**
5
+ * 上传 API
6
+ */
7
+ export default class UploadApi extends ApiBase {
8
+ /** 图片上传 */
9
+ public async imgUpload(file: File) {
10
+ const params: any = {
11
+ name: this.ctx.user.data.nick,
12
+ email: this.ctx.user.data.email,
13
+ page_key: this.ctx.conf.pageKey,
14
+ }
15
+
16
+ if (this.ctx.conf.site) params.site_name = this.ctx.conf.site
17
+
18
+ const form = ToFormData(params)
19
+ form.set('file', file)
20
+
21
+ const init: RequestInit = {
22
+ method: 'POST',
23
+ body: form
24
+ }
25
+
26
+ const json = await this.Fetch('/img-upload', init)
27
+ return ((json.data || {}) as any) as { img_file: string, img_url: string }
28
+ }
29
+ }
@@ -0,0 +1,75 @@
1
+ import { UserData, NotifyData } from '~/types/artalk-data'
2
+ import ApiBase from './api-base'
3
+ import { ToFormData } from './request'
4
+
5
+ /**
6
+ * 用户 API
7
+ */
8
+ export default class UserApi extends ApiBase {
9
+ /** 用户 · 登录 */
10
+ public async login(name: string, email: string, password: string) {
11
+ const params: any = {
12
+ name, email, password
13
+ }
14
+
15
+ if (this.ctx.conf.site) params.site_name = this.ctx.conf.site
16
+
17
+ const data = await this.POST<any>('/login', params)
18
+ return (data.token as string)
19
+ }
20
+
21
+ /** 用户 · 获取 */
22
+ public userGet(name: string, email: string) {
23
+ const ctrl = new AbortController()
24
+ const params: any = {
25
+ name, email, site_name: (this.ctx.conf.site || '')
26
+ }
27
+
28
+ const req = this.Fetch(`/user-get`, {
29
+ method: 'POST',
30
+ body: ToFormData(params),
31
+ signal: ctrl.signal,
32
+ }).then((json) => ({
33
+ user: json.data.user as UserData|null,
34
+ is_login: json.data.is_login as boolean,
35
+ unread: (json.data.unread || []) as NotifyData[],
36
+ unread_count: json.data.unread_count || 0,
37
+ }))
38
+
39
+ return {
40
+ req,
41
+ abort: () => { ctrl.abort() },
42
+ }
43
+ }
44
+
45
+ /** 用户 · 登录状态 */
46
+ public async loginStatus() {
47
+ const data = await this.POST<any>('/login-status', {
48
+ name: this.ctx.user.data.nick,
49
+ email: this.ctx.user.data.email
50
+ })
51
+ return (data || { is_login: false, is_admin: false }) as { is_login: boolean, is_admin: boolean }
52
+ }
53
+
54
+ /** 用户 · 注销 */
55
+ public async logout() {
56
+ return this.POST('/logout')
57
+ }
58
+
59
+ /** 已读标记 */
60
+ public markRead(notifyKey: string, readAll = false) {
61
+ const params: any = {
62
+ site_name: this.ctx.conf.site || '',
63
+ notify_key: notifyKey,
64
+ }
65
+
66
+ if (readAll) {
67
+ delete params.notify_key
68
+ params.read_all = true
69
+ params.name = this.ctx.user.data.nick
70
+ params.email = this.ctx.user.data.email
71
+ }
72
+
73
+ return this.POST(`/mark-read`, params)
74
+ }
75
+ }
package/src/artalk.ts CHANGED
@@ -6,7 +6,6 @@ import ArtalkPlug from '~/types/plug'
6
6
  import Context from '~/types/context'
7
7
  import ConcreteContext from './context'
8
8
  import defaults from './defaults'
9
- import { internal as internalLocales } from './i18n'
10
9
 
11
10
  import CheckerLauncher from './lib/checker'
12
11
  import Editor from './editor'
@@ -30,17 +29,15 @@ export default class Artalk {
30
29
  public ctx!: Context
31
30
  public $root!: HTMLElement
32
31
 
33
- public checkerLauncher!: CheckerLauncher
34
- public editor!: Editor
35
- public list!: List
36
- public sidebarLayer!: SidebarLayer
37
-
38
32
  /** Plugins (in global scope) */
39
33
  protected static Plugins: ArtalkPlug[] = [ Stat.PvCountWidget ]
40
34
 
41
35
  /** Plugins (in a instance scope) */
42
36
  protected instancePlugins: ArtalkPlug[] = []
43
37
 
38
+ /** 禁用的组件 */
39
+ public static DisabledComponents: string[] = []
40
+
44
41
  constructor(customConf: Partial<ArtalkConfig>) {
45
42
  /* 初始化基本配置 */
46
43
  this.conf = Artalk.HandelBaseConf(customConf)
@@ -63,30 +60,50 @@ export default class Artalk {
63
60
  this.initDarkMode()
64
61
  Utils.initMarked(this.ctx)
65
62
 
66
- // CheckerLauncher
67
- this.checkerLauncher = new CheckerLauncher(this.ctx)
68
- this.ctx.setCheckerLauncher(this.checkerLauncher)
69
-
70
- // 编辑器
71
- this.editor = new Editor(this.ctx)
72
- this.ctx.setEditor(this.editor)
73
- this.$root.appendChild(this.editor.$el)
74
-
75
- // 评论列表
76
- this.list = new List(this.ctx)
77
- this.ctx.setList(this.list)
78
- this.$root.appendChild(this.list.$el)
79
-
80
- // 侧边栏
81
- this.sidebarLayer = new SidebarLayer(this.ctx)
82
- this.ctx.setSidebarLayer(this.sidebarLayer)
83
- this.$root.appendChild(this.sidebarLayer.$el)
63
+ const Components: { [name: string]: () => void } = {
64
+ // CheckerLauncher
65
+ checkerLauncher: () => {
66
+ const checkerLauncher = new CheckerLauncher(this.ctx)
67
+ this.ctx.setCheckerLauncher(checkerLauncher)
68
+ },
69
+
70
+ // 编辑器
71
+ editor: () => {
72
+ const editor = new Editor(this.ctx)
73
+ this.ctx.setEditor(editor)
74
+ this.$root.appendChild(editor.$el)
75
+ },
76
+
77
+ // 评论列表
78
+ list: () => {
79
+ // 评论列表
80
+ const list = new List(this.ctx)
81
+ this.ctx.setList(list)
82
+ this.$root.appendChild(list.$el)
83
+
84
+ // 评论获取
85
+ list.fetchComments(0)
86
+ },
87
+
88
+ // 侧边栏 Layer
89
+ sidebarLayer: () => {
90
+ const sidebarLayer = new SidebarLayer(this.ctx)
91
+ this.ctx.setSidebarLayer(sidebarLayer)
92
+ this.$root.appendChild(sidebarLayer.$el)
93
+ },
94
+
95
+ // 默认事件绑定
96
+ eventsDefault: () => {
97
+ this.initEventBind()
98
+ }
99
+ }
84
100
 
85
- // 评论获取
86
- this.list.fetchComments(0)
101
+ // 组件初始化
102
+ Object.entries(Components).forEach(([name, initComponent]) => {
103
+ if (Artalk.DisabledComponents.includes(name)) return
87
104
 
88
- // 事件绑定初始化
89
- this.initEventBind()
105
+ initComponent()
106
+ })
90
107
 
91
108
  // 插件初始化 (global scope)
92
109
  Artalk.Plugins.forEach(plugin => {
@@ -96,7 +113,7 @@ export default class Artalk {
96
113
  }
97
114
 
98
115
  /** 基本配置初始化 */
99
- private static HandelBaseConf(customConf: Partial<ArtalkConfig>): ArtalkConfig {
116
+ public static HandelBaseConf(customConf: Partial<ArtalkConfig>): ArtalkConfig {
100
117
  // 合并默认配置
101
118
  const conf: ArtalkConfig = Utils.mergeDeep(Artalk.defaults, customConf)
102
119
 
@@ -141,7 +158,7 @@ export default class Artalk {
141
158
  Ui.showLoading(this.$root)
142
159
  let backendConf = {}
143
160
  try {
144
- backendConf = await this.ctx.getApi().conf()
161
+ backendConf = await this.ctx.getApi().system.conf()
145
162
  } catch (err) { console.error("Load config from remote err", err) }
146
163
  this.ctx.conf = Utils.mergeDeep(this.ctx.conf, backendConf)
147
164
  Ui.hideLoading(this.$root)
@@ -151,8 +168,7 @@ export default class Artalk {
151
168
  private initEventBind() {
152
169
  // 锚点快速跳转评论
153
170
  window.addEventListener('hashchange', () => {
154
- this.list.goToCommentDelay = false
155
- this.list.checkGoToCommentByUrlHash()
171
+ this.ctx.listHashGotoCheck()
156
172
  })
157
173
 
158
174
  // 本地用户数据变更
@@ -164,20 +180,8 @@ export default class Artalk {
164
180
 
165
181
  /** 语言初始化 */
166
182
  private initLocale() {
167
- if (typeof this.conf.locale === 'string') {
168
- if (this.conf.locale === 'auto') { // 自动切换
169
- this.conf.locale = ((navigator.languages) ? navigator.languages[0] : navigator.language)
170
- }
171
-
172
- this.conf.locale = this.conf.locale.replace(
173
- /^([a-zA-Z]+)(-[a-zA-Z]+)?$/,
174
- (_, p1: string, p2: string) => (p1.toLowerCase() + (p2 || '').toUpperCase())
175
- )
176
-
177
- if (!internalLocales[this.conf.locale]) { // 语言不存在时,使用 en
178
- console.log(`Locale "${this.conf.locale}" not found.`)
179
- this.conf.locale = 'en'
180
- }
183
+ if (this.conf.locale === 'auto') { // 自适应语言
184
+ this.conf.locale = navigator.language
181
185
  }
182
186
  }
183
187
 
@@ -16,7 +16,7 @@ export default class CommentActions {
16
16
  public vote(type: 'up'|'down') {
17
17
  const actionBtn = (type === 'up') ? this.comment.getRender().voteBtnUp : this.comment.getRender().voteBtnDown
18
18
 
19
- this.ctx.getApi().vote(this.data.id, `comment_${type}`)
19
+ this.ctx.getApi().comment.vote(this.data.id, `comment_${type}`)
20
20
  .then((v) => {
21
21
  this.data.vote_up = v.up
22
22
  this.data.vote_down = v.down
@@ -45,7 +45,7 @@ export default class CommentActions {
45
45
  modify.is_pinned = !modify.is_pinned
46
46
  }
47
47
 
48
- this.ctx.getApi().commentEdit(modify).then((data) => {
48
+ this.ctx.getApi().comment.commentEdit(modify).then((data) => {
49
49
  btnElem.setLoading(false)
50
50
 
51
51
  // 刷新当前 Comment UI
@@ -64,7 +64,7 @@ export default class CommentActions {
64
64
  if (btnElem.isLoading) return // 若正在删除中
65
65
 
66
66
  btnElem.setLoading(true, `${this.ctx.$t('deleting')}...`)
67
- this.ctx.getApi().commentDel(this.data.id, this.data.site_name)
67
+ this.ctx.getApi().comment.commentDel(this.data.id, this.data.site_name)
68
68
  .then(() => {
69
69
  btnElem.setLoading(false)
70
70
  if (this.cConf.onDelete) this.cConf.onDelete(this.comment)
@@ -172,16 +172,13 @@ export default class Comment extends Component {
172
172
  return Utils.timeAgo(new Date(this.data.date), this.ctx)
173
173
  }
174
174
 
175
- /** 获取用户 UserAgent 浏览器 */
176
- public getUserUaBrowser() {
175
+ /** 获取用户 UserAgent 信息 */
176
+ public getUserUA() {
177
177
  const info = UADetect(this.data.ua)
178
- return `${info.browser} ${info.version}`
179
- }
180
-
181
- /** 获取用户 UserAgent 系统 */
182
- public getUserUaOS() {
183
- const info = UADetect(this.data.ua)
184
- return `${info.os} ${info.osVersion}`
178
+ return {
179
+ browser: `${info.browser} ${info.version}`,
180
+ os: `${info.os} ${info.osVersion}`
181
+ }
185
182
  }
186
183
 
187
184
  /** 获取配置 */