artalk 2.4.4 → 2.5.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/README.md +27 -0
- package/dist/Artalk.css +1 -1
- package/dist/Artalk.es.js +4872 -4072
- package/dist/Artalk.iife.js +1 -1
- package/dist/Artalk.js +1 -1
- package/dist/ArtalkLite.css +1 -1
- package/dist/ArtalkLite.es.js +2468 -1779
- package/dist/ArtalkLite.iife.js +1 -1
- package/dist/ArtalkLite.js +1 -1
- package/dist/i18n/jp.js +1 -1
- package/dist/i18n/zh-TW.js +1 -1
- package/dist/src/api/index.d.ts +2 -2
- package/dist/src/api/system.d.ts +5 -2
- package/dist/src/artalk.d.ts +42 -34
- package/dist/src/comment/comment.d.ts +2 -2
- package/dist/src/comment/height-limit.d.ts +30 -0
- package/dist/src/comment/render-ctx.d.ts +23 -0
- package/dist/src/comment/render.d.ts +2 -50
- package/dist/src/comment/renders/actions.d.ts +5 -0
- package/dist/src/comment/renders/avatar.d.ts +5 -0
- package/dist/src/comment/renders/content.d.ts +5 -0
- package/dist/src/comment/renders/header.d.ts +5 -0
- package/dist/src/comment/renders/index.d.ts +2 -0
- package/dist/src/comment/renders/pending.d.ts +5 -0
- package/dist/src/comment/renders/reply-at.d.ts +5 -0
- package/dist/src/comment/renders/reply-to.d.ts +5 -0
- package/dist/src/components/dialog.d.ts +1 -1
- package/dist/src/config.d.ts +8 -0
- package/dist/src/context.d.ts +15 -29
- package/dist/src/editor/edit-mode.d.ts +9 -0
- package/dist/src/editor/editor-ui.d.ts +25 -0
- package/dist/src/editor/editor.d.ts +37 -45
- package/dist/src/editor/funcs.d.ts +2 -0
- package/dist/src/editor/mover.d.ts +8 -0
- package/dist/src/editor/plug-manager.d.ts +17 -0
- package/dist/src/editor/plugs/editor-plug.d.ts +13 -1
- package/dist/src/editor/reply.d.ts +9 -0
- package/dist/src/editor/submit.d.ts +16 -0
- package/dist/src/editor/ui.d.ts +25 -0
- package/dist/src/i18n/index.d.ts +11 -7
- package/dist/src/layer/index.d.ts +2 -2
- package/dist/src/layer/layer.d.ts +1 -1
- package/dist/src/lib/checker/captcha-renders.d.ts +5 -0
- package/dist/src/lib/checker/index.d.ts +18 -10
- package/dist/src/lib/dark-mode.d.ts +4 -0
- package/dist/src/lib/marked.d.ts +9 -0
- package/dist/src/lib/utils.d.ts +1 -5
- package/dist/src/list/list-lite.d.ts +15 -11
- package/dist/src/list/list-nest.d.ts +2 -2
- package/dist/src/list/list-ui.d.ts +7 -0
- package/dist/src/list/paginator/adaptors/index.d.ts +12 -0
- package/dist/src/list/paginator/adaptors/pagination.d.ts +9 -0
- package/dist/src/list/paginator/adaptors/read-more.d.ts +10 -0
- package/dist/src/list/paginator/holder.d.ts +16 -0
- package/dist/src/list/paginator/index.d.ts +10 -0
- package/dist/src/service.d.ts +40 -0
- package/dist/types/artalk-config.d.ts +5 -2
- package/dist/types/artalk-data.d.ts +3 -3
- package/dist/types/context.d.ts +11 -17
- package/dist/types/global.d.ts +1 -0
- package/package.json +6 -7
- package/src/api/system.ts +3 -12
- package/src/artalk.ts +101 -158
- package/src/comment/comment.ts +7 -6
- package/src/comment/height-limit.ts +100 -0
- package/src/comment/render-ctx.ts +30 -0
- package/src/comment/render.ts +31 -375
- package/src/comment/renders/actions.ts +114 -0
- package/src/comment/renders/avatar.ts +22 -0
- package/src/comment/renders/content.ts +39 -0
- package/src/comment/renders/header.ts +84 -0
- package/src/comment/renders/index.ts +19 -0
- package/src/comment/renders/pending.ts +12 -0
- package/src/comment/renders/reply-at.ts +16 -0
- package/src/comment/renders/reply-to.ts +24 -0
- package/src/config.ts +65 -0
- package/src/context.ts +31 -86
- package/src/defaults.ts +1 -0
- package/src/editor/edit-mode.ts +102 -0
- package/src/editor/editor.ts +100 -395
- package/src/editor/funcs.ts +96 -0
- package/src/editor/mover.ts +43 -0
- package/src/editor/plug-manager.ts +172 -0
- package/src/editor/plugs/editor-plug.ts +20 -2
- package/src/editor/plugs/{emoticons-plug.less → emoticons-plug.scss} +5 -4
- package/src/editor/plugs/emoticons-plug.ts +2 -1
- package/src/editor/plugs/header-input-plug.ts +5 -5
- package/src/editor/plugs/{preview-plug.less → preview-plug.scss} +1 -1
- package/src/editor/plugs/preview-plug.ts +8 -4
- package/src/editor/plugs/upload-plug.ts +10 -13
- package/src/editor/reply.ts +62 -0
- package/src/editor/submit.ts +98 -0
- package/src/editor/ui.ts +36 -0
- package/src/i18n/index.ts +21 -10
- package/src/layer/index.ts +2 -2
- package/src/layer/layer.ts +4 -4
- package/src/layer/sidebar-layer.ts +2 -1
- package/src/lib/checker/admin.ts +36 -0
- package/src/lib/checker/captcha-renders.ts +68 -0
- package/src/lib/checker/captcha.ts +26 -0
- package/src/lib/checker/index.ts +38 -20
- package/src/lib/dark-mode.ts +48 -0
- package/src/lib/marked.ts +133 -0
- package/src/lib/utils.ts +1 -123
- package/src/list/list-lite.ts +59 -162
- package/src/list/list-ui.ts +80 -0
- package/src/list/list.ts +16 -8
- package/src/list/paginator/adaptors/index.ts +21 -0
- package/src/list/paginator/adaptors/pagination.ts +42 -0
- package/src/list/paginator/adaptors/read-more.ts +56 -0
- package/src/list/paginator/holder.ts +55 -0
- package/src/list/paginator/index.ts +13 -0
- package/src/service.ts +118 -0
- package/src/style/{_color.less → _color.scss} +3 -3
- package/src/style/{_extend.less → _extend.scss} +1 -1
- package/src/style/_variables.scss +3 -0
- package/src/style/{comment.less → comment.scss} +36 -12
- package/src/style/{editor.less → editor.scss} +11 -11
- package/src/style/{list.less → list.scss} +8 -6
- package/src/style/{main.less → main.scss} +24 -23
- package/src/style/{sidebar-layer.less → sidebar-layer.scss} +8 -8
- package/types/artalk-config.d.ts +5 -2
- package/types/artalk-data.d.ts +3 -3
- package/types/context.d.ts +11 -17
- package/types/global.d.ts +1 -0
- package/CNAME +0 -1
- package/LICENSE +0 -165
- package/index.html +0 -157
- package/postcss.config.js +0 -5
- package/scripts/build-i18n.mjs +0 -46
- package/src/lib/checker/admin-checker.ts +0 -36
- package/src/lib/checker/captcha-checker.ts +0 -84
- package/src/style/_variables.less +0 -3
- package/test/ArtalkCount.html +0 -16
- package/test/ArtalkI18n.html +0 -16
- package/test/ArtalkLite.html +0 -15
- package/test/CSRF.html +0 -26
- package/tsconfig.json +0 -12
- package/vite-base.config.ts +0 -21
- package/vite-demo.config.ts +0 -12
- package/vite-lite.config.ts +0 -22
- package/vite.config.ts +0 -27
- /package/dist/src/lib/checker/{admin-checker.d.ts → admin.d.ts} +0 -0
- /package/dist/src/lib/checker/{captcha-checker.d.ts → captcha.d.ts} +0 -0
- /package/src/style/{pagination.less → pagination.scss} +0 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import Editor from './editor'
|
|
2
|
+
import { createMover } from './mover'
|
|
3
|
+
import { createPlugManager } from './plug-manager'
|
|
4
|
+
import { createReplyManager } from './reply'
|
|
5
|
+
import { createEditModeManager } from './edit-mode'
|
|
6
|
+
import { createSubmitManager } from './submit'
|
|
7
|
+
|
|
8
|
+
const EditorFuncs = {
|
|
9
|
+
localStorage, header, textarea, submitBtn, submitManager,
|
|
10
|
+
plugs, mover, replyManager, editModeManager,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default function initEditorFuncs(editor: Editor) {
|
|
14
|
+
Object.entries(EditorFuncs).forEach(([k, init]) => {
|
|
15
|
+
init(editor)
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// ================== Editor Functions ======================
|
|
20
|
+
|
|
21
|
+
function localStorage(editor: Editor) {
|
|
22
|
+
const localContent = window.localStorage.getItem('ArtalkContent') || ''
|
|
23
|
+
if (localContent.trim() !== '') {
|
|
24
|
+
editor.showNotify(editor.$t('restoredMsg'), 'i')
|
|
25
|
+
editor.setContent(localContent)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
editor.getUI().$textarea.addEventListener('input', () => (editor.saveToLocalStorage()))
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function header(editor: Editor) {
|
|
32
|
+
const $inputs = editor.getHeaderInputEls()
|
|
33
|
+
Object.entries($inputs).forEach(([key, $input]) => {
|
|
34
|
+
$input.value = editor.ctx.user.data[key] || ''
|
|
35
|
+
$input.addEventListener('input', () => onHeaderInput(editor, key, $input))
|
|
36
|
+
|
|
37
|
+
// 设置 Placeholder
|
|
38
|
+
$input.placeholder = `${editor.$t(key as any)}`
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function onHeaderInput(editor: Editor, field: string, $input: HTMLInputElement) {
|
|
43
|
+
if (editor.isEditMode) return // 评论编辑模式,不修改个人信息
|
|
44
|
+
|
|
45
|
+
editor.ctx.user.update({
|
|
46
|
+
[field]: $input.value.trim()
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
editor.getPlugs()?.triggerHeaderInputEvt(field, $input)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function textarea(editor: Editor) {
|
|
53
|
+
// 占位符
|
|
54
|
+
editor.getUI().$textarea.placeholder = editor.ctx.conf.placeholder || editor.$t('placeholder')
|
|
55
|
+
|
|
56
|
+
// 修复按下 Tab 输入的内容
|
|
57
|
+
editor.getUI().$textarea.addEventListener('keydown', (e) => {
|
|
58
|
+
const keyCode = e.keyCode || e.which
|
|
59
|
+
|
|
60
|
+
if (keyCode === 9) {
|
|
61
|
+
e.preventDefault()
|
|
62
|
+
editor.insertContent('\t')
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
// 输入框高度随内容而变化
|
|
67
|
+
editor.getUI().$textarea.addEventListener('input', () => {
|
|
68
|
+
editor.adjustTextareaHeight()
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function submitBtn(editor: Editor) {
|
|
73
|
+
editor.refreshSendBtnText()
|
|
74
|
+
editor.getUI().$submitBtn.addEventListener('click', () => (editor.submit()))
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function plugs(editor: Editor) {
|
|
78
|
+
editor.setPlugs(createPlugManager(editor))
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function mover(editor: Editor) {
|
|
82
|
+
if (!editor.conf.editorTravel) return
|
|
83
|
+
editor.setMover(createMover(editor))
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function replyManager(editor: Editor) {
|
|
87
|
+
editor.setReplyManager(createReplyManager(editor))
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function editModeManager(editor: Editor) {
|
|
91
|
+
editor.setEditModeManager(createEditModeManager(editor))
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function submitManager(editor: Editor) {
|
|
95
|
+
editor.setSubmitManager(createSubmitManager(editor))
|
|
96
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import Editor from './editor'
|
|
2
|
+
import * as Utils from '../lib/utils'
|
|
3
|
+
|
|
4
|
+
export interface Mover {
|
|
5
|
+
editor: Editor
|
|
6
|
+
isMoved: boolean
|
|
7
|
+
move(afterEl: HTMLElement): void
|
|
8
|
+
back(): void
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function createMover(editor: Editor): Mover {
|
|
12
|
+
const m: Mover = {
|
|
13
|
+
editor,
|
|
14
|
+
isMoved: false,
|
|
15
|
+
move: (el) => move(m, el),
|
|
16
|
+
back: () => back(m)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return m
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function move(m: Mover, afterEl: HTMLElement) {
|
|
23
|
+
if (m.isMoved) return
|
|
24
|
+
m.isMoved = true
|
|
25
|
+
|
|
26
|
+
const editorEl = m.editor.getUI().$el
|
|
27
|
+
|
|
28
|
+
editorEl.after(Utils.createElement('<div class="atk-editor-travel-placeholder"></div>'))
|
|
29
|
+
|
|
30
|
+
const $travelPlace = Utils.createElement('<div></div>')
|
|
31
|
+
afterEl.after($travelPlace)
|
|
32
|
+
$travelPlace.replaceWith(editorEl)
|
|
33
|
+
|
|
34
|
+
editorEl.classList.add('atk-fade-in') // 添加渐入动画
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function back(m: Mover) {
|
|
38
|
+
if (!m.isMoved) return
|
|
39
|
+
m.isMoved = false
|
|
40
|
+
m.editor.ctx.$root.querySelector('.atk-editor-travel-placeholder')?.replaceWith(m.editor.getUI().$el)
|
|
41
|
+
|
|
42
|
+
m.editor.cancelReply() // 取消回复
|
|
43
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import type ArtalkConfig from '~/types/artalk-config'
|
|
2
|
+
import EmoticonsPlug from './plugs/emoticons-plug'
|
|
3
|
+
import UploadPlug from './plugs/upload-plug'
|
|
4
|
+
import PreviewPlug from './plugs/preview-plug'
|
|
5
|
+
import HeaderInputPlug from './plugs/header-input-plug'
|
|
6
|
+
import EditorPlug from './plugs/editor-plug'
|
|
7
|
+
import Editor from './editor'
|
|
8
|
+
|
|
9
|
+
/** The default enabled plugs */
|
|
10
|
+
const ENABLED_PLUGS = [ EmoticonsPlug, UploadPlug, PreviewPlug, HeaderInputPlug ]
|
|
11
|
+
|
|
12
|
+
/** Context of an editor plug manager */
|
|
13
|
+
export interface PlugManager {
|
|
14
|
+
editor: Editor
|
|
15
|
+
plugList: { [name: string]: EditorPlug }
|
|
16
|
+
openedPlugName: string|null
|
|
17
|
+
|
|
18
|
+
openPlugPanel(plugName: string): void
|
|
19
|
+
closePlugPanel(): void
|
|
20
|
+
|
|
21
|
+
triggerHeaderInputEvt(field: string, $input: HTMLInputElement): void
|
|
22
|
+
triggerContentUpdatedEvt(content: string): void
|
|
23
|
+
getTransformedContent(raw: string): string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Create an editor plug manager */
|
|
27
|
+
export function createPlugManager(editor: Editor): PlugManager {
|
|
28
|
+
const ctx: PlugManager = {
|
|
29
|
+
editor,
|
|
30
|
+
plugList: {},
|
|
31
|
+
openedPlugName: null,
|
|
32
|
+
openPlugPanel: (p: string) => openPlugPanel(ctx, p),
|
|
33
|
+
closePlugPanel: () => closePlugPanel(ctx),
|
|
34
|
+
|
|
35
|
+
triggerHeaderInputEvt: (f, $) => triggerHeaderInputEvt(ctx, f, $),
|
|
36
|
+
triggerContentUpdatedEvt: (c) => triggerContentUpdatedEvt(ctx, c),
|
|
37
|
+
getTransformedContent: (r) => getTransformedContent(ctx, r),
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// handle ui
|
|
41
|
+
editor.getUI().$plugPanelWrap.innerHTML = ''
|
|
42
|
+
editor.getUI().$plugPanelWrap.style.display = 'none'
|
|
43
|
+
editor.getUI().$plugBtnWrap.innerHTML = ''
|
|
44
|
+
|
|
45
|
+
// init the all enabled plugs
|
|
46
|
+
const DISABLED = getDisabledPlugNames(editor.conf)
|
|
47
|
+
|
|
48
|
+
ENABLED_PLUGS
|
|
49
|
+
.filter(p => !DISABLED.includes(p.name)) // 禁用的插件
|
|
50
|
+
.forEach((Plug) => {
|
|
51
|
+
initPlugItem(ctx, Plug)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
return ctx
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Get the name list of disabled plugs */
|
|
58
|
+
function getDisabledPlugNames(conf: ArtalkConfig) {
|
|
59
|
+
return [
|
|
60
|
+
{k: 'upload', v: conf.imgUpload},
|
|
61
|
+
{k: 'emoticons', v: conf.emoticons},
|
|
62
|
+
{k: 'preview', v: conf.preview}
|
|
63
|
+
].filter(n => !n.v).flatMap(n => n.k)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Initialization of a plug item */
|
|
67
|
+
function initPlugItem(ctx: PlugManager, Plug: typeof ENABLED_PLUGS[number]) {
|
|
68
|
+
const plugName = Plug.Name
|
|
69
|
+
|
|
70
|
+
// create the plug instance
|
|
71
|
+
const plug = new Plug(ctx.editor)
|
|
72
|
+
ctx.plugList[plugName] = plug
|
|
73
|
+
|
|
74
|
+
// gen plug ui
|
|
75
|
+
genPlugBtn(ctx, plugName, plug)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Gen the plug btn (and plug panel) on editor ui */
|
|
79
|
+
function genPlugBtn(ctx: PlugManager, plugName: string, plug: EditorPlug) {
|
|
80
|
+
const $btn = plug.getBtn()
|
|
81
|
+
if (!$btn) return
|
|
82
|
+
ctx.editor.getUI().$plugBtnWrap.appendChild($btn)
|
|
83
|
+
$btn.onclick = $btn.onclick || (() => {
|
|
84
|
+
// the event when click plug btn
|
|
85
|
+
|
|
86
|
+
// removing the active class from all the buttons
|
|
87
|
+
ctx.editor.getUI().$plugBtnWrap
|
|
88
|
+
.querySelectorAll('.active')
|
|
89
|
+
.forEach(item => item.classList.remove('active'))
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
// if the plugName is the same as the openedPlugName, then close the plugPanel
|
|
93
|
+
if (plugName === ctx.openedPlugName) {
|
|
94
|
+
closePlugPanel(ctx)
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// open the plug current clicked plug panel
|
|
99
|
+
openPlugPanel(ctx, plugName)
|
|
100
|
+
|
|
101
|
+
// add active class for current plug panel
|
|
102
|
+
$btn.classList.add('active')
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
// initialization of plug panel
|
|
106
|
+
const $panel = plug.getPanel()
|
|
107
|
+
if ($panel) {
|
|
108
|
+
$panel.setAttribute('data-plug-name', plugName)
|
|
109
|
+
$panel.style.display = 'none'
|
|
110
|
+
ctx.editor.getUI().$plugPanelWrap.appendChild($panel)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Open the editor plug panel */
|
|
115
|
+
function openPlugPanel(ctx: PlugManager, plugName: string) {
|
|
116
|
+
Object.entries(ctx.plugList).forEach(([aPlugName, plug]) => {
|
|
117
|
+
const plugPanel = plug.getPanel()
|
|
118
|
+
if (!plugPanel) return
|
|
119
|
+
|
|
120
|
+
if (aPlugName === plugName) {
|
|
121
|
+
plugPanel.style.display = ''
|
|
122
|
+
if (plug.onPanelShow) plug.onPanelShow()
|
|
123
|
+
} else {
|
|
124
|
+
plugPanel.style.display = 'none'
|
|
125
|
+
if (plug.onPanelHide) plug.onPanelHide()
|
|
126
|
+
}
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
ctx.editor.getUI().$plugPanelWrap.style.display = ''
|
|
130
|
+
ctx.openedPlugName = plugName
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Close the editor plug panel */
|
|
134
|
+
function closePlugPanel(ctx: PlugManager) {
|
|
135
|
+
if (!ctx.openedPlugName) return
|
|
136
|
+
|
|
137
|
+
const plug = ctx.plugList[ctx.openedPlugName]
|
|
138
|
+
if (!plug) return
|
|
139
|
+
|
|
140
|
+
if (plug.onPanelHide) plug.onPanelHide()
|
|
141
|
+
|
|
142
|
+
ctx.editor.getUI().$plugPanelWrap.style.display = 'none'
|
|
143
|
+
ctx.openedPlugName = null
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ============== Events ====================
|
|
147
|
+
|
|
148
|
+
/** Trigger event when editor header input changed */
|
|
149
|
+
function triggerHeaderInputEvt(ctx: PlugManager, field: string, $input: HTMLInputElement) {
|
|
150
|
+
Object.entries(ctx.plugList).forEach(([plugName, plug]) => {
|
|
151
|
+
if (!plug.onHeaderInput) return
|
|
152
|
+
plug.onHeaderInput(field, $input)
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Trigger event when editor content updated */
|
|
157
|
+
function triggerContentUpdatedEvt(ctx: PlugManager, content: string) {
|
|
158
|
+
Object.entries(ctx.plugList).forEach(([plugName, plug]) => {
|
|
159
|
+
if (!plug.onContentUpdated) return
|
|
160
|
+
plug.onContentUpdated(content)
|
|
161
|
+
})
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Get the content which is transformed by plugs */
|
|
165
|
+
function getTransformedContent(ctx: PlugManager, rawContent: string) {
|
|
166
|
+
let result = rawContent
|
|
167
|
+
Object.entries(ctx.plugList).forEach(([plugName, plug]) => {
|
|
168
|
+
if (!plug.contentTransformer) return
|
|
169
|
+
result = plug.contentTransformer(result)
|
|
170
|
+
})
|
|
171
|
+
return result
|
|
172
|
+
}
|
|
@@ -18,31 +18,49 @@ abstract class EditorPlug {
|
|
|
18
18
|
protected $panel?: HTMLElement
|
|
19
19
|
protected $btn?: HTMLElement
|
|
20
20
|
public onHeaderInput?: (key: string, $input: HTMLElement) => void
|
|
21
|
+
public contentTransformer?: (rawContent: string) => string
|
|
22
|
+
public onContentUpdated?: (content: string) => void
|
|
21
23
|
|
|
22
24
|
public constructor(editor: Editor) {
|
|
23
25
|
this.editor = editor
|
|
24
26
|
}
|
|
25
27
|
|
|
28
|
+
/** Name of plug */
|
|
26
29
|
public static Name: string
|
|
27
30
|
|
|
31
|
+
/** Register plug panel will provide a plug el */
|
|
28
32
|
protected registerPanel(html: string = '<div></div>') {
|
|
29
33
|
this.$panel = Utils.createElement(html)
|
|
30
34
|
return this.$panel
|
|
31
35
|
}
|
|
32
36
|
|
|
37
|
+
/** Register plug btn will add a btn on the bottom of editor */
|
|
33
38
|
protected registerBtn(html: string) {
|
|
34
39
|
this.$btn = Utils.createElement(`<span class="atk-plug-btn" data-plug-name="${this.constructor.name}">${html}</span>`)
|
|
35
40
|
return this.$btn
|
|
36
41
|
}
|
|
37
42
|
|
|
38
|
-
|
|
39
|
-
|
|
43
|
+
/** Register the event of header input is changed */
|
|
44
|
+
protected registerHeaderInputEvt(func: (key: string, $input: HTMLElement) => void) {
|
|
45
|
+
this.onHeaderInput = func
|
|
40
46
|
}
|
|
41
47
|
|
|
48
|
+
/** Register the content transformer to handle the content of the last submit by the editor */
|
|
49
|
+
protected registerContentTransformer(func: (raw: string) => string) {
|
|
50
|
+
this.contentTransformer = func
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Register the event of editor content is updated */
|
|
54
|
+
protected registerContentUpdatedEvt(func: (content: string) => void) {
|
|
55
|
+
this.onContentUpdated = func
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Get panel element of plug */
|
|
42
59
|
public getPanel() {
|
|
43
60
|
return this.$panel
|
|
44
61
|
}
|
|
45
62
|
|
|
63
|
+
/** Get btn element of plug */
|
|
46
64
|
public getBtn() {
|
|
47
65
|
return this.$btn
|
|
48
66
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
width: 100%;
|
|
4
4
|
|
|
5
5
|
& > .atk-grp-wrap {
|
|
6
|
-
.atk-slim-scrollbar
|
|
6
|
+
@extend .atk-slim-scrollbar;
|
|
7
7
|
overflow-y: scroll;
|
|
8
8
|
overflow-x: hidden;
|
|
9
9
|
height: 100%; // calc(100% - 30px);
|
|
@@ -27,9 +27,10 @@
|
|
|
27
27
|
padding: 5px;
|
|
28
28
|
cursor: pointer;
|
|
29
29
|
user-select: none;
|
|
30
|
-
border-radius: (
|
|
31
|
-
font-size:
|
|
30
|
+
border-radius: calc($block-radius / 2);
|
|
31
|
+
font-size: 15px;
|
|
32
32
|
min-width: 35px;
|
|
33
|
+
margin: 2px;
|
|
33
34
|
|
|
34
35
|
& > img {
|
|
35
36
|
max-height: 100%;
|
|
@@ -60,7 +61,7 @@
|
|
|
60
61
|
float: left;
|
|
61
62
|
display: block;
|
|
62
63
|
cursor: pointer;
|
|
63
|
-
font-size:
|
|
64
|
+
font-size: $font-size;
|
|
64
65
|
|
|
65
66
|
&:hover,
|
|
66
67
|
&.active {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import './emoticons-plug.
|
|
1
|
+
import './emoticons-plug.scss'
|
|
2
2
|
|
|
3
3
|
import * as Utils from '~/src/lib/utils'
|
|
4
4
|
import * as Ui from '~/src/lib/ui'
|
|
@@ -29,6 +29,7 @@ export default class EmoticonsPlug extends EditorPlug {
|
|
|
29
29
|
|
|
30
30
|
this.registerPanel(`<div class="atk-editor-plug-emoticons"></div>`)
|
|
31
31
|
this.registerBtn(this.ctx.$t('emoticon'))
|
|
32
|
+
this.registerContentTransformer((raw) => this.transEmoticonImageText(raw))
|
|
32
33
|
|
|
33
34
|
// 表情包预加载
|
|
34
35
|
window.setTimeout(() => {
|
|
@@ -14,11 +14,11 @@ export default class HeaderInputPlug extends EditorPlug {
|
|
|
14
14
|
})
|
|
15
15
|
|
|
16
16
|
// Link URL 自动补全协议
|
|
17
|
-
this.editor.$link.addEventListener('change', () => {
|
|
18
|
-
const link = this.editor.$link.value.trim()
|
|
17
|
+
this.editor.getUI().$link.addEventListener('change', () => {
|
|
18
|
+
const link = this.editor.getUI().$link.value.trim()
|
|
19
19
|
if (!!link && !/^(http|https):\/\//.test(link)) {
|
|
20
|
-
this.editor.$link.value = `https://${link}`
|
|
21
|
-
this.ctx.user.update({ link: this.editor.$link.value })
|
|
20
|
+
this.editor.getUI().$link.value = `https://${link}`
|
|
21
|
+
this.ctx.user.update({ link: this.editor.getUI().$link.value })
|
|
22
22
|
}
|
|
23
23
|
})
|
|
24
24
|
}
|
|
@@ -58,7 +58,7 @@ export default class HeaderInputPlug extends EditorPlug {
|
|
|
58
58
|
|
|
59
59
|
// 自动填入 link
|
|
60
60
|
if (data.user && data.user.link) {
|
|
61
|
-
this.editor.$link.value = data.user.link
|
|
61
|
+
this.editor.getUI().$link.value = data.user.link
|
|
62
62
|
this.ctx.user.update({ link: data.user.link })
|
|
63
63
|
}
|
|
64
64
|
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import './preview-plug.
|
|
1
|
+
import './preview-plug.scss'
|
|
2
2
|
|
|
3
3
|
import Editor from '../editor'
|
|
4
4
|
import EditorPlug from './editor-plug'
|
|
@@ -15,8 +15,12 @@ export default class PreviewPlug extends EditorPlug {
|
|
|
15
15
|
this.registerPanel(`<div class="atk-editor-plug-preview"></div>`)
|
|
16
16
|
|
|
17
17
|
let btnText = this.editor.$t('preview')
|
|
18
|
-
if (this.ctx.
|
|
18
|
+
if (this.ctx.getMarkedInstance()) 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>`
|
|
19
19
|
this.registerBtn(btnText)
|
|
20
|
+
|
|
21
|
+
this.registerContentUpdatedEvt((content) => {
|
|
22
|
+
this.updateContent()
|
|
23
|
+
})
|
|
20
24
|
}
|
|
21
25
|
|
|
22
26
|
public onPanelShow() {
|
|
@@ -26,8 +30,8 @@ export default class PreviewPlug extends EditorPlug {
|
|
|
26
30
|
const event = () => {
|
|
27
31
|
this.updateContent()
|
|
28
32
|
}
|
|
29
|
-
this.editor.$textarea.addEventListener('input', event)
|
|
30
|
-
this.editor.$textarea.addEventListener('change', event)
|
|
33
|
+
this.editor.getUI().$textarea.addEventListener('input', event)
|
|
34
|
+
this.editor.getUI().$textarea.addEventListener('change', event)
|
|
31
35
|
this.isBind = true
|
|
32
36
|
}
|
|
33
37
|
}
|
|
@@ -33,12 +33,9 @@ export default class UploadPlug extends EditorPlug {
|
|
|
33
33
|
$input.click() // 显示选择图片对话框
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
this.ctx.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
this.ctx.checkAdminShowEl()
|
|
40
|
-
}
|
|
41
|
-
})
|
|
36
|
+
if (!this.ctx.conf.imgUpload) {
|
|
37
|
+
this.getBtn()!.setAttribute('atk-only-admin-show', '')
|
|
38
|
+
}
|
|
42
39
|
|
|
43
40
|
// 统一从 FileList 获取文件并上传图片方法
|
|
44
41
|
const uploadFromFileList = (files?: FileList) => {
|
|
@@ -53,12 +50,12 @@ export default class UploadPlug extends EditorPlug {
|
|
|
53
50
|
// 拖拽图片
|
|
54
51
|
// @link https://developer.mozilla.org/zh-CN/docs/Web/API/HTML_Drag_and_Drop_API/File_drag_and_drop
|
|
55
52
|
// 阻止浏览器的默认释放行为
|
|
56
|
-
this.editor.$textarea.addEventListener('dragover', (evt) => {
|
|
53
|
+
this.editor.getUI().$textarea.addEventListener('dragover', (evt) => {
|
|
57
54
|
evt.stopPropagation()
|
|
58
55
|
evt.preventDefault()
|
|
59
56
|
})
|
|
60
57
|
|
|
61
|
-
this.editor.$textarea.addEventListener('drop', (evt) => {
|
|
58
|
+
this.editor.getUI().$textarea.addEventListener('drop', (evt) => {
|
|
62
59
|
const files = evt.dataTransfer?.files
|
|
63
60
|
if (files?.length) {
|
|
64
61
|
evt.preventDefault()
|
|
@@ -67,7 +64,7 @@ export default class UploadPlug extends EditorPlug {
|
|
|
67
64
|
})
|
|
68
65
|
|
|
69
66
|
// 粘贴图片
|
|
70
|
-
this.editor.$textarea.addEventListener('paste', (evt) => {
|
|
67
|
+
this.editor.getUI().$textarea.addEventListener('paste', (evt) => {
|
|
71
68
|
const files = evt.clipboardData?.files
|
|
72
69
|
if (files?.length) {
|
|
73
70
|
evt.preventDefault()
|
|
@@ -88,7 +85,7 @@ export default class UploadPlug extends EditorPlug {
|
|
|
88
85
|
|
|
89
86
|
// 插入图片前换一行
|
|
90
87
|
let insertPrefix = '\n'
|
|
91
|
-
if (this.editor.$textarea.value.trim() === '') insertPrefix = ''
|
|
88
|
+
if (this.editor.getUI().$textarea.value.trim() === '') insertPrefix = ''
|
|
92
89
|
|
|
93
90
|
// 插入占位加载文字
|
|
94
91
|
const uploadPlaceholderTxt = `${insertPrefix}`
|
|
@@ -98,7 +95,7 @@ export default class UploadPlug extends EditorPlug {
|
|
|
98
95
|
let resp: any
|
|
99
96
|
try {
|
|
100
97
|
if (!this.ctx.conf.imgUploader) {
|
|
101
|
-
// 使用
|
|
98
|
+
// 使用 Artalk 进行图片上传
|
|
102
99
|
resp = await this.ctx.getApi().upload.imgUpload(file)
|
|
103
100
|
} else {
|
|
104
101
|
// 使用自定义的图片上传器
|
|
@@ -115,10 +112,10 @@ export default class UploadPlug extends EditorPlug {
|
|
|
115
112
|
if (!Utils.isValidURL(imgURL)) imgURL = Utils.getURLBasedOnApi(this.ctx, imgURL)
|
|
116
113
|
|
|
117
114
|
// 上传成功插入图片
|
|
118
|
-
this.editor.setContent(this.editor.$textarea.value.replace(uploadPlaceholderTxt, `${insertPrefix}`))
|
|
115
|
+
this.editor.setContent(this.editor.getUI().$textarea.value.replace(uploadPlaceholderTxt, `${insertPrefix}`))
|
|
119
116
|
} else {
|
|
120
117
|
// 上传失败删除加载文字
|
|
121
|
-
this.editor.setContent(this.editor.$textarea.value.replace(uploadPlaceholderTxt, ''))
|
|
118
|
+
this.editor.setContent(this.editor.getUI().$textarea.value.replace(uploadPlaceholderTxt, ''))
|
|
122
119
|
}
|
|
123
120
|
}
|
|
124
121
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { CommentData } from '~/types/artalk-data'
|
|
2
|
+
import Editor from './editor'
|
|
3
|
+
import * as Utils from '../lib/utils'
|
|
4
|
+
import * as Ui from '../lib/ui'
|
|
5
|
+
|
|
6
|
+
export interface ReplyManager {
|
|
7
|
+
editor: Editor
|
|
8
|
+
comment?: CommentData
|
|
9
|
+
setReply(comment: CommentData, $comment: HTMLElement, scroll?: boolean): void
|
|
10
|
+
cancelReply(): void
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function createReplyManager(editor: Editor): ReplyManager {
|
|
14
|
+
const m: ReplyManager = {
|
|
15
|
+
editor,
|
|
16
|
+
comment: undefined,
|
|
17
|
+
setReply: (c, $, s) => setReply(m, c, $, s),
|
|
18
|
+
cancelReply: () => cancelReply(m)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return m
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function setReply(m: ReplyManager, commentData: CommentData, $comment: HTMLElement, scroll = true) {
|
|
25
|
+
m.editor.cancelEditComment()
|
|
26
|
+
cancelReply(m)
|
|
27
|
+
|
|
28
|
+
const ui = m.editor.getUI()
|
|
29
|
+
if (!ui.$sendReply) {
|
|
30
|
+
ui.$sendReply = Utils.createElement(
|
|
31
|
+
`<div class="atk-send-reply">` +
|
|
32
|
+
`${m.editor.$t('reply')} ` +
|
|
33
|
+
`<span class="atk-text"></span><span class="atk-cancel">×</span>` +
|
|
34
|
+
`</div>`
|
|
35
|
+
)
|
|
36
|
+
ui.$sendReply.querySelector<HTMLElement>('.atk-text')!.innerText = `@${commentData.nick}`
|
|
37
|
+
ui.$sendReply.addEventListener('click', () => {
|
|
38
|
+
m.editor.cancelReply()
|
|
39
|
+
})
|
|
40
|
+
ui.$textareaWrap.append(ui.$sendReply)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
m.comment = commentData
|
|
44
|
+
m.editor.travel($comment)
|
|
45
|
+
|
|
46
|
+
if (scroll) Ui.scrollIntoView(ui.$el)
|
|
47
|
+
|
|
48
|
+
ui.$textarea.focus()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function cancelReply(m: ReplyManager) {
|
|
52
|
+
if (!m.comment) return
|
|
53
|
+
|
|
54
|
+
const ui = m.editor.getUI()
|
|
55
|
+
if (ui.$sendReply) {
|
|
56
|
+
ui.$sendReply.remove()
|
|
57
|
+
ui.$sendReply = undefined
|
|
58
|
+
}
|
|
59
|
+
m.comment = undefined
|
|
60
|
+
|
|
61
|
+
m.editor.travelBack()
|
|
62
|
+
}
|