artalk 2.6.3 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Artalk.css +1 -1
- package/dist/Artalk.es.js +7673 -7330
- package/dist/Artalk.iife.js +1 -1
- package/dist/Artalk.js +1 -1
- package/dist/ArtalkLite.css +1 -1
- package/dist/ArtalkLite.es.js +5304 -4961
- package/dist/ArtalkLite.iife.js +1 -1
- package/dist/ArtalkLite.js +1 -1
- package/dist/src/api/{api-base.d.ts → _base.d.ts} +3 -7
- package/dist/src/api/_options.d.ts +19 -0
- package/dist/src/api/{request.d.ts → _request.d.ts} +3 -3
- package/dist/src/api/admin.d.ts +1 -1
- package/dist/src/api/captcha.d.ts +1 -1
- package/dist/src/api/comment.d.ts +2 -2
- package/dist/src/api/index.d.ts +2 -4
- package/dist/src/api/page.d.ts +3 -3
- package/dist/src/api/site.d.ts +2 -2
- package/dist/src/api/system.d.ts +13 -3
- package/dist/src/api/upload.d.ts +1 -1
- package/dist/src/api/user.d.ts +2 -2
- package/dist/src/artalk.d.ts +14 -37
- package/dist/src/comment/actions.d.ts +1 -1
- package/dist/src/comment/comment.d.ts +3 -4
- package/dist/src/comment/render.d.ts +21 -3
- package/dist/src/comment/renders/actions.d.ts +2 -2
- package/dist/src/comment/renders/avatar.d.ts +2 -2
- package/dist/src/comment/renders/content.d.ts +2 -2
- package/dist/src/comment/renders/header.d.ts +2 -2
- package/dist/src/comment/renders/index.d.ts +2 -2
- package/dist/src/comment/renders/pending.d.ts +2 -2
- package/dist/src/comment/renders/reply-at.d.ts +2 -2
- package/dist/src/comment/renders/reply-to.d.ts +2 -2
- package/dist/src/components/action-btn.d.ts +3 -5
- package/dist/src/{lib → components}/checker/admin.d.ts +1 -1
- package/dist/src/{lib → components}/checker/captcha-renders.d.ts +1 -1
- package/dist/src/{lib → components}/checker/captcha.d.ts +1 -1
- package/dist/src/{lib → components}/checker/index.d.ts +10 -7
- package/dist/src/components/dark-mode.d.ts +2 -0
- package/dist/src/components/dialog.d.ts +1 -3
- package/dist/src/components/error-dialog.d.ts +8 -0
- package/dist/src/components/pagination.d.ts +4 -3
- package/dist/src/components/read-more-btn.d.ts +3 -3
- package/dist/src/config.d.ts +24 -8
- package/dist/src/context.d.ts +16 -35
- package/dist/src/data.d.ts +27 -0
- package/dist/src/defaults.d.ts +1 -1
- package/dist/src/editor/editor.d.ts +14 -59
- package/dist/src/editor/state.d.ts +20 -0
- package/dist/src/i18n/zh-CN.d.ts +1 -1
- package/dist/src/layer/index.d.ts +4 -3
- package/dist/src/layer/layer-manager.d.ts +9 -0
- package/dist/src/layer/layer.d.ts +8 -28
- package/dist/src/layer/scrollbar-helper.d.ts +5 -0
- package/dist/src/layer/sidebar-layer.d.ts +6 -6
- package/dist/src/layer/wrap.d.ts +13 -0
- package/dist/src/lib/component.d.ts +3 -8
- package/dist/src/lib/event-manager.d.ts +28 -0
- package/dist/src/lib/highlight.d.ts +1 -0
- package/dist/src/lib/marked-renderer.d.ts +4 -0
- package/dist/src/lib/marked.d.ts +4 -2
- package/dist/src/lib/sanitizer.d.ts +1 -0
- package/dist/src/lib/ui.d.ts +3 -2
- package/dist/src/lib/user.d.ts +8 -5
- package/dist/src/lib/utils.d.ts +11 -5
- package/dist/src/list/comment.d.ts +3 -0
- package/dist/src/list/layout.d.ts +23 -0
- package/dist/src/list/list.d.ts +13 -25
- package/dist/src/list/{list-nest.d.ts → nest.d.ts} +1 -1
- package/dist/src/list/page.d.ts +2 -0
- package/dist/src/list/paginator/index.d.ts +22 -7
- package/dist/src/list/paginator/read-more.d.ts +18 -0
- package/dist/src/list/paginator/up-down.d.ts +14 -0
- package/dist/src/main.d.ts +2 -0
- package/dist/src/plugins/admin-only-elem.d.ts +2 -0
- package/dist/src/plugins/conf-remoter.d.ts +2 -0
- package/dist/src/plugins/dark-mode.d.ts +2 -0
- package/dist/src/plugins/editor/_kit.d.ts +27 -0
- package/dist/src/plugins/editor/_plug.d.ts +29 -0
- package/dist/src/plugins/editor/_sample.d.ts +5 -0
- package/dist/src/plugins/editor/closable.d.ts +7 -0
- package/dist/src/{editor/plugs/emoticons-plug.d.ts → plugins/editor/emoticons.d.ts} +10 -16
- package/dist/src/plugins/editor/header-event.d.ts +6 -0
- package/dist/src/plugins/editor/header-link.d.ts +6 -0
- package/dist/src/plugins/editor/header-user.d.ts +16 -0
- package/dist/src/plugins/editor/index.d.ts +6 -0
- package/dist/src/plugins/editor/local-storage.d.ts +6 -0
- package/dist/src/plugins/editor/mover.d.ts +6 -0
- package/dist/src/plugins/editor/preview.d.ts +7 -0
- package/dist/src/plugins/editor/state-edit.d.ts +11 -0
- package/dist/src/plugins/editor/state-reply.d.ts +8 -0
- package/dist/src/plugins/editor/submit-add.d.ts +18 -0
- package/dist/src/plugins/editor/submit-btn.d.ts +5 -0
- package/dist/src/plugins/editor/submit.d.ts +17 -0
- package/dist/src/plugins/editor/textarea.d.ts +8 -0
- package/dist/src/plugins/editor/upload.d.ts +9 -0
- package/dist/src/plugins/editor-kit.d.ts +42 -0
- package/dist/src/plugins/index.d.ts +2 -0
- package/dist/src/plugins/list/copyright.d.ts +2 -0
- package/dist/src/plugins/list/count.d.ts +2 -0
- package/dist/src/plugins/list/dropdown.d.ts +2 -0
- package/dist/src/plugins/list/error-dialog.d.ts +2 -0
- package/dist/src/plugins/list/fetch.d.ts +2 -0
- package/dist/src/plugins/list/goto-first.d.ts +3 -0
- package/dist/src/plugins/list/goto.d.ts +2 -0
- package/dist/src/plugins/list/index.d.ts +3 -0
- package/dist/src/plugins/list/loading.d.ts +2 -0
- package/dist/src/plugins/list/no-comment.d.ts +2 -0
- package/dist/src/plugins/list/reach-bottom.d.ts +2 -0
- package/dist/src/plugins/list/sidebar-btn.d.ts +2 -0
- package/dist/src/plugins/list/time-ticking.d.ts +3 -0
- package/dist/src/plugins/list/unread-badge.d.ts +2 -0
- package/dist/src/plugins/list/unread.d.ts +2 -0
- package/dist/src/plugins/list/with-editor.d.ts +2 -0
- package/dist/src/plugins/markdown.d.ts +2 -0
- package/dist/src/plugins/stat.d.ts +13 -0
- package/dist/src/plugins/version-check.d.ts +2 -0
- package/dist/src/service.d.ts +19 -23
- package/dist/tests/e2e/comment-add.spec.d.ts +1 -0
- package/dist/types/config.d.ts +110 -0
- package/dist/types/context.d.ts +56 -136
- package/dist/types/data.d.ts +194 -0
- package/dist/types/editor.d.ts +76 -0
- package/dist/types/event.d.ts +36 -25
- package/dist/types/index.d.ts +7 -0
- package/dist/types/plugin.d.ts +2 -0
- package/dist/types/sidebar.d.ts +3 -0
- package/package.json +5 -2
- package/src/api/_base.ts +18 -0
- package/src/api/_options.ts +25 -0
- package/src/api/{request.ts → _request.ts} +19 -18
- package/src/api/admin.ts +1 -1
- package/src/api/captcha.ts +1 -1
- package/src/api/comment.ts +4 -4
- package/src/api/index.ts +3 -12
- package/src/api/page.ts +5 -5
- package/src/api/site.ts +2 -2
- package/src/api/system.ts +19 -7
- package/src/api/upload.ts +3 -3
- package/src/api/user.ts +3 -3
- package/src/artalk.ts +49 -97
- package/src/comment/actions.ts +1 -4
- package/src/comment/comment.ts +9 -6
- package/src/comment/render.ts +27 -5
- package/src/comment/renders/actions.ts +45 -45
- package/src/comment/renders/avatar.ts +7 -7
- package/src/comment/renders/content.ts +18 -18
- package/src/comment/renders/header.ts +13 -13
- package/src/comment/renders/index.ts +3 -3
- package/src/comment/renders/pending.ts +5 -5
- package/src/comment/renders/reply-at.ts +8 -8
- package/src/comment/renders/reply-to.ts +13 -13
- package/src/components/action-btn.ts +11 -13
- package/src/{lib → components}/checker/admin.ts +6 -6
- package/src/{lib → components}/checker/captcha-renders.ts +7 -6
- package/src/{lib → components}/checker/captcha.ts +1 -1
- package/src/{lib → components}/checker/index.ts +21 -26
- package/src/components/dark-mode.ts +30 -0
- package/src/components/dialog.ts +4 -9
- package/src/components/error-dialog.ts +35 -0
- package/src/components/pagination.ts +10 -6
- package/src/components/read-more-btn.ts +7 -7
- package/src/config.ts +60 -6
- package/src/context.ts +39 -144
- package/src/data.ts +110 -0
- package/src/defaults.ts +1 -1
- package/src/editor/editor.ts +41 -146
- package/src/editor/state.ts +52 -0
- package/src/i18n/zh-CN.ts +1 -1
- package/src/layer/index.ts +4 -4
- package/src/layer/layer-manager.ts +32 -0
- package/src/layer/layer.ts +20 -144
- package/src/layer/scrollbar-helper.ts +24 -0
- package/src/layer/sidebar-layer.ts +74 -64
- package/src/layer/wrap.ts +77 -0
- package/src/lib/component.ts +4 -11
- package/src/lib/detect.ts +1 -1
- package/src/lib/event-manager.ts +47 -0
- package/src/lib/highlight.ts +5 -0
- package/src/lib/marked-renderer.ts +32 -0
- package/src/lib/marked.ts +43 -104
- package/src/lib/sanitizer.ts +57 -0
- package/src/lib/ui.ts +22 -15
- package/src/lib/user.ts +21 -16
- package/src/lib/utils.ts +29 -18
- package/src/list/comment.ts +20 -0
- package/src/list/layout.ts +120 -0
- package/src/list/list.html +3 -1
- package/src/list/list.ts +56 -200
- package/src/list/{list-nest.ts → nest.ts} +1 -1
- package/src/list/page.ts +97 -0
- package/src/list/paginator/index.ts +20 -8
- package/src/list/paginator/read-more.ts +67 -0
- package/src/list/paginator/up-down.ts +51 -0
- package/src/main.ts +2 -0
- package/src/plugins/admin-only-elem.ts +40 -0
- package/src/plugins/conf-remoter.ts +54 -0
- package/src/plugins/dark-mode.ts +12 -0
- package/src/plugins/editor/_kit.ts +57 -0
- package/src/plugins/editor/_plug.ts +60 -0
- package/src/plugins/editor/_sample.ts +8 -0
- package/src/plugins/editor/closable.ts +44 -0
- package/src/{editor/plugs/emoticons-plug.ts → plugins/editor/emoticons.ts} +51 -52
- package/src/plugins/editor/header-event.ts +35 -0
- package/src/plugins/editor/header-link.ts +31 -0
- package/src/plugins/editor/header-user.ts +98 -0
- package/src/plugins/editor/index.ts +40 -0
- package/src/plugins/editor/local-storage.ts +36 -0
- package/src/plugins/editor/mover.ts +29 -0
- package/src/plugins/editor/preview.ts +41 -0
- package/src/plugins/editor/state-edit.ts +116 -0
- package/src/plugins/editor/state-reply.ts +91 -0
- package/src/plugins/editor/submit-add.ts +33 -0
- package/src/plugins/editor/submit-btn.ts +25 -0
- package/src/plugins/editor/submit.ts +73 -0
- package/src/plugins/editor/textarea.ts +55 -0
- package/src/plugins/editor/upload.ts +145 -0
- package/src/plugins/editor-kit.ts +161 -0
- package/src/plugins/index.ts +16 -0
- package/src/plugins/list/copyright.ts +16 -0
- package/src/plugins/list/count.ts +31 -0
- package/src/plugins/list/dropdown.ts +95 -0
- package/src/plugins/list/error-dialog.ts +22 -0
- package/src/plugins/list/fetch.ts +64 -0
- package/src/plugins/list/goto-first.ts +23 -0
- package/src/plugins/list/goto.ts +72 -0
- package/src/plugins/list/index.ts +25 -0
- package/src/plugins/list/loading.ts +17 -0
- package/src/plugins/list/no-comment.ts +25 -0
- package/src/plugins/list/reach-bottom.ts +46 -0
- package/src/plugins/list/sidebar-btn.ts +39 -0
- package/src/plugins/list/time-ticking.ts +22 -0
- package/src/plugins/list/unread-badge.ts +26 -0
- package/src/plugins/list/unread.ts +38 -0
- package/src/plugins/list/with-editor.ts +59 -0
- package/src/plugins/markdown.ts +10 -0
- package/src/plugins/stat.ts +79 -0
- package/src/plugins/version-check.ts +37 -0
- package/src/service.ts +34 -61
- package/src/style/comment.scss +5 -7
- package/src/style/editor.scss +5 -0
- package/src/style/list.scss +2 -7
- package/types/{artalk-config.d.ts → config.ts} +16 -2
- package/types/context.ts +84 -0
- package/{dist/types/artalk-data.d.ts → types/data.ts} +41 -0
- package/types/editor.ts +95 -0
- package/types/event.ts +40 -0
- package/types/index.ts +7 -0
- package/types/plugin.ts +3 -0
- package/types/sidebar.ts +3 -0
- package/dist/src/comment/render-ctx.d.ts +0 -23
- package/dist/src/editor/edit-mode.d.ts +0 -9
- package/dist/src/editor/funcs.d.ts +0 -2
- package/dist/src/editor/index.d.ts +0 -2
- package/dist/src/editor/mover.d.ts +0 -8
- package/dist/src/editor/plug-manager.d.ts +0 -17
- package/dist/src/editor/plugs/editor-plug.d.ts +0 -38
- package/dist/src/editor/plugs/header-input-plug.d.ts +0 -13
- package/dist/src/editor/plugs/preview-plug.d.ts +0 -10
- package/dist/src/editor/plugs/upload-plug.d.ts +0 -10
- package/dist/src/editor/reply.d.ts +0 -9
- package/dist/src/editor/submit.d.ts +0 -16
- package/dist/src/lib/dark-mode.d.ts +0 -4
- package/dist/src/lib/stat.d.ts +0 -15
- package/dist/src/list/index.d.ts +0 -2
- package/dist/src/list/list-lite.d.ts +0 -77
- package/dist/src/list/list-ui.d.ts +0 -16
- package/dist/src/list/paginator/adaptors/index.d.ts +0 -12
- package/dist/src/list/paginator/adaptors/pagination.d.ts +0 -9
- package/dist/src/list/paginator/adaptors/read-more.d.ts +0 -10
- package/dist/src/list/paginator/holder.d.ts +0 -16
- package/dist/types/artalk-config.d.ts +0 -139
- package/dist/types/plug.d.ts +0 -4
- package/src/api/api-base.ts +0 -27
- package/src/comment/render-ctx.ts +0 -30
- package/src/editor/edit-mode.ts +0 -103
- package/src/editor/funcs.ts +0 -97
- package/src/editor/index.ts +0 -3
- package/src/editor/mover.ts +0 -43
- package/src/editor/plug-manager.ts +0 -172
- package/src/editor/plugs/editor-plug.ts +0 -69
- package/src/editor/plugs/header-input-plug.ts +0 -79
- package/src/editor/plugs/preview-plug.ts +0 -44
- package/src/editor/plugs/upload-plug.ts +0 -122
- package/src/editor/reply.ts +0 -62
- package/src/editor/submit.ts +0 -99
- package/src/lib/dark-mode.ts +0 -48
- package/src/lib/stat.ts +0 -71
- package/src/list/index.ts +0 -3
- package/src/list/list-lite.ts +0 -424
- package/src/list/list-ui.ts +0 -137
- package/src/list/paginator/adaptors/index.ts +0 -21
- package/src/list/paginator/adaptors/pagination.ts +0 -42
- package/src/list/paginator/adaptors/read-more.ts +0 -56
- package/src/list/paginator/holder.ts +0 -56
- package/types/artalk-data.d.ts +0 -230
- package/types/context.d.ts +0 -142
- package/types/event.d.ts +0 -28
- package/types/plug.d.ts +0 -4
- /package/src/{editor/plugs/emoticons-plug.scss → plugins/editor/emoticons.scss} +0 -0
- /package/src/{editor/plugs/preview-plug.scss → plugins/editor/preview.scss} +0 -0
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import type { ContextApi, SidebarShowPayload } from '~/types'
|
|
2
2
|
import Component from '@/lib/component'
|
|
3
3
|
import * as Utils from '@/lib/utils'
|
|
4
4
|
import * as Ui from '@/lib/ui'
|
|
5
5
|
import SidebarHTML from './sidebar-layer.html?raw'
|
|
6
6
|
import User from '../lib/user'
|
|
7
|
-
import Layer from './layer'
|
|
8
|
-
|
|
9
|
-
export interface SidebarShowPayload {
|
|
10
|
-
view?: 'comments'|'sites'|'pages'|'transfer'
|
|
11
|
-
}
|
|
7
|
+
import type { Layer } from './layer'
|
|
12
8
|
|
|
13
9
|
export default class SidebarLayer extends Component {
|
|
14
10
|
public layer?: Layer
|
|
@@ -17,7 +13,7 @@ export default class SidebarLayer extends Component {
|
|
|
17
13
|
public $iframeWrap: HTMLElement
|
|
18
14
|
public $iframe?: HTMLIFrameElement
|
|
19
15
|
|
|
20
|
-
constructor(ctx:
|
|
16
|
+
constructor(ctx: ContextApi) {
|
|
21
17
|
super(ctx)
|
|
22
18
|
|
|
23
19
|
this.$el = Utils.createElement(SidebarHTML)
|
|
@@ -40,72 +36,31 @@ export default class SidebarLayer extends Component {
|
|
|
40
36
|
this.$el.style.transform = '' // 动画清除,防止二次打开失效
|
|
41
37
|
|
|
42
38
|
// 获取 Layer
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
this.layer.afterHide = () => {
|
|
46
|
-
// 防止评论框被吞
|
|
47
|
-
if (this.ctx.conf.editorTravel === true) {
|
|
48
|
-
this.ctx.editorTravelBack()
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
this.layer.show()
|
|
53
|
-
|
|
54
|
-
// viewWrap 滚动条归位
|
|
55
|
-
// this.$viewWrap.scrollTo(0, 0)
|
|
39
|
+
this.initLayer()
|
|
40
|
+
this.layer!.show()
|
|
56
41
|
|
|
57
42
|
// 管理员身份验证 (若身份失效,弹出验证窗口)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
this.layer?.hide()
|
|
62
|
-
this.firstShow = true
|
|
63
|
-
|
|
64
|
-
this.ctx.checkAdmin({
|
|
65
|
-
onSuccess: () => {
|
|
66
|
-
setTimeout(() => {
|
|
67
|
-
this.show(conf)
|
|
68
|
-
}, 500)
|
|
69
|
-
},
|
|
70
|
-
onCancel: () => {}
|
|
71
|
-
})
|
|
72
|
-
}
|
|
73
|
-
})()
|
|
43
|
+
this.authCheck({
|
|
44
|
+
onSuccess: () => this.show(conf) // retry show after auth check
|
|
45
|
+
})
|
|
74
46
|
|
|
75
47
|
// 第一次加载
|
|
76
48
|
if (this.firstShow) {
|
|
77
49
|
this.$iframeWrap.innerHTML = ''
|
|
78
|
-
this.$iframe =
|
|
79
|
-
|
|
80
|
-
// 准备 Iframe 参数
|
|
81
|
-
const baseURL = (import.meta.env.DEV) ? 'http://localhost:23367/'
|
|
82
|
-
: Utils.getURLBasedOnApi(this.ctx, '/sidebar/')
|
|
83
|
-
|
|
84
|
-
const query: any = {
|
|
85
|
-
pageKey: this.conf.pageKey,
|
|
86
|
-
site: this.conf.site || '',
|
|
87
|
-
user: JSON.stringify(User.data),
|
|
88
|
-
time: +new Date()
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (conf.view) query.view = conf.view
|
|
92
|
-
if (this.conf.darkMode) query.darkMode = '1'
|
|
93
|
-
if (typeof this.conf.locale === 'string') query.locale = this.conf.locale
|
|
94
|
-
|
|
95
|
-
const urlParams = new URLSearchParams(query);
|
|
96
|
-
this.iframeLoad(`${baseURL}?${urlParams.toString()}`)
|
|
97
|
-
|
|
50
|
+
this.$iframe = this.createIframe(conf.view)
|
|
98
51
|
this.$iframeWrap.append(this.$iframe)
|
|
99
52
|
this.firstShow = false
|
|
100
53
|
} else {
|
|
54
|
+
const $iframe = this.$iframe!
|
|
55
|
+
|
|
101
56
|
// 夜间模式
|
|
102
|
-
const isIframeSrcDarkMode =
|
|
57
|
+
const isIframeSrcDarkMode = $iframe.src.includes('darkMode=1')
|
|
103
58
|
|
|
104
59
|
if (this.conf.darkMode && !isIframeSrcDarkMode)
|
|
105
|
-
this.iframeLoad(`${this.$iframe!.src}&darkMode=1`)
|
|
60
|
+
this.iframeLoad($iframe, `${this.$iframe!.src}&darkMode=1`)
|
|
106
61
|
|
|
107
62
|
if (!this.conf.darkMode && isIframeSrcDarkMode)
|
|
108
|
-
this.iframeLoad(this.$iframe!.src.replace('&darkMode=1', ''))
|
|
63
|
+
this.iframeLoad($iframe, this.$iframe!.src.replace('&darkMode=1', ''))
|
|
109
64
|
}
|
|
110
65
|
|
|
111
66
|
// 执行滑动显示动画
|
|
@@ -115,7 +70,7 @@ export default class SidebarLayer extends Component {
|
|
|
115
70
|
|
|
116
71
|
// 清空 unread
|
|
117
72
|
setTimeout(() => {
|
|
118
|
-
this.ctx.
|
|
73
|
+
this.ctx.getData().updateUnreads([])
|
|
119
74
|
}, 0)
|
|
120
75
|
|
|
121
76
|
this.ctx.trigger('sidebar-show')
|
|
@@ -131,14 +86,69 @@ export default class SidebarLayer extends Component {
|
|
|
131
86
|
this.ctx.trigger('sidebar-hide')
|
|
132
87
|
}
|
|
133
88
|
|
|
134
|
-
|
|
135
|
-
|
|
89
|
+
// --------------------------------------------------
|
|
90
|
+
|
|
91
|
+
private async authCheck(opts: { onSuccess: () => void }) {
|
|
92
|
+
const resp = await this.ctx.getApi().user.loginStatus()
|
|
93
|
+
if (resp.is_admin && !resp.is_login) {
|
|
94
|
+
this.firstShow = true
|
|
95
|
+
|
|
96
|
+
this.ctx.checkAdmin({
|
|
97
|
+
onSuccess: () => {
|
|
98
|
+
setTimeout(() => {
|
|
99
|
+
opts.onSuccess()
|
|
100
|
+
}, 500)
|
|
101
|
+
},
|
|
102
|
+
onCancel: () => {
|
|
103
|
+
this.hide()
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private initLayer() {
|
|
110
|
+
if (this.layer) return
|
|
111
|
+
|
|
112
|
+
this.layer = this.ctx.get('layerManager').create('sidebar', this.$el)
|
|
113
|
+
this.layer.setOnAfterHide(() => {
|
|
114
|
+
// 防止评论框被吞
|
|
115
|
+
this.ctx.editorResetState()
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private createIframe(view?: string) {
|
|
120
|
+
const $iframe = Utils.createElement<HTMLIFrameElement>('<iframe></iframe>')
|
|
121
|
+
|
|
122
|
+
// 准备 Iframe 参数
|
|
123
|
+
const baseURL = (import.meta.env.DEV) ? 'http://localhost:23367/'
|
|
124
|
+
: Utils.getURLBasedOnApi({
|
|
125
|
+
base: this.ctx.conf.server,
|
|
126
|
+
path: '/sidebar/',
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
const query: any = {
|
|
130
|
+
pageKey: this.conf.pageKey,
|
|
131
|
+
site: this.conf.site || '',
|
|
132
|
+
user: JSON.stringify(User.data),
|
|
133
|
+
time: +new Date()
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (view) query.view = view
|
|
137
|
+
if (this.conf.darkMode) query.darkMode = '1'
|
|
138
|
+
if (typeof this.conf.locale === 'string') query.locale = this.conf.locale
|
|
139
|
+
|
|
140
|
+
const urlParams = new URLSearchParams(query);
|
|
141
|
+
this.iframeLoad($iframe, `${baseURL}?${urlParams.toString()}`)
|
|
142
|
+
|
|
143
|
+
return $iframe
|
|
144
|
+
}
|
|
136
145
|
|
|
137
|
-
|
|
146
|
+
private iframeLoad($iframe: HTMLIFrameElement, src: string) {
|
|
147
|
+
$iframe.src = src
|
|
138
148
|
|
|
139
149
|
// 加载动画
|
|
140
150
|
Ui.showLoading(this.$iframeWrap)
|
|
141
|
-
|
|
151
|
+
$iframe.onload = () => {
|
|
142
152
|
Ui.hideLoading(this.$iframeWrap)
|
|
143
153
|
}
|
|
144
154
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import * as Utils from '@/lib/utils'
|
|
2
|
+
import { getScrollbarHelper } from './scrollbar-helper'
|
|
3
|
+
|
|
4
|
+
export class LayerWrap {
|
|
5
|
+
private $wrap: HTMLElement
|
|
6
|
+
private $mask: HTMLElement
|
|
7
|
+
private allowMaskClose: boolean = true
|
|
8
|
+
private items: HTMLElement[] = []
|
|
9
|
+
|
|
10
|
+
constructor() {
|
|
11
|
+
this.$wrap = Utils.createElement(
|
|
12
|
+
`<div class="atk-layer-wrap" style="display: none;"><div class="atk-layer-mask"></div></div>`
|
|
13
|
+
)
|
|
14
|
+
this.$mask = this.$wrap.querySelector<HTMLElement>('.atk-layer-mask')!
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
createItem(name: string, el?: HTMLElement) {
|
|
18
|
+
if (!el) {
|
|
19
|
+
el = document.createElement('div')
|
|
20
|
+
el.classList.add('atk-layer-item')
|
|
21
|
+
el.setAttribute('data-layer-name', name)
|
|
22
|
+
el.style.display = 'none'
|
|
23
|
+
}
|
|
24
|
+
this.$wrap.appendChild(el)
|
|
25
|
+
this.items.push(el)
|
|
26
|
+
return el
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
getWrap() {
|
|
30
|
+
return this.$wrap
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
getMask() {
|
|
34
|
+
return this.$mask
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
setMaskClose(enabled: boolean) {
|
|
38
|
+
this.allowMaskClose = enabled
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
show() {
|
|
42
|
+
this.$wrap.style.display = 'block'
|
|
43
|
+
this.$mask.style.display = 'block'
|
|
44
|
+
this.$mask.classList.add('atk-fade-in')
|
|
45
|
+
this.$mask.onclick = () => {
|
|
46
|
+
if (this.allowMaskClose) this.hide()
|
|
47
|
+
}
|
|
48
|
+
getScrollbarHelper().lock()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
hide(callback?: () => void) {
|
|
52
|
+
// if wrap contains more than one item, do not hide entire wrap
|
|
53
|
+
if (this.items.filter(e => e.isConnected && e.style.display !== 'none').length > 1) {
|
|
54
|
+
callback && callback()
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const onAfterHide = () => {
|
|
59
|
+
this.$wrap.style.display = 'none'
|
|
60
|
+
this.$wrap.classList.remove('atk-fade-out')
|
|
61
|
+
|
|
62
|
+
callback && callback()
|
|
63
|
+
|
|
64
|
+
getScrollbarHelper().unlock()
|
|
65
|
+
|
|
66
|
+
this.$wrap.onanimationend = null
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// perform animation
|
|
70
|
+
this.$wrap.classList.add('atk-fade-out')
|
|
71
|
+
if (window.getComputedStyle(this.$wrap)['animation-name'] !== 'none') {
|
|
72
|
+
this.$wrap.onanimationend = () => onAfterHide()
|
|
73
|
+
} else {
|
|
74
|
+
onAfterHide()
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
package/src/lib/component.ts
CHANGED
|
@@ -1,19 +1,12 @@
|
|
|
1
|
-
import ArtalkConfig from '~/types
|
|
2
|
-
import Context from '~/types/context'
|
|
3
|
-
import { I18n } from '../i18n'
|
|
1
|
+
import type { ArtalkConfig, ContextApi } from '~/types'
|
|
4
2
|
|
|
5
3
|
export default abstract class Component {
|
|
6
4
|
public $el!: HTMLElement
|
|
7
|
-
|
|
8
|
-
public ctx: Context
|
|
9
5
|
public readonly conf: ArtalkConfig
|
|
10
6
|
|
|
11
|
-
public constructor(
|
|
12
|
-
|
|
7
|
+
public constructor(
|
|
8
|
+
public ctx: ContextApi
|
|
9
|
+
) {
|
|
13
10
|
this.conf = ctx.conf
|
|
14
11
|
}
|
|
15
|
-
|
|
16
|
-
public $t(key: keyof I18n, args: {[key: string]: string} = {}): string {
|
|
17
|
-
return this.ctx.$t(key, args)
|
|
18
|
-
}
|
|
19
12
|
}
|
package/src/lib/detect.ts
CHANGED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export type EventHandler<T> = (payload: T) => void
|
|
2
|
+
export interface Event<PayloadMap, K extends keyof PayloadMap = keyof PayloadMap> extends EventOptions {
|
|
3
|
+
name: K
|
|
4
|
+
handler: EventHandler<PayloadMap[K]>
|
|
5
|
+
}
|
|
6
|
+
export interface EventOptions {
|
|
7
|
+
once?: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface EventManagerFuncs<PayloadMap> {
|
|
11
|
+
on<K extends keyof PayloadMap>(name: K, handler: EventHandler<PayloadMap[K]>, opts?: EventOptions): void
|
|
12
|
+
off<K extends keyof PayloadMap>(name: K, handler: EventHandler<PayloadMap[K]>): void
|
|
13
|
+
trigger<K extends keyof PayloadMap>(name: K, payload?: PayloadMap[K]): void
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default class EventManager<PayloadMap> implements EventManagerFuncs<PayloadMap> {
|
|
17
|
+
private events: Event<PayloadMap>[] = []
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Add an event listener for a specific event name
|
|
21
|
+
*/
|
|
22
|
+
public on<K extends keyof PayloadMap>(name: K, handler: EventHandler<PayloadMap[K]>, opts: EventOptions = {}) {
|
|
23
|
+
this.events.push({ name, handler: handler as EventHandler<PayloadMap[keyof PayloadMap]>, ...opts })
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Remove an event listener for a specific event name and handler
|
|
28
|
+
*/
|
|
29
|
+
public off<K extends keyof PayloadMap>(name: K, handler: EventHandler<PayloadMap[K]>) {
|
|
30
|
+
if (!handler) return // not allow remove all events with same name
|
|
31
|
+
this.events = this.events.filter((evt) =>
|
|
32
|
+
!(evt.name === name && evt.handler === handler))
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Trigger an event with an optional payload
|
|
37
|
+
*/
|
|
38
|
+
public trigger<K extends keyof PayloadMap>(name: K, payload?: PayloadMap[K]) {
|
|
39
|
+
this.events
|
|
40
|
+
.slice(0) // make a copy, in case listeners are removed while iterating
|
|
41
|
+
.filter((evt) => evt.name === name && typeof evt.handler === 'function')
|
|
42
|
+
.forEach((evt) => {
|
|
43
|
+
if (evt.once) this.off(name, evt.handler)
|
|
44
|
+
evt.handler(payload!)
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { marked as libMarked } from 'marked'
|
|
2
|
+
import { renderCode } from './highlight'
|
|
3
|
+
|
|
4
|
+
export function getRenderer() {
|
|
5
|
+
const renderer = new libMarked.Renderer()
|
|
6
|
+
renderer.link = markedLinkRenderer(renderer, renderer.link)
|
|
7
|
+
renderer.code = markedCodeRenderer()
|
|
8
|
+
return renderer
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const markedLinkRenderer = (renderer: any, orgLinkRenderer: Function) => (href: string, title: string, text: string): string => {
|
|
12
|
+
const localLink = href?.startsWith(`${window.location.protocol}//${window.location.hostname}`);
|
|
13
|
+
const html = orgLinkRenderer.call(renderer, href, title, text);
|
|
14
|
+
return html.replace(/^<a /, `<a target="_blank" ${!localLink ? `rel="noreferrer noopener nofollow"` : ''} `);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const markedCodeRenderer = () => (block: string, lang: string | undefined): string => {
|
|
18
|
+
// Colorize the block only if the language is known to highlight.js
|
|
19
|
+
const realLang = (!lang ? 'plaintext' : lang)
|
|
20
|
+
let colorized = block
|
|
21
|
+
if ((window as any).hljs) {
|
|
22
|
+
if (realLang && (window as any).hljs.getLanguage(realLang)) {
|
|
23
|
+
colorized = (window as any).hljs.highlight(realLang, block).value
|
|
24
|
+
}
|
|
25
|
+
} else {
|
|
26
|
+
colorized = renderCode(block)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return `<pre rel="${realLang}">\n`
|
|
30
|
+
+ `<code class="hljs language-${realLang}">${colorized.replace(/&/g, '&')}</code>\n`
|
|
31
|
+
+ `</pre>`
|
|
32
|
+
}
|
package/src/lib/marked.ts
CHANGED
|
@@ -1,133 +1,72 @@
|
|
|
1
|
-
import { marked as libMarked } from 'marked'
|
|
2
|
-
import insane from 'insane'
|
|
3
|
-
import hanabi from 'hanabi'
|
|
4
|
-
import Context from '~/types/context'
|
|
1
|
+
import { marked as libMarked, MarkedOptions } from 'marked'
|
|
5
2
|
|
|
6
|
-
|
|
3
|
+
import { sanitize } from './sanitizer'
|
|
4
|
+
import { renderCode } from './highlight'
|
|
5
|
+
import { getRenderer } from './marked-renderer'
|
|
7
6
|
|
|
7
|
+
type Replacer = (raw: string) => string
|
|
8
8
|
export type TMarked = typeof libMarked
|
|
9
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
|
+
|
|
10
24
|
/** Get Marked instance */
|
|
11
25
|
export function getInstance() {
|
|
12
26
|
return instance
|
|
13
27
|
}
|
|
14
28
|
|
|
29
|
+
export function setReplacers(arr: Replacer[]) {
|
|
30
|
+
replacers = arr
|
|
31
|
+
}
|
|
32
|
+
|
|
15
33
|
/** 初始化 marked */
|
|
16
34
|
export function initMarked() {
|
|
17
35
|
try { if (!libMarked.name) return } catch { return }
|
|
18
36
|
|
|
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
37
|
// @see https://github.com/markedjs/marked/blob/4afb228d956a415624c4e5554bb8f25d047676fe/src/Tokenizer.js#L329
|
|
45
|
-
const nMarked = libMarked
|
|
46
38
|
libMarked.setOptions({
|
|
47
|
-
renderer,
|
|
48
|
-
|
|
49
|
-
gfm: true,
|
|
50
|
-
breaks: true,
|
|
51
|
-
smartLists: true,
|
|
52
|
-
smartypants: true,
|
|
53
|
-
xhtml: false,
|
|
54
|
-
sanitize: false,
|
|
55
|
-
silent: true,
|
|
39
|
+
renderer: getRenderer(),
|
|
40
|
+
...markedOptions
|
|
56
41
|
})
|
|
57
42
|
|
|
58
|
-
instance =
|
|
43
|
+
instance = libMarked
|
|
59
44
|
}
|
|
60
45
|
|
|
61
46
|
/** 解析 markdown */
|
|
62
|
-
export default function marked(
|
|
47
|
+
export default function marked(src: string): string {
|
|
63
48
|
let markedContent = getInstance()?.parse(src)
|
|
64
|
-
if (!markedContent) {
|
|
65
|
-
|
|
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>')
|
|
49
|
+
if (!markedContent) { // 无 Markdown 模式简单处理
|
|
50
|
+
markedContent = simpleMarked(src)
|
|
73
51
|
}
|
|
74
52
|
|
|
75
|
-
|
|
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
|
-
})
|
|
53
|
+
let dest = sanitize(markedContent)
|
|
126
54
|
|
|
127
55
|
// 内容替换器
|
|
128
|
-
|
|
129
|
-
if (typeof replacer === 'function')
|
|
56
|
+
replacers.forEach((replacer) => {
|
|
57
|
+
if (typeof replacer === 'function')
|
|
58
|
+
dest = replacer(dest)
|
|
130
59
|
})
|
|
131
60
|
|
|
132
61
|
return dest
|
|
133
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
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
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
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { NotifyLevel } from '~/types'
|
|
1
2
|
import * as Utils from './utils'
|
|
2
3
|
|
|
3
4
|
/** 显示加载 */
|
|
@@ -53,29 +54,35 @@ export function isVisible(el: HTMLElement, viewport: HTMLElement = document.docu
|
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
/** 滚动到元素中心 */
|
|
56
|
-
export function scrollIntoView(elem: HTMLElement, enableAnim: boolean = true) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
top: top > 0 ? top : 0,
|
|
65
|
-
left: 0,
|
|
66
|
-
// behavior: 'smooth',
|
|
67
|
-
})
|
|
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
|
|
68
65
|
} else {
|
|
69
|
-
|
|
70
|
-
|
|
66
|
+
const rect = elem.getBoundingClientRect()
|
|
67
|
+
const elemTop = rect.top + window.scrollY
|
|
68
|
+
top = elemTop - (window.innerHeight / 2 - rect.height / 2)
|
|
71
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)
|
|
72
79
|
}
|
|
73
80
|
|
|
74
81
|
/** 显示消息 */
|
|
75
82
|
export function showNotify(
|
|
76
83
|
wrapElem: HTMLElement,
|
|
77
84
|
msg: string,
|
|
78
|
-
type:
|
|
85
|
+
type: NotifyLevel,
|
|
79
86
|
) {
|
|
80
87
|
const colors = { s: '#57d59f', e: '#ff6f6c', w: '#ffc721', i: '#2ebcfc' }
|
|
81
88
|
const timeout = 3000 // 持续显示时间 ms
|