artalk 2.7.1 → 2.7.3
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/LICENSE +21 -0
- package/README.md +1 -1
- package/dist/Artalk.cjs +2 -0
- package/dist/Artalk.cjs.map +1 -0
- package/dist/Artalk.css +1 -1
- package/dist/Artalk.iife.js +1 -1
- package/dist/Artalk.iife.js.map +1 -1
- package/dist/Artalk.js +1 -1
- package/dist/Artalk.js.map +1 -1
- package/dist/{Artalk.es.js → Artalk.mjs} +250 -222
- package/dist/Artalk.mjs.map +1 -0
- package/dist/ArtalkLite.cjs +2 -0
- package/dist/ArtalkLite.cjs.map +1 -0
- package/dist/ArtalkLite.css +1 -1
- package/dist/ArtalkLite.iife.js +1 -1
- package/dist/ArtalkLite.iife.js.map +1 -1
- package/dist/ArtalkLite.js +1 -1
- package/dist/ArtalkLite.js.map +1 -1
- package/dist/{ArtalkLite.es.js → ArtalkLite.mjs} +250 -227
- package/dist/ArtalkLite.mjs.map +1 -0
- package/dist/i18n/jp.cjs +1 -0
- package/dist/i18n/jp.d.cts +3 -0
- package/dist/i18n/jp.d.ts +3 -0
- package/dist/i18n/jp.js +1 -1
- package/dist/i18n/jp.mjs +93 -0
- package/dist/i18n/zh-TW.cjs +1 -0
- package/dist/i18n/zh-TW.d.cts +3 -0
- package/dist/i18n/zh-TW.d.ts +3 -0
- package/dist/i18n/zh-TW.js +1 -1
- package/dist/i18n/zh-TW.mjs +93 -0
- package/dist/main.d.cts +1476 -0
- package/dist/main.d.ts +1476 -0
- package/package.json +80 -22
- package/dist/Artalk.es.js.map +0 -1
- package/dist/ArtalkLite.es.js.map +0 -1
- package/dist/src/api/_base.d.ts +0 -10
- package/dist/src/api/_options.d.ts +0 -19
- package/dist/src/api/_request.d.ts +0 -12
- package/dist/src/api/admin.d.ts +0 -10
- package/dist/src/api/captcha.d.ts +0 -14
- package/dist/src/api/comment.d.ts +0 -29
- package/dist/src/api/index.d.ts +0 -29
- package/dist/src/api/page.d.ts +0 -22
- package/dist/src/api/site.d.ts +0 -17
- package/dist/src/api/system.d.ts +0 -33
- package/dist/src/api/upload.d.ts +0 -11
- package/dist/src/api/user.d.ts +0 -42
- package/dist/src/artalk.d.ts +0 -35
- package/dist/src/comment/actions.d.ts +0 -17
- package/dist/src/comment/comment.d.ts +0 -66
- package/dist/src/comment/height-limit.d.ts +0 -30
- package/dist/src/comment/index.d.ts +0 -2
- package/dist/src/comment/render.d.ts +0 -46
- package/dist/src/comment/renders/actions.d.ts +0 -5
- package/dist/src/comment/renders/avatar.d.ts +0 -5
- package/dist/src/comment/renders/content.d.ts +0 -5
- package/dist/src/comment/renders/header.d.ts +0 -5
- package/dist/src/comment/renders/index.d.ts +0 -2
- package/dist/src/comment/renders/pending.d.ts +0 -5
- package/dist/src/comment/renders/reply-at.d.ts +0 -5
- package/dist/src/comment/renders/reply-to.d.ts +0 -5
- package/dist/src/components/action-btn.d.ts +0 -50
- package/dist/src/components/checker/admin.d.ts +0 -3
- package/dist/src/components/checker/captcha-renders.d.ts +0 -5
- package/dist/src/components/checker/captcha.d.ts +0 -3
- package/dist/src/components/checker/index.d.ts +0 -51
- package/dist/src/components/dark-mode.d.ts +0 -2
- package/dist/src/components/dialog.d.ts +0 -16
- package/dist/src/components/error-dialog.d.ts +0 -8
- package/dist/src/components/pagination.d.ts +0 -31
- package/dist/src/components/read-more-btn.d.ts +0 -33
- package/dist/src/config.d.ts +0 -24
- package/dist/src/context.d.ts +0 -45
- package/dist/src/data.d.ts +0 -27
- package/dist/src/defaults.d.ts +0 -3
- package/dist/src/editor/editor.d.ts +0 -31
- package/dist/src/editor/state.d.ts +0 -20
- package/dist/src/editor/ui.d.ts +0 -25
- package/dist/src/i18n/en.d.ts +0 -72
- package/dist/src/i18n/external.d.ts +0 -73
- package/dist/src/i18n/index.d.ts +0 -233
- package/dist/src/i18n/jp.d.ts +0 -72
- package/dist/src/i18n/zh-CN.d.ts +0 -3
- package/dist/src/i18n/zh-TW.d.ts +0 -72
- package/dist/src/layer/index.d.ts +0 -4
- package/dist/src/layer/layer-manager.d.ts +0 -9
- package/dist/src/layer/layer.d.ts +0 -12
- package/dist/src/layer/scrollbar-helper.d.ts +0 -5
- package/dist/src/layer/sidebar-layer.d.ts +0 -20
- package/dist/src/layer/wrap.d.ts +0 -13
- package/dist/src/lib/component.d.ts +0 -7
- package/dist/src/lib/detect.d.ts +0 -10
- package/dist/src/lib/event-manager.d.ts +0 -28
- package/dist/src/lib/highlight.d.ts +0 -1
- package/dist/src/lib/marked-renderer.d.ts +0 -4
- package/dist/src/lib/marked.d.ts +0 -11
- package/dist/src/lib/sanitizer.d.ts +0 -1
- package/dist/src/lib/ui.d.ts +0 -24
- package/dist/src/lib/user.d.ts +0 -19
- package/dist/src/lib/utils.d.ts +0 -41
- package/dist/src/list/comment.d.ts +0 -3
- package/dist/src/list/layout.d.ts +0 -23
- package/dist/src/list/list.d.ts +0 -14
- package/dist/src/list/nest.d.ts +0 -10
- package/dist/src/list/page.d.ts +0 -2
- package/dist/src/list/paginator/index.d.ts +0 -25
- package/dist/src/list/paginator/read-more.d.ts +0 -18
- package/dist/src/list/paginator/up-down.d.ts +0 -14
- package/dist/src/main.d.ts +0 -7
- package/dist/src/plugins/admin-only-elem.d.ts +0 -2
- package/dist/src/plugins/conf-remoter.d.ts +0 -2
- package/dist/src/plugins/dark-mode.d.ts +0 -2
- package/dist/src/plugins/editor/_kit.d.ts +0 -27
- package/dist/src/plugins/editor/_plug.d.ts +0 -29
- package/dist/src/plugins/editor/_sample.d.ts +0 -5
- package/dist/src/plugins/editor/closable.d.ts +0 -7
- package/dist/src/plugins/editor/emoticons.d.ts +0 -30
- package/dist/src/plugins/editor/header-event.d.ts +0 -6
- package/dist/src/plugins/editor/header-link.d.ts +0 -6
- package/dist/src/plugins/editor/header-user.d.ts +0 -16
- package/dist/src/plugins/editor/index.d.ts +0 -6
- package/dist/src/plugins/editor/local-storage.d.ts +0 -6
- package/dist/src/plugins/editor/mover.d.ts +0 -6
- package/dist/src/plugins/editor/preview.d.ts +0 -7
- package/dist/src/plugins/editor/state-edit.d.ts +0 -11
- package/dist/src/plugins/editor/state-reply.d.ts +0 -8
- package/dist/src/plugins/editor/submit-add.d.ts +0 -18
- package/dist/src/plugins/editor/submit-btn.d.ts +0 -5
- package/dist/src/plugins/editor/submit.d.ts +0 -17
- package/dist/src/plugins/editor/textarea.d.ts +0 -8
- package/dist/src/plugins/editor/upload.d.ts +0 -9
- package/dist/src/plugins/editor-kit.d.ts +0 -42
- package/dist/src/plugins/index.d.ts +0 -2
- package/dist/src/plugins/list/copyright.d.ts +0 -2
- package/dist/src/plugins/list/count.d.ts +0 -2
- package/dist/src/plugins/list/dropdown.d.ts +0 -2
- package/dist/src/plugins/list/error-dialog.d.ts +0 -2
- package/dist/src/plugins/list/fetch.d.ts +0 -2
- package/dist/src/plugins/list/goto-first.d.ts +0 -3
- package/dist/src/plugins/list/goto.d.ts +0 -2
- package/dist/src/plugins/list/index.d.ts +0 -3
- package/dist/src/plugins/list/loading.d.ts +0 -2
- package/dist/src/plugins/list/no-comment.d.ts +0 -2
- package/dist/src/plugins/list/reach-bottom.d.ts +0 -2
- package/dist/src/plugins/list/sidebar-btn.d.ts +0 -2
- package/dist/src/plugins/list/time-ticking.d.ts +0 -3
- package/dist/src/plugins/list/unread-badge.d.ts +0 -2
- package/dist/src/plugins/list/unread.d.ts +0 -2
- package/dist/src/plugins/list/with-editor.d.ts +0 -2
- package/dist/src/plugins/markdown.d.ts +0 -2
- package/dist/src/plugins/stat.d.ts +0 -13
- package/dist/src/plugins/version-check.d.ts +0 -2
- package/dist/src/service.d.ts +0 -42
- package/dist/tests/e2e/comment-add.spec.d.ts +0 -1
- package/dist/types/config.d.ts +0 -110
- package/dist/types/context.d.ts +0 -62
- package/dist/types/data.d.ts +0 -194
- package/dist/types/editor.d.ts +0 -76
- package/dist/types/event.d.ts +0 -39
- package/dist/types/global.d.ts +0 -1
- package/dist/types/index.d.ts +0 -7
- package/dist/types/plugin.d.ts +0 -2
- package/dist/types/sidebar.d.ts +0 -3
- package/src/api/_base.ts +0 -18
- package/src/api/_options.ts +0 -25
- package/src/api/_request.ts +0 -104
- package/src/api/admin.ts +0 -18
- package/src/api/captcha.ts +0 -24
- package/src/api/comment.ts +0 -84
- package/src/api/index.ts +0 -29
- package/src/api/page.ts +0 -83
- package/src/api/site.ts +0 -51
- package/src/api/system.ts +0 -49
- package/src/api/upload.ts +0 -28
- package/src/api/user.ts +0 -132
- package/src/artalk.ts +0 -115
- package/src/comment/actions.ts +0 -83
- package/src/comment/comment.html +0 -18
- package/src/comment/comment.ts +0 -192
- package/src/comment/height-limit.ts +0 -100
- package/src/comment/index.ts +0 -3
- package/src/comment/render.ts +0 -150
- package/src/comment/renders/actions.ts +0 -114
- package/src/comment/renders/avatar.ts +0 -22
- package/src/comment/renders/content.ts +0 -39
- package/src/comment/renders/header.ts +0 -84
- package/src/comment/renders/index.ts +0 -19
- package/src/comment/renders/pending.ts +0 -12
- package/src/comment/renders/reply-at.ts +0 -16
- package/src/comment/renders/reply-to.ts +0 -24
- package/src/components/action-btn.ts +0 -170
- package/src/components/checker/admin.ts +0 -37
- package/src/components/checker/captcha-renders.ts +0 -69
- package/src/components/checker/captcha.ts +0 -26
- package/src/components/checker/index.ts +0 -192
- package/src/components/dark-mode.ts +0 -30
- package/src/components/dialog.ts +0 -62
- package/src/components/error-dialog.ts +0 -35
- package/src/components/pagination.ts +0 -153
- package/src/components/read-more-btn.ts +0 -92
- package/src/config.ts +0 -119
- package/src/context.ts +0 -146
- package/src/data.ts +0 -110
- package/src/defaults.ts +0 -61
- package/src/editor/editor.html +0 -18
- package/src/editor/editor.ts +0 -114
- package/src/editor/state.ts +0 -52
- package/src/editor/ui.ts +0 -36
- package/src/i18n/en.ts +0 -84
- package/src/i18n/external.ts +0 -10
- package/src/i18n/index.ts +0 -67
- package/src/i18n/jp.ts +0 -86
- package/src/i18n/zh-CN.ts +0 -88
- package/src/i18n/zh-TW.ts +0 -86
- package/src/layer/index.ts +0 -4
- package/src/layer/layer-manager.ts +0 -32
- package/src/layer/layer.ts +0 -39
- package/src/layer/scrollbar-helper.ts +0 -24
- package/src/layer/sidebar-layer.html +0 -8
- package/src/layer/sidebar-layer.ts +0 -155
- package/src/layer/wrap.ts +0 -77
- package/src/lib/component.ts +0 -12
- package/src/lib/detect.ts +0 -222
- package/src/lib/event-manager.ts +0 -47
- package/src/lib/highlight.ts +0 -5
- package/src/lib/marked-renderer.ts +0 -32
- package/src/lib/marked.ts +0 -72
- package/src/lib/sanitizer.ts +0 -57
- package/src/lib/ui.ts +0 -193
- package/src/lib/user.ts +0 -55
- package/src/lib/utils.ts +0 -236
- package/src/list/comment.ts +0 -20
- package/src/list/layout.ts +0 -120
- package/src/list/list.html +0 -20
- package/src/list/list.ts +0 -79
- package/src/list/nest.ts +0 -66
- package/src/list/page.ts +0 -97
- package/src/list/paginator/index.ts +0 -25
- package/src/list/paginator/read-more.ts +0 -67
- package/src/list/paginator/up-down.ts +0 -51
- package/src/main.ts +0 -14
- package/src/plugins/admin-only-elem.ts +0 -40
- package/src/plugins/conf-remoter.ts +0 -54
- package/src/plugins/dark-mode.ts +0 -12
- package/src/plugins/editor/_kit.ts +0 -57
- package/src/plugins/editor/_plug.ts +0 -60
- package/src/plugins/editor/_sample.ts +0 -8
- package/src/plugins/editor/closable.ts +0 -44
- package/src/plugins/editor/emoticons.scss +0 -72
- package/src/plugins/editor/emoticons.ts +0 -293
- package/src/plugins/editor/header-event.ts +0 -35
- package/src/plugins/editor/header-link.ts +0 -31
- package/src/plugins/editor/header-user.ts +0 -98
- package/src/plugins/editor/index.ts +0 -40
- package/src/plugins/editor/local-storage.ts +0 -36
- package/src/plugins/editor/mover.ts +0 -29
- package/src/plugins/editor/preview.scss +0 -7
- package/src/plugins/editor/preview.ts +0 -41
- package/src/plugins/editor/state-edit.ts +0 -116
- package/src/plugins/editor/state-reply.ts +0 -91
- package/src/plugins/editor/submit-add.ts +0 -33
- package/src/plugins/editor/submit-btn.ts +0 -25
- package/src/plugins/editor/submit.ts +0 -73
- package/src/plugins/editor/textarea.ts +0 -55
- package/src/plugins/editor/upload.ts +0 -145
- package/src/plugins/editor-kit.ts +0 -161
- package/src/plugins/index.ts +0 -16
- package/src/plugins/list/copyright.ts +0 -16
- package/src/plugins/list/count.ts +0 -31
- package/src/plugins/list/dropdown.ts +0 -95
- package/src/plugins/list/error-dialog.ts +0 -22
- package/src/plugins/list/fetch.ts +0 -64
- package/src/plugins/list/goto-first.ts +0 -23
- package/src/plugins/list/goto.ts +0 -72
- package/src/plugins/list/index.ts +0 -25
- package/src/plugins/list/loading.ts +0 -17
- package/src/plugins/list/no-comment.ts +0 -25
- package/src/plugins/list/reach-bottom.ts +0 -46
- package/src/plugins/list/sidebar-btn.ts +0 -39
- package/src/plugins/list/time-ticking.ts +0 -22
- package/src/plugins/list/unread-badge.ts +0 -26
- package/src/plugins/list/unread.ts +0 -38
- package/src/plugins/list/with-editor.ts +0 -59
- package/src/plugins/markdown.ts +0 -10
- package/src/plugins/stat.ts +0 -82
- package/src/plugins/version-check.ts +0 -36
- package/src/service.ts +0 -91
- package/src/style/_color.scss +0 -41
- package/src/style/_extend.scss +0 -20
- package/src/style/_variables.scss +0 -3
- package/src/style/comment.scss +0 -360
- package/src/style/editor.scss +0 -194
- package/src/style/list.scss +0 -212
- package/src/style/main.scss +0 -486
- package/src/style/pagination.scss +0 -58
- package/src/style/sidebar-layer.scss +0 -92
- package/types/config.ts +0 -153
- package/types/context.ts +0 -84
- package/types/data.ts +0 -271
- package/types/editor.ts +0 -95
- package/types/event.ts +0 -40
- package/types/global.d.ts +0 -1
- package/types/index.ts +0 -7
- package/types/plugin.ts +0 -3
- package/types/sidebar.ts +0 -3
|
@@ -1,293 +0,0 @@
|
|
|
1
|
-
import './emoticons.scss'
|
|
2
|
-
|
|
3
|
-
import type { EmoticonListData, EmoticonGrpData } from '~/types'
|
|
4
|
-
import * as Utils from '@/lib/utils'
|
|
5
|
-
import * as Ui from '@/lib/ui'
|
|
6
|
-
import $t from '@/i18n'
|
|
7
|
-
import EditorPlug from './_plug'
|
|
8
|
-
import type PlugKit from './_kit'
|
|
9
|
-
|
|
10
|
-
type OwOFormatType = {
|
|
11
|
-
[key: string] : {
|
|
12
|
-
type: 'emoticon'|'emoji'|'image',
|
|
13
|
-
container: {icon: string, text: string}[]
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export default class Emoticons extends EditorPlug {
|
|
18
|
-
private emoticons: EmoticonListData = []
|
|
19
|
-
private loadingTask: Promise<void>|null = null
|
|
20
|
-
|
|
21
|
-
private $grpWrap!: HTMLElement
|
|
22
|
-
private $grpSwitcher!: HTMLElement
|
|
23
|
-
|
|
24
|
-
constructor(kit: PlugKit) {
|
|
25
|
-
super(kit)
|
|
26
|
-
|
|
27
|
-
this.kit.useMounted(() => {
|
|
28
|
-
this.usePanel(`<div class="atk-editor-plug-emoticons"></div>`)
|
|
29
|
-
this.useBtn($t('emoticon'))
|
|
30
|
-
})
|
|
31
|
-
this.kit.useUnmounted(() => {})
|
|
32
|
-
|
|
33
|
-
this.useContentTransformer((raw) => this.transEmoticonImageText(raw))
|
|
34
|
-
this.usePanelShow(() => {
|
|
35
|
-
;(async () => {
|
|
36
|
-
await this.loadEmoticonsData()
|
|
37
|
-
|
|
38
|
-
// 初始化元素
|
|
39
|
-
if (!this.isImgLoaded) {
|
|
40
|
-
this.initEmoticonsList()
|
|
41
|
-
this.isImgLoaded = true
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// 延迟执行,防止无法读取高度
|
|
45
|
-
setTimeout(() => {
|
|
46
|
-
this.changeListHeight()
|
|
47
|
-
}, 30)
|
|
48
|
-
})()
|
|
49
|
-
})
|
|
50
|
-
this.usePanelHide(() => {
|
|
51
|
-
this.$panel!.parentElement!.style.height = ''
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
// 表情包预加载
|
|
55
|
-
window.setTimeout(() => {
|
|
56
|
-
this.loadEmoticonsData()
|
|
57
|
-
}, 1000) // 延迟 1s 加载
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
private isListLoaded = false
|
|
61
|
-
private isImgLoaded = false
|
|
62
|
-
|
|
63
|
-
public async loadEmoticonsData() {
|
|
64
|
-
if (this.isListLoaded) return
|
|
65
|
-
if (this.loadingTask !== null) {
|
|
66
|
-
await this.loadingTask
|
|
67
|
-
return
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// 数据处理
|
|
71
|
-
this.loadingTask = (async () => {
|
|
72
|
-
Ui.showLoading(this.$panel!)
|
|
73
|
-
this.emoticons = await this.handleData(this.kit.useConf().emoticons)
|
|
74
|
-
Ui.hideLoading(this.$panel!)
|
|
75
|
-
this.loadingTask = null
|
|
76
|
-
this.isListLoaded = true
|
|
77
|
-
})()
|
|
78
|
-
await this.loadingTask
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
private async handleData(data: any): Promise<any[]> {
|
|
82
|
-
if (!Array.isArray(data) && ['object', 'string'].includes(typeof data)) {
|
|
83
|
-
data = [data]
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (!Array.isArray(data)) {
|
|
87
|
-
Ui.setError(this.$panel!, "表情包数据必须为 Array/Object/String 类型")
|
|
88
|
-
Ui.hideLoading(this.$panel!)
|
|
89
|
-
return []
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const pushGrp = (grp: EmoticonGrpData) => {
|
|
93
|
-
if (typeof grp !== 'object') return
|
|
94
|
-
if (grp.name && data.find(o => o.name === grp.name)) return
|
|
95
|
-
data.push(grp)
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// 加载子内容
|
|
99
|
-
const remoteLoad = async (d: any[]) => {
|
|
100
|
-
await Promise.all(d.map(async (grp, index) => {
|
|
101
|
-
if (typeof grp === 'object' && !Array.isArray(grp)) {
|
|
102
|
-
pushGrp(grp)
|
|
103
|
-
} else if (Array.isArray(grp)) {
|
|
104
|
-
await remoteLoad(grp)
|
|
105
|
-
} else if (typeof grp === "string") {
|
|
106
|
-
const grpData = await this.remoteLoad(grp)
|
|
107
|
-
|
|
108
|
-
if (Array.isArray(grpData)) await remoteLoad(grpData)
|
|
109
|
-
else if (typeof grpData === 'object') pushGrp(grpData)
|
|
110
|
-
}
|
|
111
|
-
}))
|
|
112
|
-
}
|
|
113
|
-
await remoteLoad(data)
|
|
114
|
-
|
|
115
|
-
// 处理数组数据
|
|
116
|
-
data.forEach((item: any) => {
|
|
117
|
-
if (this.isOwOFormat(item)) {
|
|
118
|
-
const c = this.convertOwO(item)
|
|
119
|
-
c.forEach((grp) => { pushGrp(grp) })
|
|
120
|
-
} else if (Array.isArray(item)) {
|
|
121
|
-
item.forEach((grp) => { pushGrp(grp) })
|
|
122
|
-
}
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
// 剔除非法数据
|
|
126
|
-
data = data.filter((item: any) => (typeof item === 'object' && !Array.isArray(item) && !!item && !!item.name))
|
|
127
|
-
|
|
128
|
-
// console.log(data)
|
|
129
|
-
|
|
130
|
-
this.solveNullKey(data)
|
|
131
|
-
this.solveSameKey(data)
|
|
132
|
-
|
|
133
|
-
return data
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/** 远程加载 */
|
|
137
|
-
private async remoteLoad(url: string): Promise<any> {
|
|
138
|
-
if (!url) return []
|
|
139
|
-
|
|
140
|
-
try {
|
|
141
|
-
const resp = await fetch(url)
|
|
142
|
-
const json = await resp.json()
|
|
143
|
-
return json
|
|
144
|
-
} catch (err) {
|
|
145
|
-
Ui.hideLoading(this.$panel!)
|
|
146
|
-
Ui.setError(this.$panel!, `表情加载失败 ${String(err)}`)
|
|
147
|
-
return []
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/** 避免表情 item.key 为 null 的情况 */
|
|
152
|
-
private solveNullKey(data: EmoticonGrpData[]) {
|
|
153
|
-
data.forEach((grp) => {
|
|
154
|
-
grp.items.forEach((item, index) => {
|
|
155
|
-
if (!item.key) item.key = `${grp.name} ${index+1}`
|
|
156
|
-
})
|
|
157
|
-
})
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/** 避免相同 item.key */
|
|
161
|
-
private solveSameKey(data: EmoticonGrpData[]) {
|
|
162
|
-
const tmp: {[key: string]: number} = {}
|
|
163
|
-
data.forEach((grp) => {
|
|
164
|
-
grp.items.forEach(item => {
|
|
165
|
-
if (!item.key || String(item.key).trim() === "") return
|
|
166
|
-
if (!tmp[item.key]) tmp[item.key] = 1
|
|
167
|
-
else tmp[item.key]++
|
|
168
|
-
|
|
169
|
-
if (tmp[item.key] > 1) item.key = `${item.key} ${tmp[item.key]}`
|
|
170
|
-
})
|
|
171
|
-
})
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/** 判断是否为 OwO 格式 */
|
|
175
|
-
private isOwOFormat(data: any) {
|
|
176
|
-
try {
|
|
177
|
-
return (typeof data === 'object') && !!Object.values(data).length
|
|
178
|
-
&& Array.isArray(Object.keys(Object.values<any>(data)[0].container))
|
|
179
|
-
&& Object.keys(Object.values<any>(data)[0].container[0]).includes('icon')
|
|
180
|
-
} catch { return false }
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/** 转换 OwO 格式数据 */
|
|
184
|
-
private convertOwO(owoData: OwOFormatType): EmoticonListData {
|
|
185
|
-
const dest: EmoticonListData = []
|
|
186
|
-
Object.entries(owoData).forEach(([grpName, grp]) => {
|
|
187
|
-
const nGrp: EmoticonGrpData = { name: grpName, type: grp.type, items: [] }
|
|
188
|
-
grp.container.forEach((item, index) => {
|
|
189
|
-
// 图片标签提取 src 属性值
|
|
190
|
-
const iconStr = item.icon
|
|
191
|
-
if (/<(img|IMG)/.test(iconStr)) {
|
|
192
|
-
const find = /src=["'](.*?)["']/.exec(iconStr)
|
|
193
|
-
if (find && find.length > 1) item.icon = find[1]
|
|
194
|
-
}
|
|
195
|
-
nGrp.items.push({ key: item.text || `${grpName} ${index+1}`, val: item.icon })
|
|
196
|
-
})
|
|
197
|
-
dest.push(nGrp)
|
|
198
|
-
})
|
|
199
|
-
return dest
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/** 初始化表情列表界面 */
|
|
203
|
-
private initEmoticonsList() {
|
|
204
|
-
// 表情列表
|
|
205
|
-
this.$grpWrap = Utils.createElement(`<div class="atk-grp-wrap"></div>`)
|
|
206
|
-
this.$panel!.append(this.$grpWrap)
|
|
207
|
-
|
|
208
|
-
this.emoticons.forEach((grp, index) => {
|
|
209
|
-
const $grp = Utils.createElement(`<div class="atk-grp" style="display: none;"></div>`)
|
|
210
|
-
this.$grpWrap.append($grp)
|
|
211
|
-
$grp.setAttribute('data-index', String(index))
|
|
212
|
-
$grp.setAttribute('data-grp-name', grp.name)
|
|
213
|
-
$grp.setAttribute('data-type', grp.type)
|
|
214
|
-
grp.items.forEach((item) => {
|
|
215
|
-
const $item = Utils.createElement(`<span class="atk-item"></span>`)
|
|
216
|
-
$grp.append($item)
|
|
217
|
-
|
|
218
|
-
if (!!item.key && !(new RegExp(`^(${grp.name})?\\s?[0-9]+$`).test(item.key)))
|
|
219
|
-
$item.setAttribute('title', item.key)
|
|
220
|
-
|
|
221
|
-
if (grp.type === 'image') {
|
|
222
|
-
const imgEl = document.createElement('img')
|
|
223
|
-
imgEl.src = item.val
|
|
224
|
-
imgEl.alt = item.key
|
|
225
|
-
$item.append(imgEl)
|
|
226
|
-
} else {
|
|
227
|
-
$item.innerText = item.val
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
$item.onclick = () => {
|
|
231
|
-
if (grp.type === 'image') {
|
|
232
|
-
this.kit.useEditor().insertContent(`:[${item.key}]`)
|
|
233
|
-
} else {
|
|
234
|
-
this.kit.useEditor().insertContent(item.val || '')
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
})
|
|
238
|
-
})
|
|
239
|
-
|
|
240
|
-
// 表情分类切换 bar
|
|
241
|
-
if (this.emoticons.length > 1) {
|
|
242
|
-
this.$grpSwitcher = Utils.createElement(`<div class="atk-grp-switcher"></div>`)
|
|
243
|
-
this.$panel!.append(this.$grpSwitcher)
|
|
244
|
-
this.emoticons.forEach((grp, index) => {
|
|
245
|
-
const $item = Utils.createElement('<span />')
|
|
246
|
-
$item.innerText = grp.name
|
|
247
|
-
$item.setAttribute('data-index', String(index))
|
|
248
|
-
$item.onclick = () => (this.openGrp(index))
|
|
249
|
-
this.$grpSwitcher.append($item)
|
|
250
|
-
})
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// 默认打开第一个分类
|
|
254
|
-
if (this.emoticons.length > 0) this.openGrp(0)
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/** 打开一个表情组 */
|
|
258
|
-
public openGrp(index: number) {
|
|
259
|
-
Array.from(this.$grpWrap.children).forEach((item) => {
|
|
260
|
-
const el = item as HTMLElement
|
|
261
|
-
if (el.getAttribute('data-index') !== String(index)) {
|
|
262
|
-
el.style.display = 'none'
|
|
263
|
-
} else {
|
|
264
|
-
el.style.display = ''
|
|
265
|
-
}
|
|
266
|
-
})
|
|
267
|
-
|
|
268
|
-
this.$grpSwitcher?.querySelectorAll('span.active').forEach(item => item.classList.remove('active'))
|
|
269
|
-
this.$grpSwitcher?.querySelector(`span[data-index="${index}"]`)?.classList.add('active')
|
|
270
|
-
|
|
271
|
-
this.changeListHeight()
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
private changeListHeight() {
|
|
275
|
-
/* const listWrapHeight = Utils.getHeight(this.$grpWrapem)
|
|
276
|
-
this.editor.plugWrapEl.style.height = `${listWrapHeight > 150 ? listWrapHeight : 150}px` */
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
/** 处理评论 content 中的表情内容 */
|
|
280
|
-
public transEmoticonImageText(text: string) {
|
|
281
|
-
if (!this.emoticons || !Array.isArray(this.emoticons))
|
|
282
|
-
return text
|
|
283
|
-
|
|
284
|
-
this.emoticons.forEach((grp) => {
|
|
285
|
-
if (grp.type !== 'image') return
|
|
286
|
-
Object.entries(grp.items).forEach(([index, item]) => {
|
|
287
|
-
text = text.split(`:[${item.key}]`).join(`<img src="${item.val}" atk-emoticon="${item.key}">`) // replaceAll(...)
|
|
288
|
-
})
|
|
289
|
-
})
|
|
290
|
-
|
|
291
|
-
return text
|
|
292
|
-
}
|
|
293
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import EditorPlug from './_plug'
|
|
2
|
-
import type PlugKit from './_kit'
|
|
3
|
-
|
|
4
|
-
export default class HeaderEvent extends EditorPlug {
|
|
5
|
-
private get $inputs() {
|
|
6
|
-
return this.kit.useEditor().getHeaderInputEls()
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
constructor(kit: PlugKit) {
|
|
10
|
-
super(kit)
|
|
11
|
-
|
|
12
|
-
const inputEventFns: {[name: string]: () => void} = {}
|
|
13
|
-
const changeEventFns: {[name: string]: () => void} = {}
|
|
14
|
-
|
|
15
|
-
const trigger = (evt: 'header-input'|'header-change', $input: HTMLInputElement, field: string) => () => {
|
|
16
|
-
this.kit.useEvents().trigger(evt, { field, $input })
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
this.kit.useMounted(() => {
|
|
20
|
-
// batch bind the events
|
|
21
|
-
Object.entries(this.$inputs).forEach(([key, $input]) => {
|
|
22
|
-
$input.addEventListener('input', inputEventFns[key] = trigger('header-input', $input, key))
|
|
23
|
-
$input.addEventListener('change', changeEventFns[key] = trigger('header-change', $input, key))
|
|
24
|
-
})
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
this.kit.useUnmounted(() => {
|
|
28
|
-
// unmount the event
|
|
29
|
-
Object.entries(this.$inputs).forEach(([key, $input]) => {
|
|
30
|
-
$input.removeEventListener('input', inputEventFns[key])
|
|
31
|
-
$input.removeEventListener('change', changeEventFns[key])
|
|
32
|
-
})
|
|
33
|
-
})
|
|
34
|
-
}
|
|
35
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import User from '@/lib/user'
|
|
2
|
-
import EditorPlug from './_plug'
|
|
3
|
-
import type PlugKit from './_kit'
|
|
4
|
-
|
|
5
|
-
export default class HeaderLink extends EditorPlug {
|
|
6
|
-
constructor(kit: PlugKit) {
|
|
7
|
-
super(kit)
|
|
8
|
-
|
|
9
|
-
const onLinkChange = ({ field }: { field: string }) => {
|
|
10
|
-
if (field === 'link') this.onLinkInputChange()
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
// bind events
|
|
14
|
-
this.kit.useMounted(() => {
|
|
15
|
-
this.kit.useEvents().on('header-change', onLinkChange)
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
this.kit.useUnmounted(() => {
|
|
19
|
-
this.kit.useEvents().off('header-change', onLinkChange)
|
|
20
|
-
})
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
private onLinkInputChange() {
|
|
24
|
-
// auto and force add protocol prefix for user input link
|
|
25
|
-
const link = this.kit.useUI().$link.value.trim()
|
|
26
|
-
if (!!link && !/^(http|https):\/\//.test(link)) {
|
|
27
|
-
this.kit.useUI().$link.value = `https://${link}`
|
|
28
|
-
User.update({ link: this.kit.useUI().$link.value })
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import User from '@/lib/user'
|
|
2
|
-
import $t from '@/i18n'
|
|
3
|
-
import EditorPlug from './_plug'
|
|
4
|
-
import type PlugKit from './_kit'
|
|
5
|
-
|
|
6
|
-
export default class HeaderUser extends EditorPlug {
|
|
7
|
-
constructor(kit: PlugKit) {
|
|
8
|
-
super(kit)
|
|
9
|
-
|
|
10
|
-
const onInput = ({ $input, field }: { $input: HTMLInputElement, field: string }) => {
|
|
11
|
-
if (this.kit.useEditor().getState() === 'edit')
|
|
12
|
-
return // TODO prevent execute when editing, since update comment.user not support
|
|
13
|
-
|
|
14
|
-
// update user data
|
|
15
|
-
User.update({ [field]: $input.value.trim() })
|
|
16
|
-
|
|
17
|
-
// remote fetch user info
|
|
18
|
-
if (field === 'nick' || field === 'email')
|
|
19
|
-
this.fetchUserInfo() // must after update user data, since fetchUserInfo() will use User.data
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
this.kit.useMounted(() => {
|
|
23
|
-
Object.entries(this.kit.useEditor().getHeaderInputEls())
|
|
24
|
-
.forEach(([key, $input]) => {
|
|
25
|
-
// set placeholder
|
|
26
|
-
$input.placeholder = `${$t(key as any)}`
|
|
27
|
-
|
|
28
|
-
// sync header values from User.data
|
|
29
|
-
$input.value = User.data[key] || ''
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
// bind events
|
|
33
|
-
this.kit.useEvents().on('header-input', onInput)
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
this.kit.useUnmounted(() => {
|
|
37
|
-
this.kit.useEvents().off('header-input', onInput)
|
|
38
|
-
})
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
private query = {
|
|
42
|
-
timer: <number|null>null,
|
|
43
|
-
abortFn: <(() => void)|null>null
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Fetch user info from server
|
|
48
|
-
*/
|
|
49
|
-
private fetchUserInfo() {
|
|
50
|
-
User.logout() // clear login status
|
|
51
|
-
|
|
52
|
-
if (this.query.timer) window.clearTimeout(this.query.timer) // clear the not executed timeout task
|
|
53
|
-
if (this.query.abortFn) this.query.abortFn() // abort the last request (if request is pending not finished)
|
|
54
|
-
|
|
55
|
-
this.query.timer = window.setTimeout(() => {
|
|
56
|
-
this.query.timer = null // clear the timer (clarify the timer is executing)
|
|
57
|
-
|
|
58
|
-
const {req, abort} = this.kit.useApi().user.userGet(
|
|
59
|
-
User.data.nick, User.data.email
|
|
60
|
-
)
|
|
61
|
-
this.query.abortFn = abort
|
|
62
|
-
req.then(data => this.onUserInfoFetched(data))
|
|
63
|
-
.catch((err) => {})
|
|
64
|
-
.finally(() => {
|
|
65
|
-
this.query.abortFn = null // clear the abort function (clarify the request is finished)
|
|
66
|
-
})
|
|
67
|
-
}, 400) // delay to reduce request
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Function called when user info fetched
|
|
72
|
-
*
|
|
73
|
-
* @param data The response data from server
|
|
74
|
-
*/
|
|
75
|
-
private onUserInfoFetched(
|
|
76
|
-
data: any // TODO fix type
|
|
77
|
-
) {
|
|
78
|
-
// If api response is not login, logout
|
|
79
|
-
if (!data.is_login) User.logout()
|
|
80
|
-
|
|
81
|
-
// Update unread notifies
|
|
82
|
-
this.kit.useGlobalCtx().getData().updateUnreads(data.unread)
|
|
83
|
-
|
|
84
|
-
// If user is admin and not login,
|
|
85
|
-
if (User.checkHasBasicUserInfo() && !data.is_login && data.user?.is_admin) {
|
|
86
|
-
// then show login window
|
|
87
|
-
this.kit.useGlobalCtx().checkAdmin({
|
|
88
|
-
onSuccess: () => {}
|
|
89
|
-
})
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Auto fill user link from server
|
|
93
|
-
if (data.user && data.user.link) {
|
|
94
|
-
this.kit.useUI().$link.value = data.user.link
|
|
95
|
-
User.update({ link: data.user.link })
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import type { ArtalkConfig } from '~/types'
|
|
2
|
-
import type EditorPlug from './_plug'
|
|
3
|
-
import LocalStorage from './local-storage'
|
|
4
|
-
import Textarea from './textarea'
|
|
5
|
-
import SubmitBtn from './submit-btn'
|
|
6
|
-
import Submit from './submit'
|
|
7
|
-
import StateReply from './state-reply'
|
|
8
|
-
import StateEdit from './state-edit'
|
|
9
|
-
import Closable from './closable'
|
|
10
|
-
import HeaderEvent from './header-event'
|
|
11
|
-
import HeaderUser from './header-user'
|
|
12
|
-
import HeaderLink from './header-link'
|
|
13
|
-
import Mover from './mover'
|
|
14
|
-
import Emoticons from './emoticons'
|
|
15
|
-
import Upload from './upload'
|
|
16
|
-
import Preview from './preview'
|
|
17
|
-
|
|
18
|
-
/** The default enabled plugs */
|
|
19
|
-
export const ENABLED_PLUGS: (typeof EditorPlug)[] = [
|
|
20
|
-
// Core
|
|
21
|
-
LocalStorage,
|
|
22
|
-
HeaderEvent, HeaderUser, HeaderLink,
|
|
23
|
-
Textarea,
|
|
24
|
-
Submit, SubmitBtn,
|
|
25
|
-
Mover, StateReply, StateEdit,
|
|
26
|
-
Closable,
|
|
27
|
-
|
|
28
|
-
// Extensions
|
|
29
|
-
Emoticons, Upload, Preview
|
|
30
|
-
]
|
|
31
|
-
|
|
32
|
-
/** Get the name list of disabled plugs */
|
|
33
|
-
export function getDisabledPlugByConf(conf: ArtalkConfig): (typeof EditorPlug)[] {
|
|
34
|
-
return [
|
|
35
|
-
{k: Upload, v: conf.imgUpload},
|
|
36
|
-
{k: Emoticons, v: conf.emoticons},
|
|
37
|
-
{k: Preview, v: conf.preview},
|
|
38
|
-
{k: Mover, v: conf.editorTravel},
|
|
39
|
-
].filter(n => !n.v).flatMap(n => n.k)
|
|
40
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import $t from '@/i18n'
|
|
2
|
-
import type PlugKit from './_kit'
|
|
3
|
-
import EditorPlug from './_plug'
|
|
4
|
-
|
|
5
|
-
const LocalStorageKey = 'ArtalkContent'
|
|
6
|
-
|
|
7
|
-
export default class LocalStorage extends EditorPlug {
|
|
8
|
-
constructor(kit: PlugKit) {
|
|
9
|
-
super(kit)
|
|
10
|
-
|
|
11
|
-
const onContentUpdated = () => {
|
|
12
|
-
this.save()
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
this.kit.useMounted(() => {
|
|
16
|
-
// load editor content from localStorage when init
|
|
17
|
-
const localContent = window.localStorage.getItem(LocalStorageKey) || ''
|
|
18
|
-
if (localContent.trim() !== '') {
|
|
19
|
-
this.kit.useEditor().showNotify($t('restoredMsg'), 'i')
|
|
20
|
-
this.kit.useEditor().setContent(localContent)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// bind event
|
|
24
|
-
this.kit.useEvents().on('content-updated', onContentUpdated)
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
this.kit.useUnmounted(() => {
|
|
28
|
-
this.kit.useEvents().off('content-updated', onContentUpdated)
|
|
29
|
-
})
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Save editor content to localStorage
|
|
33
|
-
public save() {
|
|
34
|
-
window.localStorage.setItem(LocalStorageKey, this.kit.useEditor().getContentRaw().trim())
|
|
35
|
-
}
|
|
36
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import * as Utils from '@/lib/utils'
|
|
2
|
-
import EditorPlug from './_plug'
|
|
3
|
-
|
|
4
|
-
export default class Mover extends EditorPlug {
|
|
5
|
-
private isMoved = false
|
|
6
|
-
|
|
7
|
-
move(afterEl: HTMLElement) {
|
|
8
|
-
if (this.isMoved) return
|
|
9
|
-
this.isMoved = true
|
|
10
|
-
|
|
11
|
-
const editorEl = this.kit.useUI().$el
|
|
12
|
-
|
|
13
|
-
editorEl.after(Utils.createElement('<div class="atk-editor-travel-placeholder"></div>'))
|
|
14
|
-
|
|
15
|
-
const $travelPlace = Utils.createElement('<div></div>')
|
|
16
|
-
afterEl.after($travelPlace)
|
|
17
|
-
$travelPlace.replaceWith(editorEl)
|
|
18
|
-
|
|
19
|
-
editorEl.classList.add('atk-fade-in') // 添加渐入动画
|
|
20
|
-
editorEl.classList.add('editor-traveling')
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
back() {
|
|
24
|
-
if (!this.isMoved) return
|
|
25
|
-
this.isMoved = false
|
|
26
|
-
this.kit.useGlobalCtx().$root.querySelector('.atk-editor-travel-placeholder')?.replaceWith(this.kit.useUI().$el)
|
|
27
|
-
this.kit.useUI().$el.classList.remove('editor-traveling')
|
|
28
|
-
}
|
|
29
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import './preview.scss'
|
|
2
|
-
|
|
3
|
-
import * as marked from '@/lib/marked'
|
|
4
|
-
import $t from '@/i18n'
|
|
5
|
-
import EditorPlug from './_plug'
|
|
6
|
-
import type PlugKit from './_kit'
|
|
7
|
-
|
|
8
|
-
export default class Preview extends EditorPlug {
|
|
9
|
-
private isPlugPanelShow = false
|
|
10
|
-
|
|
11
|
-
constructor(kit: PlugKit) {
|
|
12
|
-
super(kit)
|
|
13
|
-
|
|
14
|
-
this.kit.useMounted(() => {
|
|
15
|
-
this.usePanel(`<div class="atk-editor-plug-preview"></div>`)
|
|
16
|
-
|
|
17
|
-
// initialize plug button
|
|
18
|
-
let btnText = $t('preview')
|
|
19
|
-
if (marked.getInstance()) btnText += ` <i title="Markdown is supported"><svg class="markdown" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M14.85 3H1.15C.52 3 0 3.52 0 4.15v7.69C0 12.48.52 13 1.15 13h13.69c.64 0 1.15-.52 1.15-1.15v-7.7C16 3.52 15.48 3 14.85 3zM9 11H7V8L5.5 9.92 4 8v3H2V5h2l1.5 2L7 5h2v6zm2.99.5L9.5 8H11V5h2v3h1.5l-2.51 3.5z"></path></svg></i>`
|
|
20
|
-
this.useBtn(btnText)
|
|
21
|
-
})
|
|
22
|
-
this.kit.useUnmounted(() => {})
|
|
23
|
-
|
|
24
|
-
// function to update content
|
|
25
|
-
this.kit.useEvents().on('content-updated', (content) => {
|
|
26
|
-
this.isPlugPanelShow && this.updateContent()
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
this.usePanelShow(() => {
|
|
30
|
-
this.isPlugPanelShow = true
|
|
31
|
-
this.updateContent()
|
|
32
|
-
})
|
|
33
|
-
this.usePanelHide(() => {
|
|
34
|
-
this.isPlugPanelShow = false
|
|
35
|
-
})
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
updateContent() {
|
|
39
|
-
this.$panel!.innerHTML = this.kit.useEditor().getContentMarked()
|
|
40
|
-
}
|
|
41
|
-
}
|