artalk 2.2.12 → 2.3.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 +3216 -2883
- package/dist/Artalk.iife.js +1 -1
- package/dist/Artalk.js +1 -1
- package/dist/ArtalkLite.css +1 -0
- package/dist/ArtalkLite.es.js +5096 -0
- package/dist/ArtalkLite.iife.js +1 -0
- package/dist/ArtalkLite.js +1 -0
- package/index.html +2 -2
- package/package.json +6 -5
- package/src/api/index.ts +20 -1
- package/src/api/request.ts +7 -7
- package/src/artalk.ts +68 -50
- package/src/comment/actions.ts +87 -0
- package/src/{components/html → comment}/comment.html +1 -1
- package/src/comment/comment.ts +191 -0
- package/src/comment/index.ts +3 -0
- package/src/comment/render.ts +425 -0
- package/src/components/action-btn.ts +6 -3
- package/src/components/dialog.ts +7 -3
- package/src/components/read-more-btn.ts +6 -2
- package/src/context.ts +200 -13
- package/src/defaults.ts +16 -5
- package/src/editor/editor.html +18 -0
- package/src/editor/editor.ts +420 -0
- package/src/editor/index.ts +3 -0
- package/src/editor/plugs/editor-plug.ts +51 -0
- package/src/{components/editor-plugs → editor/plugs}/emoticons-plug.less +1 -0
- package/src/{components/editor-plugs → editor/plugs}/emoticons-plug.ts +65 -63
- package/src/editor/plugs/header-input-plug.ts +79 -0
- package/src/{components/editor-plugs → editor/plugs}/preview-plug.less +0 -0
- package/src/editor/plugs/preview-plug.ts +40 -0
- package/src/editor/plugs/upload-plug.ts +125 -0
- package/src/i18n/en.ts +77 -0
- package/src/i18n/index.ts +21 -0
- package/src/i18n/zh-CN.ts +81 -0
- package/src/layer/index.ts +4 -0
- package/src/{components → layer}/layer.ts +9 -9
- package/src/{components/html → layer}/sidebar-layer.html +0 -0
- package/src/{components → layer}/sidebar-layer.ts +17 -46
- package/src/lib/checker/admin-checker.ts +7 -6
- package/src/lib/checker/captcha-checker.ts +5 -5
- package/src/lib/checker/index.ts +22 -17
- package/src/lib/component.ts +7 -2
- package/src/lib/ui.ts +3 -10
- package/src/lib/user.ts +20 -3
- package/src/lib/utils.ts +23 -9
- package/src/list/index.ts +3 -0
- package/src/{components → list}/list-lite.ts +80 -99
- package/src/{components → list}/list-nest.ts +0 -0
- package/src/{components/html → list}/list.html +3 -6
- package/src/{components → list}/list.ts +37 -35
- package/src/style/comment.less +23 -10
- package/src/style/editor.less +16 -10
- package/src/style/list.less +4 -7
- package/src/style/main.less +3 -1
- package/src/style/pagination.less +1 -1
- package/test/ArtalkLite.html +15 -0
- package/types/artalk-config.d.ts +50 -32
- package/types/context.d.ts +128 -0
- package/types/event.d.ts +6 -66
- package/types/global.d.ts +1 -0
- package/{vite-common.config.ts → vite-base.config.ts} +6 -4
- package/vite-demo.config.ts +2 -4
- package/vite-lite.config.ts +22 -0
- package/vite.config.ts +9 -12
- package/src/components/_component.ts.txt +0 -10
- package/src/components/comment.ts +0 -605
- package/src/components/editor-plugs/_example-plug.ts.txt +0 -29
- package/src/components/editor-plugs/editor-plug.ts +0 -20
- package/src/components/editor-plugs/preview-plug.ts +0 -48
- package/src/components/editor.ts +0 -607
- package/src/components/html/editor.html +0 -18
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import Context from '
|
|
1
|
+
import Context from '~/types/context'
|
|
2
2
|
import Component from '../lib/component'
|
|
3
3
|
import * as Utils from '../lib/utils'
|
|
4
4
|
import * as Ui from '../lib/ui'
|
|
@@ -40,19 +40,19 @@ export default class Layer extends Component {
|
|
|
40
40
|
this.$wrap.append(this.$el)
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
getName
|
|
43
|
+
getName() {
|
|
44
44
|
return this.name
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
getWrapEl
|
|
47
|
+
getWrapEl() {
|
|
48
48
|
return this.$wrap
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
getEl
|
|
51
|
+
getEl() {
|
|
52
52
|
return this.$el
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
show
|
|
55
|
+
show() {
|
|
56
56
|
this.fireAllActionTimer()
|
|
57
57
|
|
|
58
58
|
this.$wrap.style.display = 'block'
|
|
@@ -126,7 +126,7 @@ export default class Layer extends Component {
|
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
/** 销毁 - 无动画 */
|
|
129
|
-
disposeNow
|
|
129
|
+
disposeNow() {
|
|
130
130
|
this.$el.remove()
|
|
131
131
|
this.pageBodyScrollBarShow()
|
|
132
132
|
// this.$el dispose
|
|
@@ -134,21 +134,21 @@ export default class Layer extends Component {
|
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
/** 销毁 */
|
|
137
|
-
dispose
|
|
137
|
+
dispose() {
|
|
138
138
|
this.hide()
|
|
139
139
|
this.$el.remove()
|
|
140
140
|
// this.$el dispose
|
|
141
141
|
this.checkCleanLayer()
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
checkCleanLayer
|
|
144
|
+
checkCleanLayer() {
|
|
145
145
|
if (this.getWrapEl().querySelectorAll('.atk-layer-item').length === 0) {
|
|
146
146
|
this.$wrap.style.display = 'none'
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
export function GetLayerWrap
|
|
151
|
+
export function GetLayerWrap(ctx: Context): { $wrap: HTMLElement, $mask: HTMLElement } {
|
|
152
152
|
let $wrap = document.querySelector<HTMLElement>(`.atk-layer-wrap#ctx-${ctx.cid}`)
|
|
153
153
|
if (!$wrap) {
|
|
154
154
|
$wrap = Utils.createElement(
|
|
File without changes
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import '../style/sidebar-layer.less'
|
|
2
2
|
|
|
3
|
-
import Context from '
|
|
3
|
+
import Context from '~/types/context'
|
|
4
4
|
import Component from '@/lib/component'
|
|
5
5
|
import * as Utils from '@/lib/utils'
|
|
6
6
|
import * as Ui from '@/lib/ui'
|
|
7
|
-
import
|
|
8
|
-
import SidebarHTML from './html/sidebar-layer.html?raw'
|
|
7
|
+
import SidebarHTML from './sidebar-layer.html?raw'
|
|
9
8
|
import Layer from './layer'
|
|
10
9
|
import Api from '../api'
|
|
11
10
|
|
|
11
|
+
export interface SidebarShowPayload {
|
|
12
|
+
view?: 'comments'|'sites'|'pages'|'transfer'
|
|
13
|
+
}
|
|
14
|
+
|
|
12
15
|
export default class SidebarLayer extends Component {
|
|
13
16
|
public layer?: Layer
|
|
14
17
|
public $header: HTMLElement
|
|
@@ -29,15 +32,13 @@ export default class SidebarLayer extends Component {
|
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
// event
|
|
32
|
-
this.ctx.on('sidebar-show', (conf) => (this.show(conf || {})))
|
|
33
|
-
this.ctx.on('sidebar-hide', () => (this.hide()))
|
|
34
35
|
this.ctx.on('user-changed', () => { this.firstShow = true })
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
private firstShow = true
|
|
38
39
|
|
|
39
40
|
/** 显示 */
|
|
40
|
-
public async show(conf: SidebarShowPayload) {
|
|
41
|
+
public async show(conf: SidebarShowPayload = {}) {
|
|
41
42
|
this.$el.style.transform = '' // 动画清除,防止二次打开失效
|
|
42
43
|
|
|
43
44
|
// 获取 Layer
|
|
@@ -46,7 +47,7 @@ export default class SidebarLayer extends Component {
|
|
|
46
47
|
this.layer.afterHide = () => {
|
|
47
48
|
// 防止评论框被吞
|
|
48
49
|
if (this.ctx.conf.editorTravel === true) {
|
|
49
|
-
this.ctx.
|
|
50
|
+
this.ctx.editorTravelBack()
|
|
50
51
|
}
|
|
51
52
|
}
|
|
52
53
|
}
|
|
@@ -62,12 +63,12 @@ export default class SidebarLayer extends Component {
|
|
|
62
63
|
|
|
63
64
|
// 管理员身份验证 (若身份失效,弹出验证窗口)
|
|
64
65
|
;(async () => {
|
|
65
|
-
const resp = await
|
|
66
|
+
const resp = await this.ctx.getApi().loginStatus()
|
|
66
67
|
if (resp.is_admin && !resp.is_login) {
|
|
67
68
|
this.layer?.hide()
|
|
68
69
|
this.firstShow = true
|
|
69
70
|
|
|
70
|
-
this.ctx.
|
|
71
|
+
this.ctx.checkAdmin({
|
|
71
72
|
onSuccess: () => {
|
|
72
73
|
setTimeout(() => {
|
|
73
74
|
this.show(conf)
|
|
@@ -79,7 +80,7 @@ export default class SidebarLayer extends Component {
|
|
|
79
80
|
})()
|
|
80
81
|
|
|
81
82
|
// 清空 unread
|
|
82
|
-
this.ctx.
|
|
83
|
+
this.ctx.updateNotifies([])
|
|
83
84
|
|
|
84
85
|
// 第一次加载
|
|
85
86
|
if (this.firstShow) {
|
|
@@ -87,7 +88,7 @@ export default class SidebarLayer extends Component {
|
|
|
87
88
|
this.$iframe = Utils.createElement<HTMLIFrameElement>('<iframe></iframe>')
|
|
88
89
|
|
|
89
90
|
// 准备 Iframe 参数
|
|
90
|
-
const baseURL = (import.meta.env.
|
|
91
|
+
const baseURL = (import.meta.env.DEV) ? 'http://localhost:23367/'
|
|
91
92
|
: Utils.getURLBasedOnApi(this.ctx, '/sidebar/')
|
|
92
93
|
|
|
93
94
|
const query: any = {
|
|
@@ -115,6 +116,8 @@ export default class SidebarLayer extends Component {
|
|
|
115
116
|
if (!this.conf.darkMode && isIframeSrcDarkMode)
|
|
116
117
|
this.iframeLoad(this.$iframe!.src.replace('&darkMode=1', ''))
|
|
117
118
|
}
|
|
119
|
+
|
|
120
|
+
this.ctx.trigger('sidebar-show')
|
|
118
121
|
}
|
|
119
122
|
|
|
120
123
|
/** 隐藏 */
|
|
@@ -123,9 +126,11 @@ export default class SidebarLayer extends Component {
|
|
|
123
126
|
this.$el.style.transform = ''
|
|
124
127
|
|
|
125
128
|
this.layer?.hide()
|
|
129
|
+
|
|
130
|
+
this.ctx.trigger('sidebar-hide')
|
|
126
131
|
}
|
|
127
132
|
|
|
128
|
-
|
|
133
|
+
private iframeLoad(src: string) {
|
|
129
134
|
if (!this.$iframe) return
|
|
130
135
|
|
|
131
136
|
this.$iframe.src = src
|
|
@@ -135,39 +140,5 @@ export default class SidebarLayer extends Component {
|
|
|
135
140
|
this.$iframe.onload = () => {
|
|
136
141
|
Ui.hideLoading(this.$iframeWrap)
|
|
137
142
|
}
|
|
138
|
-
|
|
139
|
-
// this.checkReqStatus(src) // 判不准,删了,没啥用
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
loadingTimer: number|null = null
|
|
143
|
-
|
|
144
|
-
// 测试可访问性 (由于 iframe 测不准,需要额外请求)
|
|
145
|
-
public async checkReqStatus(url: string) {
|
|
146
|
-
if (this.loadingTimer !== null) window.clearTimeout(this.loadingTimer)
|
|
147
|
-
|
|
148
|
-
this.loadingTimer = window.setTimeout(async () => {
|
|
149
|
-
try {
|
|
150
|
-
await fetch(url)
|
|
151
|
-
} catch (err) {
|
|
152
|
-
console.log(err)
|
|
153
|
-
// 请求失败,显示错误提示
|
|
154
|
-
const $errAlert = Utils.createElement(
|
|
155
|
-
`<div class="atk-err-alert">` +
|
|
156
|
-
` <div class="atk-title">侧边栏似乎打开失败</div>` +
|
|
157
|
-
` <div class="atk-text"><span id="AtkReload">重新加载</span> / <span id="AtkCancel">取消</span></div>` +
|
|
158
|
-
`</div>`
|
|
159
|
-
)
|
|
160
|
-
const $reloadBtn = $errAlert.querySelector<HTMLElement>('#AtkReload')!
|
|
161
|
-
const $cancelBtn = $errAlert.querySelector<HTMLElement>('#AtkCancel')!
|
|
162
|
-
$reloadBtn.onclick = () => {
|
|
163
|
-
this.iframeLoad(url)
|
|
164
|
-
$errAlert.remove()
|
|
165
|
-
}
|
|
166
|
-
$cancelBtn.onclick = () => { // 提供取消按钮,防止误判
|
|
167
|
-
$errAlert.remove()
|
|
168
|
-
}
|
|
169
|
-
this.$iframeWrap.append($errAlert)
|
|
170
|
-
}
|
|
171
|
-
}, 2000) // 2s 后开始检测
|
|
172
143
|
}
|
|
173
144
|
}
|
|
@@ -12,19 +12,20 @@ const AdminChecker: Checker = {
|
|
|
12
12
|
password: inputVal
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
return
|
|
15
|
+
return that.ctx.getApi().login(data.name, data.email, data.password)
|
|
16
16
|
},
|
|
17
17
|
|
|
18
18
|
body(that, ctx) {
|
|
19
|
-
return Utils.createElement(
|
|
19
|
+
return Utils.createElement(`<span>${that.ctx.$t('adminCheck')}</span>`)
|
|
20
20
|
},
|
|
21
21
|
|
|
22
22
|
onSuccess(that, ctx, userToken, inputVal, formEl) {
|
|
23
|
-
that.ctx.user.
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
that.ctx.user.update({
|
|
24
|
+
isAdmin: true,
|
|
25
|
+
token: userToken
|
|
26
|
+
})
|
|
26
27
|
that.ctx.trigger('user-changed', that.ctx.user.data)
|
|
27
|
-
that.ctx.
|
|
28
|
+
that.ctx.listReload()
|
|
28
29
|
},
|
|
29
30
|
|
|
30
31
|
onError(that, ctx, err, inputVal, formEl) {}
|
|
@@ -5,7 +5,7 @@ import { Checker } from '.'
|
|
|
5
5
|
|
|
6
6
|
const CaptchaChecker: Checker = {
|
|
7
7
|
request(that, ctx, inputVal) {
|
|
8
|
-
return
|
|
8
|
+
return that.ctx.getApi().captchaCheck(inputVal)
|
|
9
9
|
},
|
|
10
10
|
|
|
11
11
|
body(that, ctx) {
|
|
@@ -35,7 +35,7 @@ const CaptchaChecker: Checker = {
|
|
|
35
35
|
if (stop) return
|
|
36
36
|
let isPass = false
|
|
37
37
|
try {
|
|
38
|
-
const resp = await
|
|
38
|
+
const resp = await that.ctx.getApi().captchaStatus()
|
|
39
39
|
isPass = resp.is_pass
|
|
40
40
|
} catch { isPass = false }
|
|
41
41
|
if (isPass) {
|
|
@@ -55,18 +55,18 @@ const CaptchaChecker: Checker = {
|
|
|
55
55
|
|
|
56
56
|
// 图片验证方式
|
|
57
57
|
const elem = Utils.createElement(
|
|
58
|
-
`<span><img class="atk-captcha-img" src="${that.captchaConf.imgData || ''}"
|
|
58
|
+
`<span><img class="atk-captcha-img" src="${that.captchaConf.imgData || ''}">${that.ctx.$t('captchaCheck')}</span>`
|
|
59
59
|
);
|
|
60
60
|
|
|
61
61
|
// 刷新验证码
|
|
62
62
|
elem.querySelector<HTMLElement>('.atk-captcha-img')!.onclick = () => {
|
|
63
63
|
const imgEl = elem.querySelector('.atk-captcha-img')
|
|
64
|
-
|
|
64
|
+
that.ctx.getApi().captchaGet()
|
|
65
65
|
.then((imgData) => {
|
|
66
66
|
imgEl!.setAttribute('src', imgData)
|
|
67
67
|
})
|
|
68
68
|
.catch((err) => {
|
|
69
|
-
console.error('
|
|
69
|
+
console.error('Failed to get captcha image ', err)
|
|
70
70
|
})
|
|
71
71
|
}
|
|
72
72
|
return elem
|
package/src/lib/checker/index.ts
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
|
-
import Context from '
|
|
2
|
-
import Layer from '@/components/layer'
|
|
1
|
+
import Context from '~/types/context'
|
|
3
2
|
import Dialog from '@/components/dialog'
|
|
4
|
-
import
|
|
3
|
+
import Layer from '@/layer'
|
|
5
4
|
import * as Utils from '../utils'
|
|
6
5
|
import * as Ui from '../ui'
|
|
7
6
|
import CaptchaChecker from './captcha-checker'
|
|
8
7
|
import AdminChecker from './admin-checker'
|
|
9
8
|
|
|
9
|
+
export interface CheckerCaptchaPayload extends CheckerPayload {
|
|
10
|
+
imgData?: string
|
|
11
|
+
iframe?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface CheckerPayload {
|
|
15
|
+
onSuccess?: (inputVal: string, dialogEl?: HTMLElement) => void
|
|
16
|
+
onMount?: (dialogEl: HTMLElement) => void
|
|
17
|
+
onCancel?: () => void
|
|
18
|
+
}
|
|
19
|
+
|
|
10
20
|
/**
|
|
11
21
|
* Checker 发射台
|
|
12
22
|
*/
|
|
@@ -18,22 +28,17 @@ export default class CheckerLauncher {
|
|
|
18
28
|
|
|
19
29
|
constructor(ctx: Context) {
|
|
20
30
|
this.ctx = ctx
|
|
21
|
-
|
|
22
|
-
this.initEventBind()
|
|
23
31
|
}
|
|
24
32
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
this.
|
|
28
|
-
if (conf.imgData) this.captchaConf.imgData = conf.imgData
|
|
29
|
-
if (conf.iframe) this.captchaConf.iframe = conf.iframe
|
|
33
|
+
public checkCaptcha(payload: CheckerCaptchaPayload) {
|
|
34
|
+
this.captchaConf.imgData = payload.imgData
|
|
35
|
+
this.captchaConf.iframe = payload.iframe
|
|
30
36
|
|
|
31
|
-
|
|
32
|
-
|
|
37
|
+
this.fire(CaptchaChecker, payload)
|
|
38
|
+
}
|
|
33
39
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
})
|
|
40
|
+
public checkAdmin(payload: CheckerPayload) {
|
|
41
|
+
this.fire(AdminChecker, payload)
|
|
37
42
|
}
|
|
38
43
|
|
|
39
44
|
public fire(checker: Checker, payload: CheckerPayload) {
|
|
@@ -87,7 +92,7 @@ export default class CheckerLauncher {
|
|
|
87
92
|
}
|
|
88
93
|
|
|
89
94
|
let btnTextOrg: string | undefined
|
|
90
|
-
const dialog = new Dialog(formEl)
|
|
95
|
+
const dialog = new Dialog(this.ctx, formEl)
|
|
91
96
|
|
|
92
97
|
// 确认按钮
|
|
93
98
|
dialog.setYes((btnEl) => {
|
|
@@ -104,7 +109,7 @@ export default class CheckerLauncher {
|
|
|
104
109
|
btnEl.classList.remove('error')
|
|
105
110
|
}
|
|
106
111
|
|
|
107
|
-
btnEl.innerText = '
|
|
112
|
+
btnEl.innerText = `${this.ctx.$t('loading')}...`
|
|
108
113
|
|
|
109
114
|
// 发送请求
|
|
110
115
|
checker
|
package/src/lib/component.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import ArtalkConfig from '~/types/artalk-config'
|
|
2
|
-
import Context from '
|
|
2
|
+
import Context from '~/types/context'
|
|
3
|
+
import { I18n } from '../i18n'
|
|
3
4
|
|
|
4
|
-
export default class Component {
|
|
5
|
+
export default abstract class Component {
|
|
5
6
|
public $el!: HTMLElement
|
|
6
7
|
|
|
7
8
|
public ctx: Context
|
|
@@ -11,4 +12,8 @@ export default class Component {
|
|
|
11
12
|
this.ctx = ctx
|
|
12
13
|
this.conf = ctx.conf
|
|
13
14
|
}
|
|
15
|
+
|
|
16
|
+
public $t(key: keyof I18n, args: {[key: string]: string} = {}): string {
|
|
17
|
+
return this.ctx.$t(key, args)
|
|
18
|
+
}
|
|
14
19
|
}
|
package/src/lib/ui.ts
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import * as Utils from './utils'
|
|
2
|
-
import Context from '../context'
|
|
3
2
|
|
|
4
3
|
/** 显示加载 */
|
|
5
|
-
export function showLoading(parentElem: HTMLElement
|
|
6
|
-
if (parentElem instanceof Context) parentElem = parentElem.$root
|
|
7
|
-
|
|
4
|
+
export function showLoading(parentElem: HTMLElement, conf?: { transparentBg?: boolean }) {
|
|
8
5
|
let $loading = parentElem.querySelector<HTMLElement>('.atk-loading')
|
|
9
6
|
if (!$loading) {
|
|
10
7
|
$loading = Utils.createElement(
|
|
@@ -29,9 +26,7 @@ export function showLoading(parentElem: HTMLElement|Context, conf?: { transparen
|
|
|
29
26
|
}
|
|
30
27
|
|
|
31
28
|
/** 隐藏加载 */
|
|
32
|
-
export function hideLoading(parentElem: HTMLElement
|
|
33
|
-
if (parentElem instanceof Context) parentElem = parentElem.$root
|
|
34
|
-
|
|
29
|
+
export function hideLoading(parentElem: HTMLElement) {
|
|
35
30
|
const $loading = parentElem.querySelector<HTMLElement>('.atk-loading')
|
|
36
31
|
if ($loading) $loading.style.display = 'none'
|
|
37
32
|
}
|
|
@@ -133,9 +128,7 @@ export function playFadeOutAnim(elem: HTMLElement, after?: () => void) {
|
|
|
133
128
|
}
|
|
134
129
|
|
|
135
130
|
/** 设置全局错误 */
|
|
136
|
-
export function setError(parentElem: HTMLElement
|
|
137
|
-
if (parentElem instanceof Context) parentElem = parentElem.$root
|
|
138
|
-
|
|
131
|
+
export function setError(parentElem: HTMLElement, html: string | HTMLElement | null, title: string = '<span class="atk-error-title">Artalk Error</span>') {
|
|
139
132
|
let elem = parentElem.querySelector<HTMLElement>('.atk-error-layer')
|
|
140
133
|
if (html === null) {
|
|
141
134
|
if (elem !== null) elem.remove()
|
package/src/lib/user.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import ArtalkConfig, { LocalUser } from "~/types/artalk-config"
|
|
2
|
+
import Context from '~/types/context'
|
|
2
3
|
|
|
3
4
|
export default class User {
|
|
5
|
+
public ctx: Context
|
|
4
6
|
public data: LocalUser
|
|
5
7
|
|
|
6
|
-
constructor
|
|
8
|
+
public constructor(ctx: Context) {
|
|
9
|
+
this.ctx = ctx
|
|
10
|
+
|
|
7
11
|
// 从 localStorage 导入
|
|
8
12
|
const localUser = JSON.parse(window.localStorage.getItem('ArtalkUser') || '{}')
|
|
9
13
|
this.data = {
|
|
@@ -16,12 +20,25 @@ export default class User {
|
|
|
16
20
|
}
|
|
17
21
|
|
|
18
22
|
/** 保存用户到 localStorage 中 */
|
|
19
|
-
public
|
|
23
|
+
public update(obj: Partial<LocalUser> = {}) {
|
|
24
|
+
Object.entries(obj).forEach(([key, value]) => {
|
|
25
|
+
this.data[key] = value
|
|
26
|
+
})
|
|
27
|
+
|
|
20
28
|
window.localStorage.setItem('ArtalkUser', JSON.stringify(this.data))
|
|
29
|
+
this.ctx.trigger('user-changed', this.ctx.user.data)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** 注销,清除用户登录状态 */
|
|
33
|
+
public logout() {
|
|
34
|
+
this.update({
|
|
35
|
+
token: '',
|
|
36
|
+
isAdmin: false
|
|
37
|
+
})
|
|
21
38
|
}
|
|
22
39
|
|
|
23
40
|
/** 是否已填写基本用户信息 */
|
|
24
|
-
public checkHasBasicUserInfo
|
|
41
|
+
public checkHasBasicUserInfo() {
|
|
25
42
|
return !!this.data.nick && !!this.data.email
|
|
26
43
|
}
|
|
27
44
|
}
|
package/src/lib/utils.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { marked as libMarked } from 'marked'
|
|
2
2
|
import insane from 'insane'
|
|
3
3
|
import hanabi from 'hanabi'
|
|
4
|
-
import Context from '
|
|
4
|
+
import Context from '~/types/context'
|
|
5
5
|
|
|
6
6
|
export function createElement<E extends HTMLElement = HTMLElement>(htmlStr: string = ''): E {
|
|
7
7
|
const div = document.createElement('div')
|
|
@@ -58,7 +58,7 @@ export function dateFormat(date: Date) {
|
|
|
58
58
|
return `${vYear}-${vMonth}-${vDay}`
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
export function timeAgo(date: Date) {
|
|
61
|
+
export function timeAgo(date: Date, ctx: Context) {
|
|
62
62
|
try {
|
|
63
63
|
const oldTime = date.getTime()
|
|
64
64
|
const currTime = new Date().getTime()
|
|
@@ -77,16 +77,16 @@ export function timeAgo(date: Date) {
|
|
|
77
77
|
// 计算相差秒数
|
|
78
78
|
const leave3 = leave2 % (60 * 1000) // 计算分钟数后剩余的毫秒数
|
|
79
79
|
const seconds = Math.round(leave3 / 1000)
|
|
80
|
-
return `${seconds}
|
|
80
|
+
return `${seconds} ${ctx.$t('seconds')}`
|
|
81
81
|
}
|
|
82
|
-
return `${minutes}
|
|
82
|
+
return `${minutes} ${ctx.$t('minutes')}`
|
|
83
83
|
}
|
|
84
|
-
return `${hours}
|
|
84
|
+
return `${hours} ${ctx.$t('hours')}`
|
|
85
85
|
}
|
|
86
|
-
if (days < 0) return '
|
|
86
|
+
if (days < 0) return ctx.$t('now')
|
|
87
87
|
|
|
88
88
|
if (days < 8) {
|
|
89
|
-
return `${days}
|
|
89
|
+
return `${days} ${ctx.$t('days')}`
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
return dateFormat(date)
|
|
@@ -118,7 +118,7 @@ export function onImagesLoaded($container: HTMLElement, event: Function) {
|
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
export function getGravatarURL(ctx: Context, emailMD5: string) {
|
|
121
|
-
return `${(ctx.conf.gravatar
|
|
121
|
+
return `${(ctx.conf.gravatar.mirror).replace(/\/$/, '')}/${emailMD5}?d=${encodeURIComponent(ctx.conf.gravatar.default)}&s=80`
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
export function sleep(ms: number) {
|
|
@@ -142,6 +142,8 @@ export function versionCompare(a: string, b: string) {
|
|
|
142
142
|
|
|
143
143
|
/** 初始化 marked */
|
|
144
144
|
export function initMarked(ctx: Context) {
|
|
145
|
+
try { if (!libMarked.name) return } catch { return }
|
|
146
|
+
|
|
145
147
|
const renderer = new libMarked.Renderer()
|
|
146
148
|
const orgLinkRenderer = renderer.link
|
|
147
149
|
renderer.link = (href, title, text) => {
|
|
@@ -186,9 +188,21 @@ export function initMarked(ctx: Context) {
|
|
|
186
188
|
|
|
187
189
|
/** 解析 markdown */
|
|
188
190
|
export function marked(ctx: Context, src: string): string {
|
|
191
|
+
let markedContent = ctx.markedInstance?.parse(src)
|
|
192
|
+
if (!markedContent) {
|
|
193
|
+
// 无 Markdown 模式简单处理
|
|
194
|
+
markedContent = src
|
|
195
|
+
// .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
|
196
|
+
.replace(/```\s*([^]+?.*?[^]+?[^]+?)```/g, (_, code) => `<pre><code>${hanabi(code)}</code></pre>`)
|
|
197
|
+
// .replace(/`([^`]+?)`/g, '<code>$1</code>')
|
|
198
|
+
.replace(/!\[(.*?)\]\((.*?)\)/g, (_, alt, imgSrc) => `<img src="${imgSrc}" alt="${alt}" />`)
|
|
199
|
+
.replace(/\[(.*?)\]\((.*?)\)/g, (_, text, link) => `<a href="${link}" target="_blank">${text}</a>`)
|
|
200
|
+
.replace(/\n/g, '<br>')
|
|
201
|
+
}
|
|
202
|
+
|
|
189
203
|
// @link https://github.com/markedjs/marked/discussions/1232
|
|
190
204
|
// @link https://gist.github.com/lionel-rowe/bb384465ba4e4c81a9c8dada84167225
|
|
191
|
-
let dest = insane(
|
|
205
|
+
let dest = insane(markedContent, {
|
|
192
206
|
allowedClasses: {},
|
|
193
207
|
allowedSchemes: ['http', 'https', 'mailto'],
|
|
194
208
|
allowedTags: [
|