artalk 2.2.1 → 2.2.4
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 +178 -71
- package/dist/Artalk.iife.js +1 -1
- package/dist/Artalk.js +1 -1
- package/index.html +10 -0
- package/package.json +2 -2
- package/src/api/index.ts +11 -7
- package/src/artalk.ts +14 -4
- package/src/components/comment.ts +1 -1
- package/src/components/editor.ts +11 -4
- package/src/components/layer.ts +7 -9
- package/src/components/list-lite.ts +19 -4
- package/src/components/sidebar-layer.ts +43 -35
- package/src/lib/checker/captcha-checker.ts +1 -1
- package/src/lib/utils.ts +61 -10
- package/src/style/comment.less +1 -0
- package/src/style/main.less +63 -12
- package/types/event.d.ts +5 -1
package/index.html
CHANGED
|
@@ -29,6 +29,16 @@
|
|
|
29
29
|
body.dark-mode .to-night { background: #fff;border: 1px solid #2d3235;color: #0083ff; }
|
|
30
30
|
.to-top { right: 25px; }
|
|
31
31
|
</style>
|
|
32
|
+
<!--<link rel="stylesheet"
|
|
33
|
+
href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/styles/arta.min.css">
|
|
34
|
+
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/highlight.min.js"></script>-->
|
|
35
|
+
|
|
36
|
+
<!-- For mobile testing -->
|
|
37
|
+
<script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
|
|
38
|
+
<script>
|
|
39
|
+
// VConsole will be exported to `window.VConsole` by default.
|
|
40
|
+
var vConsole = new window.VConsole();
|
|
41
|
+
</script>
|
|
32
42
|
</head>
|
|
33
43
|
<body>
|
|
34
44
|
<div class="card billboard atk-fade-in">
|
package/package.json
CHANGED
package/src/api/index.ts
CHANGED
|
@@ -119,8 +119,11 @@ export default class Api {
|
|
|
119
119
|
|
|
120
120
|
/** 用户 · 登录状态 */
|
|
121
121
|
public async loginStatus() {
|
|
122
|
-
const data = await POST<any>(this.ctx, `${this.baseURL}/login-status
|
|
123
|
-
|
|
122
|
+
const data = await POST<any>(this.ctx, `${this.baseURL}/login-status`, {
|
|
123
|
+
name: this.ctx.user.data.nick,
|
|
124
|
+
email: this.ctx.user.data.email
|
|
125
|
+
})
|
|
126
|
+
return (data || { is_login: false, is_admin: false }) as { is_login: boolean, is_admin: boolean }
|
|
124
127
|
}
|
|
125
128
|
|
|
126
129
|
// ============================
|
|
@@ -164,13 +167,14 @@ export default class Api {
|
|
|
164
167
|
}
|
|
165
168
|
|
|
166
169
|
/** 页面 · 数据更新 */
|
|
167
|
-
public async pageFetch(id
|
|
168
|
-
const params: any = {
|
|
169
|
-
|
|
170
|
-
|
|
170
|
+
public async pageFetch(id?: number, siteName?: string, getStatus?: boolean) {
|
|
171
|
+
const params: any = {}
|
|
172
|
+
if (id) params.id = id
|
|
173
|
+
if (siteName) params.site_name = siteName
|
|
174
|
+
if (getStatus) params.get_status = getStatus
|
|
171
175
|
|
|
172
176
|
const d = await POST<any>(this.ctx, `${this.baseURL}/admin/page-fetch`, params)
|
|
173
|
-
return (d
|
|
177
|
+
return (d as any)
|
|
174
178
|
}
|
|
175
179
|
|
|
176
180
|
// ============================
|
package/src/artalk.ts
CHANGED
|
@@ -9,7 +9,7 @@ import Editor from './components/editor'
|
|
|
9
9
|
import List from './components/list'
|
|
10
10
|
import SidebarLayer from './components/sidebar-layer'
|
|
11
11
|
|
|
12
|
-
import { GetLayerWrap } from './components/layer'
|
|
12
|
+
import Layer, { GetLayerWrap } from './components/layer'
|
|
13
13
|
import { EventPayloadMap, Handler } from '~/types/event'
|
|
14
14
|
import Api from './api'
|
|
15
15
|
import * as Utils from './lib/utils'
|
|
@@ -37,6 +37,7 @@ export default class Artalk {
|
|
|
37
37
|
|
|
38
38
|
// 默认 pageKey
|
|
39
39
|
if (!this.conf.pageKey) {
|
|
40
|
+
// @link http://bl.ocks.org/abernier/3070589
|
|
40
41
|
this.conf.pageKey = `${window.location.protocol}//${window.location.host}${window.location.pathname}`
|
|
41
42
|
}
|
|
42
43
|
|
|
@@ -56,6 +57,7 @@ export default class Artalk {
|
|
|
56
57
|
// 界面初始化
|
|
57
58
|
this.$root.classList.add('artalk')
|
|
58
59
|
this.$root.innerHTML = ''
|
|
60
|
+
this.initLayer()
|
|
59
61
|
this.initDarkMode()
|
|
60
62
|
|
|
61
63
|
// 组件初始化
|
|
@@ -133,6 +135,13 @@ export default class Artalk {
|
|
|
133
135
|
this.list.fetchComments(0)
|
|
134
136
|
}
|
|
135
137
|
|
|
138
|
+
/** Layer 初始化 */
|
|
139
|
+
initLayer() {
|
|
140
|
+
// 记录页面原始 Styles
|
|
141
|
+
Layer.BodyOrgOverflow = document.body.style.overflow
|
|
142
|
+
Layer.BodyOrgPaddingRight = document.body.style.paddingRight
|
|
143
|
+
}
|
|
144
|
+
|
|
136
145
|
/** 暗黑模式 · 初始化 */
|
|
137
146
|
public initDarkMode() {
|
|
138
147
|
const darkModeClassName = 'atk-dark-mode'
|
|
@@ -163,14 +172,15 @@ export default class Artalk {
|
|
|
163
172
|
|
|
164
173
|
/** PV */
|
|
165
174
|
public async initPV() {
|
|
175
|
+
const pvNum = await new Api(this.ctx).pv()
|
|
176
|
+
if (Number.isNaN(Number(pvNum)))
|
|
177
|
+
return
|
|
178
|
+
|
|
166
179
|
if (!this.conf.pvEl || !document.querySelector(this.conf.pvEl))
|
|
167
180
|
return
|
|
168
181
|
|
|
169
182
|
const $pv = document.querySelector<HTMLElement>(this.conf.pvEl)!
|
|
170
183
|
// $pv.innerText = '-' // 默认占位符
|
|
171
|
-
const pvNum = await new Api(this.ctx).pv()
|
|
172
|
-
if (Number.isNaN(Number(pvNum)))
|
|
173
|
-
return
|
|
174
184
|
|
|
175
185
|
$pv.innerText = String(pvNum)
|
|
176
186
|
}
|
package/src/components/editor.ts
CHANGED
|
@@ -22,6 +22,7 @@ export default class Editor extends Component {
|
|
|
22
22
|
public $bottom: HTMLElement
|
|
23
23
|
public $plugBtnWrap: HTMLElement
|
|
24
24
|
public $imgUploadBtn?: HTMLElement
|
|
25
|
+
public $imgUploadInput?: HTMLInputElement
|
|
25
26
|
public $submitBtn: HTMLButtonElement
|
|
26
27
|
public $notifyWrap: HTMLElement
|
|
27
28
|
|
|
@@ -265,12 +266,16 @@ export default class Editor extends Component {
|
|
|
265
266
|
this.$imgUploadBtn = Utils.createElement(`<span class="atk-plug-btn">图片</span>`)
|
|
266
267
|
this.$plugBtnWrap.querySelector('[data-plug-name="preview"]')!.before(this.$imgUploadBtn) // 显示在预览图标之前
|
|
267
268
|
|
|
269
|
+
this.$imgUploadInput = document.createElement('input')
|
|
270
|
+
this.$imgUploadInput.type = 'file'
|
|
271
|
+
this.$imgUploadInput.style.display = 'none'
|
|
272
|
+
this.$imgUploadInput.accept = this.allowImgExts.map(o => `.${o}`).join(',')
|
|
273
|
+
this.$imgUploadBtn.after(this.$imgUploadInput)
|
|
274
|
+
|
|
268
275
|
// 按钮点击
|
|
269
276
|
this.$imgUploadBtn.onclick = () => {
|
|
270
277
|
// 选择图片
|
|
271
|
-
const $input =
|
|
272
|
-
$input.type = 'file'
|
|
273
|
-
$input.accept = this.allowImgExts.map(o => `.${o}`).join(',')
|
|
278
|
+
const $input = this.$imgUploadInput!
|
|
274
279
|
$input.onchange = () => {
|
|
275
280
|
(async () => { // 解决阻塞 UI 问题
|
|
276
281
|
if (!$input.files || $input.files.length === 0) return
|
|
@@ -501,7 +506,7 @@ export default class Editor extends Component {
|
|
|
501
506
|
|
|
502
507
|
// 回复不同页面的评论
|
|
503
508
|
if (this.replyComment !== null && this.replyComment.page_key !== this.ctx.conf.pageKey) {
|
|
504
|
-
window.open(`${this.replyComment.
|
|
509
|
+
window.open(`${this.replyComment.page_url}#atk-comment-${nComment.id}`)
|
|
505
510
|
}
|
|
506
511
|
|
|
507
512
|
this.ctx.trigger('list-insert', nComment)
|
|
@@ -551,6 +556,8 @@ export default class Editor extends Component {
|
|
|
551
556
|
const $travelPlace = Utils.createElement('<div></div>')
|
|
552
557
|
$afterEl.after($travelPlace)
|
|
553
558
|
$travelPlace.replaceWith(this.$el)
|
|
559
|
+
|
|
560
|
+
this.$el.classList.add('atk-fade-in') // 添加渐入动画
|
|
554
561
|
}
|
|
555
562
|
|
|
556
563
|
travelBack () {
|
package/src/components/layer.ts
CHANGED
|
@@ -10,8 +10,8 @@ export default class Layer extends Component {
|
|
|
10
10
|
|
|
11
11
|
private maskClickHideEnable: boolean = true
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
public static BodyOrgOverflow: string
|
|
14
|
+
public static BodyOrgPaddingRight: string
|
|
15
15
|
|
|
16
16
|
public afterHide?: Function
|
|
17
17
|
|
|
@@ -73,10 +73,11 @@ export default class Layer extends Component {
|
|
|
73
73
|
this.$wrap.classList.add('atk-fade-out')
|
|
74
74
|
this.$el.style.display = 'none'
|
|
75
75
|
|
|
76
|
+
// body style 禁止滚动解除
|
|
77
|
+
this.pageBodyScrollBarShow()
|
|
78
|
+
|
|
76
79
|
this.newActionTimer(() => {
|
|
77
80
|
this.$wrap.style.display = 'none'
|
|
78
|
-
// body style 禁止滚动解除
|
|
79
|
-
this.pageBodyScrollBarShow()
|
|
80
81
|
this.checkCleanLayer()
|
|
81
82
|
}, 450)
|
|
82
83
|
this.newActionTimer(() => {
|
|
@@ -91,8 +92,6 @@ export default class Layer extends Component {
|
|
|
91
92
|
|
|
92
93
|
// 页面滚动条隐藏
|
|
93
94
|
pageBodyScrollBarHide() {
|
|
94
|
-
this.bodyStyleOrgOverflow = document.body.style.overflow
|
|
95
|
-
this.bodyStyleOrgPaddingRight = document.body.style.paddingRight
|
|
96
95
|
document.body.style.overflow = 'hidden'
|
|
97
96
|
|
|
98
97
|
const bpr = parseInt(window.getComputedStyle(document.body, null).getPropertyValue('padding-right'), 10)
|
|
@@ -101,8 +100,8 @@ export default class Layer extends Component {
|
|
|
101
100
|
|
|
102
101
|
// 页面滚动条显示
|
|
103
102
|
pageBodyScrollBarShow() {
|
|
104
|
-
document.body.style.overflow =
|
|
105
|
-
document.body.style.paddingRight =
|
|
103
|
+
document.body.style.overflow = Layer.BodyOrgOverflow
|
|
104
|
+
document.body.style.paddingRight = Layer.BodyOrgPaddingRight
|
|
106
105
|
}
|
|
107
106
|
|
|
108
107
|
// Timers
|
|
@@ -128,7 +127,6 @@ export default class Layer extends Component {
|
|
|
128
127
|
|
|
129
128
|
/** 销毁 - 无动画 */
|
|
130
129
|
disposeNow () {
|
|
131
|
-
document.body.style.overflow = ''
|
|
132
130
|
this.$el.remove()
|
|
133
131
|
this.pageBodyScrollBarShow()
|
|
134
132
|
// this.$el dispose
|
|
@@ -91,7 +91,7 @@ export default class ListLite extends Component {
|
|
|
91
91
|
try {
|
|
92
92
|
listData = await new Api(this.ctx).get(offset, this.pageSize, this.flatMode, this.paramsEditor)
|
|
93
93
|
} catch (e: any) {
|
|
94
|
-
this.onError(e.msg || String(e), offset)
|
|
94
|
+
this.onError(e.msg || String(e), offset, e.data)
|
|
95
95
|
throw e
|
|
96
96
|
} finally {
|
|
97
97
|
hideLoading()
|
|
@@ -219,7 +219,7 @@ export default class ListLite extends Component {
|
|
|
219
219
|
}
|
|
220
220
|
|
|
221
221
|
/** 错误处理 */
|
|
222
|
-
protected onError(msg: any, offset: number) {
|
|
222
|
+
protected onError(msg: any, offset: number, errData?: any) {
|
|
223
223
|
msg = String(msg)
|
|
224
224
|
console.error(msg)
|
|
225
225
|
|
|
@@ -237,9 +237,24 @@ export default class ListLite extends Component {
|
|
|
237
237
|
$err.appendChild($retryBtn)
|
|
238
238
|
|
|
239
239
|
const adminBtn = Utils.createElement('<span atk-only-admin-show> | <span style="cursor:pointer;">打开控制台</span></span>')
|
|
240
|
-
adminBtn.onclick = () => (this.ctx.trigger('sidebar-show'))
|
|
241
|
-
if (!this.ctx.user.data.isAdmin) adminBtn.classList.add('atk-hide')
|
|
242
240
|
$err.appendChild(adminBtn)
|
|
241
|
+
if (!this.ctx.user.data.isAdmin) adminBtn.classList.add('atk-hide')
|
|
242
|
+
|
|
243
|
+
let sidebarView = ''
|
|
244
|
+
|
|
245
|
+
// 找不到站点错误,打开侧边栏并填入创建站点表单
|
|
246
|
+
if (errData?.err_no_site) {
|
|
247
|
+
const viewLoadParam = {
|
|
248
|
+
create_name: this.ctx.conf.site,
|
|
249
|
+
create_urls: `${window.location.protocol}//${window.location.host}`
|
|
250
|
+
}
|
|
251
|
+
// TODO 真的是飞鸽传书啊
|
|
252
|
+
sidebarView = `sites|${JSON.stringify(viewLoadParam)}`
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
adminBtn.onclick = () => (this.ctx.trigger('sidebar-show', {
|
|
256
|
+
view: sidebarView
|
|
257
|
+
}))
|
|
243
258
|
|
|
244
259
|
Ui.setError(this.$el, $err)
|
|
245
260
|
}
|
|
@@ -7,6 +7,7 @@ import SidebarHTML from './html/sidebar-layer.html?raw'
|
|
|
7
7
|
import * as Ui from '@/lib/ui'
|
|
8
8
|
import Layer from './layer'
|
|
9
9
|
import Api from '../api'
|
|
10
|
+
import { SidebarShowPayload } from '~/types/event'
|
|
10
11
|
|
|
11
12
|
export default class SidebarLayer extends Component {
|
|
12
13
|
public layer?: Layer
|
|
@@ -28,7 +29,7 @@ export default class SidebarLayer extends Component {
|
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
// event
|
|
31
|
-
this.ctx.on('sidebar-show', () => (this.show()))
|
|
32
|
+
this.ctx.on('sidebar-show', (conf) => (this.show(conf || {})))
|
|
32
33
|
this.ctx.on('sidebar-hide', () => (this.hide()))
|
|
33
34
|
this.ctx.on('user-changed', () => { this.firstShow = true })
|
|
34
35
|
}
|
|
@@ -36,7 +37,7 @@ export default class SidebarLayer extends Component {
|
|
|
36
37
|
private firstShow = true
|
|
37
38
|
|
|
38
39
|
/** 显示 */
|
|
39
|
-
public async show() {
|
|
40
|
+
public async show(conf: SidebarShowPayload) {
|
|
40
41
|
this.$el.style.transform = '' // 动画清除,防止二次打开失效
|
|
41
42
|
|
|
42
43
|
// 获取 Layer
|
|
@@ -59,53 +60,60 @@ export default class SidebarLayer extends Component {
|
|
|
59
60
|
this.$el.style.transform = 'translate(0, 0)'
|
|
60
61
|
}, 20)
|
|
61
62
|
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
this.layer?.hide()
|
|
78
|
-
}
|
|
79
|
-
})
|
|
80
|
-
}))
|
|
81
|
-
}
|
|
63
|
+
// 管理员身份验证 (若身份失效,弹出验证窗口)
|
|
64
|
+
;(async () => {
|
|
65
|
+
const resp = await new Api(this.ctx).loginStatus()
|
|
66
|
+
if (resp.is_admin && !resp.is_login) {
|
|
67
|
+
this.layer?.hide()
|
|
68
|
+
this.firstShow = true
|
|
69
|
+
|
|
70
|
+
this.ctx.trigger('checker-admin', {
|
|
71
|
+
onSuccess: () => {
|
|
72
|
+
setTimeout(() => {
|
|
73
|
+
this.show(conf)
|
|
74
|
+
}, 500)
|
|
75
|
+
},
|
|
76
|
+
onCancel: () => {}
|
|
77
|
+
})
|
|
82
78
|
}
|
|
79
|
+
})()
|
|
80
|
+
|
|
81
|
+
// 清空 unread
|
|
82
|
+
this.ctx.trigger('unread-update', { notifies: [] })
|
|
83
83
|
|
|
84
|
+
// 第一次加载
|
|
85
|
+
if (this.firstShow) {
|
|
84
86
|
this.$iframeWrap.innerHTML = ''
|
|
85
87
|
this.$iframe = Utils.createElement<HTMLIFrameElement>('<iframe></iframe>')
|
|
86
88
|
|
|
89
|
+
// 准备 Iframe 参数
|
|
87
90
|
const baseURL = (import.meta.env.MODE === 'development') ? 'http://localhost:23367/'
|
|
88
91
|
: Utils.getURLBasedOnApi(this.ctx, 'sidebar/')
|
|
89
|
-
const userData = encodeURIComponent(JSON.stringify(this.ctx.user.data))
|
|
90
92
|
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
+
|
|
96
|
-
|
|
97
|
-
|
|
93
|
+
const query: any = {
|
|
94
|
+
pageKey: this.conf.pageKey,
|
|
95
|
+
site: this.conf.site || '',
|
|
96
|
+
user: JSON.stringify(this.ctx.user.data),
|
|
97
|
+
time: +new Date()
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (conf.view) query.view = conf.view
|
|
101
|
+
if (this.conf.darkMode) query.darkMode = '1'
|
|
102
|
+
|
|
103
|
+
const urlParams = new URLSearchParams(query);
|
|
104
|
+
this.iframeLoad(`${baseURL}?${urlParams.toString()}`)
|
|
98
105
|
|
|
99
106
|
this.$iframeWrap.append(this.$iframe)
|
|
100
107
|
this.firstShow = false
|
|
101
108
|
} else {
|
|
102
109
|
// 暗黑模式
|
|
103
|
-
|
|
110
|
+
const isIframeSrcDarkMode = this.$iframe!.src.includes('darkMode=1')
|
|
111
|
+
|
|
112
|
+
if (this.conf.darkMode && !isIframeSrcDarkMode)
|
|
104
113
|
this.iframeLoad(`${this.$iframe!.src}&darkMode=1`)
|
|
105
|
-
|
|
106
|
-
if (!this.conf.darkMode &&
|
|
107
|
-
this.iframeLoad(this.$iframe!.src.replace(
|
|
108
|
-
}
|
|
114
|
+
|
|
115
|
+
if (!this.conf.darkMode && isIframeSrcDarkMode)
|
|
116
|
+
this.iframeLoad(this.$iframe!.src.replace('&darkMode=1', ''))
|
|
109
117
|
}
|
|
110
118
|
}
|
|
111
119
|
|
|
@@ -15,7 +15,7 @@ const CaptchaChecker: Checker = {
|
|
|
15
15
|
const $iframe = Utils.createElement<HTMLIFrameElement>(`<iframe class="atk-fade-in"></iframe>`)
|
|
16
16
|
$iframe.style.display = 'none'
|
|
17
17
|
Ui.showLoading($iframeWrap, { transparentBg: true })
|
|
18
|
-
$iframe.src = `${that.ctx.conf.server}/captcha/get`
|
|
18
|
+
$iframe.src = `${that.ctx.conf.server}/captcha/get?t=${+new Date()}`
|
|
19
19
|
$iframe.onload = () => {
|
|
20
20
|
$iframe.style.display = ''
|
|
21
21
|
Ui.hideLoading($iframeWrap)
|
package/src/lib/utils.ts
CHANGED
|
@@ -150,25 +150,34 @@ export function initMarked(ctx: Context) {
|
|
|
150
150
|
return html.replace(/^<a /, `<a target="_blank" ${!localLink ? `rel="noreferrer noopener nofollow"` : ''} `);
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
+
renderer.code = (block, lang) => {
|
|
154
|
+
// Colorize the block only if the language is known to highlight.js
|
|
155
|
+
const realLang = (!lang ? 'plaintext' : lang)
|
|
156
|
+
let colorized = block
|
|
157
|
+
if ((window as any).hljs) {
|
|
158
|
+
if (realLang && (window as any).hljs.getLanguage(realLang)) {
|
|
159
|
+
colorized = (window as any).hljs.highlight(realLang, block).value
|
|
160
|
+
}
|
|
161
|
+
} else {
|
|
162
|
+
colorized = hanabi(block)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return `<pre rel="${realLang}">\n`
|
|
166
|
+
+ `<code class="hljs language-${realLang}">${colorized.replace(/&/g, '&')}</code>\n`
|
|
167
|
+
+ `</pre>`
|
|
168
|
+
}
|
|
169
|
+
|
|
153
170
|
// @see https://github.com/markedjs/marked/blob/4afb228d956a415624c4e5554bb8f25d047676fe/src/Tokenizer.js#L329
|
|
154
171
|
const nMarked = libMarked
|
|
155
172
|
libMarked.setOptions({
|
|
156
173
|
renderer,
|
|
157
|
-
highlight: (code) => hanabi(code),
|
|
158
174
|
pedantic: false,
|
|
159
175
|
gfm: true,
|
|
160
176
|
breaks: true,
|
|
161
177
|
smartLists: true,
|
|
162
178
|
smartypants: true,
|
|
163
179
|
xhtml: false,
|
|
164
|
-
sanitize:
|
|
165
|
-
sanitizer: (html) => insane(html, {
|
|
166
|
-
...insane.defaults,
|
|
167
|
-
allowedAttributes: {
|
|
168
|
-
...insane.defaults.allowedAttributes,
|
|
169
|
-
img: ['src', 'atk-emoticon']
|
|
170
|
-
},
|
|
171
|
-
}),
|
|
180
|
+
sanitize: false,
|
|
172
181
|
silent: true,
|
|
173
182
|
})
|
|
174
183
|
|
|
@@ -177,7 +186,49 @@ export function initMarked(ctx: Context) {
|
|
|
177
186
|
|
|
178
187
|
/** 解析 markdown */
|
|
179
188
|
export function marked(ctx: Context, src: string): string {
|
|
180
|
-
|
|
189
|
+
// @link https://gist.github.com/lionel-rowe/bb384465ba4e4c81a9c8dada84167225
|
|
190
|
+
return insane(ctx.markedInstance.parse(src), {
|
|
191
|
+
allowedClasses: {},
|
|
192
|
+
allowedSchemes: ['http', 'https', 'mailto'],
|
|
193
|
+
allowedTags: [
|
|
194
|
+
'a', 'abbr', 'article', 'b', 'blockquote', 'br', 'caption', 'code', 'del', 'details', 'div', 'em',
|
|
195
|
+
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'ins', 'kbd', 'li', 'main', 'mark',
|
|
196
|
+
'ol', 'p', 'pre', 'section', 'span', 'strike', 'strong', 'sub', 'summary', 'sup', 'table',
|
|
197
|
+
'tbody', 'td', 'th', 'thead', 'tr', 'u', 'ul'
|
|
198
|
+
],
|
|
199
|
+
allowedAttributes: {
|
|
200
|
+
'*': ['title', 'accesskey'],
|
|
201
|
+
a: ['href', 'name', 'target', 'aria-label'],
|
|
202
|
+
img: ['src', 'alt', 'title', 'atk-emoticon', 'aria-label'],
|
|
203
|
+
// for code highlight
|
|
204
|
+
code: ['class'],
|
|
205
|
+
span: ['class', 'style'],
|
|
206
|
+
},
|
|
207
|
+
filter: node => {
|
|
208
|
+
// allow hljs style
|
|
209
|
+
const allowed = [
|
|
210
|
+
[ 'code', /^hljs\W+language-(.*)$/ ],
|
|
211
|
+
[ 'span', /^(hljs-.*)$/ ]
|
|
212
|
+
]
|
|
213
|
+
allowed.forEach(([ tag, reg ]) => {
|
|
214
|
+
if (
|
|
215
|
+
node.tag === tag
|
|
216
|
+
&& !!node.attrs.class
|
|
217
|
+
&& !(reg as RegExp).test(node.attrs.class)
|
|
218
|
+
) {
|
|
219
|
+
delete node.attrs.class
|
|
220
|
+
}
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
// allow <span> set color sty
|
|
224
|
+
if (node.tag === 'span' && !!node.attrs.style
|
|
225
|
+
&& !/^color:(\W+)?#[0-9a-f]{3,6};?$/i.test(node.attrs.style)) {
|
|
226
|
+
delete node.attrs.style
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return true
|
|
230
|
+
}
|
|
231
|
+
})
|
|
181
232
|
}
|
|
182
233
|
|
|
183
234
|
/** 获取修正后的 UserAgent */
|
package/src/style/comment.less
CHANGED
package/src/style/main.less
CHANGED
|
@@ -7,28 +7,48 @@
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
.artalk, .atk-layer-wrap {
|
|
10
|
+
line-height: @line-height;
|
|
11
|
+
color: var(--at-color-font);
|
|
12
|
+
font-family: @font-family;
|
|
13
|
+
word-wrap: break-word;
|
|
14
|
+
word-break: break-all;
|
|
15
|
+
|
|
10
16
|
* {
|
|
11
17
|
box-sizing: border-box;
|
|
12
|
-
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
input, textarea, button, optgroup, select {
|
|
21
|
+
font-family: inherit;
|
|
22
|
+
color: inherit;
|
|
23
|
+
font-size: inherit;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@codeFont: source code pro, Consolas, Monaco, Menlo, sans-serif;
|
|
27
|
+
|
|
28
|
+
code {
|
|
29
|
+
font-family: @codeFont;
|
|
30
|
+
margin: 0 .3em;
|
|
31
|
+
padding: .1em .2em;
|
|
32
|
+
background-color: var(--at-color-bg-grey);
|
|
13
33
|
color: var(--at-color-font);
|
|
14
|
-
|
|
15
|
-
word-wrap: break-word;
|
|
16
|
-
word-break: break-all;
|
|
34
|
+
border-radius: @block-radius;
|
|
17
35
|
}
|
|
18
36
|
|
|
19
37
|
pre {
|
|
20
|
-
|
|
38
|
+
margin: 10px 0 0 0;
|
|
39
|
+
padding: 0;
|
|
40
|
+
line-height: 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
pre code {
|
|
44
|
+
* { font-family: @codeFont; }
|
|
21
45
|
line-height: @line-height;
|
|
46
|
+
display: block;
|
|
47
|
+
padding: 10px 15px;
|
|
48
|
+
white-space: pre-wrap;
|
|
22
49
|
background-color: var(--at-color-bg-grey);
|
|
23
50
|
color: var(--at-color-font);
|
|
24
|
-
padding: 10px 15px;
|
|
25
|
-
border-radius: @block-radius;
|
|
26
51
|
margin: 0;
|
|
27
|
-
font-family: Source Code Pro,courier new,Input Mono,PT Mono,SFMono-Regular,Consolas,Monaco,Menlo,PingFang SC,Liberation Mono,Microsoft YaHei,Courier,monospace;
|
|
28
|
-
|
|
29
|
-
code {
|
|
30
|
-
white-space: pre-wrap;
|
|
31
|
-
}
|
|
32
52
|
}
|
|
33
53
|
|
|
34
54
|
a {
|
|
@@ -55,6 +75,28 @@
|
|
|
55
75
|
img {
|
|
56
76
|
max-width: 100%;
|
|
57
77
|
}
|
|
78
|
+
|
|
79
|
+
table {
|
|
80
|
+
width: 100%;
|
|
81
|
+
border-collapse: collapse;
|
|
82
|
+
border-spacing: 0;
|
|
83
|
+
margin-bottom: 1.5em;
|
|
84
|
+
font-size: .96em
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
td, th {
|
|
88
|
+
text-align: left;
|
|
89
|
+
padding: 4px 8px 4px 10px;
|
|
90
|
+
border: 1px solid var(--at-color-border)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
td {
|
|
94
|
+
vertical-align: top
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
tr:nth-child(even) {
|
|
98
|
+
background-color: var(--at-color-bg-grey);
|
|
99
|
+
}
|
|
58
100
|
}
|
|
59
101
|
|
|
60
102
|
.atk-hide {
|
|
@@ -156,6 +198,9 @@
|
|
|
156
198
|
.atk-fade-out {
|
|
157
199
|
animation: atkFadeOut both .2s;
|
|
158
200
|
}
|
|
201
|
+
.atk-rotate {
|
|
202
|
+
animation: atkRotate 2s linear infinite;
|
|
203
|
+
}
|
|
159
204
|
|
|
160
205
|
@keyframes atkFadeIn {
|
|
161
206
|
0% { opacity: 0 }
|
|
@@ -167,6 +212,11 @@
|
|
|
167
212
|
0% {}
|
|
168
213
|
}
|
|
169
214
|
|
|
215
|
+
@keyframes atkRotate {
|
|
216
|
+
from { transform: rotate(0deg); }
|
|
217
|
+
to { transform: rotate(360deg); }
|
|
218
|
+
}
|
|
219
|
+
|
|
170
220
|
i.atk-icon {
|
|
171
221
|
width: 20px;
|
|
172
222
|
height: 20px;
|
|
@@ -175,6 +225,7 @@ i.atk-icon {
|
|
|
175
225
|
background-repeat: no-repeat;
|
|
176
226
|
mask-repeat: no-repeat;
|
|
177
227
|
mask-position: center;
|
|
228
|
+
mask-size: contain;
|
|
178
229
|
}
|
|
179
230
|
|
|
180
231
|
.atk-icon-sync {
|
package/types/event.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ export interface EventPayloadMap {
|
|
|
19
19
|
'list-refresh-ui': undefined // 刷新 List UI
|
|
20
20
|
|
|
21
21
|
// Sidebar
|
|
22
|
-
'sidebar-show':
|
|
22
|
+
'sidebar-show': SidebarShowPayload // 侧边栏显示
|
|
23
23
|
'sidebar-hide': undefined // 侧边栏隐藏
|
|
24
24
|
|
|
25
25
|
// Editor
|
|
@@ -70,6 +70,10 @@ export interface EditorReplyPayload {
|
|
|
70
70
|
scroll?: boolean
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
export interface SidebarShowPayload {
|
|
74
|
+
view?: 'comments'|'sites'|'pages'|'transfer'|string
|
|
75
|
+
}
|
|
76
|
+
|
|
73
77
|
// ============================================
|
|
74
78
|
export interface Handler<P> {
|
|
75
79
|
(payload: P): void
|