@ebl-vue/editor-full 2.31.34 → 2.31.36

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 (97) hide show
  1. package/.postcssrc.yml +33 -0
  2. package/dist/index.d.ts +1 -2
  3. package/dist/index.mjs +183 -182
  4. package/dist/index.mjs.map +1 -0
  5. package/package.json +1 -1
  6. package/postcss.config.js +15 -0
  7. package/src/components/Editor/Editor.vue +293 -0
  8. package/src/components/index.ts +27 -0
  9. package/src/constants/index.ts +1 -0
  10. package/src/i18n/zh-cn.ts +160 -0
  11. package/src/icons/index.ts +93 -0
  12. package/src/index.ts +21 -0
  13. package/src/installer.ts +21 -0
  14. package/src/plugins/alert/index.ts +455 -0
  15. package/src/plugins/block-alignment/index.ts +117 -0
  16. package/src/plugins/block-alignment/readme.md +1 -0
  17. package/src/plugins/code/LICENSE +21 -0
  18. package/src/plugins/code/index.ts +619 -0
  19. package/src/plugins/code/utils/string.ts +34 -0
  20. package/src/plugins/color-picker/index.ts +132 -0
  21. package/src/plugins/delimiter/index.ts +121 -0
  22. package/src/plugins/drag-drop/index.css +19 -0
  23. package/src/plugins/drag-drop/index.ts +151 -0
  24. package/src/plugins/drag-drop/readme.md +1 -0
  25. package/src/plugins/header/H1.ts +404 -0
  26. package/src/plugins/header/H2.ts +403 -0
  27. package/src/plugins/header/H3.ts +404 -0
  28. package/src/plugins/header/H4.ts +404 -0
  29. package/src/plugins/header/H5.ts +403 -0
  30. package/src/plugins/header/H6.ts +404 -0
  31. package/src/plugins/header/index.ts +15 -0
  32. package/src/plugins/header/types.d.ts +46 -0
  33. package/src/plugins/imageResizeCrop/ImageTune.ts +635 -0
  34. package/src/plugins/imageResizeCrop/index.css +230 -0
  35. package/src/plugins/imageResizeCrop/index.ts +5 -0
  36. package/src/plugins/imageResizeCrop/types.d.ts +23 -0
  37. package/src/plugins/imageTool/index.ts +510 -0
  38. package/src/plugins/imageTool/types/codexteam__ajax.d.ts +89 -0
  39. package/src/plugins/imageTool/types/types.ts +236 -0
  40. package/src/plugins/imageTool/ui.ts +313 -0
  41. package/src/plugins/imageTool/uploader.ts +287 -0
  42. package/src/plugins/imageTool/utils/dom.ts +24 -0
  43. package/src/plugins/imageTool/utils/index.ts +73 -0
  44. package/src/plugins/imageTool/utils/isPromise.ts +10 -0
  45. package/src/plugins/indent/index.ts +695 -0
  46. package/src/plugins/inline-code/index.ts +203 -0
  47. package/src/plugins/list/ListRenderer/ChecklistRenderer.ts +208 -0
  48. package/src/plugins/list/ListRenderer/ListRenderer.ts +73 -0
  49. package/src/plugins/list/ListRenderer/OrderedListRenderer.ts +123 -0
  50. package/src/plugins/list/ListRenderer/UnorderedListRenderer.ts +123 -0
  51. package/src/plugins/list/ListRenderer/index.ts +6 -0
  52. package/src/plugins/list/ListTabulator/index.ts +1179 -0
  53. package/src/plugins/list/index.ts +488 -0
  54. package/src/plugins/list/styles/CssPrefix.ts +4 -0
  55. package/src/plugins/list/types/Elements.ts +14 -0
  56. package/src/plugins/list/types/ItemMeta.ts +40 -0
  57. package/src/plugins/list/types/ListParams.ts +102 -0
  58. package/src/plugins/list/types/ListRenderer.ts +6 -0
  59. package/src/plugins/list/types/OlCounterType.ts +63 -0
  60. package/src/plugins/list/types/index.ts +14 -0
  61. package/src/plugins/list/utils/focusItem.ts +18 -0
  62. package/src/plugins/list/utils/getChildItems.ts +40 -0
  63. package/src/plugins/list/utils/getItemChildWrapper.ts +10 -0
  64. package/src/plugins/list/utils/getItemContentElement.ts +10 -0
  65. package/src/plugins/list/utils/getSiblings.ts +52 -0
  66. package/src/plugins/list/utils/isLastItem.ts +9 -0
  67. package/src/plugins/list/utils/itemHasSublist.ts +10 -0
  68. package/src/plugins/list/utils/normalizeData.ts +83 -0
  69. package/src/plugins/list/utils/removeChildWrapperIfEmpty.ts +31 -0
  70. package/src/plugins/list/utils/renderToolboxInput.ts +113 -0
  71. package/src/plugins/list/utils/stripNumbers.ts +7 -0
  72. package/src/plugins/list/utils/type-guards.ts +8 -0
  73. package/src/plugins/marker/index.ts +199 -0
  74. package/src/plugins/outline/index.ts +62 -0
  75. package/src/plugins/outline/outline.css +52 -0
  76. package/src/plugins/paragraph/index.ts +384 -0
  77. package/src/plugins/paragraph/types/icons.d.ts +4 -0
  78. package/src/plugins/paragraph/utils/makeFragment.ts +17 -0
  79. package/src/plugins/quote/index.ts +203 -0
  80. package/src/plugins/table/index.ts +4 -0
  81. package/src/plugins/table/plugin.ts +255 -0
  82. package/src/plugins/table/table.ts +1202 -0
  83. package/src/plugins/table/toolbox.ts +166 -0
  84. package/src/plugins/table/utils/dom.ts +130 -0
  85. package/src/plugins/table/utils/popover.ts +185 -0
  86. package/src/plugins/table/utils/throttled.ts +22 -0
  87. package/src/plugins/underline/index.ts +214 -0
  88. package/src/plugins/undo/index.ts +526 -0
  89. package/src/plugins/undo/observer.ts +101 -0
  90. package/src/plugins/undo/vanilla-caret-js.ts +102 -0
  91. package/src/style.css +139 -0
  92. package/src/types.ts +3 -0
  93. package/src/utils/AxiosService.ts +87 -0
  94. package/src/utils/index.ts +15 -0
  95. package/src/utils/install.ts +19 -0
  96. package/tsconfig.json +37 -0
  97. package/vite.config.ts +81 -0
