artalk 2.4.4 → 2.5.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/README.md +27 -0
- package/dist/Artalk.css +1 -1
- package/dist/Artalk.es.js +4872 -4072
- package/dist/Artalk.iife.js +1 -1
- package/dist/Artalk.js +1 -1
- package/dist/ArtalkLite.css +1 -1
- package/dist/ArtalkLite.es.js +2468 -1779
- package/dist/ArtalkLite.iife.js +1 -1
- package/dist/ArtalkLite.js +1 -1
- package/dist/i18n/jp.js +1 -1
- package/dist/i18n/zh-TW.js +1 -1
- package/dist/src/api/index.d.ts +2 -2
- package/dist/src/api/system.d.ts +5 -2
- package/dist/src/artalk.d.ts +42 -34
- package/dist/src/comment/comment.d.ts +2 -2
- package/dist/src/comment/height-limit.d.ts +30 -0
- package/dist/src/comment/render-ctx.d.ts +23 -0
- package/dist/src/comment/render.d.ts +2 -50
- package/dist/src/comment/renders/actions.d.ts +5 -0
- package/dist/src/comment/renders/avatar.d.ts +5 -0
- package/dist/src/comment/renders/content.d.ts +5 -0
- package/dist/src/comment/renders/header.d.ts +5 -0
- package/dist/src/comment/renders/index.d.ts +2 -0
- package/dist/src/comment/renders/pending.d.ts +5 -0
- package/dist/src/comment/renders/reply-at.d.ts +5 -0
- package/dist/src/comment/renders/reply-to.d.ts +5 -0
- package/dist/src/components/dialog.d.ts +1 -1
- package/dist/src/config.d.ts +8 -0
- package/dist/src/context.d.ts +15 -29
- package/dist/src/editor/edit-mode.d.ts +9 -0
- package/dist/src/editor/editor-ui.d.ts +25 -0
- package/dist/src/editor/editor.d.ts +37 -45
- package/dist/src/editor/funcs.d.ts +2 -0
- package/dist/src/editor/mover.d.ts +8 -0
- package/dist/src/editor/plug-manager.d.ts +17 -0
- package/dist/src/editor/plugs/editor-plug.d.ts +13 -1
- package/dist/src/editor/reply.d.ts +9 -0
- package/dist/src/editor/submit.d.ts +16 -0
- package/dist/src/editor/ui.d.ts +25 -0
- package/dist/src/i18n/index.d.ts +11 -7
- package/dist/src/layer/index.d.ts +2 -2
- package/dist/src/layer/layer.d.ts +1 -1
- package/dist/src/lib/checker/captcha-renders.d.ts +5 -0
- package/dist/src/lib/checker/index.d.ts +18 -10
- package/dist/src/lib/dark-mode.d.ts +4 -0
- package/dist/src/lib/marked.d.ts +9 -0
- package/dist/src/lib/utils.d.ts +1 -5
- package/dist/src/list/list-lite.d.ts +15 -11
- package/dist/src/list/list-nest.d.ts +2 -2
- package/dist/src/list/list-ui.d.ts +7 -0
- package/dist/src/list/paginator/adaptors/index.d.ts +12 -0
- package/dist/src/list/paginator/adaptors/pagination.d.ts +9 -0
- package/dist/src/list/paginator/adaptors/read-more.d.ts +10 -0
- package/dist/src/list/paginator/holder.d.ts +16 -0
- package/dist/src/list/paginator/index.d.ts +10 -0
- package/dist/src/service.d.ts +40 -0
- package/dist/types/artalk-config.d.ts +5 -2
- package/dist/types/artalk-data.d.ts +3 -3
- package/dist/types/context.d.ts +11 -17
- package/dist/types/global.d.ts +1 -0
- package/package.json +6 -7
- package/src/api/system.ts +3 -12
- package/src/artalk.ts +101 -158
- package/src/comment/comment.ts +7 -6
- package/src/comment/height-limit.ts +100 -0
- package/src/comment/render-ctx.ts +30 -0
- package/src/comment/render.ts +31 -375
- package/src/comment/renders/actions.ts +114 -0
- package/src/comment/renders/avatar.ts +22 -0
- package/src/comment/renders/content.ts +39 -0
- package/src/comment/renders/header.ts +84 -0
- package/src/comment/renders/index.ts +19 -0
- package/src/comment/renders/pending.ts +12 -0
- package/src/comment/renders/reply-at.ts +16 -0
- package/src/comment/renders/reply-to.ts +24 -0
- package/src/config.ts +65 -0
- package/src/context.ts +31 -86
- package/src/defaults.ts +1 -0
- package/src/editor/edit-mode.ts +102 -0
- package/src/editor/editor.ts +100 -395
- package/src/editor/funcs.ts +96 -0
- package/src/editor/mover.ts +43 -0
- package/src/editor/plug-manager.ts +172 -0
- package/src/editor/plugs/editor-plug.ts +20 -2
- package/src/editor/plugs/{emoticons-plug.less → emoticons-plug.scss} +5 -4
- package/src/editor/plugs/emoticons-plug.ts +2 -1
- package/src/editor/plugs/header-input-plug.ts +5 -5
- package/src/editor/plugs/{preview-plug.less → preview-plug.scss} +1 -1
- package/src/editor/plugs/preview-plug.ts +8 -4
- package/src/editor/plugs/upload-plug.ts +10 -13
- package/src/editor/reply.ts +62 -0
- package/src/editor/submit.ts +98 -0
- package/src/editor/ui.ts +36 -0
- package/src/i18n/index.ts +21 -10
- package/src/layer/index.ts +2 -2
- package/src/layer/layer.ts +4 -4
- package/src/layer/sidebar-layer.ts +2 -1
- package/src/lib/checker/admin.ts +36 -0
- package/src/lib/checker/captcha-renders.ts +68 -0
- package/src/lib/checker/captcha.ts +26 -0
- package/src/lib/checker/index.ts +38 -20
- package/src/lib/dark-mode.ts +48 -0
- package/src/lib/marked.ts +133 -0
- package/src/lib/utils.ts +1 -123
- package/src/list/list-lite.ts +59 -162
- package/src/list/list-ui.ts +80 -0
- package/src/list/list.ts +16 -8
- package/src/list/paginator/adaptors/index.ts +21 -0
- package/src/list/paginator/adaptors/pagination.ts +42 -0
- package/src/list/paginator/adaptors/read-more.ts +56 -0
- package/src/list/paginator/holder.ts +55 -0
- package/src/list/paginator/index.ts +13 -0
- package/src/service.ts +118 -0
- package/src/style/{_color.less → _color.scss} +3 -3
- package/src/style/{_extend.less → _extend.scss} +1 -1
- package/src/style/_variables.scss +3 -0
- package/src/style/{comment.less → comment.scss} +36 -12
- package/src/style/{editor.less → editor.scss} +11 -11
- package/src/style/{list.less → list.scss} +8 -6
- package/src/style/{main.less → main.scss} +24 -23
- package/src/style/{sidebar-layer.less → sidebar-layer.scss} +8 -8
- package/types/artalk-config.d.ts +5 -2
- package/types/artalk-data.d.ts +3 -3
- package/types/context.d.ts +11 -17
- package/types/global.d.ts +1 -0
- package/CNAME +0 -1
- package/LICENSE +0 -165
- package/index.html +0 -157
- package/postcss.config.js +0 -5
- package/scripts/build-i18n.mjs +0 -46
- package/src/lib/checker/admin-checker.ts +0 -36
- package/src/lib/checker/captcha-checker.ts +0 -84
- package/src/style/_variables.less +0 -3
- package/test/ArtalkCount.html +0 -16
- package/test/ArtalkI18n.html +0 -16
- package/test/ArtalkLite.html +0 -15
- package/test/CSRF.html +0 -26
- package/tsconfig.json +0 -12
- package/vite-base.config.ts +0 -21
- package/vite-demo.config.ts +0 -12
- package/vite-lite.config.ts +0 -22
- package/vite.config.ts +0 -27
- /package/dist/src/lib/checker/{admin-checker.d.ts → admin.d.ts} +0 -0
- /package/dist/src/lib/checker/{captcha-checker.d.ts → captcha.d.ts} +0 -0
- /package/src/style/{pagination.less → pagination.scss} +0 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { marked as libMarked } from 'marked'
|
|
2
|
+
import insane from 'insane'
|
|
3
|
+
import hanabi from 'hanabi'
|
|
4
|
+
import Context from '~/types/context'
|
|
5
|
+
|
|
6
|
+
let instance: (typeof libMarked)|undefined
|
|
7
|
+
|
|
8
|
+
export type TMarked = typeof libMarked
|
|
9
|
+
|
|
10
|
+
/** Get Marked instance */
|
|
11
|
+
export function getInstance() {
|
|
12
|
+
return instance
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** 初始化 marked */
|
|
16
|
+
export function initMarked() {
|
|
17
|
+
try { if (!libMarked.name) return } catch { return }
|
|
18
|
+
|
|
19
|
+
const renderer = new libMarked.Renderer()
|
|
20
|
+
const orgLinkRenderer = renderer.link
|
|
21
|
+
renderer.link = (href, title, text) => {
|
|
22
|
+
const localLink = href?.startsWith(`${window.location.protocol}//${window.location.hostname}`);
|
|
23
|
+
const html = orgLinkRenderer.call(renderer as any, href, title, text);
|
|
24
|
+
return html.replace(/^<a /, `<a target="_blank" ${!localLink ? `rel="noreferrer noopener nofollow"` : ''} `);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
renderer.code = (block, lang) => {
|
|
28
|
+
// Colorize the block only if the language is known to highlight.js
|
|
29
|
+
const realLang = (!lang ? 'plaintext' : lang)
|
|
30
|
+
let colorized = block
|
|
31
|
+
if ((window as any).hljs) {
|
|
32
|
+
if (realLang && (window as any).hljs.getLanguage(realLang)) {
|
|
33
|
+
colorized = (window as any).hljs.highlight(realLang, block).value
|
|
34
|
+
}
|
|
35
|
+
} else {
|
|
36
|
+
colorized = hanabi(block)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return `<pre rel="${realLang}">\n`
|
|
40
|
+
+ `<code class="hljs language-${realLang}">${colorized.replace(/&/g, '&')}</code>\n`
|
|
41
|
+
+ `</pre>`
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// @see https://github.com/markedjs/marked/blob/4afb228d956a415624c4e5554bb8f25d047676fe/src/Tokenizer.js#L329
|
|
45
|
+
const nMarked = libMarked
|
|
46
|
+
libMarked.setOptions({
|
|
47
|
+
renderer,
|
|
48
|
+
pedantic: false,
|
|
49
|
+
gfm: true,
|
|
50
|
+
breaks: true,
|
|
51
|
+
smartLists: true,
|
|
52
|
+
smartypants: true,
|
|
53
|
+
xhtml: false,
|
|
54
|
+
sanitize: false,
|
|
55
|
+
silent: true,
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
instance = nMarked
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** 解析 markdown */
|
|
62
|
+
export default function marked(ctx: Context, src: string): string {
|
|
63
|
+
let markedContent = getInstance()?.parse(src)
|
|
64
|
+
if (!markedContent) {
|
|
65
|
+
// 无 Markdown 模式简单处理
|
|
66
|
+
markedContent = src
|
|
67
|
+
// .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
|
68
|
+
.replace(/```\s*([^]+?.*?[^]+?[^]+?)```/g, (_, code) => `<pre><code>${hanabi(code)}</code></pre>`)
|
|
69
|
+
// .replace(/`([^`]+?)`/g, '<code>$1</code>')
|
|
70
|
+
.replace(/!\[(.*?)\]\((.*?)\)/g, (_, alt, imgSrc) => `<img src="${imgSrc}" alt="${alt}" />`)
|
|
71
|
+
.replace(/\[(.*?)\]\((.*?)\)/g, (_, text, link) => `<a href="${link}" target="_blank">${text}</a>`)
|
|
72
|
+
.replace(/\n/g, '<br>')
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// @link https://github.com/markedjs/marked/discussions/1232
|
|
76
|
+
// @link https://gist.github.com/lionel-rowe/bb384465ba4e4c81a9c8dada84167225
|
|
77
|
+
let dest = insane(markedContent, {
|
|
78
|
+
allowedClasses: {},
|
|
79
|
+
// @refer CVE-2018-8495
|
|
80
|
+
// @link https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-8495
|
|
81
|
+
// @link https://leucosite.com/Microsoft-Edge-RCE/
|
|
82
|
+
// @link https://medium.com/@knownsec404team/analysis-of-the-security-issues-of-url-scheme-in-pc-from-cve-2018-8495-934478a36756
|
|
83
|
+
allowedSchemes: [
|
|
84
|
+
'http', 'https', 'mailto',
|
|
85
|
+
'data' // for support base64 encoded image (安全性有待考虑)
|
|
86
|
+
],
|
|
87
|
+
allowedTags: [
|
|
88
|
+
'a', 'abbr', 'article', 'b', 'blockquote', 'br', 'caption', 'code', 'del', 'details', 'div', 'em',
|
|
89
|
+
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'ins', 'kbd', 'li', 'main', 'mark',
|
|
90
|
+
'ol', 'p', 'pre', 'section', 'span', 'strike', 'strong', 'sub', 'summary', 'sup', 'table',
|
|
91
|
+
'tbody', 'td', 'th', 'thead', 'tr', 'u', 'ul'
|
|
92
|
+
],
|
|
93
|
+
allowedAttributes: {
|
|
94
|
+
'*': ['title', 'accesskey'],
|
|
95
|
+
a: ['href', 'name', 'target', 'aria-label', 'rel'],
|
|
96
|
+
img: ['src', 'alt', 'title', 'atk-emoticon', 'aria-label'],
|
|
97
|
+
// for code highlight
|
|
98
|
+
code: ['class'],
|
|
99
|
+
span: ['class', 'style'],
|
|
100
|
+
},
|
|
101
|
+
filter: node => {
|
|
102
|
+
// allow hljs style
|
|
103
|
+
const allowed = [
|
|
104
|
+
[ 'code', /^hljs\W+language-(.*)$/ ],
|
|
105
|
+
[ 'span', /^(hljs-.*)$/ ]
|
|
106
|
+
]
|
|
107
|
+
allowed.forEach(([ tag, reg ]) => {
|
|
108
|
+
if (
|
|
109
|
+
node.tag === tag
|
|
110
|
+
&& !!node.attrs.class
|
|
111
|
+
&& !(reg as RegExp).test(node.attrs.class)
|
|
112
|
+
) {
|
|
113
|
+
delete node.attrs.class
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
// allow <span> set color sty
|
|
118
|
+
if (node.tag === 'span' && !!node.attrs.style
|
|
119
|
+
&& !/^color:(\W+)?#[0-9a-f]{3,6};?$/i.test(node.attrs.style)) {
|
|
120
|
+
delete node.attrs.style
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return true
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
// 内容替换器
|
|
128
|
+
ctx.markedReplacers.forEach((replacer) => {
|
|
129
|
+
if (typeof replacer === 'function') dest = replacer(dest)
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
return dest
|
|
133
|
+
}
|
package/src/lib/utils.ts
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import { marked as libMarked } from 'marked'
|
|
2
|
-
import insane from 'insane'
|
|
3
|
-
import hanabi from 'hanabi'
|
|
4
1
|
import Context from '~/types/context'
|
|
5
2
|
|
|
6
3
|
export function createElement<E extends HTMLElement = HTMLElement>(htmlStr: string = ''): E {
|
|
@@ -77,6 +74,7 @@ export function timeAgo(date: Date, ctx: Context) {
|
|
|
77
74
|
// 计算相差秒数
|
|
78
75
|
const leave3 = leave2 % (60 * 1000) // 计算分钟数后剩余的毫秒数
|
|
79
76
|
const seconds = Math.round(leave3 / 1000)
|
|
77
|
+
if (seconds < 10) return ctx.$t('now')
|
|
80
78
|
return `${seconds} ${ctx.$t('seconds')}`
|
|
81
79
|
}
|
|
82
80
|
return `${minutes} ${ctx.$t('minutes')}`
|
|
@@ -140,126 +138,6 @@ export function versionCompare(a: string, b: string) {
|
|
|
140
138
|
return 0
|
|
141
139
|
}
|
|
142
140
|
|
|
143
|
-
/** 初始化 marked */
|
|
144
|
-
export function initMarked(ctx: Context) {
|
|
145
|
-
try { if (!libMarked.name) return } catch { return }
|
|
146
|
-
|
|
147
|
-
const renderer = new libMarked.Renderer()
|
|
148
|
-
const orgLinkRenderer = renderer.link
|
|
149
|
-
renderer.link = (href, title, text) => {
|
|
150
|
-
const localLink = href?.startsWith(`${window.location.protocol}//${window.location.hostname}`);
|
|
151
|
-
const html = orgLinkRenderer.call(renderer as any, href, title, text);
|
|
152
|
-
return html.replace(/^<a /, `<a target="_blank" ${!localLink ? `rel="noreferrer noopener nofollow"` : ''} `);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
renderer.code = (block, lang) => {
|
|
156
|
-
// Colorize the block only if the language is known to highlight.js
|
|
157
|
-
const realLang = (!lang ? 'plaintext' : lang)
|
|
158
|
-
let colorized = block
|
|
159
|
-
if ((window as any).hljs) {
|
|
160
|
-
if (realLang && (window as any).hljs.getLanguage(realLang)) {
|
|
161
|
-
colorized = (window as any).hljs.highlight(realLang, block).value
|
|
162
|
-
}
|
|
163
|
-
} else {
|
|
164
|
-
colorized = hanabi(block)
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
return `<pre rel="${realLang}">\n`
|
|
168
|
-
+ `<code class="hljs language-${realLang}">${colorized.replace(/&/g, '&')}</code>\n`
|
|
169
|
-
+ `</pre>`
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// @see https://github.com/markedjs/marked/blob/4afb228d956a415624c4e5554bb8f25d047676fe/src/Tokenizer.js#L329
|
|
173
|
-
const nMarked = libMarked
|
|
174
|
-
libMarked.setOptions({
|
|
175
|
-
renderer,
|
|
176
|
-
pedantic: false,
|
|
177
|
-
gfm: true,
|
|
178
|
-
breaks: true,
|
|
179
|
-
smartLists: true,
|
|
180
|
-
smartypants: true,
|
|
181
|
-
xhtml: false,
|
|
182
|
-
sanitize: false,
|
|
183
|
-
silent: true,
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
ctx.markedInstance = nMarked
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/** 解析 markdown */
|
|
190
|
-
export function marked(ctx: Context, src: string): string {
|
|
191
|
-
let markedContent = ctx.markedInstance?.parse(src)
|
|
192
|
-
if (!markedContent) {
|
|
193
|
-
// 无 Markdown 模式简单处理
|
|
194
|
-
markedContent = src
|
|
195
|
-
// .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
|
196
|
-
.replace(/```\s*([^]+?.*?[^]+?[^]+?)```/g, (_, code) => `<pre><code>${hanabi(code)}</code></pre>`)
|
|
197
|
-
// .replace(/`([^`]+?)`/g, '<code>$1</code>')
|
|
198
|
-
.replace(/!\[(.*?)\]\((.*?)\)/g, (_, alt, imgSrc) => `<img src="${imgSrc}" alt="${alt}" />`)
|
|
199
|
-
.replace(/\[(.*?)\]\((.*?)\)/g, (_, text, link) => `<a href="${link}" target="_blank">${text}</a>`)
|
|
200
|
-
.replace(/\n/g, '<br>')
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// @link https://github.com/markedjs/marked/discussions/1232
|
|
204
|
-
// @link https://gist.github.com/lionel-rowe/bb384465ba4e4c81a9c8dada84167225
|
|
205
|
-
let dest = insane(markedContent, {
|
|
206
|
-
allowedClasses: {},
|
|
207
|
-
// @refer CVE-2018-8495
|
|
208
|
-
// @link https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-8495
|
|
209
|
-
// @link https://leucosite.com/Microsoft-Edge-RCE/
|
|
210
|
-
// @link https://medium.com/@knownsec404team/analysis-of-the-security-issues-of-url-scheme-in-pc-from-cve-2018-8495-934478a36756
|
|
211
|
-
allowedSchemes: [
|
|
212
|
-
'http', 'https', 'mailto',
|
|
213
|
-
'data' // for support base64 encoded image (安全性有待考虑)
|
|
214
|
-
],
|
|
215
|
-
allowedTags: [
|
|
216
|
-
'a', 'abbr', 'article', 'b', 'blockquote', 'br', 'caption', 'code', 'del', 'details', 'div', 'em',
|
|
217
|
-
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'ins', 'kbd', 'li', 'main', 'mark',
|
|
218
|
-
'ol', 'p', 'pre', 'section', 'span', 'strike', 'strong', 'sub', 'summary', 'sup', 'table',
|
|
219
|
-
'tbody', 'td', 'th', 'thead', 'tr', 'u', 'ul'
|
|
220
|
-
],
|
|
221
|
-
allowedAttributes: {
|
|
222
|
-
'*': ['title', 'accesskey'],
|
|
223
|
-
a: ['href', 'name', 'target', 'aria-label', 'rel'],
|
|
224
|
-
img: ['src', 'alt', 'title', 'atk-emoticon', 'aria-label'],
|
|
225
|
-
// for code highlight
|
|
226
|
-
code: ['class'],
|
|
227
|
-
span: ['class', 'style'],
|
|
228
|
-
},
|
|
229
|
-
filter: node => {
|
|
230
|
-
// allow hljs style
|
|
231
|
-
const allowed = [
|
|
232
|
-
[ 'code', /^hljs\W+language-(.*)$/ ],
|
|
233
|
-
[ 'span', /^(hljs-.*)$/ ]
|
|
234
|
-
]
|
|
235
|
-
allowed.forEach(([ tag, reg ]) => {
|
|
236
|
-
if (
|
|
237
|
-
node.tag === tag
|
|
238
|
-
&& !!node.attrs.class
|
|
239
|
-
&& !(reg as RegExp).test(node.attrs.class)
|
|
240
|
-
) {
|
|
241
|
-
delete node.attrs.class
|
|
242
|
-
}
|
|
243
|
-
})
|
|
244
|
-
|
|
245
|
-
// allow <span> set color sty
|
|
246
|
-
if (node.tag === 'span' && !!node.attrs.style
|
|
247
|
-
&& !/^color:(\W+)?#[0-9a-f]{3,6};?$/i.test(node.attrs.style)) {
|
|
248
|
-
delete node.attrs.style
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
return true
|
|
252
|
-
}
|
|
253
|
-
})
|
|
254
|
-
|
|
255
|
-
// 内容替换器
|
|
256
|
-
ctx.markedReplacers.forEach((replacer) => {
|
|
257
|
-
if (typeof replacer === 'function') dest = replacer(dest)
|
|
258
|
-
})
|
|
259
|
-
|
|
260
|
-
return dest
|
|
261
|
-
}
|
|
262
|
-
|
|
263
141
|
/** 获取修正后的 UserAgent */
|
|
264
142
|
export async function getCorrectUserAgent() {
|
|
265
143
|
const uaRaw = navigator.userAgent
|
package/src/list/list-lite.ts
CHANGED
|
@@ -4,24 +4,32 @@ import Component from '../lib/component'
|
|
|
4
4
|
import * as Utils from '../lib/utils'
|
|
5
5
|
import * as Ui from '../lib/ui'
|
|
6
6
|
import Comment from '../comment'
|
|
7
|
-
import
|
|
8
|
-
import ReadMoreBtn from '../components/read-more-btn'
|
|
7
|
+
import PgHolder, { TPgMode } from './paginator'
|
|
9
8
|
import * as ListNest from './list-nest'
|
|
10
|
-
import
|
|
9
|
+
import * as ListUi from './list-ui'
|
|
10
|
+
import { version as ARTALK_VERSION } from '../../package.json'
|
|
11
|
+
import { handleBackendRefConf } from '../config'
|
|
11
12
|
|
|
12
13
|
export default class ListLite extends Component {
|
|
14
|
+
/** 列表评论集区域元素 */
|
|
13
15
|
protected $commentsWrap: HTMLElement
|
|
14
16
|
|
|
17
|
+
/** 最后一次请求得到的列表数据 */
|
|
15
18
|
protected data?: ListData
|
|
19
|
+
|
|
20
|
+
/** 是否处于加载中状态 */
|
|
16
21
|
protected isLoading: boolean = false
|
|
17
22
|
|
|
18
|
-
|
|
23
|
+
/** 配置是否已加载 */
|
|
24
|
+
private confLoaded = false
|
|
25
|
+
|
|
26
|
+
/** 无评论显示文字 */
|
|
27
|
+
public noCommentText?: string
|
|
19
28
|
|
|
20
29
|
/** 嵌套模式下的排序方式 */
|
|
21
30
|
private _nestSortBy?: ListNest.SortByType
|
|
22
31
|
public get nestSortBy() {
|
|
23
|
-
|
|
24
|
-
return this.ctx.conf.nestSort || 'DATE_ASC'
|
|
32
|
+
return this._nestSortBy || this.ctx.conf.nestSort || 'DATE_ASC'
|
|
25
33
|
}
|
|
26
34
|
public set nestSortBy(val: ListNest.SortByType) {
|
|
27
35
|
this._nestSortBy = val
|
|
@@ -48,14 +56,18 @@ export default class ListLite extends Component {
|
|
|
48
56
|
}
|
|
49
57
|
|
|
50
58
|
/** 分页方式 */
|
|
51
|
-
public _pageMode?:
|
|
59
|
+
public _pageMode?: TPgMode
|
|
52
60
|
public get pageMode() {
|
|
53
61
|
return this._pageMode || (this.conf.pagination.readMore ? 'read-more' : 'pagination')
|
|
54
62
|
}
|
|
55
63
|
public set pageMode(val: 'pagination'|'read-more') {
|
|
56
64
|
this._pageMode = val
|
|
65
|
+
this.pgHolder?.setMode(this._pageMode)
|
|
57
66
|
}
|
|
58
67
|
|
|
68
|
+
/** 分页方式持有者 */
|
|
69
|
+
public pgHolder?: PgHolder
|
|
70
|
+
|
|
59
71
|
/** 每页数量 (每次请求获取量) */
|
|
60
72
|
private _pageSize?: number
|
|
61
73
|
public get pageSize() {
|
|
@@ -65,18 +77,17 @@ export default class ListLite extends Component {
|
|
|
65
77
|
this._pageSize = val
|
|
66
78
|
}
|
|
67
79
|
|
|
68
|
-
|
|
69
|
-
public
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
protected readMoreBtn?: ReadMoreBtn // 阅读更多
|
|
73
|
-
protected autoLoadScrollEvent?: any // 自动加载滚动事件
|
|
80
|
+
/** 监听指定元素上的滚动 */
|
|
81
|
+
public scrollListenerAt?: HTMLElement
|
|
82
|
+
/** 翻页归位到指定元素 */
|
|
83
|
+
public repositionAt?: HTMLElement
|
|
74
84
|
|
|
85
|
+
// 一些 Hook 函数
|
|
75
86
|
public renderComment?: (comment: Comment) => void
|
|
76
87
|
public paramsEditor?: (params: any) => void
|
|
77
88
|
public onAfterLoad?: (data: ListData) => void
|
|
78
89
|
|
|
79
|
-
|
|
90
|
+
/** 未读标记 */
|
|
80
91
|
protected unread: NotifyData[] = []
|
|
81
92
|
public unreadHighlight?: boolean // 高亮未读
|
|
82
93
|
|
|
@@ -111,16 +122,23 @@ export default class ListLite extends Component {
|
|
|
111
122
|
this.data = undefined
|
|
112
123
|
}
|
|
113
124
|
|
|
125
|
+
public getLoading() {
|
|
126
|
+
return this.isLoading
|
|
127
|
+
}
|
|
128
|
+
|
|
114
129
|
public getCommentsWrapEl() {
|
|
115
130
|
return this.$commentsWrap
|
|
116
131
|
}
|
|
117
132
|
|
|
118
|
-
/**
|
|
133
|
+
/** 设置加载状态 */
|
|
119
134
|
public setLoading(val: boolean, isFirstLoad: boolean = false) {
|
|
120
135
|
this.isLoading = val
|
|
121
|
-
if (isFirstLoad)
|
|
122
|
-
|
|
123
|
-
|
|
136
|
+
if (isFirstLoad) {
|
|
137
|
+
Ui.setLoading(val, this.$el)
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
this.pgHolder?.setLoading(val)
|
|
124
142
|
}
|
|
125
143
|
|
|
126
144
|
/** 评论获取 */
|
|
@@ -144,6 +162,7 @@ export default class ListLite extends Component {
|
|
|
144
162
|
// 请求评论数据
|
|
145
163
|
let listData: ListData
|
|
146
164
|
try {
|
|
165
|
+
// 执行请求
|
|
147
166
|
listData = await this.ctx.getApi().comment
|
|
148
167
|
.get(offset, this.pageSize, this.flatMode, this.paramsEditor)
|
|
149
168
|
} catch (e: any) {
|
|
@@ -167,112 +186,54 @@ export default class ListLite extends Component {
|
|
|
167
186
|
}
|
|
168
187
|
}
|
|
169
188
|
|
|
170
|
-
private confLoaded = false
|
|
171
|
-
|
|
172
189
|
protected onLoad(data: ListData, offset: number) {
|
|
173
190
|
this.data = data
|
|
174
191
|
|
|
175
192
|
// 装载后端提供的配置
|
|
176
193
|
if (!this.confLoaded) {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
194
|
+
// 仅应用一次配置
|
|
195
|
+
const backendRefConf = handleBackendRefConf(data.conf.frontend_conf)
|
|
196
|
+
if (this.conf.useBackendConf) this.ctx.updateConf(backendRefConf)
|
|
197
|
+
else this.ctx.updateConf({}) // 让事件监听 `on('conf-loaded')` 有效,与前者保持相同生命周期环节
|
|
181
198
|
this.confLoaded = true
|
|
182
199
|
}
|
|
183
200
|
|
|
184
|
-
//
|
|
185
|
-
|
|
186
|
-
if (this.ctx.conf.versionCheck && this.versionCheck('frontend', feMinVersion, ARTALK_VERSION)) return
|
|
187
|
-
if (this.ctx.conf.versionCheck && this.versionCheck('backend', backendMinVersion, data.api_version?.version)) return
|
|
201
|
+
// 前后端版本一致性检测
|
|
202
|
+
if (this.ctx.conf.versionCheck && ListUi.versionCheckDialog(this, ARTALK_VERSION, data.api_version.version)) return
|
|
188
203
|
|
|
189
204
|
// 导入数据
|
|
190
205
|
this.importComments(data.comments)
|
|
191
206
|
|
|
192
|
-
//
|
|
193
|
-
this.refreshPagination(offset, (this.flatMode ? data.total : data.total_roots))
|
|
207
|
+
// 分页功能
|
|
208
|
+
this.refreshPagination(offset, (this.flatMode ? data.total : data.total_roots))
|
|
194
209
|
|
|
195
210
|
// 加载后事件
|
|
196
211
|
this.refreshUI()
|
|
197
212
|
|
|
213
|
+
// 未读消息提示功能
|
|
198
214
|
this.ctx.updateNotifies(data.unread || [])
|
|
215
|
+
|
|
216
|
+
// 事件触发,列表已加载
|
|
199
217
|
this.ctx.trigger('list-loaded')
|
|
200
218
|
|
|
219
|
+
// Hook 函数调用
|
|
201
220
|
if (this.onAfterLoad) this.onAfterLoad(data)
|
|
202
221
|
}
|
|
203
222
|
|
|
204
223
|
/** 分页模式 */
|
|
205
224
|
private refreshPagination(offset: number, total: number) {
|
|
206
|
-
const modePagination = (this.pageMode === 'pagination')
|
|
207
|
-
const modeReadMoreBtn = (this.pageMode === 'read-more')
|
|
208
|
-
const initialized = (modePagination) ? !!this.pagination : !!this.readMoreBtn
|
|
209
|
-
|
|
210
225
|
// 初始化
|
|
211
|
-
if (!
|
|
212
|
-
this.
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
// 更新
|
|
216
|
-
if (modePagination) this.pagination!.update(offset, total)
|
|
217
|
-
if (modeReadMoreBtn) this.readMoreBtn!.update(offset, total)
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
private initPagination() {
|
|
221
|
-
// 加载更多按钮
|
|
222
|
-
if (this.pageMode === 'read-more') {
|
|
223
|
-
this.readMoreBtn = new ReadMoreBtn({
|
|
226
|
+
if (!this.pgHolder) {
|
|
227
|
+
this.pgHolder = new PgHolder({
|
|
228
|
+
list: this,
|
|
229
|
+
mode: this.pageMode,
|
|
224
230
|
pageSize: this.pageSize,
|
|
225
|
-
|
|
226
|
-
await this.fetchComments(o)
|
|
227
|
-
},
|
|
228
|
-
text: this.ctx.$t('loadMore'),
|
|
231
|
+
total
|
|
229
232
|
})
|
|
230
|
-
this.$el.append(this.readMoreBtn.$el)
|
|
231
|
-
|
|
232
|
-
// 滚动到底部自动加载
|
|
233
|
-
if (this.conf.pagination.autoLoad) {
|
|
234
|
-
// 添加滚动事件监听
|
|
235
|
-
const at = this.scrollListenerAt || document
|
|
236
|
-
if (this.autoLoadScrollEvent) at.removeEventListener('scroll', this.autoLoadScrollEvent) // 解除原有
|
|
237
|
-
this.autoLoadScrollEvent = () => {
|
|
238
|
-
if (this.pageMode !== 'read-more'
|
|
239
|
-
|| !this.readMoreBtn?.hasMore
|
|
240
|
-
|| this.isLoading
|
|
241
|
-
) return
|
|
242
|
-
|
|
243
|
-
const $target = this.$el.querySelector<HTMLElement>('.atk-list-comments-wrap > .atk-comment-wrap:nth-last-child(3)') // 获取倒数第3个评论元素
|
|
244
|
-
if ($target && Ui.isVisible($target, this.scrollListenerAt)) {
|
|
245
|
-
this.readMoreBtn.click() // 自动点击加载更多按钮
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
at.addEventListener('scroll', this.autoLoadScrollEvent)
|
|
249
|
-
}
|
|
250
233
|
}
|
|
251
234
|
|
|
252
|
-
//
|
|
253
|
-
|
|
254
|
-
this.pagination = new Pagination((!this.flatMode ? this.data!.total_roots : this.data!.total), {
|
|
255
|
-
pageSize: this.pageSize,
|
|
256
|
-
onChange: async (o) => {
|
|
257
|
-
if (this.ctx.conf.editorTravel === true) {
|
|
258
|
-
this.ctx.editorTravelBack() // 防止评论框被吞
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
await this.fetchComments(o)
|
|
262
|
-
|
|
263
|
-
// 滚动到第一个评论的位置
|
|
264
|
-
if (this.repositionAt) {
|
|
265
|
-
const at = this.scrollListenerAt || window
|
|
266
|
-
at.scroll({
|
|
267
|
-
top: this.repositionAt ? Utils.getOffset(this.repositionAt).top : 0,
|
|
268
|
-
left: 0,
|
|
269
|
-
})
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
})
|
|
273
|
-
|
|
274
|
-
this.$el.append(this.pagination.$el)
|
|
275
|
-
}
|
|
235
|
+
// 更新
|
|
236
|
+
this.pgHolder?.update(offset, total)
|
|
276
237
|
}
|
|
277
238
|
|
|
278
239
|
/** 错误处理 */
|
|
@@ -286,58 +247,17 @@ export default class ListLite extends Component {
|
|
|
286
247
|
|
|
287
248
|
// 加载更多按钮显示错误
|
|
288
249
|
if (offset !== 0 && this.pageMode === 'read-more') {
|
|
289
|
-
this.
|
|
250
|
+
this.pgHolder?.showErr(this.$t('loadFail'))
|
|
290
251
|
return
|
|
291
252
|
}
|
|
292
253
|
|
|
293
254
|
// 显示错误对话框
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
const $retryBtn = Utils.createElement(`<span style="cursor:pointer;">${this.$t('listRetry')}</span>`)
|
|
297
|
-
$retryBtn.onclick = () => (this.fetchComments(0))
|
|
298
|
-
$err.appendChild($retryBtn)
|
|
299
|
-
|
|
300
|
-
const adminBtn = Utils.createElement('<span atk-only-admin-show> | <span style="cursor:pointer;">打开控制台</span></span>')
|
|
301
|
-
$err.appendChild(adminBtn)
|
|
302
|
-
if (!this.ctx.user.data.isAdmin) adminBtn.classList.add('atk-hide')
|
|
303
|
-
|
|
304
|
-
let sidebarView = ''
|
|
305
|
-
|
|
306
|
-
// 找不到站点错误,打开侧边栏并填入创建站点表单
|
|
307
|
-
if (errData?.err_no_site) {
|
|
308
|
-
const viewLoadParam = {
|
|
309
|
-
create_name: this.ctx.conf.site,
|
|
310
|
-
create_urls: `${window.location.protocol}//${window.location.host}`
|
|
311
|
-
}
|
|
312
|
-
// TODO 真的是飞鸽传书啊
|
|
313
|
-
sidebarView = `sites|${JSON.stringify(viewLoadParam)}`
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
adminBtn.onclick = () => this.ctx.showSidebar({
|
|
317
|
-
view: sidebarView as any
|
|
318
|
-
})
|
|
319
|
-
|
|
320
|
-
Ui.setError(this.$el, $err)
|
|
255
|
+
Ui.setError(this.$el, ListUi.renderErrorDialog(this, msg, errData))
|
|
321
256
|
}
|
|
322
257
|
|
|
323
258
|
/** 刷新界面 */
|
|
324
259
|
public refreshUI() {
|
|
325
|
-
|
|
326
|
-
const isNoComment = this.ctx.getCommentList().length <= 0
|
|
327
|
-
let $noComment = this.$commentsWrap.querySelector<HTMLElement>('.atk-list-no-comment')
|
|
328
|
-
|
|
329
|
-
if (isNoComment) {
|
|
330
|
-
if (!$noComment) {
|
|
331
|
-
$noComment = Utils.createElement('<div class="atk-list-no-comment"></div>')
|
|
332
|
-
$noComment.innerHTML = this.noCommentText || this.ctx.conf.noComment || this.ctx.$t('noComment')
|
|
333
|
-
this.$commentsWrap.appendChild($noComment)
|
|
334
|
-
}
|
|
335
|
-
} else {
|
|
336
|
-
$noComment?.remove()
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// 仅管理员显示控制
|
|
340
|
-
this.ctx.checkAdminShowEl()
|
|
260
|
+
ListUi.refreshUI(this)
|
|
341
261
|
}
|
|
342
262
|
|
|
343
263
|
/** 重新加载列表 */
|
|
@@ -501,27 +421,4 @@ export default class ListLite extends Component {
|
|
|
501
421
|
})
|
|
502
422
|
}
|
|
503
423
|
}
|
|
504
|
-
|
|
505
|
-
/** 版本检测 */
|
|
506
|
-
public versionCheck(name: 'frontend'|'backend', needVersion: string, curtVersion: string): boolean {
|
|
507
|
-
const needUpdate = Utils.versionCompare(needVersion, curtVersion) === 1
|
|
508
|
-
if (needUpdate) {
|
|
509
|
-
// 需要更新
|
|
510
|
-
const errEl = Utils.createElement(`<div>Artalk ${this.$t(name)}版本已过时,请更新以获得完整体验<br/>`
|
|
511
|
-
+ `如果你是管理员,请前往 “<a href="https://artalk.js.org/" target="_blank">官方文档</a>” 获得帮助`
|
|
512
|
-
+ `<br/><br/>`
|
|
513
|
-
+ `<span style="color: var(--at-color-meta);">当前${this.$t(name)}版本 ${curtVersion},需求版本 >= ${needVersion}</span><br/><br/>`
|
|
514
|
-
+ `</div>`)
|
|
515
|
-
const ignoreBtn = Utils.createElement('<span style="cursor:pointer;">忽略</span>')
|
|
516
|
-
ignoreBtn.onclick = () => {
|
|
517
|
-
Ui.setError(this.$el.parentElement!, null)
|
|
518
|
-
this.ctx.conf.versionCheck = false
|
|
519
|
-
this.fetchComments(0)
|
|
520
|
-
}
|
|
521
|
-
errEl.append(ignoreBtn)
|
|
522
|
-
Ui.setError(this.$el.parentElement!, errEl, '<span class="atk-warn-title">Artalk Warn</span>')
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
return needUpdate
|
|
526
|
-
}
|
|
527
424
|
}
|