artalk 2.7.0 → 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 +209 -188
- package/dist/Artalk.es.js.map +1 -0
- package/dist/Artalk.iife.js +2 -1
- package/dist/Artalk.iife.js.map +1 -0
- package/dist/Artalk.js +2 -1
- package/dist/Artalk.js.map +1 -0
- package/dist/ArtalkLite.css +1 -1
- package/dist/ArtalkLite.es.js +209 -188
- package/dist/ArtalkLite.es.js.map +1 -0
- package/dist/ArtalkLite.iife.js +2 -1
- package/dist/ArtalkLite.iife.js.map +1 -0
- package/dist/ArtalkLite.js +2 -1
- package/dist/ArtalkLite.js.map +1 -0
- 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/artalk.d.ts +0 -2
- package/dist/src/components/checker/index.d.ts +2 -0
- package/dist/src/i18n/en.d.ts +4 -0
- package/dist/src/i18n/external.d.ts +4 -0
- package/dist/src/i18n/index.d.ts +12 -0
- package/dist/src/i18n/jp.d.ts +4 -0
- package/dist/src/i18n/zh-TW.d.ts +4 -0
- package/dist/src/lib/user.d.ts +8 -6
- package/dist/src/main.d.ts +3 -0
- 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 -119
- 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 -81
- package/src/i18n/external.ts +0 -10
- package/src/i18n/index.ts +0 -67
- package/src/i18n/jp.ts +0 -83
- package/src/i18n/zh-CN.ts +0 -85
- package/src/i18n/zh-TW.ts +0 -83
- 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 -5
- 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 -79
- package/src/plugins/version-check.ts +0 -37
- 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/marked.ts
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { marked as libMarked, MarkedOptions } from 'marked'
|
|
2
|
-
|
|
3
|
-
import { sanitize } from './sanitizer'
|
|
4
|
-
import { renderCode } from './highlight'
|
|
5
|
-
import { getRenderer } from './marked-renderer'
|
|
6
|
-
|
|
7
|
-
type Replacer = (raw: string) => string
|
|
8
|
-
export type TMarked = typeof libMarked
|
|
9
|
-
|
|
10
|
-
let instance: (typeof libMarked)|undefined
|
|
11
|
-
let replacers: Replacer[] = []
|
|
12
|
-
|
|
13
|
-
const markedOptions: MarkedOptions = {
|
|
14
|
-
pedantic: false,
|
|
15
|
-
gfm: true,
|
|
16
|
-
breaks: true,
|
|
17
|
-
smartLists: true,
|
|
18
|
-
smartypants: true,
|
|
19
|
-
xhtml: false,
|
|
20
|
-
sanitize: false,
|
|
21
|
-
silent: true,
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/** Get Marked instance */
|
|
25
|
-
export function getInstance() {
|
|
26
|
-
return instance
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function setReplacers(arr: Replacer[]) {
|
|
30
|
-
replacers = arr
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/** 初始化 marked */
|
|
34
|
-
export function initMarked() {
|
|
35
|
-
try { if (!libMarked.name) return } catch { return }
|
|
36
|
-
|
|
37
|
-
// @see https://github.com/markedjs/marked/blob/4afb228d956a415624c4e5554bb8f25d047676fe/src/Tokenizer.js#L329
|
|
38
|
-
libMarked.setOptions({
|
|
39
|
-
renderer: getRenderer(),
|
|
40
|
-
...markedOptions
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
instance = libMarked
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** 解析 markdown */
|
|
47
|
-
export default function marked(src: string): string {
|
|
48
|
-
let markedContent = getInstance()?.parse(src)
|
|
49
|
-
if (!markedContent) { // 无 Markdown 模式简单处理
|
|
50
|
-
markedContent = simpleMarked(src)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
let dest = sanitize(markedContent)
|
|
54
|
-
|
|
55
|
-
// 内容替换器
|
|
56
|
-
replacers.forEach((replacer) => {
|
|
57
|
-
if (typeof replacer === 'function')
|
|
58
|
-
dest = replacer(dest)
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
return dest
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function simpleMarked(src: string) {
|
|
65
|
-
return src
|
|
66
|
-
// .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
|
67
|
-
.replace(/```\s*([^]+?.*?[^]+?[^]+?)```/g, (_, code) => `<pre><code>${renderCode(code)}</code></pre>`)
|
|
68
|
-
// .replace(/`([^`]+?)`/g, '<code>$1</code>')
|
|
69
|
-
.replace(/!\[(.*?)\]\((.*?)\)/g, (_, alt, imgSrc) => `<img src="${imgSrc}" alt="${alt}" />`)
|
|
70
|
-
.replace(/\[(.*?)\]\((.*?)\)/g, (_, text, link) => `<a href="${link}" target="_blank">${text}</a>`)
|
|
71
|
-
.replace(/\n/g, '<br>')
|
|
72
|
-
}
|
package/src/lib/sanitizer.ts
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import insane from 'insane'
|
|
2
|
-
|
|
3
|
-
const insaneOptions = {
|
|
4
|
-
allowedClasses: {},
|
|
5
|
-
// @refer CVE-2018-8495
|
|
6
|
-
// @link https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-8495
|
|
7
|
-
// @link https://leucosite.com/Microsoft-Edge-RCE/
|
|
8
|
-
// @link https://medium.com/@knownsec404team/analysis-of-the-security-issues-of-url-scheme-in-pc-from-cve-2018-8495-934478a36756
|
|
9
|
-
allowedSchemes: [
|
|
10
|
-
'http', 'https', 'mailto',
|
|
11
|
-
'data' // for support base64 encoded image (安全性有待考虑)
|
|
12
|
-
],
|
|
13
|
-
allowedTags: [
|
|
14
|
-
'a', 'abbr', 'article', 'b', 'blockquote', 'br', 'caption', 'code', 'del', 'details', 'div', 'em',
|
|
15
|
-
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'ins', 'kbd', 'li', 'main', 'mark',
|
|
16
|
-
'ol', 'p', 'pre', 'section', 'span', 'strike', 'strong', 'sub', 'summary', 'sup', 'table',
|
|
17
|
-
'tbody', 'td', 'th', 'thead', 'tr', 'u', 'ul'
|
|
18
|
-
],
|
|
19
|
-
allowedAttributes: {
|
|
20
|
-
'*': ['title', 'accesskey'],
|
|
21
|
-
a: ['href', 'name', 'target', 'aria-label', 'rel'],
|
|
22
|
-
img: ['src', 'alt', 'title', 'atk-emoticon', 'aria-label'],
|
|
23
|
-
// for code highlight
|
|
24
|
-
code: ['class'],
|
|
25
|
-
span: ['class', 'style'],
|
|
26
|
-
},
|
|
27
|
-
filter: node => {
|
|
28
|
-
// allow hljs style
|
|
29
|
-
const allowed = [
|
|
30
|
-
[ 'code', /^hljs\W+language-(.*)$/ ],
|
|
31
|
-
[ 'span', /^(hljs-.*)$/ ]
|
|
32
|
-
]
|
|
33
|
-
allowed.forEach(([ tag, reg ]) => {
|
|
34
|
-
if (
|
|
35
|
-
node.tag === tag
|
|
36
|
-
&& !!node.attrs.class
|
|
37
|
-
&& !(reg as RegExp).test(node.attrs.class)
|
|
38
|
-
) {
|
|
39
|
-
delete node.attrs.class
|
|
40
|
-
}
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
// allow <span> set color sty
|
|
44
|
-
if (node.tag === 'span' && !!node.attrs.style
|
|
45
|
-
&& !/^color:(\W+)?#[0-9a-f]{3,6};?$/i.test(node.attrs.style)) {
|
|
46
|
-
delete node.attrs.style
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return true
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function sanitize(content: string): string {
|
|
54
|
-
// @link https://github.com/markedjs/marked/discussions/1232
|
|
55
|
-
// @link https://gist.github.com/lionel-rowe/bb384465ba4e4c81a9c8dada84167225
|
|
56
|
-
return insane(content, insaneOptions)
|
|
57
|
-
}
|
package/src/lib/ui.ts
DELETED
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
import type { NotifyLevel } from '~/types'
|
|
2
|
-
import * as Utils from './utils'
|
|
3
|
-
|
|
4
|
-
/** 显示加载 */
|
|
5
|
-
export function showLoading(parentElem: HTMLElement, conf?: { transparentBg?: boolean }) {
|
|
6
|
-
// Use :scope https://developer.mozilla.org/en-US/docs/Web/CSS/:scope
|
|
7
|
-
let $loading = parentElem.querySelector<HTMLElement>(':scope > .atk-loading')
|
|
8
|
-
if (!$loading) {
|
|
9
|
-
$loading = Utils.createElement(
|
|
10
|
-
`<div class="atk-loading atk-fade-in" style="display: none;">
|
|
11
|
-
<div class="atk-loading-spinner">
|
|
12
|
-
<svg viewBox="25 25 50 50"><circle cx="50" cy="50" r="20" fill="none" stroke-width="2" stroke-miterlimit="10"></circle></svg>
|
|
13
|
-
</div>
|
|
14
|
-
</div>`)
|
|
15
|
-
if (conf?.transparentBg) $loading.style.background = 'transparent'
|
|
16
|
-
parentElem.appendChild($loading)
|
|
17
|
-
}
|
|
18
|
-
$loading.style.display = ''
|
|
19
|
-
|
|
20
|
-
// spinner 延迟显示,若加载等待时间太短,没必要显示(闪一下即可)
|
|
21
|
-
const $spinner = $loading.querySelector<HTMLElement>('.atk-loading-spinner')
|
|
22
|
-
if ($spinner) {
|
|
23
|
-
$spinner.style.display = 'none'
|
|
24
|
-
window.setTimeout(() => {
|
|
25
|
-
$spinner.style.display = ''
|
|
26
|
-
}, 500)
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/** 隐藏加载 */
|
|
31
|
-
export function hideLoading(parentElem: HTMLElement) {
|
|
32
|
-
const $loading = parentElem.querySelector<HTMLElement>(':scope > .atk-loading')
|
|
33
|
-
if ($loading) $loading.style.display = 'none'
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/** 元素加载动画 */
|
|
37
|
-
export function setLoading(val: boolean, parentElem: HTMLElement) {
|
|
38
|
-
if (val) showLoading(parentElem)
|
|
39
|
-
else hideLoading(parentElem)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** 元素是否用户可见 */
|
|
43
|
-
export function isVisible(el: HTMLElement, viewport: HTMLElement = document.documentElement) {
|
|
44
|
-
const viewportHeight = viewport.clientHeight
|
|
45
|
-
|
|
46
|
-
const docViewTop = viewport.scrollTop
|
|
47
|
-
const docViewBottom = docViewTop + viewportHeight
|
|
48
|
-
|
|
49
|
-
const rect = el.getBoundingClientRect()
|
|
50
|
-
const elemTop = rect.top + docViewTop
|
|
51
|
-
const elemBottom = elemTop + rect.height
|
|
52
|
-
|
|
53
|
-
return (elemBottom <= docViewBottom) //&& (elemTop >= docViewTop) 注释因为假如 el 比 viewport 还高就会失效
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/** 滚动到元素中心 */
|
|
57
|
-
export function scrollIntoView(elem: HTMLElement, enableAnim: boolean = true, relativeTo?: HTMLElement) {
|
|
58
|
-
let top: number
|
|
59
|
-
|
|
60
|
-
if (relativeTo) {
|
|
61
|
-
const containerRect = relativeTo.getBoundingClientRect()
|
|
62
|
-
const elementRect = elem.getBoundingClientRect()
|
|
63
|
-
top = elementRect.top - containerRect.top + relativeTo.scrollTop
|
|
64
|
-
top += Utils.getHeight(relativeTo) / 2 - Utils.getHeight(elem) / 2
|
|
65
|
-
} else {
|
|
66
|
-
const rect = elem.getBoundingClientRect()
|
|
67
|
-
const elemTop = rect.top + window.scrollY
|
|
68
|
-
top = elemTop - (window.innerHeight / 2 - rect.height / 2)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const scrollOptions: ScrollToOptions = {
|
|
72
|
-
top, left: 0,
|
|
73
|
-
// behavior: enableAnim ? 'smooth' : 'instant',
|
|
74
|
-
behavior: 'instant'
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (relativeTo) relativeTo.scroll(scrollOptions)
|
|
78
|
-
else window.scroll(scrollOptions)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/** 显示消息 */
|
|
82
|
-
export function showNotify(
|
|
83
|
-
wrapElem: HTMLElement,
|
|
84
|
-
msg: string,
|
|
85
|
-
type: NotifyLevel,
|
|
86
|
-
) {
|
|
87
|
-
const colors = { s: '#57d59f', e: '#ff6f6c', w: '#ffc721', i: '#2ebcfc' }
|
|
88
|
-
const timeout = 3000 // 持续显示时间 ms
|
|
89
|
-
|
|
90
|
-
const notifyElem = Utils.createElement(
|
|
91
|
-
`<div class="atk-notify atk-fade-in" style="background-color: ${colors[type]}"><span class="atk-notify-content"></span></div>`
|
|
92
|
-
)
|
|
93
|
-
const notifyContentEl = notifyElem.querySelector<HTMLElement>('.atk-notify-content')!
|
|
94
|
-
notifyContentEl.innerHTML = Utils.htmlEncode(msg).replace('\n', '<br/>')
|
|
95
|
-
|
|
96
|
-
wrapElem.appendChild(notifyElem)
|
|
97
|
-
|
|
98
|
-
const notifyRemove = () => {
|
|
99
|
-
notifyElem.classList.add('atk-fade-out')
|
|
100
|
-
setTimeout(() => {
|
|
101
|
-
notifyElem.remove()
|
|
102
|
-
}, 200)
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
let timeoutFn: number
|
|
106
|
-
if (timeout > 0) {
|
|
107
|
-
timeoutFn = window.setTimeout(() => {
|
|
108
|
-
notifyRemove()
|
|
109
|
-
}, timeout)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
notifyElem.addEventListener('click', () => {
|
|
113
|
-
notifyRemove()
|
|
114
|
-
window.clearTimeout(timeoutFn)
|
|
115
|
-
})
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/** fade 动画 */
|
|
119
|
-
export function playFadeAnim(
|
|
120
|
-
elem: HTMLElement,
|
|
121
|
-
after?: () => void,
|
|
122
|
-
type: 'in' | 'out' = 'in'
|
|
123
|
-
) {
|
|
124
|
-
elem.classList.add(`atk-fade-${type}`)
|
|
125
|
-
// 动画结束清除 class
|
|
126
|
-
const onAnimEnded = () => {
|
|
127
|
-
elem.classList.remove(`atk-fade-${type}`)
|
|
128
|
-
elem.removeEventListener('animationend', onAnimEnded)
|
|
129
|
-
if (after) after()
|
|
130
|
-
}
|
|
131
|
-
elem.addEventListener('animationend', onAnimEnded)
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/** 渐入动画 */
|
|
135
|
-
export function playFadeInAnim(elem: HTMLElement, after?: () => void) {
|
|
136
|
-
playFadeAnim(elem, after, 'in')
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/** 渐出动画 */
|
|
140
|
-
export function playFadeOutAnim(elem: HTMLElement, after?: () => void) {
|
|
141
|
-
playFadeAnim(elem, after, 'out')
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/** 设置全局错误 */
|
|
145
|
-
export function setError(parentElem: HTMLElement, html: string | HTMLElement | null, title: string = '<span class="atk-error-title">Artalk Error</span>') {
|
|
146
|
-
let elem = parentElem.querySelector<HTMLElement>('.atk-error-layer')
|
|
147
|
-
if (html === null) {
|
|
148
|
-
if (elem !== null) elem.remove()
|
|
149
|
-
return
|
|
150
|
-
}
|
|
151
|
-
if (!elem) {
|
|
152
|
-
elem = Utils.createElement(
|
|
153
|
-
`<div class="atk-error-layer">${title}<span class="atk-error-text"></span></div>`
|
|
154
|
-
)
|
|
155
|
-
parentElem.appendChild(elem)
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const errorTextEl = elem.querySelector<HTMLElement>('.atk-error-text')!
|
|
159
|
-
errorTextEl.innerHTML = ''
|
|
160
|
-
if (html === null) return
|
|
161
|
-
|
|
162
|
-
if (html instanceof HTMLElement) {
|
|
163
|
-
errorTextEl.appendChild(html)
|
|
164
|
-
} else {
|
|
165
|
-
errorTextEl.innerText = html
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
export function getScrollBarWidth() {
|
|
170
|
-
const inner = document.createElement('p')
|
|
171
|
-
inner.style.width = '100%'
|
|
172
|
-
inner.style.height = '200px'
|
|
173
|
-
|
|
174
|
-
const outer = document.createElement('div')
|
|
175
|
-
outer.style.position = 'absolute'
|
|
176
|
-
outer.style.top = '0px'
|
|
177
|
-
outer.style.left = '0px'
|
|
178
|
-
outer.style.visibility = 'hidden'
|
|
179
|
-
outer.style.width = '200px'
|
|
180
|
-
outer.style.height = '150px'
|
|
181
|
-
outer.style.overflow = 'hidden'
|
|
182
|
-
outer.appendChild(inner)
|
|
183
|
-
|
|
184
|
-
document.body.appendChild(outer)
|
|
185
|
-
const w1 = inner.offsetWidth
|
|
186
|
-
outer.style.overflow = 'scroll'
|
|
187
|
-
let w2 = inner.offsetWidth
|
|
188
|
-
if (w1 === w2) w2 = outer.clientWidth
|
|
189
|
-
|
|
190
|
-
document.body.removeChild(outer)
|
|
191
|
-
|
|
192
|
-
return (w1 - w2)
|
|
193
|
-
}
|
package/src/lib/user.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import type { LocalUser } from '~/types'
|
|
2
|
-
|
|
3
|
-
const LOCAL_USER_KEY = 'ArtalkUser'
|
|
4
|
-
|
|
5
|
-
class User {
|
|
6
|
-
data: LocalUser
|
|
7
|
-
onUserChanged: ((user: LocalUser) => void) | null = null
|
|
8
|
-
|
|
9
|
-
constructor() {
|
|
10
|
-
// 从 localStorage 导入
|
|
11
|
-
const localUser = JSON.parse(window.localStorage.getItem(LOCAL_USER_KEY) || '{}')
|
|
12
|
-
this.data = {
|
|
13
|
-
nick: localUser.nick || '',
|
|
14
|
-
email: localUser.email || '',
|
|
15
|
-
link: localUser.link || '',
|
|
16
|
-
token: localUser.token || '',
|
|
17
|
-
isAdmin: localUser.isAdmin || false
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/** 保存用户到 localStorage 中 */
|
|
22
|
-
update(obj: Partial<LocalUser> = {}) {
|
|
23
|
-
Object.entries(obj).forEach(([key, value]) => {
|
|
24
|
-
this.data[key] = value
|
|
25
|
-
})
|
|
26
|
-
|
|
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
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Logout
|
|
37
|
-
*
|
|
38
|
-
* @description Logout will clear login status, but not clear user data (nick, email, link)
|
|
39
|
-
*/
|
|
40
|
-
logout() {
|
|
41
|
-
this.update({
|
|
42
|
-
token: '',
|
|
43
|
-
isAdmin: false
|
|
44
|
-
})
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/** 是否已填写基本用户信息 */
|
|
48
|
-
checkHasBasicUserInfo() {
|
|
49
|
-
return !!this.data.nick && !!this.data.email
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const UserInstance = new User()
|
|
54
|
-
|
|
55
|
-
export default UserInstance
|
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
|
-
}
|