artalk 2.4.3 → 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.
Files changed (145) hide show
  1. package/README.md +27 -0
  2. package/dist/Artalk.css +1 -1
  3. package/dist/Artalk.es.js +4895 -4067
  4. package/dist/Artalk.iife.js +1 -1
  5. package/dist/Artalk.js +1 -1
  6. package/dist/ArtalkLite.css +1 -1
  7. package/dist/ArtalkLite.es.js +2496 -1779
  8. package/dist/ArtalkLite.iife.js +1 -1
  9. package/dist/ArtalkLite.js +1 -1
  10. package/dist/i18n/jp.js +1 -1
  11. package/dist/i18n/zh-TW.js +1 -1
  12. package/dist/src/api/index.d.ts +2 -2
  13. package/dist/src/api/system.d.ts +5 -2
  14. package/dist/src/artalk.d.ts +42 -34
  15. package/dist/src/comment/comment.d.ts +2 -2
  16. package/dist/src/comment/height-limit.d.ts +30 -0
  17. package/dist/src/comment/render-ctx.d.ts +23 -0
  18. package/dist/src/comment/render.d.ts +2 -50
  19. package/dist/src/comment/renders/actions.d.ts +5 -0
  20. package/dist/src/comment/renders/avatar.d.ts +5 -0
  21. package/dist/src/comment/renders/content.d.ts +5 -0
  22. package/dist/src/comment/renders/header.d.ts +5 -0
  23. package/dist/src/comment/renders/index.d.ts +2 -0
  24. package/dist/src/comment/renders/pending.d.ts +5 -0
  25. package/dist/src/comment/renders/reply-at.d.ts +5 -0
  26. package/dist/src/comment/renders/reply-to.d.ts +5 -0
  27. package/dist/src/components/dialog.d.ts +1 -1
  28. package/dist/src/config.d.ts +8 -0
  29. package/dist/src/context.d.ts +15 -29
  30. package/dist/src/editor/edit-mode.d.ts +9 -0
  31. package/dist/src/editor/editor-ui.d.ts +25 -0
  32. package/dist/src/editor/editor.d.ts +37 -45
  33. package/dist/src/editor/funcs.d.ts +2 -0
  34. package/dist/src/editor/mover.d.ts +8 -0
  35. package/dist/src/editor/plug-manager.d.ts +17 -0
  36. package/dist/src/editor/plugs/editor-plug.d.ts +13 -1
  37. package/dist/src/editor/reply.d.ts +9 -0
  38. package/dist/src/editor/submit.d.ts +16 -0
  39. package/dist/src/editor/ui.d.ts +25 -0
  40. package/dist/src/i18n/index.d.ts +11 -7
  41. package/dist/src/layer/index.d.ts +2 -2
  42. package/dist/src/layer/layer.d.ts +1 -1
  43. package/dist/src/lib/checker/captcha-renders.d.ts +5 -0
  44. package/dist/src/lib/checker/index.d.ts +18 -10
  45. package/dist/src/lib/dark-mode.d.ts +4 -0
  46. package/dist/src/lib/marked.d.ts +9 -0
  47. package/dist/src/lib/utils.d.ts +1 -5
  48. package/dist/src/list/list-lite.d.ts +15 -11
  49. package/dist/src/list/list-nest.d.ts +2 -2
  50. package/dist/src/list/list-ui.d.ts +7 -0
  51. package/dist/src/list/paginator/adaptors/index.d.ts +12 -0
  52. package/dist/src/list/paginator/adaptors/pagination.d.ts +9 -0
  53. package/dist/src/list/paginator/adaptors/read-more.d.ts +10 -0
  54. package/dist/src/list/paginator/holder.d.ts +16 -0
  55. package/dist/src/list/paginator/index.d.ts +10 -0
  56. package/dist/src/service.d.ts +40 -0
  57. package/dist/types/artalk-config.d.ts +5 -2
  58. package/dist/types/artalk-data.d.ts +3 -3
  59. package/dist/types/context.d.ts +11 -17
  60. package/dist/types/global.d.ts +1 -0
  61. package/package.json +6 -7
  62. package/src/api/system.ts +3 -12
  63. package/src/artalk.ts +101 -158
  64. package/src/comment/comment.ts +7 -6
  65. package/src/comment/height-limit.ts +100 -0
  66. package/src/comment/render-ctx.ts +30 -0
  67. package/src/comment/render.ts +31 -375
  68. package/src/comment/renders/actions.ts +114 -0
  69. package/src/comment/renders/avatar.ts +22 -0
  70. package/src/comment/renders/content.ts +39 -0
  71. package/src/comment/renders/header.ts +84 -0
  72. package/src/comment/renders/index.ts +19 -0
  73. package/src/comment/renders/pending.ts +12 -0
  74. package/src/comment/renders/reply-at.ts +16 -0
  75. package/src/comment/renders/reply-to.ts +24 -0
  76. package/src/config.ts +65 -0
  77. package/src/context.ts +31 -88
  78. package/src/defaults.ts +1 -0
  79. package/src/editor/edit-mode.ts +102 -0
  80. package/src/editor/editor.ts +100 -394
  81. package/src/editor/funcs.ts +96 -0
  82. package/src/editor/mover.ts +43 -0
  83. package/src/editor/plug-manager.ts +172 -0
  84. package/src/editor/plugs/editor-plug.ts +20 -2
  85. package/src/editor/plugs/{emoticons-plug.less → emoticons-plug.scss} +5 -4
  86. package/src/editor/plugs/emoticons-plug.ts +2 -1
  87. package/src/editor/plugs/header-input-plug.ts +5 -5
  88. package/src/editor/plugs/{preview-plug.less → preview-plug.scss} +1 -1
  89. package/src/editor/plugs/preview-plug.ts +8 -4
  90. package/src/editor/plugs/upload-plug.ts +10 -13
  91. package/src/editor/reply.ts +62 -0
  92. package/src/editor/submit.ts +98 -0
  93. package/src/editor/ui.ts +36 -0
  94. package/src/i18n/index.ts +21 -10
  95. package/src/layer/index.ts +2 -2
  96. package/src/layer/layer.ts +4 -4
  97. package/src/layer/sidebar-layer.ts +2 -1
  98. package/src/lib/checker/admin.ts +36 -0
  99. package/src/lib/checker/captcha-renders.ts +68 -0
  100. package/src/lib/checker/captcha.ts +26 -0
  101. package/src/lib/checker/index.ts +38 -20
  102. package/src/lib/dark-mode.ts +48 -0
  103. package/src/lib/marked.ts +133 -0
  104. package/src/lib/utils.ts +1 -123
  105. package/src/list/list-lite.ts +59 -162
  106. package/src/list/list-ui.ts +80 -0
  107. package/src/list/list.ts +16 -8
  108. package/src/list/paginator/adaptors/index.ts +21 -0
  109. package/src/list/paginator/adaptors/pagination.ts +42 -0
  110. package/src/list/paginator/adaptors/read-more.ts +56 -0
  111. package/src/list/paginator/holder.ts +55 -0
  112. package/src/list/paginator/index.ts +13 -0
  113. package/src/service.ts +118 -0
  114. package/src/style/{_color.less → _color.scss} +3 -3
  115. package/src/style/{_extend.less → _extend.scss} +1 -1
  116. package/src/style/_variables.scss +3 -0
  117. package/src/style/{comment.less → comment.scss} +36 -12
  118. package/src/style/{editor.less → editor.scss} +11 -11
  119. package/src/style/{list.less → list.scss} +8 -6
  120. package/src/style/{main.less → main.scss} +24 -23
  121. package/src/style/{sidebar-layer.less → sidebar-layer.scss} +8 -8
  122. package/types/artalk-config.d.ts +5 -2
  123. package/types/artalk-data.d.ts +3 -3
  124. package/types/context.d.ts +11 -17
  125. package/types/global.d.ts +1 -0
  126. package/CNAME +0 -1
  127. package/LICENSE +0 -165
  128. package/index.html +0 -157
  129. package/postcss.config.js +0 -5
  130. package/scripts/build-i18n.mjs +0 -46
  131. package/src/lib/checker/admin-checker.ts +0 -36
  132. package/src/lib/checker/captcha-checker.ts +0 -84
  133. package/src/style/_variables.less +0 -3
  134. package/test/ArtalkCount.html +0 -16
  135. package/test/ArtalkI18n.html +0 -16
  136. package/test/ArtalkLite.html +0 -15
  137. package/test/CSRF.html +0 -26
  138. package/tsconfig.json +0 -12
  139. package/vite-base.config.ts +0 -21
  140. package/vite-demo.config.ts +0 -12
  141. package/vite-lite.config.ts +0 -22
  142. package/vite.config.ts +0 -27
  143. /package/dist/src/lib/checker/{admin-checker.d.ts → admin.d.ts} +0 -0
  144. /package/dist/src/lib/checker/{captcha-checker.d.ts → captcha.d.ts} +0 -0
  145. /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
- protected registerHeaderInputEvt(action: (key: string, $input: HTMLElement) => void) {
39
- this.onHeaderInput = action
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: (@block-radius / 2);
31
- font-size: 16px;
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: @font-size;
64
+ font-size: $font-size;
64
65
 
65
66
  &:hover,
66
67
  &.active {
@@ -1,4 +1,4 @@
1
- import './emoticons-plug.less'
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
  })
@@ -3,5 +3,5 @@
3
3
  overflow-x: hidden;
4
4
  height: 100%;
5
5
  width: 100%;
6
- padding: 10px (10px + 5px);
6
+ padding: 10px calc(10px + 5px);
7
7
  }
@@ -1,4 +1,4 @@
1
- import './preview-plug.less'
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.markedInstance) 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>`
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.on('conf-loaded', () => {
37
- if (!this.ctx.conf.imgUpload) {
38
- this.getBtn()!.setAttribute('atk-only-admin-show', '')
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}![](Uploading ${file.name}...)`
@@ -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
- // 使用 ArtalkGo 进行图片上传
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}![](${imgURL})`))
115
+ this.editor.setContent(this.editor.getUI().$textarea.value.replace(uploadPlaceholderTxt, `${insertPrefix}![](${imgURL})`))
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
+ }