artalk 2.6.4 → 2.7.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.
- package/dist/Artalk.css +1 -1
- package/dist/Artalk.es.js +7794 -7483
- package/dist/Artalk.iife.js +1 -1
- package/dist/Artalk.js +1 -1
- package/dist/ArtalkLite.css +1 -1
- package/dist/ArtalkLite.es.js +5587 -5276
- package/dist/ArtalkLite.iife.js +1 -1
- package/dist/ArtalkLite.js +1 -1
- package/dist/src/api/{api-base.d.ts → _base.d.ts} +3 -7
- package/dist/src/api/_options.d.ts +19 -0
- package/dist/src/api/{request.d.ts → _request.d.ts} +3 -3
- package/dist/src/api/admin.d.ts +1 -1
- package/dist/src/api/captcha.d.ts +1 -1
- package/dist/src/api/comment.d.ts +2 -2
- package/dist/src/api/index.d.ts +2 -4
- package/dist/src/api/page.d.ts +3 -3
- package/dist/src/api/site.d.ts +2 -2
- package/dist/src/api/system.d.ts +13 -3
- package/dist/src/api/upload.d.ts +1 -1
- package/dist/src/api/user.d.ts +2 -2
- package/dist/src/artalk.d.ts +14 -37
- package/dist/src/comment/actions.d.ts +1 -1
- package/dist/src/comment/comment.d.ts +3 -4
- package/dist/src/comment/render.d.ts +21 -3
- package/dist/src/comment/renders/actions.d.ts +2 -2
- package/dist/src/comment/renders/avatar.d.ts +2 -2
- package/dist/src/comment/renders/content.d.ts +2 -2
- package/dist/src/comment/renders/header.d.ts +2 -2
- package/dist/src/comment/renders/index.d.ts +2 -2
- package/dist/src/comment/renders/pending.d.ts +2 -2
- package/dist/src/comment/renders/reply-at.d.ts +2 -2
- package/dist/src/comment/renders/reply-to.d.ts +2 -2
- package/dist/src/components/action-btn.d.ts +3 -5
- package/dist/src/{lib → components}/checker/admin.d.ts +1 -1
- package/dist/src/{lib → components}/checker/captcha-renders.d.ts +1 -1
- package/dist/src/{lib → components}/checker/captcha.d.ts +1 -1
- package/dist/src/{lib → components}/checker/index.d.ts +10 -7
- package/dist/src/components/dark-mode.d.ts +2 -0
- package/dist/src/components/dialog.d.ts +1 -3
- package/dist/src/components/error-dialog.d.ts +8 -0
- package/dist/src/components/pagination.d.ts +4 -3
- package/dist/src/components/read-more-btn.d.ts +3 -3
- package/dist/src/config.d.ts +24 -8
- package/dist/src/context.d.ts +16 -35
- package/dist/src/data.d.ts +27 -0
- package/dist/src/defaults.d.ts +1 -1
- package/dist/src/editor/editor.d.ts +14 -61
- package/dist/src/editor/state.d.ts +20 -0
- package/dist/src/i18n/zh-CN.d.ts +1 -1
- package/dist/src/layer/index.d.ts +4 -3
- package/dist/src/layer/layer-manager.d.ts +9 -0
- package/dist/src/layer/layer.d.ts +8 -28
- package/dist/src/layer/scrollbar-helper.d.ts +5 -0
- package/dist/src/layer/sidebar-layer.d.ts +6 -6
- package/dist/src/layer/wrap.d.ts +13 -0
- package/dist/src/lib/component.d.ts +3 -8
- package/dist/src/lib/event-manager.d.ts +28 -0
- package/dist/src/lib/highlight.d.ts +1 -0
- package/dist/src/lib/marked-renderer.d.ts +4 -0
- package/dist/src/lib/marked.d.ts +4 -2
- package/dist/src/lib/sanitizer.d.ts +1 -0
- package/dist/src/lib/ui.d.ts +3 -2
- package/dist/src/lib/user.d.ts +8 -5
- package/dist/src/lib/utils.d.ts +11 -5
- package/dist/src/list/comment.d.ts +3 -0
- package/dist/src/list/layout.d.ts +23 -0
- package/dist/src/list/list.d.ts +13 -25
- package/dist/src/list/{list-nest.d.ts → nest.d.ts} +1 -1
- package/dist/src/list/page.d.ts +2 -0
- package/dist/src/list/paginator/index.d.ts +22 -7
- package/dist/src/list/paginator/read-more.d.ts +18 -0
- package/dist/src/list/paginator/up-down.d.ts +14 -0
- package/dist/src/main.d.ts +2 -0
- package/dist/src/plugins/admin-only-elem.d.ts +2 -0
- package/dist/src/plugins/conf-remoter.d.ts +2 -0
- package/dist/src/plugins/dark-mode.d.ts +2 -0
- package/dist/src/plugins/editor/_kit.d.ts +27 -0
- package/dist/src/plugins/editor/_plug.d.ts +29 -0
- package/dist/src/plugins/editor/_sample.d.ts +5 -0
- package/dist/src/plugins/editor/closable.d.ts +7 -0
- package/dist/src/{editor/plugs/emoticons-plug.d.ts → plugins/editor/emoticons.d.ts} +10 -16
- package/dist/src/plugins/editor/header-event.d.ts +6 -0
- package/dist/src/plugins/editor/header-link.d.ts +6 -0
- package/dist/src/plugins/editor/header-user.d.ts +16 -0
- package/dist/src/plugins/editor/index.d.ts +6 -0
- package/dist/src/plugins/editor/local-storage.d.ts +6 -0
- package/dist/src/plugins/editor/mover.d.ts +6 -0
- package/dist/src/plugins/editor/preview.d.ts +7 -0
- package/dist/src/plugins/editor/state-edit.d.ts +11 -0
- package/dist/src/plugins/editor/state-reply.d.ts +8 -0
- package/dist/src/plugins/editor/submit-add.d.ts +18 -0
- package/dist/src/plugins/editor/submit-btn.d.ts +5 -0
- package/dist/src/plugins/editor/submit.d.ts +17 -0
- package/dist/src/plugins/editor/textarea.d.ts +8 -0
- package/dist/src/plugins/editor/upload.d.ts +9 -0
- package/dist/src/plugins/editor-kit.d.ts +42 -0
- package/dist/src/plugins/index.d.ts +2 -0
- package/dist/src/plugins/list/copyright.d.ts +2 -0
- package/dist/src/plugins/list/count.d.ts +2 -0
- package/dist/src/plugins/list/dropdown.d.ts +2 -0
- package/dist/src/plugins/list/error-dialog.d.ts +2 -0
- package/dist/src/plugins/list/fetch.d.ts +2 -0
- package/dist/src/plugins/list/goto-first.d.ts +3 -0
- package/dist/src/plugins/list/goto.d.ts +2 -0
- package/dist/src/plugins/list/index.d.ts +3 -0
- package/dist/src/plugins/list/loading.d.ts +2 -0
- package/dist/src/plugins/list/no-comment.d.ts +2 -0
- package/dist/src/plugins/list/reach-bottom.d.ts +2 -0
- package/dist/src/plugins/list/sidebar-btn.d.ts +2 -0
- package/dist/src/plugins/list/time-ticking.d.ts +3 -0
- package/dist/src/plugins/list/unread-badge.d.ts +2 -0
- package/dist/src/plugins/list/unread.d.ts +2 -0
- package/dist/src/plugins/list/with-editor.d.ts +2 -0
- package/dist/src/plugins/markdown.d.ts +2 -0
- package/dist/src/plugins/stat.d.ts +13 -0
- package/dist/src/plugins/version-check.d.ts +2 -0
- package/dist/src/service.d.ts +19 -23
- package/dist/tests/e2e/comment-add.spec.d.ts +1 -0
- package/dist/types/config.d.ts +110 -0
- package/dist/types/context.d.ts +56 -136
- package/dist/types/data.d.ts +194 -0
- package/dist/types/editor.d.ts +76 -0
- package/dist/types/event.d.ts +36 -25
- package/dist/types/index.d.ts +7 -0
- package/dist/types/plugin.d.ts +2 -0
- package/dist/types/sidebar.d.ts +3 -0
- package/package.json +5 -2
- package/src/api/_base.ts +18 -0
- package/src/api/_options.ts +25 -0
- package/src/api/{request.ts → _request.ts} +19 -18
- package/src/api/admin.ts +1 -1
- package/src/api/captcha.ts +1 -1
- package/src/api/comment.ts +4 -4
- package/src/api/index.ts +3 -12
- package/src/api/page.ts +5 -5
- package/src/api/site.ts +2 -2
- package/src/api/system.ts +19 -7
- package/src/api/upload.ts +3 -3
- package/src/api/user.ts +3 -3
- package/src/artalk.ts +49 -97
- package/src/comment/actions.ts +1 -4
- package/src/comment/comment.ts +9 -6
- package/src/comment/render.ts +27 -5
- package/src/comment/renders/actions.ts +45 -45
- package/src/comment/renders/avatar.ts +7 -7
- package/src/comment/renders/content.ts +18 -18
- package/src/comment/renders/header.ts +13 -13
- package/src/comment/renders/index.ts +3 -3
- package/src/comment/renders/pending.ts +5 -5
- package/src/comment/renders/reply-at.ts +8 -8
- package/src/comment/renders/reply-to.ts +13 -13
- package/src/components/action-btn.ts +11 -13
- package/src/{lib → components}/checker/admin.ts +6 -6
- package/src/{lib → components}/checker/captcha-renders.ts +7 -6
- package/src/{lib → components}/checker/captcha.ts +1 -1
- package/src/{lib → components}/checker/index.ts +21 -26
- package/src/components/dark-mode.ts +30 -0
- package/src/components/dialog.ts +4 -9
- package/src/components/error-dialog.ts +35 -0
- package/src/components/pagination.ts +10 -6
- package/src/components/read-more-btn.ts +7 -7
- package/src/config.ts +60 -6
- package/src/context.ts +39 -144
- package/src/data.ts +110 -0
- package/src/defaults.ts +1 -1
- package/src/editor/editor.ts +39 -152
- package/src/editor/state.ts +52 -0
- package/src/i18n/zh-CN.ts +1 -1
- package/src/layer/index.ts +4 -4
- package/src/layer/layer-manager.ts +32 -0
- package/src/layer/layer.ts +20 -144
- package/src/layer/scrollbar-helper.ts +24 -0
- package/src/layer/sidebar-layer.ts +74 -64
- package/src/layer/wrap.ts +77 -0
- package/src/lib/component.ts +4 -11
- package/src/lib/detect.ts +1 -1
- package/src/lib/event-manager.ts +47 -0
- package/src/lib/highlight.ts +5 -0
- package/src/lib/marked-renderer.ts +32 -0
- package/src/lib/marked.ts +43 -104
- package/src/lib/sanitizer.ts +57 -0
- package/src/lib/ui.ts +22 -15
- package/src/lib/user.ts +21 -16
- package/src/lib/utils.ts +29 -18
- package/src/list/comment.ts +20 -0
- package/src/list/layout.ts +120 -0
- package/src/list/list.html +3 -1
- package/src/list/list.ts +56 -200
- package/src/list/{list-nest.ts → nest.ts} +1 -1
- package/src/list/page.ts +97 -0
- package/src/list/paginator/index.ts +20 -8
- package/src/list/paginator/read-more.ts +67 -0
- package/src/list/paginator/up-down.ts +51 -0
- package/src/main.ts +2 -0
- package/src/plugins/admin-only-elem.ts +40 -0
- package/src/plugins/conf-remoter.ts +54 -0
- package/src/plugins/dark-mode.ts +12 -0
- package/src/plugins/editor/_kit.ts +57 -0
- package/src/plugins/editor/_plug.ts +60 -0
- package/src/plugins/editor/_sample.ts +8 -0
- package/src/plugins/editor/closable.ts +44 -0
- package/src/{editor/plugs/emoticons-plug.ts → plugins/editor/emoticons.ts} +51 -52
- package/src/plugins/editor/header-event.ts +35 -0
- package/src/plugins/editor/header-link.ts +31 -0
- package/src/plugins/editor/header-user.ts +98 -0
- package/src/plugins/editor/index.ts +40 -0
- package/src/plugins/editor/local-storage.ts +36 -0
- package/src/plugins/editor/mover.ts +29 -0
- package/src/plugins/editor/preview.ts +41 -0
- package/src/plugins/editor/state-edit.ts +116 -0
- package/src/plugins/editor/state-reply.ts +91 -0
- package/src/plugins/editor/submit-add.ts +33 -0
- package/src/plugins/editor/submit-btn.ts +25 -0
- package/src/plugins/editor/submit.ts +73 -0
- package/src/plugins/editor/textarea.ts +55 -0
- package/src/plugins/editor/upload.ts +145 -0
- package/src/plugins/editor-kit.ts +161 -0
- package/src/plugins/index.ts +16 -0
- package/src/plugins/list/copyright.ts +16 -0
- package/src/plugins/list/count.ts +31 -0
- package/src/plugins/list/dropdown.ts +95 -0
- package/src/plugins/list/error-dialog.ts +22 -0
- package/src/plugins/list/fetch.ts +64 -0
- package/src/plugins/list/goto-first.ts +23 -0
- package/src/plugins/list/goto.ts +72 -0
- package/src/plugins/list/index.ts +25 -0
- package/src/plugins/list/loading.ts +17 -0
- package/src/plugins/list/no-comment.ts +25 -0
- package/src/plugins/list/reach-bottom.ts +46 -0
- package/src/plugins/list/sidebar-btn.ts +39 -0
- package/src/plugins/list/time-ticking.ts +22 -0
- package/src/plugins/list/unread-badge.ts +26 -0
- package/src/plugins/list/unread.ts +38 -0
- package/src/plugins/list/with-editor.ts +59 -0
- package/src/plugins/markdown.ts +10 -0
- package/src/plugins/stat.ts +79 -0
- package/src/plugins/version-check.ts +37 -0
- package/src/service.ts +34 -61
- package/src/style/comment.scss +5 -7
- package/src/style/editor.scss +5 -0
- package/src/style/list.scss +1 -1
- package/types/{artalk-config.d.ts → config.ts} +16 -2
- package/types/context.ts +84 -0
- package/types/{artalk-data.d.ts → data.ts} +41 -0
- package/types/editor.ts +95 -0
- package/types/event.ts +40 -0
- package/types/index.ts +7 -0
- package/types/plugin.ts +3 -0
- package/types/sidebar.ts +3 -0
- package/dist/src/comment/render-ctx.d.ts +0 -23
- package/dist/src/editor/edit-mode.d.ts +0 -9
- package/dist/src/editor/funcs.d.ts +0 -2
- package/dist/src/editor/index.d.ts +0 -2
- package/dist/src/editor/mover.d.ts +0 -8
- package/dist/src/editor/plug-manager.d.ts +0 -17
- package/dist/src/editor/plugs/editor-plug.d.ts +0 -38
- package/dist/src/editor/plugs/header-input-plug.d.ts +0 -13
- package/dist/src/editor/plugs/preview-plug.d.ts +0 -10
- package/dist/src/editor/plugs/upload-plug.d.ts +0 -10
- package/dist/src/editor/reply.d.ts +0 -9
- package/dist/src/editor/submit.d.ts +0 -16
- package/dist/src/lib/dark-mode.d.ts +0 -4
- package/dist/src/lib/stat.d.ts +0 -15
- package/dist/src/list/index.d.ts +0 -2
- package/dist/src/list/list-lite.d.ts +0 -77
- package/dist/src/list/list-ui.d.ts +0 -16
- package/dist/src/list/paginator/adaptors/index.d.ts +0 -12
- package/dist/src/list/paginator/adaptors/pagination.d.ts +0 -9
- package/dist/src/list/paginator/adaptors/read-more.d.ts +0 -10
- package/dist/src/list/paginator/holder.d.ts +0 -16
- package/dist/types/artalk-config.d.ts +0 -139
- package/dist/types/artalk-data.d.ts +0 -230
- package/dist/types/plug.d.ts +0 -4
- package/src/api/api-base.ts +0 -27
- package/src/comment/render-ctx.ts +0 -30
- package/src/editor/edit-mode.ts +0 -105
- package/src/editor/funcs.ts +0 -150
- package/src/editor/index.ts +0 -3
- package/src/editor/mover.ts +0 -43
- package/src/editor/plug-manager.ts +0 -172
- package/src/editor/plugs/editor-plug.ts +0 -69
- package/src/editor/plugs/header-input-plug.ts +0 -79
- package/src/editor/plugs/preview-plug.ts +0 -44
- package/src/editor/plugs/upload-plug.ts +0 -122
- package/src/editor/reply.ts +0 -62
- package/src/editor/submit.ts +0 -99
- package/src/lib/dark-mode.ts +0 -48
- package/src/lib/stat.ts +0 -71
- package/src/list/index.ts +0 -3
- package/src/list/list-lite.ts +0 -424
- package/src/list/list-ui.ts +0 -137
- package/src/list/paginator/adaptors/index.ts +0 -21
- package/src/list/paginator/adaptors/pagination.ts +0 -42
- package/src/list/paginator/adaptors/read-more.ts +0 -56
- package/src/list/paginator/holder.ts +0 -56
- package/types/context.d.ts +0 -142
- package/types/event.d.ts +0 -28
- package/types/plug.d.ts +0 -4
- /package/src/{editor/plugs/emoticons-plug.scss → plugins/editor/emoticons.scss} +0 -0
- /package/src/{editor/plugs/preview-plug.scss → plugins/editor/preview.scss} +0 -0
package/src/lib/user.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { LocalUser } from
|
|
2
|
-
|
|
1
|
+
import type { LocalUser } from '~/types'
|
|
2
|
+
|
|
3
|
+
const LOCAL_USER_KEY = 'ArtalkUser'
|
|
3
4
|
|
|
4
5
|
class User {
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
data: LocalUser
|
|
7
|
+
onUserChanged: ((user: LocalUser) => void) | null = null
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
constructor() {
|
|
9
10
|
// 从 localStorage 导入
|
|
10
|
-
const localUser = JSON.parse(window.localStorage.getItem(
|
|
11
|
+
const localUser = JSON.parse(window.localStorage.getItem(LOCAL_USER_KEY) || '{}')
|
|
11
12
|
this.data = {
|
|
12
13
|
nick: localUser.nick || '',
|
|
13
14
|
email: localUser.email || '',
|
|
@@ -17,22 +18,26 @@ class User {
|
|
|
17
18
|
}
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
public setContext(ctx: Context) {
|
|
21
|
-
this.ctx = ctx
|
|
22
|
-
}
|
|
23
|
-
|
|
24
21
|
/** 保存用户到 localStorage 中 */
|
|
25
|
-
|
|
22
|
+
update(obj: Partial<LocalUser> = {}) {
|
|
26
23
|
Object.entries(obj).forEach(([key, value]) => {
|
|
27
24
|
this.data[key] = value
|
|
28
25
|
})
|
|
29
26
|
|
|
30
|
-
window.localStorage.setItem(
|
|
31
|
-
this.
|
|
27
|
+
window.localStorage.setItem(LOCAL_USER_KEY, JSON.stringify(this.data))
|
|
28
|
+
this.onUserChanged && this.onUserChanged(this.data)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public setOnUserChanged(fn: ((user: LocalUser) => void) | null) {
|
|
32
|
+
this.onUserChanged = fn
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
/**
|
|
35
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Logout
|
|
37
|
+
*
|
|
38
|
+
* @description Logout will clear login status, but not clear user data (nick, email, link)
|
|
39
|
+
*/
|
|
40
|
+
logout() {
|
|
36
41
|
this.update({
|
|
37
42
|
token: '',
|
|
38
43
|
isAdmin: false
|
|
@@ -40,7 +45,7 @@ class User {
|
|
|
40
45
|
}
|
|
41
46
|
|
|
42
47
|
/** 是否已填写基本用户信息 */
|
|
43
|
-
|
|
48
|
+
checkHasBasicUserInfo() {
|
|
44
49
|
return !!this.data.nick && !!this.data.email
|
|
45
50
|
}
|
|
46
51
|
}
|
package/src/lib/utils.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import Context from '~/types/context'
|
|
2
|
-
|
|
3
1
|
export function createElement<E extends HTMLElement = HTMLElement>(htmlStr: string = ''): E {
|
|
4
2
|
const div = document.createElement('div')
|
|
5
3
|
div.innerHTML = htmlStr.trim()
|
|
@@ -29,11 +27,25 @@ export function getQueryParam(name: string) {
|
|
|
29
27
|
return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
|
|
30
28
|
}
|
|
31
29
|
|
|
32
|
-
export function getOffset(el: HTMLElement) {
|
|
33
|
-
const
|
|
30
|
+
export function getOffset(el: HTMLElement, relativeTo?: HTMLElement) {
|
|
31
|
+
const getOffsetRecursive = (element: HTMLElement): { top: number, left: number } => {
|
|
32
|
+
const rect = element.getBoundingClientRect()
|
|
33
|
+
const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft
|
|
34
|
+
const scrollTop = window.pageYOffset || document.documentElement.scrollTop
|
|
35
|
+
return {
|
|
36
|
+
top: rect.top + scrollTop,
|
|
37
|
+
left: rect.left + scrollLeft
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const elOffset = getOffsetRecursive(el)
|
|
42
|
+
if (!relativeTo) return elOffset
|
|
43
|
+
|
|
44
|
+
const relativeToOffset = getOffsetRecursive(relativeTo)
|
|
45
|
+
|
|
34
46
|
return {
|
|
35
|
-
top:
|
|
36
|
-
left:
|
|
47
|
+
top: elOffset.top - relativeToOffset.top,
|
|
48
|
+
left: elOffset.left - relativeToOffset.left
|
|
37
49
|
}
|
|
38
50
|
}
|
|
39
51
|
|
|
@@ -55,7 +67,7 @@ export function dateFormat(date: Date) {
|
|
|
55
67
|
return `${vYear}-${vMonth}-${vDay}`
|
|
56
68
|
}
|
|
57
69
|
|
|
58
|
-
export function timeAgo(date: Date,
|
|
70
|
+
export function timeAgo(date: Date, $t: Function = (n: string) => n) {
|
|
59
71
|
try {
|
|
60
72
|
const oldTime = date.getTime()
|
|
61
73
|
const currTime = new Date().getTime()
|
|
@@ -74,17 +86,17 @@ export function timeAgo(date: Date, ctx: Context) {
|
|
|
74
86
|
// 计算相差秒数
|
|
75
87
|
const leave3 = leave2 % (60 * 1000) // 计算分钟数后剩余的毫秒数
|
|
76
88
|
const seconds = Math.round(leave3 / 1000)
|
|
77
|
-
if (seconds < 10) return
|
|
78
|
-
return `${seconds} ${
|
|
89
|
+
if (seconds < 10) return $t('now')
|
|
90
|
+
return `${seconds} ${$t('seconds')}`
|
|
79
91
|
}
|
|
80
|
-
return `${minutes} ${
|
|
92
|
+
return `${minutes} ${$t('minutes')}`
|
|
81
93
|
}
|
|
82
|
-
return `${hours} ${
|
|
94
|
+
return `${hours} ${$t('hours')}`
|
|
83
95
|
}
|
|
84
|
-
if (days < 0) return
|
|
96
|
+
if (days < 0) return $t('now')
|
|
85
97
|
|
|
86
98
|
if (days < 8) {
|
|
87
|
-
return `${days} ${
|
|
99
|
+
return `${days} ${$t('days')}`
|
|
88
100
|
}
|
|
89
101
|
|
|
90
102
|
return dateFormat(date)
|
|
@@ -115,9 +127,8 @@ export function onImagesLoaded($container: HTMLElement, event: Function) {
|
|
|
115
127
|
}
|
|
116
128
|
}
|
|
117
129
|
|
|
118
|
-
export function getGravatarURL(
|
|
119
|
-
|
|
120
|
-
return `${mirror.replace(/\/$/, '')}/${emailMD5}?${params.replace(/^\?/, '')}`
|
|
130
|
+
export function getGravatarURL(opts: { params: string, mirror: string, emailMD5: string }) {
|
|
131
|
+
return `${opts.mirror.replace(/\/$/, '')}/${opts.emailMD5}?${opts.params.replace(/^\?/, '')}`
|
|
121
132
|
}
|
|
122
133
|
|
|
123
134
|
export function sleep(ms: number) {
|
|
@@ -185,8 +196,8 @@ export function isValidURL(urlRaw: string) {
|
|
|
185
196
|
}
|
|
186
197
|
|
|
187
198
|
/** 获取基于 conf.server 的 URL */
|
|
188
|
-
export function getURLBasedOnApi(
|
|
189
|
-
return getURLBasedOn(
|
|
199
|
+
export function getURLBasedOnApi(opts: { base: string, path: string }) {
|
|
200
|
+
return getURLBasedOn(opts.base, opts.path)
|
|
190
201
|
}
|
|
191
202
|
|
|
192
203
|
/** 获取基于某个 baseURL 的 URL */
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ContextApi, CommentData } from '~/types'
|
|
2
|
+
import Comment from '../comment/comment'
|
|
3
|
+
|
|
4
|
+
export function createComment(ctx: ContextApi, comment: CommentData, ctxComments: CommentData[]): Comment {
|
|
5
|
+
const instance = new Comment(ctx, comment, {
|
|
6
|
+
isFlatMode: ctx.getData().getListLastFetch()?.params.flatMode!,
|
|
7
|
+
afterRender: () => {
|
|
8
|
+
ctx.trigger('comment-rendered', instance)
|
|
9
|
+
},
|
|
10
|
+
onDelete: (c: Comment) => {
|
|
11
|
+
ctx.getData().deleteComment(c.getID())
|
|
12
|
+
},
|
|
13
|
+
replyTo: (comment.rid ? ctxComments.find(c => c.id === comment.rid) : undefined)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
// 渲染元素
|
|
17
|
+
instance.render()
|
|
18
|
+
|
|
19
|
+
return instance
|
|
20
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type { CommentData } from '~/types'
|
|
2
|
+
import * as Ui from '@/lib/ui'
|
|
3
|
+
import Comment from '../comment/comment'
|
|
4
|
+
import * as ListNest from './nest'
|
|
5
|
+
|
|
6
|
+
export interface LayoutOptions {
|
|
7
|
+
$commentsWrap: HTMLElement
|
|
8
|
+
nestSortBy: ListNest.SortByType
|
|
9
|
+
nestMax: number
|
|
10
|
+
flatMode: boolean
|
|
11
|
+
|
|
12
|
+
createCommentNode(comment: CommentData, ctxComments: CommentData[]): Comment
|
|
13
|
+
findCommentNode(id: number): Comment|undefined
|
|
14
|
+
getCommentDataList(): CommentData[]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default class ListLayout {
|
|
18
|
+
constructor(private options: LayoutOptions) {}
|
|
19
|
+
|
|
20
|
+
// TODO refactor `if syntax` to strategy pattern
|
|
21
|
+
import(comments: CommentData[]) {
|
|
22
|
+
if (this.options.flatMode) {
|
|
23
|
+
comments.forEach((commentData: CommentData) => {
|
|
24
|
+
this.putCommentFlatMode(commentData, comments, 'append')
|
|
25
|
+
})
|
|
26
|
+
} else {
|
|
27
|
+
this.importCommentsNestMode(comments)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
insert(comment: CommentData) {
|
|
32
|
+
if (!this.options.flatMode) {
|
|
33
|
+
this.insertCommentNest(comment)
|
|
34
|
+
} else {
|
|
35
|
+
this.insertCommentFlatMode(comment)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 导入评论 · 嵌套模式
|
|
40
|
+
private importCommentsNestMode(srcData: CommentData[]) {
|
|
41
|
+
// 遍历 root 评论
|
|
42
|
+
const rootNodes = ListNest.makeNestCommentNodeList(srcData, this.options.nestSortBy, this.options.nestMax)
|
|
43
|
+
rootNodes.forEach((rootNode: ListNest.CommentNode) => {
|
|
44
|
+
const rootC = this.options.createCommentNode(rootNode.comment, srcData)
|
|
45
|
+
|
|
46
|
+
// 显示并播放渐入动画
|
|
47
|
+
this.options.$commentsWrap?.appendChild(rootC.getEl())
|
|
48
|
+
rootC.getRender().playFadeAnim()
|
|
49
|
+
|
|
50
|
+
// 加载子评论
|
|
51
|
+
const loadChildren = (parentC: Comment, parentNode: ListNest.CommentNode) => {
|
|
52
|
+
parentNode.children.forEach((node: ListNest.CommentNode) => {
|
|
53
|
+
const childD = node.comment
|
|
54
|
+
const childC = this.options.createCommentNode(childD, srcData)
|
|
55
|
+
|
|
56
|
+
// 插入到父评论中
|
|
57
|
+
parentC.putChild(childC)
|
|
58
|
+
|
|
59
|
+
// 递归加载子评论
|
|
60
|
+
loadChildren(childC, node)
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
loadChildren(rootC, rootNode)
|
|
64
|
+
|
|
65
|
+
// 限高检测
|
|
66
|
+
rootC.getRender().checkHeightLimit()
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** 导入评论 · 平铺模式 */
|
|
71
|
+
private putCommentFlatMode(cData: CommentData, ctxData: CommentData[], insertMode: 'append'|'prepend') {
|
|
72
|
+
if (cData.is_collapsed) cData.is_allow_reply = false
|
|
73
|
+
const comment = this.options.createCommentNode(cData, ctxData)
|
|
74
|
+
|
|
75
|
+
// 可见评论添加到界面
|
|
76
|
+
// 注:不可见评论用于显示 “引用内容”
|
|
77
|
+
if (cData.visible) {
|
|
78
|
+
if (insertMode === 'append') this.options.$commentsWrap?.append(comment.getEl())
|
|
79
|
+
if (insertMode === 'prepend') this.options.$commentsWrap?.prepend(comment.getEl())
|
|
80
|
+
comment.getRender().playFadeAnim()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 平铺评论插入后 · 内容限高检测
|
|
84
|
+
comment.getRender().checkHeightLimit()
|
|
85
|
+
|
|
86
|
+
return comment
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private insertCommentNest(commentData: CommentData) {
|
|
90
|
+
// 嵌套模式
|
|
91
|
+
const comment = this.options.createCommentNode(commentData, this.options.getCommentDataList())
|
|
92
|
+
|
|
93
|
+
if (commentData.rid === 0) {
|
|
94
|
+
// root评论 新增
|
|
95
|
+
this.options.$commentsWrap?.prepend(comment.getEl())
|
|
96
|
+
} else {
|
|
97
|
+
// 子评论 新增
|
|
98
|
+
const parent = this.options.findCommentNode(commentData.rid)
|
|
99
|
+
if (parent) {
|
|
100
|
+
parent.putChild(comment, (this.options.nestSortBy === 'DATE_ASC' ? 'append' : 'prepend'))
|
|
101
|
+
|
|
102
|
+
// 若父评论存在 “子评论部分” 限高,取消限高
|
|
103
|
+
comment.getParents().forEach((p) => {
|
|
104
|
+
p.getRender().heightLimitRemoveForChildren()
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
comment.getRender().checkHeightLimit()
|
|
110
|
+
|
|
111
|
+
Ui.scrollIntoView(comment.getEl()) // 滚动到可以见
|
|
112
|
+
comment.getRender().playFadeAnim() // 播放评论渐出动画
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private insertCommentFlatMode(commentData: CommentData) {
|
|
116
|
+
// 平铺模式
|
|
117
|
+
const comment = this.putCommentFlatMode(commentData, this.options.getCommentDataList(), 'prepend')
|
|
118
|
+
Ui.scrollIntoView(comment.getEl()) // 滚动到可见
|
|
119
|
+
}
|
|
120
|
+
}
|
package/src/list/list.html
CHANGED
package/src/list/list.ts
CHANGED
|
@@ -1,223 +1,79 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import { version as ARTALK_VERSION } from '../../package.json'
|
|
1
|
+
import type { ContextApi } from '~/types'
|
|
2
|
+
import Component from '../lib/component'
|
|
4
3
|
import * as Utils from '../lib/utils'
|
|
5
|
-
import
|
|
6
|
-
import User from '../lib/user'
|
|
4
|
+
import CommentNode from '../comment/comment'
|
|
7
5
|
import ListHTML from './list.html?raw'
|
|
8
|
-
import
|
|
9
|
-
import
|
|
6
|
+
import ListLayout from './layout'
|
|
7
|
+
import { createComment as createCommentNode } from './comment'
|
|
8
|
+
import { initListPaginatorFunc } from './page'
|
|
10
9
|
|
|
11
|
-
export default class List extends
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
private $commentCount!: HTMLElement
|
|
16
|
-
private $commentCountNum!: HTMLElement
|
|
17
|
-
private $dropdownWrap?: HTMLElement
|
|
10
|
+
export default class List extends Component {
|
|
11
|
+
/** 列表评论集区域元素 */
|
|
12
|
+
$commentsWrap!: HTMLElement
|
|
13
|
+
public getCommentsWrapEl() { return this.$commentsWrap }
|
|
18
14
|
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
protected commentNodes: CommentNode[] = []
|
|
16
|
+
getCommentNodes() { return this.commentNodes }
|
|
21
17
|
|
|
18
|
+
constructor (ctx: ContextApi) {
|
|
22
19
|
super(ctx)
|
|
23
20
|
|
|
24
|
-
//
|
|
25
|
-
el.
|
|
26
|
-
this.$
|
|
21
|
+
// Init base element
|
|
22
|
+
this.$el = Utils.createElement(ListHTML)
|
|
23
|
+
this.$commentsWrap = this.$el.querySelector('.atk-list-comments-wrap')!
|
|
27
24
|
|
|
28
|
-
//
|
|
29
|
-
|
|
25
|
+
// Init paginator
|
|
26
|
+
initListPaginatorFunc(ctx)
|
|
30
27
|
|
|
31
|
-
//
|
|
32
|
-
this.
|
|
33
|
-
|
|
34
|
-
const countNumHTML = '<span class="atk-comment-count-num">0</span>'
|
|
35
|
-
this.$commentCount = this.$el.querySelector('.atk-comment-count')!
|
|
36
|
-
|
|
37
|
-
const refreshCountNumEl = () => {
|
|
38
|
-
this.$commentCount.innerHTML = this.$t('counter', {
|
|
39
|
-
count: countNumHTML,
|
|
40
|
-
})
|
|
41
|
-
this.$commentCountNum = this.$commentCount.querySelector('.atk-comment-count-num')!
|
|
42
|
-
}
|
|
43
|
-
refreshCountNumEl()
|
|
44
|
-
|
|
45
|
-
// copyright
|
|
46
|
-
this.$el.querySelector<HTMLElement>('.atk-copyright')!.innerHTML = `Powered By <a href="https://artalk.js.org" target="_blank" title="Artalk v${ARTALK_VERSION}">Artalk</a>`
|
|
28
|
+
// Bind events
|
|
29
|
+
this.initCrudEvents()
|
|
30
|
+
}
|
|
47
31
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
this.
|
|
32
|
+
getListLayout() {
|
|
33
|
+
return new ListLayout({
|
|
34
|
+
$commentsWrap: this.$commentsWrap,
|
|
35
|
+
nestSortBy: this.ctx.conf.nestSort,
|
|
36
|
+
nestMax: this.ctx.conf.nestMax,
|
|
37
|
+
flatMode: this.ctx.conf.flatMode as boolean,
|
|
38
|
+
// flatMode must be boolean because it had been handled when Artalk.init
|
|
39
|
+
createCommentNode: (d, c) => {
|
|
40
|
+
const node = createCommentNode(this.ctx, d, c)
|
|
41
|
+
this.commentNodes.push(node)
|
|
42
|
+
return node
|
|
43
|
+
},
|
|
44
|
+
findCommentNode: (id) => this.commentNodes.find(c => c.getID() === id),
|
|
45
|
+
getCommentDataList: () => this.ctx.getData().getComments(),
|
|
53
46
|
})
|
|
54
47
|
}
|
|
55
48
|
|
|
56
|
-
private
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
this.$unreadBadge = this.$el.querySelector('.atk-unread-badge')!
|
|
61
|
-
|
|
62
|
-
this.$openSidebarBtn.addEventListener('click', () => {
|
|
63
|
-
this.ctx.showSidebar()
|
|
49
|
+
private initCrudEvents() {
|
|
50
|
+
this.ctx.on('list-load', (comments) => {
|
|
51
|
+
// 导入数据
|
|
52
|
+
this.getListLayout().import(comments)
|
|
64
53
|
})
|
|
65
54
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (!this.data) return
|
|
69
|
-
|
|
70
|
-
this.data.page.admin_only = !this.data.page.admin_only
|
|
71
|
-
this.adminPageEditSave()
|
|
55
|
+
this.ctx.on('list-loaded', (comments) => {
|
|
56
|
+
if (comments.length === 0) this.$commentsWrap.innerHTML = ''
|
|
72
57
|
})
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/** 刷新界面 */
|
|
76
|
-
public refreshUI() {
|
|
77
|
-
super.refreshUI()
|
|
78
|
-
|
|
79
|
-
this.$commentCountNum.innerText = String(Number(this.data?.total) || 0)
|
|
80
|
-
|
|
81
|
-
// 已输入个人信息
|
|
82
|
-
if (!!User.data.nick && !!User.data.email) {
|
|
83
|
-
this.$openSidebarBtn.classList.remove('atk-hide')
|
|
84
|
-
} else {
|
|
85
|
-
this.$openSidebarBtn.classList.add('atk-hide')
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
this.$openSidebarBtn.querySelector<HTMLElement>('.atk-text')!
|
|
89
|
-
.innerText = (!User.data.isAdmin) ? this.$t('msgCenter') : this.$t('ctrlCenter')
|
|
90
|
-
|
|
91
|
-
// 关闭评论
|
|
92
|
-
if (!!this.data && !!this.data.page && this.data.page.admin_only === true) {
|
|
93
|
-
this.ctx.editorClose()
|
|
94
|
-
this.$closeCommentBtn.innerHTML = this.$t('openComment')
|
|
95
|
-
} else {
|
|
96
|
-
this.ctx.editorOpen()
|
|
97
|
-
this.$closeCommentBtn.innerHTML = this.$t('closeComment')
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// 评论列表排序 Dropdown 下拉选择层
|
|
101
|
-
if (this.ctx.conf.listSort) {
|
|
102
|
-
this.initDropdown()
|
|
103
|
-
} else {
|
|
104
|
-
ListUi.removeDropdown({
|
|
105
|
-
$dropdownWrap: this.$commentCount
|
|
106
|
-
})
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
protected onLoad(data: ListData, offset: number) {
|
|
111
|
-
super.onLoad(data, offset)
|
|
112
|
-
|
|
113
|
-
// 检测锚点跳转
|
|
114
|
-
if (!this.goToCommentFounded) this.checkGoToCommentByUrlHash()
|
|
115
|
-
|
|
116
|
-
// 防止评论框被吞
|
|
117
|
-
if (this.ctx.conf.editorTravel === true) {
|
|
118
|
-
this.ctx.editorTravelBack()
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
58
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
/** 跳到评论项位置 - 根据 `location.hash` */
|
|
126
|
-
public checkGoToCommentByUrlHash() {
|
|
127
|
-
let commentId = Number(Utils.getQueryParam('atk_comment')) // same as backend GetReplyLink()
|
|
128
|
-
if (!commentId) {
|
|
129
|
-
const match = window.location.hash.match(/#atk-comment-([0-9]+)/)
|
|
130
|
-
if (!match || !match[1] || Number.isNaN(Number(match[1]))) return
|
|
131
|
-
commentId = Number(match[1])
|
|
132
|
-
}
|
|
133
|
-
if (!commentId) return
|
|
134
|
-
|
|
135
|
-
const comment = this.ctx.findComment(commentId)
|
|
136
|
-
if (!comment) { // 若找不到评论
|
|
137
|
-
// 自动翻页
|
|
138
|
-
this.pgHolder?.next()
|
|
139
|
-
return
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// 已阅 API
|
|
143
|
-
const notifyKey = Utils.getQueryParam('atk_notify_key')
|
|
144
|
-
if (notifyKey) {
|
|
145
|
-
this.ctx.getApi().user.markRead(commentId, notifyKey)
|
|
146
|
-
.then(() => {
|
|
147
|
-
this.unread = this.unread.filter(o => o.comment_id !== commentId)
|
|
148
|
-
this.updateUnread(this.unread)
|
|
149
|
-
})
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// 若父评论存在 “子评论部分” 限高,取消限高
|
|
153
|
-
comment.getParents().forEach((p) => {
|
|
154
|
-
p.getRender().heightLimitRemoveForChildren()
|
|
59
|
+
// When comment insert
|
|
60
|
+
this.ctx.on('comment-inserted', (comment) => {
|
|
61
|
+
this.getListLayout().insert(comment)
|
|
155
62
|
})
|
|
156
63
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if (!this.goToCommentDelay) goTo()
|
|
167
|
-
else window.setTimeout(() => goTo(), 350)
|
|
168
|
-
|
|
169
|
-
this.goToCommentFounded = true
|
|
170
|
-
this.goToCommentDelay = true // reset
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/** 管理员设置页面信息 */
|
|
174
|
-
public adminPageEditSave () {
|
|
175
|
-
if (!this.data || !this.data.page) return
|
|
176
|
-
|
|
177
|
-
this.ctx.editorShowLoading()
|
|
178
|
-
this.ctx.getApi().page.pageEdit(this.data.page)
|
|
179
|
-
.then((page) => {
|
|
180
|
-
if (this.data)
|
|
181
|
-
this.data.page = { ...page }
|
|
182
|
-
this.refreshUI()
|
|
183
|
-
})
|
|
184
|
-
.catch(err => {
|
|
185
|
-
this.ctx.editorShowNotify(`${this.$t('editFail')}: ${err.msg || String(err)}`, 'e')
|
|
186
|
-
})
|
|
187
|
-
.finally(() => {
|
|
188
|
-
this.ctx.editorHideLoading()
|
|
189
|
-
})
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
public showUnreadBadge(count: number) {
|
|
193
|
-
if (count > 0) {
|
|
194
|
-
this.$unreadBadge.innerText = `${Number(count || 0)}`
|
|
195
|
-
this.$unreadBadge.style.display = 'block'
|
|
196
|
-
} else {
|
|
197
|
-
this.$unreadBadge.style.display = 'none'
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/** 初始化选择下拉层 */
|
|
202
|
-
protected initDropdown() {
|
|
203
|
-
const reloadUseParamsEditor = (func: (p: any) => void) => {
|
|
204
|
-
this.paramsEditor = func
|
|
205
|
-
this.fetchComments(0)
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
ListUi.renderDropdown({
|
|
209
|
-
$dropdownWrap: this.$commentCount,
|
|
210
|
-
dropdownList: [
|
|
211
|
-
[this.$t('sortLatest'), () => { reloadUseParamsEditor(p => { p.sort_by = 'date_desc' }) }],
|
|
212
|
-
[this.$t('sortBest'), () => { reloadUseParamsEditor(p => { p.sort_by = 'vote' }) }],
|
|
213
|
-
[this.$t('sortOldest'), () => { reloadUseParamsEditor(p => { p.sort_by = 'date_asc' }) }],
|
|
214
|
-
[this.$t('sortAuthor'), () => { reloadUseParamsEditor(p => { p.view_only_admin = true }) }],
|
|
215
|
-
]
|
|
64
|
+
// When comment delete
|
|
65
|
+
this.ctx.on('comment-deleted', (comment) => {
|
|
66
|
+
const node = this.commentNodes.find(c => c.getID() === comment.id)
|
|
67
|
+
if (!node) { console.error(`comment node id=${comment.id} not found`);return }
|
|
68
|
+
node.getEl().remove()
|
|
69
|
+
this.commentNodes = this.commentNodes.filter(c => c.getID() !== comment.id)
|
|
70
|
+
// TODO remove child nodes
|
|
216
71
|
})
|
|
217
|
-
}
|
|
218
72
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
73
|
+
// When comment update
|
|
74
|
+
this.ctx.on('comment-updated', (comment) => {
|
|
75
|
+
const node = this.commentNodes.find(c => c.getID() === comment.id)
|
|
76
|
+
node && node.setData(comment)
|
|
77
|
+
})
|
|
222
78
|
}
|
|
223
79
|
}
|