artalk 2.2.3 → 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 +89 -59
- package/dist/Artalk.iife.js +1 -1
- package/dist/Artalk.js +1 -1
- package/index.html +7 -0
- package/package.json +2 -2
- package/src/api/index.ts +11 -7
- package/src/artalk.ts +14 -4
- package/src/components/editor.ts +9 -4
- package/src/components/layer.ts +7 -9
- package/src/components/list-lite.ts +19 -4
- package/src/components/sidebar-layer.ts +43 -36
- package/src/lib/checker/captcha-checker.ts +1 -1
- package/src/style/comment.less +1 -0
- package/src/style/main.less +14 -3
- package/types/event.d.ts +5 -1
package/index.html
CHANGED
|
@@ -32,6 +32,13 @@
|
|
|
32
32
|
<!--<link rel="stylesheet"
|
|
33
33
|
href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/styles/arta.min.css">
|
|
34
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>
|
|
35
42
|
</head>
|
|
36
43
|
<body>
|
|
37
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)
|
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,54 +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
|
-
|
|
98
|
-
|
|
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()}`)
|
|
99
105
|
|
|
100
106
|
this.$iframeWrap.append(this.$iframe)
|
|
101
107
|
this.firstShow = false
|
|
102
108
|
} else {
|
|
103
109
|
// 暗黑模式
|
|
104
|
-
|
|
110
|
+
const isIframeSrcDarkMode = this.$iframe!.src.includes('darkMode=1')
|
|
111
|
+
|
|
112
|
+
if (this.conf.darkMode && !isIframeSrcDarkMode)
|
|
105
113
|
this.iframeLoad(`${this.$iframe!.src}&darkMode=1`)
|
|
106
|
-
|
|
107
|
-
if (!this.conf.darkMode &&
|
|
108
|
-
this.iframeLoad(this.$iframe!.src.replace(
|
|
109
|
-
}
|
|
114
|
+
|
|
115
|
+
if (!this.conf.darkMode && isIframeSrcDarkMode)
|
|
116
|
+
this.iframeLoad(this.$iframe!.src.replace('&darkMode=1', ''))
|
|
110
117
|
}
|
|
111
118
|
}
|
|
112
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/style/comment.less
CHANGED
package/src/style/main.less
CHANGED
|
@@ -19,9 +19,11 @@
|
|
|
19
19
|
|
|
20
20
|
input, textarea, button, optgroup, select {
|
|
21
21
|
font-family: inherit;
|
|
22
|
+
color: inherit;
|
|
23
|
+
font-size: inherit;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
|
-
@codeFont:
|
|
26
|
+
@codeFont: source code pro, Consolas, Monaco, Menlo, sans-serif;
|
|
25
27
|
|
|
26
28
|
code {
|
|
27
29
|
font-family: @codeFont;
|
|
@@ -33,8 +35,8 @@
|
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
pre {
|
|
36
|
-
margin
|
|
37
|
-
|
|
38
|
+
margin: 10px 0 0 0;
|
|
39
|
+
padding: 0;
|
|
38
40
|
line-height: 0;
|
|
39
41
|
}
|
|
40
42
|
|
|
@@ -196,6 +198,9 @@
|
|
|
196
198
|
.atk-fade-out {
|
|
197
199
|
animation: atkFadeOut both .2s;
|
|
198
200
|
}
|
|
201
|
+
.atk-rotate {
|
|
202
|
+
animation: atkRotate 2s linear infinite;
|
|
203
|
+
}
|
|
199
204
|
|
|
200
205
|
@keyframes atkFadeIn {
|
|
201
206
|
0% { opacity: 0 }
|
|
@@ -207,6 +212,11 @@
|
|
|
207
212
|
0% {}
|
|
208
213
|
}
|
|
209
214
|
|
|
215
|
+
@keyframes atkRotate {
|
|
216
|
+
from { transform: rotate(0deg); }
|
|
217
|
+
to { transform: rotate(360deg); }
|
|
218
|
+
}
|
|
219
|
+
|
|
210
220
|
i.atk-icon {
|
|
211
221
|
width: 20px;
|
|
212
222
|
height: 20px;
|
|
@@ -215,6 +225,7 @@ i.atk-icon {
|
|
|
215
225
|
background-repeat: no-repeat;
|
|
216
226
|
mask-repeat: no-repeat;
|
|
217
227
|
mask-position: center;
|
|
228
|
+
mask-size: contain;
|
|
218
229
|
}
|
|
219
230
|
|
|
220
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
|