artalk 2.7.1 → 2.7.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.
- package/README.md +1 -1
- package/dist/Artalk.css +1 -1
- package/dist/Artalk.es.js +155 -147
- package/dist/Artalk.es.js.map +1 -1
- package/dist/Artalk.iife.js +1 -1
- package/dist/Artalk.iife.js.map +1 -1
- package/dist/Artalk.js +1 -1
- package/dist/Artalk.js.map +1 -1
- package/dist/ArtalkLite.css +1 -1
- package/dist/ArtalkLite.es.js +155 -147
- package/dist/ArtalkLite.es.js.map +1 -1
- package/dist/ArtalkLite.iife.js +1 -1
- package/dist/ArtalkLite.iife.js.map +1 -1
- package/dist/ArtalkLite.js +1 -1
- package/dist/ArtalkLite.js.map +1 -1
- package/dist/i18n/jp.js +1 -1
- package/dist/i18n/zh-TW.js +1 -1
- package/dist/src/api/_base.d.ts +7 -0
- package/dist/src/api/_options.d.ts +5 -0
- package/dist/src/components/checker/index.d.ts +2 -0
- package/dist/src/i18n/en.d.ts +1 -0
- package/dist/src/i18n/external.d.ts +1 -0
- package/dist/src/i18n/index.d.ts +3 -0
- package/dist/src/i18n/jp.d.ts +1 -0
- package/dist/src/i18n/zh-TW.d.ts +1 -0
- package/dist/src/lib/user.d.ts +8 -6
- package/dist/src/plugins/editor/_kit.d.ts +7 -1
- package/dist/src/service.d.ts +2 -8
- package/dist/types/event.d.ts +1 -1
- package/package.json +43 -17
- package/dist/src/components/dark-mode.d.ts +0 -2
- package/src/api/_base.ts +0 -18
- package/src/api/_options.ts +0 -25
- package/src/api/_request.ts +0 -104
- package/src/api/admin.ts +0 -18
- package/src/api/captcha.ts +0 -24
- package/src/api/comment.ts +0 -84
- package/src/api/index.ts +0 -29
- package/src/api/page.ts +0 -83
- package/src/api/site.ts +0 -51
- package/src/api/system.ts +0 -49
- package/src/api/upload.ts +0 -28
- package/src/api/user.ts +0 -132
- package/src/artalk.ts +0 -115
- package/src/comment/actions.ts +0 -83
- package/src/comment/comment.html +0 -18
- package/src/comment/comment.ts +0 -192
- package/src/comment/height-limit.ts +0 -100
- package/src/comment/index.ts +0 -3
- package/src/comment/render.ts +0 -150
- package/src/comment/renders/actions.ts +0 -114
- package/src/comment/renders/avatar.ts +0 -22
- package/src/comment/renders/content.ts +0 -39
- package/src/comment/renders/header.ts +0 -84
- package/src/comment/renders/index.ts +0 -19
- package/src/comment/renders/pending.ts +0 -12
- package/src/comment/renders/reply-at.ts +0 -16
- package/src/comment/renders/reply-to.ts +0 -24
- package/src/components/action-btn.ts +0 -170
- package/src/components/checker/admin.ts +0 -37
- package/src/components/checker/captcha-renders.ts +0 -69
- package/src/components/checker/captcha.ts +0 -26
- package/src/components/checker/index.ts +0 -192
- package/src/components/dark-mode.ts +0 -30
- package/src/components/dialog.ts +0 -62
- package/src/components/error-dialog.ts +0 -35
- package/src/components/pagination.ts +0 -153
- package/src/components/read-more-btn.ts +0 -92
- package/src/config.ts +0 -119
- package/src/context.ts +0 -146
- package/src/data.ts +0 -110
- package/src/defaults.ts +0 -61
- package/src/editor/editor.html +0 -18
- package/src/editor/editor.ts +0 -114
- package/src/editor/state.ts +0 -52
- package/src/editor/ui.ts +0 -36
- package/src/i18n/en.ts +0 -84
- package/src/i18n/external.ts +0 -10
- package/src/i18n/index.ts +0 -67
- package/src/i18n/jp.ts +0 -86
- package/src/i18n/zh-CN.ts +0 -88
- package/src/i18n/zh-TW.ts +0 -86
- package/src/layer/index.ts +0 -4
- package/src/layer/layer-manager.ts +0 -32
- package/src/layer/layer.ts +0 -39
- package/src/layer/scrollbar-helper.ts +0 -24
- package/src/layer/sidebar-layer.html +0 -8
- package/src/layer/sidebar-layer.ts +0 -155
- package/src/layer/wrap.ts +0 -77
- package/src/lib/component.ts +0 -12
- package/src/lib/detect.ts +0 -222
- package/src/lib/event-manager.ts +0 -47
- package/src/lib/highlight.ts +0 -5
- package/src/lib/marked-renderer.ts +0 -32
- package/src/lib/marked.ts +0 -72
- package/src/lib/sanitizer.ts +0 -57
- package/src/lib/ui.ts +0 -193
- package/src/lib/user.ts +0 -55
- package/src/lib/utils.ts +0 -236
- package/src/list/comment.ts +0 -20
- package/src/list/layout.ts +0 -120
- package/src/list/list.html +0 -20
- package/src/list/list.ts +0 -79
- package/src/list/nest.ts +0 -66
- package/src/list/page.ts +0 -97
- package/src/list/paginator/index.ts +0 -25
- package/src/list/paginator/read-more.ts +0 -67
- package/src/list/paginator/up-down.ts +0 -51
- package/src/main.ts +0 -14
- package/src/plugins/admin-only-elem.ts +0 -40
- package/src/plugins/conf-remoter.ts +0 -54
- package/src/plugins/dark-mode.ts +0 -12
- package/src/plugins/editor/_kit.ts +0 -57
- package/src/plugins/editor/_plug.ts +0 -60
- package/src/plugins/editor/_sample.ts +0 -8
- package/src/plugins/editor/closable.ts +0 -44
- package/src/plugins/editor/emoticons.scss +0 -72
- package/src/plugins/editor/emoticons.ts +0 -293
- package/src/plugins/editor/header-event.ts +0 -35
- package/src/plugins/editor/header-link.ts +0 -31
- package/src/plugins/editor/header-user.ts +0 -98
- package/src/plugins/editor/index.ts +0 -40
- package/src/plugins/editor/local-storage.ts +0 -36
- package/src/plugins/editor/mover.ts +0 -29
- package/src/plugins/editor/preview.scss +0 -7
- package/src/plugins/editor/preview.ts +0 -41
- package/src/plugins/editor/state-edit.ts +0 -116
- package/src/plugins/editor/state-reply.ts +0 -91
- package/src/plugins/editor/submit-add.ts +0 -33
- package/src/plugins/editor/submit-btn.ts +0 -25
- package/src/plugins/editor/submit.ts +0 -73
- package/src/plugins/editor/textarea.ts +0 -55
- package/src/plugins/editor/upload.ts +0 -145
- package/src/plugins/editor-kit.ts +0 -161
- package/src/plugins/index.ts +0 -16
- package/src/plugins/list/copyright.ts +0 -16
- package/src/plugins/list/count.ts +0 -31
- package/src/plugins/list/dropdown.ts +0 -95
- package/src/plugins/list/error-dialog.ts +0 -22
- package/src/plugins/list/fetch.ts +0 -64
- package/src/plugins/list/goto-first.ts +0 -23
- package/src/plugins/list/goto.ts +0 -72
- package/src/plugins/list/index.ts +0 -25
- package/src/plugins/list/loading.ts +0 -17
- package/src/plugins/list/no-comment.ts +0 -25
- package/src/plugins/list/reach-bottom.ts +0 -46
- package/src/plugins/list/sidebar-btn.ts +0 -39
- package/src/plugins/list/time-ticking.ts +0 -22
- package/src/plugins/list/unread-badge.ts +0 -26
- package/src/plugins/list/unread.ts +0 -38
- package/src/plugins/list/with-editor.ts +0 -59
- package/src/plugins/markdown.ts +0 -10
- package/src/plugins/stat.ts +0 -82
- package/src/plugins/version-check.ts +0 -36
- package/src/service.ts +0 -91
- package/src/style/_color.scss +0 -41
- package/src/style/_extend.scss +0 -20
- package/src/style/_variables.scss +0 -3
- package/src/style/comment.scss +0 -360
- package/src/style/editor.scss +0 -194
- package/src/style/list.scss +0 -212
- package/src/style/main.scss +0 -486
- package/src/style/pagination.scss +0 -58
- package/src/style/sidebar-layer.scss +0 -92
- package/types/config.ts +0 -153
- package/types/context.ts +0 -84
- package/types/data.ts +0 -271
- package/types/editor.ts +0 -95
- package/types/event.ts +0 -40
- package/types/global.d.ts +0 -1
- package/types/index.ts +0 -7
- package/types/plugin.ts +0 -3
- package/types/sidebar.ts +0 -3
package/src/lib/utils.ts
DELETED
|
@@ -1,236 +0,0 @@
|
|
|
1
|
-
export function createElement<E extends HTMLElement = HTMLElement>(htmlStr: string = ''): E {
|
|
2
|
-
const div = document.createElement('div')
|
|
3
|
-
div.innerHTML = htmlStr.trim()
|
|
4
|
-
return (div.firstElementChild || div) as E
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function getHeight(el: HTMLElement) {
|
|
8
|
-
return parseFloat(getComputedStyle(el, null).height.replace('px', ''))
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function htmlEncode(str: string) {
|
|
12
|
-
const temp = document.createElement('div')
|
|
13
|
-
temp.innerText = str
|
|
14
|
-
const output = temp.innerHTML
|
|
15
|
-
return output
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function htmlDecode(str: string) {
|
|
19
|
-
const temp = document.createElement('div')
|
|
20
|
-
temp.innerHTML = str
|
|
21
|
-
const output = temp.innerText
|
|
22
|
-
return output
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function getQueryParam(name: string) {
|
|
26
|
-
const match = RegExp(`[?&]${name}=([^&]*)`).exec(window.location.search);
|
|
27
|
-
return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
|
|
28
|
-
}
|
|
29
|
-
|
|
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
|
-
|
|
46
|
-
return {
|
|
47
|
-
top: elOffset.top - relativeToOffset.top,
|
|
48
|
-
left: elOffset.left - relativeToOffset.left
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function padWithZeros(vNumber: number, width: number) {
|
|
53
|
-
let numAsString = vNumber.toString()
|
|
54
|
-
while (numAsString.length < width) {
|
|
55
|
-
numAsString = `0${numAsString}`
|
|
56
|
-
}
|
|
57
|
-
return numAsString
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function dateFormat(date: Date) {
|
|
61
|
-
const vDay = padWithZeros(date.getDate(), 2)
|
|
62
|
-
const vMonth = padWithZeros(date.getMonth() + 1, 2)
|
|
63
|
-
const vYear = padWithZeros(date.getFullYear(), 2)
|
|
64
|
-
// var vHour = padWithZeros(date.getHours(), 2);
|
|
65
|
-
// var vMinute = padWithZeros(date.getMinutes(), 2);
|
|
66
|
-
// var vSecond = padWithZeros(date.getSeconds(), 2);
|
|
67
|
-
return `${vYear}-${vMonth}-${vDay}`
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function timeAgo(date: Date, $t: Function = (n: string) => n) {
|
|
71
|
-
try {
|
|
72
|
-
const oldTime = date.getTime()
|
|
73
|
-
const currTime = new Date().getTime()
|
|
74
|
-
const diffValue = currTime - oldTime
|
|
75
|
-
|
|
76
|
-
const days = Math.floor(diffValue / (24 * 3600 * 1000))
|
|
77
|
-
if (days === 0) {
|
|
78
|
-
// 计算相差小时数
|
|
79
|
-
const leave1 = diffValue % (24 * 3600 * 1000) // 计算天数后剩余的毫秒数
|
|
80
|
-
const hours = Math.floor(leave1 / (3600 * 1000))
|
|
81
|
-
if (hours === 0) {
|
|
82
|
-
// 计算相差分钟数
|
|
83
|
-
const leave2 = leave1 % (3600 * 1000) // 计算小时数后剩余的毫秒数
|
|
84
|
-
const minutes = Math.floor(leave2 / (60 * 1000))
|
|
85
|
-
if (minutes === 0) {
|
|
86
|
-
// 计算相差秒数
|
|
87
|
-
const leave3 = leave2 % (60 * 1000) // 计算分钟数后剩余的毫秒数
|
|
88
|
-
const seconds = Math.round(leave3 / 1000)
|
|
89
|
-
if (seconds < 10) return $t('now')
|
|
90
|
-
return `${seconds} ${$t('seconds')}`
|
|
91
|
-
}
|
|
92
|
-
return `${minutes} ${$t('minutes')}`
|
|
93
|
-
}
|
|
94
|
-
return `${hours} ${$t('hours')}`
|
|
95
|
-
}
|
|
96
|
-
if (days < 0) return $t('now')
|
|
97
|
-
|
|
98
|
-
if (days < 8) {
|
|
99
|
-
return `${days} ${$t('days')}`
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return dateFormat(date)
|
|
103
|
-
} catch (error) {
|
|
104
|
-
console.error(error)
|
|
105
|
-
return ' - '
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/** 所有图片加载完毕后执行 */
|
|
110
|
-
export function onImagesLoaded($container: HTMLElement, event: Function) {
|
|
111
|
-
if (!$container) return
|
|
112
|
-
const images = $container.getElementsByTagName('img')
|
|
113
|
-
if (!images.length) return
|
|
114
|
-
let loaded = images.length
|
|
115
|
-
for (let i = 0; i < images.length; i++) {
|
|
116
|
-
if (images[i].complete) {
|
|
117
|
-
loaded--
|
|
118
|
-
} else {
|
|
119
|
-
// eslint-disable-next-line @typescript-eslint/no-loop-func
|
|
120
|
-
images[i].addEventListener('load', () => {
|
|
121
|
-
loaded--
|
|
122
|
-
if (loaded === 0) event()
|
|
123
|
-
})
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (loaded === 0) event()
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
export function getGravatarURL(opts: { params: string, mirror: string, emailMD5: string }) {
|
|
131
|
-
return `${opts.mirror.replace(/\/$/, '')}/${opts.emailMD5}?${opts.params.replace(/^\?/, '')}`
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
export function sleep(ms: number) {
|
|
135
|
-
return new Promise(resolve => { setTimeout(resolve, ms) });
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/** 版本号比较(a < b :-1 | 0 | b < a :1) */
|
|
139
|
-
export function versionCompare(a: string, b: string) {
|
|
140
|
-
const pa = a.split('.')
|
|
141
|
-
const pb = b.split('.')
|
|
142
|
-
for (let i = 0; i < 3; i++) {
|
|
143
|
-
const na = Number(pa[i])
|
|
144
|
-
const nb = Number(pb[i])
|
|
145
|
-
if (na > nb) return 1
|
|
146
|
-
if (nb > na) return -1
|
|
147
|
-
if (!Number.isNaN(na) && Number.isNaN(nb)) return 1
|
|
148
|
-
if (Number.isNaN(na) && !Number.isNaN(nb)) return -1
|
|
149
|
-
}
|
|
150
|
-
return 0
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/** 获取修正后的 UserAgent */
|
|
154
|
-
export async function getCorrectUserAgent() {
|
|
155
|
-
const uaRaw = navigator.userAgent
|
|
156
|
-
if (!(navigator as any).userAgentData || !(navigator as any).userAgentData.getHighEntropyValues) {
|
|
157
|
-
return uaRaw
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// @link https://web.dev/migrate-to-ua-ch/
|
|
161
|
-
// @link https://web.dev/user-agent-client-hints/
|
|
162
|
-
const uaData = (navigator as any).userAgentData
|
|
163
|
-
let uaGot: any = null
|
|
164
|
-
try {
|
|
165
|
-
uaGot = await uaData.getHighEntropyValues(["platformVersion"])
|
|
166
|
-
} catch (err) { console.error(err); return uaRaw }
|
|
167
|
-
const majorPlatformVersion = Number(uaGot.platformVersion.split('.')[0])
|
|
168
|
-
|
|
169
|
-
if (uaData.platform === "Windows") {
|
|
170
|
-
if (majorPlatformVersion >= 13) { // @link https://docs.microsoft.com/en-us/microsoft-edge/web-platform/how-to-detect-win11
|
|
171
|
-
// @date 2022-4-29
|
|
172
|
-
// Win 11 样本:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36"
|
|
173
|
-
return uaRaw.replace(/Windows NT 10.0/, 'Windows NT 11.0')
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
if (uaData.platform === "macOS") {
|
|
177
|
-
if (majorPlatformVersion >= 11) { // 11 => BigSur
|
|
178
|
-
// @date 2022-4-29
|
|
179
|
-
// (Intel Chip) macOS 12.3 样本:"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36"
|
|
180
|
-
// (Arm Chip) macOS 样本:"Mozilla/5.0 (Macintosh; ARM Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.2 Safari/605.1.15"
|
|
181
|
-
return uaRaw.replace(/(Mac OS X \d+_\d+_\d+|Mac OS X)/, `Mac OS X ${uaGot.platformVersion.replace(/\./g, '_')}`)
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
return uaRaw
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/** 是否为完整的 URL */
|
|
189
|
-
export function isValidURL(urlRaw: string) {
|
|
190
|
-
// @link https://www.rfc-editor.org/rfc/rfc3986
|
|
191
|
-
let url: URL
|
|
192
|
-
try {
|
|
193
|
-
url = new URL(urlRaw)
|
|
194
|
-
} catch (_) { return false }
|
|
195
|
-
return url.protocol === "http:" || url.protocol === "https:"
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/** 获取基于 conf.server 的 URL */
|
|
199
|
-
export function getURLBasedOnApi(opts: { base: string, path: string }) {
|
|
200
|
-
return getURLBasedOn(opts.base, opts.path)
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/** 获取基于某个 baseURL 的 URL */
|
|
204
|
-
export function getURLBasedOn(baseURL: string, path: string) {
|
|
205
|
-
return `${baseURL.replace(/\/$/, '')}/${path.replace(/^\//, '')}`
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Performs a deep merge of `source` into `target`.
|
|
210
|
-
* Mutates `target` only but not its objects and arrays.
|
|
211
|
-
*
|
|
212
|
-
* @author inspired by [jhildenbiddle](https://stackoverflow.com/a/48218209).
|
|
213
|
-
* @link https://gist.github.com/ahtcx/0cd94e62691f539160b32ecda18af3d6
|
|
214
|
-
*/
|
|
215
|
-
export function mergeDeep(target: any, source: any) {
|
|
216
|
-
const isObject = (obj: any) => obj && typeof obj === 'object'
|
|
217
|
-
|
|
218
|
-
if (!isObject(target) || !isObject(source)) {
|
|
219
|
-
return source
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
Object.keys(source).forEach(key => {
|
|
223
|
-
const targetValue = target[key]
|
|
224
|
-
const sourceValue = source[key]
|
|
225
|
-
|
|
226
|
-
if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {
|
|
227
|
-
target[key] = targetValue.concat(sourceValue)
|
|
228
|
-
} else if (isObject(targetValue) && isObject(sourceValue)) {
|
|
229
|
-
target[key] = mergeDeep({ ...targetValue}, sourceValue)
|
|
230
|
-
} else {
|
|
231
|
-
target[key] = sourceValue
|
|
232
|
-
}
|
|
233
|
-
})
|
|
234
|
-
|
|
235
|
-
return target
|
|
236
|
-
}
|
package/src/list/comment.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
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
|
-
}
|
package/src/list/layout.ts
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
<div class="atk-list">
|
|
2
|
-
<div class="atk-list-header">
|
|
3
|
-
<div class="atk-comment-count">
|
|
4
|
-
<div class="atk-text"></div>
|
|
5
|
-
</div>
|
|
6
|
-
<div class="atk-right-action">
|
|
7
|
-
<span data-action="admin-close-comment" class="atk-hide" atk-only-admin-show></span>
|
|
8
|
-
<span data-action="open-sidebar" class="atk-hide atk-on">
|
|
9
|
-
<span class="atk-unread-badge" style="display: none;"></span>
|
|
10
|
-
<div class="atk-text"></div>
|
|
11
|
-
</span>
|
|
12
|
-
</div>
|
|
13
|
-
</div>
|
|
14
|
-
<div class="atk-list-body">
|
|
15
|
-
<div class="atk-list-comments-wrap"></div>
|
|
16
|
-
</div>
|
|
17
|
-
<div class="atk-list-footer">
|
|
18
|
-
<div class="atk-copyright"></div>
|
|
19
|
-
</div>
|
|
20
|
-
</div>
|
package/src/list/list.ts
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import type { ContextApi } from '~/types'
|
|
2
|
-
import Component from '../lib/component'
|
|
3
|
-
import * as Utils from '../lib/utils'
|
|
4
|
-
import CommentNode from '../comment/comment'
|
|
5
|
-
import ListHTML from './list.html?raw'
|
|
6
|
-
import ListLayout from './layout'
|
|
7
|
-
import { createComment as createCommentNode } from './comment'
|
|
8
|
-
import { initListPaginatorFunc } from './page'
|
|
9
|
-
|
|
10
|
-
export default class List extends Component {
|
|
11
|
-
/** 列表评论集区域元素 */
|
|
12
|
-
$commentsWrap!: HTMLElement
|
|
13
|
-
public getCommentsWrapEl() { return this.$commentsWrap }
|
|
14
|
-
|
|
15
|
-
protected commentNodes: CommentNode[] = []
|
|
16
|
-
getCommentNodes() { return this.commentNodes }
|
|
17
|
-
|
|
18
|
-
constructor (ctx: ContextApi) {
|
|
19
|
-
super(ctx)
|
|
20
|
-
|
|
21
|
-
// Init base element
|
|
22
|
-
this.$el = Utils.createElement(ListHTML)
|
|
23
|
-
this.$commentsWrap = this.$el.querySelector('.atk-list-comments-wrap')!
|
|
24
|
-
|
|
25
|
-
// Init paginator
|
|
26
|
-
initListPaginatorFunc(ctx)
|
|
27
|
-
|
|
28
|
-
// Bind events
|
|
29
|
-
this.initCrudEvents()
|
|
30
|
-
}
|
|
31
|
-
|
|
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(),
|
|
46
|
-
})
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
private initCrudEvents() {
|
|
50
|
-
this.ctx.on('list-load', (comments) => {
|
|
51
|
-
// 导入数据
|
|
52
|
-
this.getListLayout().import(comments)
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
this.ctx.on('list-loaded', (comments) => {
|
|
56
|
-
if (comments.length === 0) this.$commentsWrap.innerHTML = ''
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
// When comment insert
|
|
60
|
-
this.ctx.on('comment-inserted', (comment) => {
|
|
61
|
-
this.getListLayout().insert(comment)
|
|
62
|
-
})
|
|
63
|
-
|
|
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
|
|
71
|
-
})
|
|
72
|
-
|
|
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
|
-
})
|
|
78
|
-
}
|
|
79
|
-
}
|
package/src/list/nest.ts
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import type { CommentData } from '~/types'
|
|
2
|
-
|
|
3
|
-
export type CommentNode = {
|
|
4
|
-
id: number
|
|
5
|
-
comment: CommentData
|
|
6
|
-
children: CommentNode[]
|
|
7
|
-
parent?: CommentNode
|
|
8
|
-
level: number
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export type SortByType = 'DATE_DESC'|'DATE_ASC'|'SRC_INDEX'|'VOTE_UP_DESC'
|
|
12
|
-
|
|
13
|
-
// 构建树状结构列表
|
|
14
|
-
export function makeNestCommentNodeList(srcData: CommentData[], sortBy: SortByType = 'DATE_DESC', nestMax = 2) {
|
|
15
|
-
const nodeList: CommentNode[] = []
|
|
16
|
-
|
|
17
|
-
const roots = srcData.filter((o) => o.rid === 0)
|
|
18
|
-
roots.forEach((root: CommentData) => {
|
|
19
|
-
const rootNode: CommentNode = {
|
|
20
|
-
id: root.id,
|
|
21
|
-
comment: root,
|
|
22
|
-
children: [],
|
|
23
|
-
level: 1,
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
rootNode.parent = rootNode
|
|
27
|
-
nodeList.push(rootNode)
|
|
28
|
-
|
|
29
|
-
;(function loadChildren(parentNode: CommentNode) {
|
|
30
|
-
const children = srcData.filter((o) => o.rid === parentNode.id)
|
|
31
|
-
if (children.length === 0) return
|
|
32
|
-
if (parentNode.level >= nestMax) parentNode = parentNode.parent!
|
|
33
|
-
children.forEach((child: CommentData) => {
|
|
34
|
-
const childNode: CommentNode = {
|
|
35
|
-
id: child.id,
|
|
36
|
-
comment: child,
|
|
37
|
-
children: [],
|
|
38
|
-
parent: parentNode,
|
|
39
|
-
level: parentNode.level + 1,
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
parentNode.children.push(childNode)
|
|
43
|
-
loadChildren(childNode)
|
|
44
|
-
})
|
|
45
|
-
})(rootNode)
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
// 排序
|
|
49
|
-
const sortFunc = (a: CommentNode, b: CommentNode): number => {
|
|
50
|
-
let v = a.id - b.id
|
|
51
|
-
if (sortBy === 'DATE_ASC') v = +new Date(a.comment.date) - +new Date(b.comment.date)
|
|
52
|
-
else if (sortBy === 'DATE_DESC') v = +new Date(b.comment.date) - +new Date(a.comment.date)
|
|
53
|
-
else if (sortBy === 'SRC_INDEX') v = srcData.indexOf(a.comment) - srcData.indexOf(b.comment)
|
|
54
|
-
else if (sortBy === 'VOTE_UP_DESC') v = b.comment.vote_up - a.comment.vote_up
|
|
55
|
-
return v
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
(function sortLevels(nodes: CommentNode[]) {
|
|
59
|
-
nodes.forEach((node) => {
|
|
60
|
-
node.children = node.children.sort(sortFunc)
|
|
61
|
-
sortLevels(node.children)
|
|
62
|
-
})
|
|
63
|
-
})(nodeList)
|
|
64
|
-
|
|
65
|
-
return nodeList
|
|
66
|
-
}
|
package/src/list/page.ts
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import type { ArtalkConfig, ContextApi } from '~/types'
|
|
2
|
-
import $t from '@/i18n'
|
|
3
|
-
import { Paginator } from './paginator'
|
|
4
|
-
import ReadMorePaginator from './paginator/read-more'
|
|
5
|
-
import UpDownPaginator from './paginator/up-down'
|
|
6
|
-
|
|
7
|
-
function createPaginatorByConf(conf: ArtalkConfig): Paginator {
|
|
8
|
-
if (conf.pagination.readMore) return new ReadMorePaginator()
|
|
9
|
-
return new UpDownPaginator()
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function getPageDataByLastData(ctx: ContextApi): { offset: number, total: number } {
|
|
13
|
-
const last = ctx.getData().getListLastFetch()
|
|
14
|
-
const r = { offset: 0, total: 0 }
|
|
15
|
-
if (!last) return r
|
|
16
|
-
|
|
17
|
-
r.offset = last.params.offset
|
|
18
|
-
if (last.data) r.total = last.params.flatMode ? last.data.total : last.data.total_roots
|
|
19
|
-
|
|
20
|
-
return r
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export const initListPaginatorFunc = (ctx: ContextApi) => {
|
|
24
|
-
let paginator: Paginator|null = null
|
|
25
|
-
|
|
26
|
-
// Init paginator when conf loaded
|
|
27
|
-
ctx.on('conf-loaded', (conf) => {
|
|
28
|
-
const list = ctx.get('list')
|
|
29
|
-
|
|
30
|
-
if (paginator) paginator.dispose() // if had been init, dispose it
|
|
31
|
-
|
|
32
|
-
// create paginator instance
|
|
33
|
-
paginator = createPaginatorByConf(conf)
|
|
34
|
-
|
|
35
|
-
// create paginator dom
|
|
36
|
-
const { offset, total } = getPageDataByLastData(ctx)
|
|
37
|
-
const $paginator = paginator.create({
|
|
38
|
-
ctx, pageSize: conf.pagination.pageSize, total,
|
|
39
|
-
|
|
40
|
-
readMoreAutoLoad: conf.pagination.autoLoad,
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
// mount paginator dom
|
|
44
|
-
list.$commentsWrap.after($paginator)
|
|
45
|
-
|
|
46
|
-
// update paginator info
|
|
47
|
-
paginator?.update(offset, total)
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
// When list loaded
|
|
51
|
-
ctx.on('list-loaded', (comments) => {
|
|
52
|
-
// update paginator info
|
|
53
|
-
const { offset, total } = getPageDataByLastData(ctx)
|
|
54
|
-
paginator?.update(offset, total)
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
// When list fetch
|
|
58
|
-
ctx.on('list-fetch', (params) => {
|
|
59
|
-
// if clear comments when fetch new page data
|
|
60
|
-
if (ctx.getData().getComments().length > 0 && paginator?.getIsClearComments(params)) {
|
|
61
|
-
ctx.getData().clearComments()
|
|
62
|
-
}
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
// When list error
|
|
66
|
-
ctx.on('list-error', () => {
|
|
67
|
-
paginator?.showErr?.($t('loadFail'))
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
// List goto auto next page when comment not found
|
|
71
|
-
const autoSwitchPageForFindComment = (commentID: number) => {
|
|
72
|
-
const comment = ctx.getData().findComment(commentID)
|
|
73
|
-
if (!!comment || !paginator?.getHasMore()) return
|
|
74
|
-
|
|
75
|
-
// wait for list loaded
|
|
76
|
-
ctx.on('list-loaded', () => {
|
|
77
|
-
autoSwitchPageForFindComment(commentID) // recursive, until comment found or no more page
|
|
78
|
-
}, { once: true })
|
|
79
|
-
|
|
80
|
-
// TODO 自动范围改为直接跳转到计算后的页面
|
|
81
|
-
setTimeout(() => {
|
|
82
|
-
paginator?.next()
|
|
83
|
-
}, 80)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
ctx.on('list-goto', (commentID) => {
|
|
87
|
-
autoSwitchPageForFindComment(commentID)
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
// loading
|
|
91
|
-
ctx.on('list-fetch', (params) => {
|
|
92
|
-
paginator?.setLoading(true)
|
|
93
|
-
})
|
|
94
|
-
ctx.on('list-fetched', ({ params }) => {
|
|
95
|
-
paginator?.setLoading(false)
|
|
96
|
-
})
|
|
97
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import type { ContextApi, ListFetchParams } from '~/types'
|
|
2
|
-
import UpDownPaginator from './up-down'
|
|
3
|
-
import ReadMorePaginator from './read-more'
|
|
4
|
-
|
|
5
|
-
export interface IPgHolderOpt {
|
|
6
|
-
ctx: ContextApi
|
|
7
|
-
total: number
|
|
8
|
-
pageSize: number
|
|
9
|
-
|
|
10
|
-
readMoreAutoLoad?: boolean
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface Paginator {
|
|
14
|
-
create(opts: IPgHolderOpt): HTMLElement
|
|
15
|
-
setLoading(val: boolean): void
|
|
16
|
-
update(offset: number, total: number): void
|
|
17
|
-
next(): void
|
|
18
|
-
showErr?(msg: string): void
|
|
19
|
-
getHasMore(): boolean
|
|
20
|
-
/** Clear comments when fetch new page data */
|
|
21
|
-
getIsClearComments(params: Partial<ListFetchParams>): boolean
|
|
22
|
-
dispose(): void
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export default { UpDownPaginator, ReadMorePaginator }
|