@@ -0,0 +1,62 @@
1
+
2
+ /**
3
+ * 文档大纲插件,用于自动生成文档大纲。
4
+ */
5
+ import "./outline.css";
6
+ import EditorJS from '@ebl-vue/editorjs';
7
+ import { OutputData } from '@ebl-vue/editorjs';
8
+ export default class Outline{
9
+ private holder: HTMLElement;
10
+
11
+ /**
12
+ * 初始化插件
13
+ * @param {EditorJS} editor - 编辑器实例
14
+ */
15
+ constructor(holderId: string,editor: EditorJS) {
16
+
17
+ this.holder = document.getElementById(holderId) as HTMLElement;
18
+
19
+ }
20
+
21
+ public render(data: OutputData) {
22
+ const ui = this.createUI(data);
23
+ this.holder.querySelector('.ebl-outline')?.remove();
24
+ this.holder.appendChild(ui);
25
+ this.holder.classList.add("outline");
26
+ }
27
+
28
+ private createUI(data: OutputData): HTMLElement {
29
+
30
+ const ui = document.createElement('div');
31
+ ui.className = 'ebl-outline';
32
+ const items = this.createList(data.blocks);
33
+ ui.append(...items);
34
+ return ui;
35
+ }
36
+
37
+ private createList(blocksData: any[]): HTMLElement[] {
38
+ const items: HTMLElement[] = [];
39
+ blocksData.forEach(blockData => {
40
+ if (blockData.type === 'h1' || blockData.type === 'h2' || blockData.type === 'h3') {
41
+ const item = document.createElement('div');
42
+ item.className = 'ebl-outline__item level_'+blockData.data.level;
43
+ item.dataset.id = blockData.id;
44
+ item.textContent = blockData.data.text.replace(/ /g,'');
45
+
46
+ item.addEventListener('click', () => {
47
+ items.forEach(el => el.classList.remove('active'));
48
+ item.classList.add('active');
49
+ const block = document.querySelector<HTMLElement>('.codex-editor .ce-block[data-id="' + blockData.id + '"]');
50
+ if (block) {
51
+ const top = block.offsetTop;
52
+ window.scrollTo(0, top);
53
+ }
54
+
55
+ });
56
+ items.push(item);
57
+ }
58
+ });
59
+ return items;
60
+
61
+ }
62
+ }
@@ -0,0 +1,52 @@
1
+ .ebl-outline{
2
+ background-color: #fff;
3
+ box-sizing: border-box;
4
+ position:fixed;
5
+ top:var(--header-height,84px);
6
+
7
+ right:0;
8
+ padding: 15px 12px;
9
+ width:280px;
10
+
11
+ overflow: hidden;
12
+ font-family: "Qihei Lenovo", "Microsoft YaHei", "微软雅黑", "宋体", sans-serif;
13
+ color: #606266;
14
+ font-size: 14px;
15
+ padding-bottom: 50px;
16
+ max-height: calc(100vh - var(--header-height,84px));
17
+ }
18
+ .ebl-outline:hover{
19
+ overflow: auto;
20
+ }
21
+
22
+
23
+
24
+ .ebl-outline__item{
25
+ cursor: pointer;
26
+ height:36px;
27
+ line-height: 36px;
28
+ padding: 0 24px;
29
+ word-break: break-word;
30
+ overflow: hidden;
31
+ text-overflow: ellipsis;
32
+ white-space: nowrap;
33
+ }
34
+
35
+ .ebl-outline .level_1{
36
+ padding-left: 24px;
37
+ }
38
+
39
+ .ebl-outline .level_2{
40
+ padding-left: 42px;
41
+ }
42
+ .ebl-outline .level_3{
43
+ padding-left: 60px;
44
+ }
45
+ .ebl-outline__item.active{
46
+ color:#222222;
47
+ font-weight: bold;
48
+ background-color: #EBEBEB;
49
+ }
50
+ .ebl-outline__item:hover{
51
+ background-color: #EBEBEB;
52
+ }
@@ -0,0 +1,384 @@
1
+ /**
2
+ * Build styles
3
+ */
4
+
5
+ import "@ebl-vue/editor-render/styles/paragraph.css"
6
+
7
+ import makeFragment from './utils/makeFragment';
8
+ import type { BlockToolConstructorOptions } from '@ebl-vue/editorjs';
9
+ import {IconText} from "../../icons";
10
+ export type ToolConstructorOptions = BlockToolConstructorOptions<ParagraphData>;
11
+
12
+
13
+ import type {
14
+ API,
15
+ ConversionConfig,
16
+ HTMLPasteEvent,
17
+ PasteConfig,
18
+ SanitizerConfig,
19
+ ToolConfig,
20
+ ToolboxConfig,
21
+ } from '@ebl-vue/editorjs';
22
+
23
+ /**
24
+ * Base Paragraph Block for the Editor.js.
25
+ * Represents a regular text block
26
+ *
27
+ * @author CodeX (team@codex.so)
28
+ * @copyright CodeX 2018
29
+ * @license The MIT License (MIT)
30
+ */
31
+
32
+ /**
33
+ * @typedef {object} ParagraphConfig
34
+ * @property {string} placeholder - placeholder for the empty paragraph
35
+ * @property {boolean} preserveBlank - Whether or not to keep blank paragraphs when saving editor data
36
+ */
37
+ export interface ParagraphConfig extends ToolConfig {
38
+ /**
39
+ * Placeholder for the empty paragraph
40
+ */
41
+ placeholder?: string;
42
+
43
+ /**
44
+ * Whether or not to keep blank paragraphs when saving editor data
45
+ */
46
+ preserveBlank?: boolean;
47
+ }
48
+
49
+ /**
50
+ * @typedef {object} ParagraphData
51
+ * @description Tool's input and output data format
52
+ * @property {string} text — Paragraph's content. Can include HTML tags: <a><b><i>
53
+ */
54
+ export interface ParagraphData {
55
+ /**
56
+ * Paragraph's content
57
+ */
58
+ text: string;
59
+ }
60
+
61
+ /**
62
+ * @typedef {object} ParagraphParams
63
+ * @description Constructor params for the Paragraph tool, use to pass initial data and settings
64
+ * @property {ParagraphData} data - Preload data for the paragraph.
65
+ * @property {ParagraphConfig} config - The configuration for the paragraph.
66
+ * @property {API} api - The Editor.js API.
67
+ * @property {boolean} readOnly - Is paragraph is read-only.
68
+ */
69
+ // interface ParagraphParams {
70
+ // /**
71
+ // * Initial data for the paragraph
72
+ // */
73
+ // data: ParagraphData;
74
+ // /**
75
+ // * Paragraph tool configuration
76
+ // */
77
+ // config: ParagraphConfig;
78
+ // /**
79
+ // * Editor.js API
80
+ // */
81
+ // api: API;
82
+ // /**
83
+ // * Is paragraph read-only.
84
+ // */
85
+ // readOnly: boolean;
86
+ // }
87
+
88
+ /**
89
+ * @typedef {object} ParagraphCSS
90
+ * @description CSS classes names
91
+ * @property {string} block - Editor.js CSS Class for block
92
+ * @property {string} wrapper - Paragraph CSS Class
93
+ */
94
+ interface ParagraphCSS {
95
+ /**
96
+ * Editor.js CSS Class for block
97
+ */
98
+ block: string;
99
+ /**
100
+ * Paragraph CSS Class
101
+ */
102
+ wrapper: string;
103
+ }
104
+
105
+ export default class Paragraph {
106
+ /**
107
+ * Default placeholder for Paragraph Tool
108
+ *
109
+ * @returns {string}
110
+ * @class
111
+ */
112
+ static get DEFAULT_PLACEHOLDER() {
113
+ return '';
114
+ }
115
+
116
+ /**
117
+ * The Editor.js API
118
+ */
119
+ api: API;
120
+
121
+ /**
122
+ * Is Paragraph Tool read-only
123
+ */
124
+ readOnly: boolean;
125
+
126
+ /**
127
+ * Paragraph Tool's CSS classes
128
+ */
129
+ private _CSS: ParagraphCSS;
130
+
131
+ /**
132
+ * Placeholder for Paragraph Tool
133
+ */
134
+ private _placeholder: string;
135
+
136
+ /**
137
+ * Paragraph's data
138
+ */
139
+ private _data: ParagraphData;
140
+
141
+ /**
142
+ * Paragraph's main Element
143
+ */
144
+ private _element: HTMLDivElement | null;
145
+
146
+ /**
147
+ * Whether or not to keep blank paragraphs when saving editor data
148
+ */
149
+ private _preserveBlank: boolean;
150
+
151
+ /**
152
+ * Render plugin`s main Element and fill it with saved data
153
+ *
154
+ * @param {object} params - constructor params
155
+ * @param {ParagraphData} params.data - previously saved data
156
+ * @param {ParagraphConfig} params.config - user config for Tool
157
+ * @param {object} params.api - editor.js api
158
+ * @param {boolean} readOnly - read only mode flag
159
+ */
160
+ constructor({ data, config, api, readOnly }: ToolConstructorOptions) {
161
+ this.api = api;
162
+ this.readOnly = readOnly;
163
+
164
+ this._CSS = {
165
+ block: this.api.styles.block,
166
+ wrapper: 'ce-paragraph',
167
+ };
168
+
169
+ if (!this.readOnly) {
170
+ this.onKeyUp = this.onKeyUp.bind(this);
171
+ }
172
+
173
+ /**
174
+ * Placeholder for paragraph if it is first Block
175
+ *
176
+ * @type {string}
177
+ */
178
+ this._placeholder = config.placeholder
179
+ ? config.placeholder
180
+ : Paragraph.DEFAULT_PLACEHOLDER;
181
+ this._data = data ?? {};
182
+ this._element = null;
183
+ this._preserveBlank = config.preserveBlank ?? false;
184
+ }
185
+
186
+ /**
187
+ * Check if text content is empty and set empty string to inner html.
188
+ * We need this because some browsers (e.g. Safari) insert <br> into empty contenteditanle elements
189
+ *
190
+ * @param {KeyboardEvent} e - key up event
191
+ */
192
+ onKeyUp(e: KeyboardEvent): void {
193
+ if (e.code !== 'Backspace' && e.code !== 'Delete') {
194
+ return;
195
+ }
196
+
197
+ if (!this._element) {
198
+ return;
199
+ }
200
+
201
+ const { textContent } = this._element;
202
+
203
+ if (textContent === '') {
204
+ this._element.innerHTML = '';
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Create Tool's view
210
+ *
211
+ * @returns {HTMLDivElement}
212
+ * @private
213
+ */
214
+ drawView(): HTMLDivElement {
215
+ const div = document.createElement('DIV');
216
+
217
+ div.classList.add(this._CSS.wrapper, this._CSS.block);
218
+ div.contentEditable = 'false';
219
+ div.dataset.placeholderActive = this.api.i18n.t(this._placeholder);
220
+
221
+ if (this._data.text) {
222
+ div.innerHTML = this._data.text;
223
+ }
224
+
225
+ if (!this.readOnly) {
226
+ div.contentEditable = 'true';
227
+ div.addEventListener('keyup', this.onKeyUp);
228
+ }
229
+
230
+ /**
231
+ * bypass property 'align' required in html div element
232
+ */
233
+ return div as HTMLDivElement;
234
+ }
235
+
236
+ /**
237
+ * Return Tool's view
238
+ *
239
+ * @returns {HTMLDivElement}
240
+ */
241
+ render(): HTMLDivElement {
242
+ this._element = this.drawView();
243
+
244
+ return this._element;
245
+ }
246
+
247
+
248
+
249
+ /**
250
+ * Method that specified how to merge two Text blocks.
251
+ * Called by Editor.js by backspace at the beginning of the Block
252
+ *
253
+ * @param {ParagraphData} data
254
+ * @public
255
+ */
256
+ merge(data: ParagraphData): void {
257
+ if (!this._element) {
258
+ return;
259
+ }
260
+
261
+ this._data.text += data.text;
262
+
263
+ /**
264
+ * We use appendChild instead of innerHTML to keep the links of the existing nodes
265
+ * (for example, shadow caret)
266
+ */
267
+ const fragment = makeFragment(data.text);
268
+
269
+ this._element.appendChild(fragment);
270
+
271
+ this._element.normalize();
272
+ }
273
+
274
+ /**
275
+ * Validate Paragraph block data:
276
+ * - check for emptiness
277
+ *
278
+ * @param {ParagraphData} savedData — data received after saving
279
+ * @returns {boolean} false if saved data is not correct, otherwise true
280
+ * @public
281
+ */
282
+ validate(savedData: ParagraphData): boolean {
283
+ if (savedData.text.trim() === '' && !this._preserveBlank) {
284
+ return false;
285
+ }
286
+
287
+ return true;
288
+ }
289
+
290
+ /**
291
+ * Extract Tool's data from the view
292
+ *
293
+ * @param {HTMLDivElement} toolsContent - Paragraph tools rendered view
294
+ * @returns {ParagraphData} - saved data
295
+ * @public
296
+ */
297
+ save(toolsContent: HTMLDivElement): ParagraphData {
298
+ let txt= {
299
+ text: toolsContent.innerHTML,
300
+ };
301
+
302
+ return txt;
303
+ }
304
+
305
+ /**
306
+ * On paste callback fired from Editor.
307
+ *
308
+ * @param {HTMLPasteEvent} event - event with pasted data
309
+ */
310
+ onPaste(event: HTMLPasteEvent): void {
311
+ const data = {
312
+ text: event.detail.data.innerHTML,
313
+ };
314
+
315
+ this._data = data;
316
+
317
+ /**
318
+ * We use requestAnimationFrame for performance purposes
319
+ */
320
+ window.requestAnimationFrame(() => {
321
+ if (!this._element) {
322
+ return;
323
+ }
324
+ this._element.innerHTML = this._data.text || '';
325
+ });
326
+ }
327
+
328
+ /**
329
+ * Enable Conversion Toolbar. Paragraph can be converted to/from other tools
330
+ * @returns {ConversionConfig}
331
+ */
332
+ static get conversionConfig(): ConversionConfig {
333
+ return {
334
+ export: 'text', // to convert Paragraph to other block, use 'text' property of saved data
335
+ import: 'text', // to covert other block's exported string to Paragraph, fill 'text' property of tool data
336
+ };
337
+ }
338
+
339
+ /**
340
+ * Sanitizer rules
341
+ * @returns {SanitizerConfig} - Edtior.js sanitizer config
342
+ */
343
+ static get sanitize(): SanitizerConfig {
344
+ return {
345
+ text: {
346
+ br: true,
347
+ },
348
+ };
349
+ }
350
+
351
+ /**
352
+ * Returns true to notify the core that read-only mode is supported
353
+ *
354
+ * @returns {boolean}
355
+ */
356
+ static get isReadOnlySupported(): boolean {
357
+ return true;
358
+ }
359
+
360
+ /**
361
+ * Used by Editor paste handling API.
362
+ * Provides configuration to handle P tags.
363
+ *
364
+ * @returns {PasteConfig} - Paragraph Paste Setting
365
+ */
366
+ static get pasteConfig(): PasteConfig {
367
+ return {
368
+ tags: ['P','div','br'],
369
+ };
370
+ }
371
+
372
+ /**
373
+ * Icon and title for displaying at the Toolbox
374
+ *
375
+ * @returns {ToolboxConfig} - Paragraph Toolbox Setting
376
+ */
377
+ static get toolbox(): ToolboxConfig {
378
+ return {
379
+ icon: IconText,
380
+ title: 'Text',
381
+
382
+ };
383
+ }
384
+ }
@@ -0,0 +1,4 @@
1
+ // temporary fix for the missing types
2
+ declare module "@codexteam/icons" {
3
+ export const IconText: string;
4
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Create a DocumentFragment and fill it with HTML from a string
3
+ *
4
+ * @param {string} htmlString - A string of valid HTML
5
+ * @returns {DocumentFragment}
6
+ */
7
+ export default function makeFragment(htmlString: string): DocumentFragment {
8
+ const tempDiv = document.createElement('div');
9
+
10
+ tempDiv.innerHTML = htmlString.trim();
11
+
12
+ const fragment = document.createDocumentFragment();
13
+
14
+ fragment.append(...Array.from(tempDiv.childNodes));
15
+
16
+ return fragment;
17
+ }
@@ -0,0 +1,203 @@
1
+ import "@ebl-vue/editor-render/styles/quote.css";
2
+
3
+
4
+ import { make } from "@editorjs/dom";
5
+ import type { API, BlockAPI, BlockTool } from "@ebl-vue/editorjs";
6
+ import { IconQuote } from "../../icons";
7
+
8
+
9
+
10
+
11
+
12
+ /**
13
+ * Data structure for the Quote block.
14
+ */
15
+ export interface QuoteData {
16
+ text: string;
17
+ }
18
+
19
+ /**
20
+ * Parameters for the Quote constructor.
21
+ */
22
+ interface QuoteParams {
23
+ data: QuoteData;
24
+ api: API;
25
+ readOnly: boolean;
26
+ block: BlockAPI;
27
+ }
28
+
29
+ /**
30
+ * CSS class names used in the Quote block.
31
+ */
32
+ interface QuoteCSS {
33
+ baseClass: string;
34
+ wrapper: string;
35
+ input: string;
36
+ text: string;
37
+ }
38
+
39
+
40
+
41
+ /**
42
+ * Quote class representing a customizable quote block for Editor.js.
43
+ */
44
+ export default class Quote implements BlockTool {
45
+ api: API;
46
+ readOnly: boolean;
47
+
48
+
49
+ private _data: QuoteData;
50
+ private _CSS: QuoteCSS;
51
+ private _quoteElement!: HTMLElement;
52
+
53
+ /**
54
+ * Creates an instance of the Quote block.
55
+ * @param params - The parameters for the Quote block.
56
+ */
57
+ constructor({ data, api, readOnly }: QuoteParams) {
58
+
59
+
60
+ this.api = api;
61
+ this.readOnly = readOnly;
62
+
63
+ this._data = {
64
+ text: data.text || ""
65
+ };
66
+
67
+ this._CSS = {
68
+ baseClass: this.api.styles.block,
69
+ wrapper: "cdx-quote",
70
+ text: "cdx-quote__text",
71
+ input: this.api.styles.input,
72
+ };
73
+
74
+ }
75
+
76
+ static get isReadOnlySupported(): boolean {
77
+ return true;
78
+ }
79
+
80
+ static get toolbox() {
81
+ return {
82
+ icon: IconQuote,
83
+ title: "Quote",
84
+ };
85
+ }
86
+
87
+ static get contentless(): boolean {
88
+ return true;
89
+ }
90
+
91
+ static get enableLineBreaks(): boolean {
92
+ return true;
93
+ }
94
+
95
+
96
+
97
+ static get conversionConfig() {
98
+ return {
99
+ import: "text",
100
+
101
+ export: function (quoteData: QuoteData): string {
102
+ return quoteData.text;
103
+ },
104
+ };
105
+ }
106
+
107
+
108
+ get CSS(): QuoteCSS {
109
+ return {
110
+ baseClass: this.api.styles.block,
111
+ wrapper: "cdx-quote",
112
+ text: "cdx-quote__text",
113
+ input: this.api.styles.input,
114
+ };
115
+ }
116
+
117
+
118
+
119
+ render(): HTMLElement {
120
+ const container = make("div", [this._CSS.baseClass, this._CSS.wrapper]);
121
+ this._quoteElement = make(
122
+ "blockquote",
123
+ [this._CSS.input, this._CSS.text, "cdx-block-quote"],
124
+ {
125
+ contentEditable: !this.readOnly,
126
+ innerHTML: this._data.text,
127
+ }
128
+ );
129
+
130
+ this._quoteElement.addEventListener("keydown", (event: KeyboardEvent) =>
131
+ this.handleKeydown(event)
132
+ );
133
+
134
+ container.appendChild(this._quoteElement);
135
+ return container;
136
+ }
137
+
138
+
139
+ get currentItem(): HTMLElement {
140
+ let currentNode = window.getSelection()!.anchorNode;
141
+
142
+ if (currentNode!.nodeType !== Node.ELEMENT_NODE) {
143
+ currentNode = currentNode!.parentNode;
144
+ }
145
+
146
+ return (currentNode as Element).closest(`.${this.CSS.text}`) as HTMLElement;
147
+ }
148
+
149
+
150
+ handleKeydown(event: KeyboardEvent) {
151
+ const currentItem = this._quoteElement;
152
+
153
+ if (event.key === "Enter") {
154
+ if (!event.shiftKey) {
155
+ event.preventDefault();
156
+ this.getOutOfQuote();
157
+ }
158
+ }
159
+
160
+ if (
161
+ event.key === "Backspace" &&
162
+ currentItem.textContent?.trim().length === 0
163
+ ) {
164
+ event.preventDefault();
165
+
166
+ const currentBlockIndex = this.api.blocks.getCurrentBlockIndex();
167
+ this.api.blocks.delete(currentBlockIndex);
168
+ this.api.blocks.insert("paragraph", { text: "" });
169
+ this.api.caret.setToBlock(currentBlockIndex);
170
+
171
+ return;
172
+ }
173
+ }
174
+
175
+
176
+ getOutOfQuote() {
177
+ this.api.blocks.insert();
178
+ this.api.caret.setToBlock(this.api.blocks.getCurrentBlockIndex());
179
+ }
180
+
181
+
182
+
183
+
184
+ save(quoteElement: HTMLDivElement): QuoteData {
185
+ const text = quoteElement.querySelector(`.${this._CSS.text}`);
186
+
187
+ return Object.assign(this._data, {
188
+ text: text?.innerHTML ?? "",
189
+ });
190
+ }
191
+
192
+ static get sanitize() {
193
+ return {
194
+ text: {
195
+ br: true,
196
+ },
197
+ };
198
+ }
199
+
200
+
201
+
202
+
203
+ }