@ebl-vue/editor-full 1.0.8
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/.postcssrc.yml +33 -0
- package/LICENSE +21 -0
- package/README.md +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.mjs +2565 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +55 -0
- package/postcss.config.js +15 -0
- package/src/components/Editor/Editor.vue +209 -0
- package/src/components/index.ts +27 -0
- package/src/constants/index.ts +1 -0
- package/src/i18n/zh-cn.ts +151 -0
- package/src/icons/index.ts +78 -0
- package/src/index.ts +11 -0
- package/src/installer.ts +22 -0
- package/src/plugins/alert/index.css +150 -0
- package/src/plugins/alert/index.ts +463 -0
- package/src/plugins/block-alignment/index.css +9 -0
- package/src/plugins/block-alignment/index.ts +116 -0
- package/src/plugins/block-alignment/readme.md +1 -0
- package/src/plugins/code/LICENSE +21 -0
- package/src/plugins/code/index.css +120 -0
- package/src/plugins/code/index.ts +530 -0
- package/src/plugins/code/utils/string.ts +34 -0
- package/src/plugins/color-picker/index.ts +138 -0
- package/src/plugins/color-picker/styles.css +27 -0
- package/src/plugins/delimiter/index.css +14 -0
- package/src/plugins/delimiter/index.ts +122 -0
- package/src/plugins/drag-drop/index.css +19 -0
- package/src/plugins/drag-drop/index.ts +151 -0
- package/src/plugins/drag-drop/readme.md +1 -0
- package/src/plugins/header/H1.ts +405 -0
- package/src/plugins/header/H2.ts +403 -0
- package/src/plugins/header/H3.ts +404 -0
- package/src/plugins/header/H4.ts +405 -0
- package/src/plugins/header/H5.ts +405 -0
- package/src/plugins/header/H6.ts +406 -0
- package/src/plugins/header/index.css +20 -0
- package/src/plugins/header/index.ts +15 -0
- package/src/plugins/header/types.d.ts +46 -0
- package/src/plugins/indent/index.css +86 -0
- package/src/plugins/indent/index.ts +697 -0
- package/src/plugins/inline-code/index.css +11 -0
- package/src/plugins/inline-code/index.ts +205 -0
- package/src/plugins/list/ListRenderer/ChecklistRenderer.ts +211 -0
- package/src/plugins/list/ListRenderer/ListRenderer.ts +73 -0
- package/src/plugins/list/ListRenderer/OrderedListRenderer.ts +123 -0
- package/src/plugins/list/ListRenderer/UnorderedListRenderer.ts +123 -0
- package/src/plugins/list/ListRenderer/index.ts +6 -0
- package/src/plugins/list/ListTabulator/index.ts +1179 -0
- package/src/plugins/list/index.ts +502 -0
- package/src/plugins/list/styles/CssPrefix.ts +4 -0
- package/src/plugins/list/styles/icons/index.ts +10 -0
- package/src/plugins/list/styles/input.css +36 -0
- package/src/plugins/list/styles/list.css +165 -0
- package/src/plugins/list/types/Elements.ts +14 -0
- package/src/plugins/list/types/ItemMeta.ts +40 -0
- package/src/plugins/list/types/ListParams.ts +102 -0
- package/src/plugins/list/types/ListRenderer.ts +6 -0
- package/src/plugins/list/types/OlCounterType.ts +63 -0
- package/src/plugins/list/types/index.ts +14 -0
- package/src/plugins/list/utils/focusItem.ts +18 -0
- package/src/plugins/list/utils/getChildItems.ts +40 -0
- package/src/plugins/list/utils/getItemChildWrapper.ts +10 -0
- package/src/plugins/list/utils/getItemContentElement.ts +10 -0
- package/src/plugins/list/utils/getSiblings.ts +52 -0
- package/src/plugins/list/utils/isLastItem.ts +9 -0
- package/src/plugins/list/utils/itemHasSublist.ts +10 -0
- package/src/plugins/list/utils/normalizeData.ts +84 -0
- package/src/plugins/list/utils/removeChildWrapperIfEmpty.ts +31 -0
- package/src/plugins/list/utils/renderToolboxInput.ts +105 -0
- package/src/plugins/list/utils/stripNumbers.ts +7 -0
- package/src/plugins/list/utils/type-guards.ts +8 -0
- package/src/plugins/list.md +15 -0
- package/src/plugins/marker/index.css +4 -0
- package/src/plugins/marker/index.ts +187 -0
- package/src/plugins/paragraph/index.css +23 -0
- package/src/plugins/paragraph/index.ts +380 -0
- package/src/plugins/paragraph/types/icons.d.ts +4 -0
- package/src/plugins/paragraph/utils/makeFragment.ts +17 -0
- package/src/plugins/quote/index.css +26 -0
- package/src/plugins/quote/index.ts +206 -0
- package/src/plugins/table/index.ts +4 -0
- package/src/plugins/table/plugin.ts +254 -0
- package/src/plugins/table/style.css +388 -0
- package/src/plugins/table/table.ts +1192 -0
- package/src/plugins/table/toolbox.ts +165 -0
- package/src/plugins/table/utils/dom.ts +128 -0
- package/src/plugins/table/utils/popover.ts +172 -0
- package/src/plugins/table/utils/throttled.ts +22 -0
- package/src/plugins/underline/index.css +3 -0
- package/src/plugins/underline/index.ts +216 -0
- package/src/plugins/undo/index.ts +509 -0
- package/src/plugins/undo/observer.ts +101 -0
- package/src/style.css +89 -0
- package/src/utils/index.ts +15 -0
- package/src/utils/install.ts +19 -0
- package/tsconfig.json +37 -0
- package/types/index.d.ts +13 -0
- package/types/plugins/index.d.ts +0 -0
- package/vite.config.ts +79 -0
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build styles
|
|
3
|
+
*/
|
|
4
|
+
import './index.css';
|
|
5
|
+
|
|
6
|
+
import makeFragment from './utils/makeFragment';
|
|
7
|
+
import type { BlockToolConstructorOptions } from '@ebl-vue/editorjs';
|
|
8
|
+
export type ToolConstructorOptions = BlockToolConstructorOptions<ParagraphData>;
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
API,
|
|
13
|
+
ConversionConfig,
|
|
14
|
+
HTMLPasteEvent,
|
|
15
|
+
PasteConfig,
|
|
16
|
+
SanitizerConfig,
|
|
17
|
+
ToolConfig,
|
|
18
|
+
ToolboxConfig,
|
|
19
|
+
} from '@ebl-vue/editorjs';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Base Paragraph Block for the Editor.js.
|
|
23
|
+
* Represents a regular text block
|
|
24
|
+
*
|
|
25
|
+
* @author CodeX (team@codex.so)
|
|
26
|
+
* @copyright CodeX 2018
|
|
27
|
+
* @license The MIT License (MIT)
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @typedef {object} ParagraphConfig
|
|
32
|
+
* @property {string} placeholder - placeholder for the empty paragraph
|
|
33
|
+
* @property {boolean} preserveBlank - Whether or not to keep blank paragraphs when saving editor data
|
|
34
|
+
*/
|
|
35
|
+
export interface ParagraphConfig extends ToolConfig {
|
|
36
|
+
/**
|
|
37
|
+
* Placeholder for the empty paragraph
|
|
38
|
+
*/
|
|
39
|
+
placeholder?: string;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Whether or not to keep blank paragraphs when saving editor data
|
|
43
|
+
*/
|
|
44
|
+
preserveBlank?: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @typedef {object} ParagraphData
|
|
49
|
+
* @description Tool's input and output data format
|
|
50
|
+
* @property {string} text — Paragraph's content. Can include HTML tags: <a><b><i>
|
|
51
|
+
*/
|
|
52
|
+
export interface ParagraphData {
|
|
53
|
+
/**
|
|
54
|
+
* Paragraph's content
|
|
55
|
+
*/
|
|
56
|
+
text: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @typedef {object} ParagraphParams
|
|
61
|
+
* @description Constructor params for the Paragraph tool, use to pass initial data and settings
|
|
62
|
+
* @property {ParagraphData} data - Preload data for the paragraph.
|
|
63
|
+
* @property {ParagraphConfig} config - The configuration for the paragraph.
|
|
64
|
+
* @property {API} api - The Editor.js API.
|
|
65
|
+
* @property {boolean} readOnly - Is paragraph is read-only.
|
|
66
|
+
*/
|
|
67
|
+
// interface ParagraphParams {
|
|
68
|
+
// /**
|
|
69
|
+
// * Initial data for the paragraph
|
|
70
|
+
// */
|
|
71
|
+
// data: ParagraphData;
|
|
72
|
+
// /**
|
|
73
|
+
// * Paragraph tool configuration
|
|
74
|
+
// */
|
|
75
|
+
// config: ParagraphConfig;
|
|
76
|
+
// /**
|
|
77
|
+
// * Editor.js API
|
|
78
|
+
// */
|
|
79
|
+
// api: API;
|
|
80
|
+
// /**
|
|
81
|
+
// * Is paragraph read-only.
|
|
82
|
+
// */
|
|
83
|
+
// readOnly: boolean;
|
|
84
|
+
// }
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @typedef {object} ParagraphCSS
|
|
88
|
+
* @description CSS classes names
|
|
89
|
+
* @property {string} block - Editor.js CSS Class for block
|
|
90
|
+
* @property {string} wrapper - Paragraph CSS Class
|
|
91
|
+
*/
|
|
92
|
+
interface ParagraphCSS {
|
|
93
|
+
/**
|
|
94
|
+
* Editor.js CSS Class for block
|
|
95
|
+
*/
|
|
96
|
+
block: string;
|
|
97
|
+
/**
|
|
98
|
+
* Paragraph CSS Class
|
|
99
|
+
*/
|
|
100
|
+
wrapper: string;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export default class Paragraph {
|
|
104
|
+
/**
|
|
105
|
+
* Default placeholder for Paragraph Tool
|
|
106
|
+
*
|
|
107
|
+
* @returns {string}
|
|
108
|
+
* @class
|
|
109
|
+
*/
|
|
110
|
+
static get DEFAULT_PLACEHOLDER() {
|
|
111
|
+
return '';
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* The Editor.js API
|
|
116
|
+
*/
|
|
117
|
+
api: API;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Is Paragraph Tool read-only
|
|
121
|
+
*/
|
|
122
|
+
readOnly: boolean;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Paragraph Tool's CSS classes
|
|
126
|
+
*/
|
|
127
|
+
private _CSS: ParagraphCSS;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Placeholder for Paragraph Tool
|
|
131
|
+
*/
|
|
132
|
+
private _placeholder: string;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Paragraph's data
|
|
136
|
+
*/
|
|
137
|
+
private _data: ParagraphData;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Paragraph's main Element
|
|
141
|
+
*/
|
|
142
|
+
private _element: HTMLDivElement | null;
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Whether or not to keep blank paragraphs when saving editor data
|
|
146
|
+
*/
|
|
147
|
+
private _preserveBlank: boolean;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Render plugin`s main Element and fill it with saved data
|
|
151
|
+
*
|
|
152
|
+
* @param {object} params - constructor params
|
|
153
|
+
* @param {ParagraphData} params.data - previously saved data
|
|
154
|
+
* @param {ParagraphConfig} params.config - user config for Tool
|
|
155
|
+
* @param {object} params.api - editor.js api
|
|
156
|
+
* @param {boolean} readOnly - read only mode flag
|
|
157
|
+
*/
|
|
158
|
+
constructor({ data, config, api, readOnly }: ToolConstructorOptions) {
|
|
159
|
+
this.api = api;
|
|
160
|
+
this.readOnly = readOnly;
|
|
161
|
+
|
|
162
|
+
this._CSS = {
|
|
163
|
+
block: this.api.styles.block,
|
|
164
|
+
wrapper: 'ce-paragraph',
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
if (!this.readOnly) {
|
|
168
|
+
this.onKeyUp = this.onKeyUp.bind(this);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Placeholder for paragraph if it is first Block
|
|
173
|
+
*
|
|
174
|
+
* @type {string}
|
|
175
|
+
*/
|
|
176
|
+
this._placeholder = config.placeholder
|
|
177
|
+
? config.placeholder
|
|
178
|
+
: Paragraph.DEFAULT_PLACEHOLDER;
|
|
179
|
+
this._data = data ?? {};
|
|
180
|
+
this._element = null;
|
|
181
|
+
this._preserveBlank = config.preserveBlank ?? false;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Check if text content is empty and set empty string to inner html.
|
|
186
|
+
* We need this because some browsers (e.g. Safari) insert <br> into empty contenteditanle elements
|
|
187
|
+
*
|
|
188
|
+
* @param {KeyboardEvent} e - key up event
|
|
189
|
+
*/
|
|
190
|
+
onKeyUp(e: KeyboardEvent): void {
|
|
191
|
+
if (e.code !== 'Backspace' && e.code !== 'Delete') {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (!this._element) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const { textContent } = this._element;
|
|
200
|
+
|
|
201
|
+
if (textContent === '') {
|
|
202
|
+
this._element.innerHTML = '';
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Create Tool's view
|
|
208
|
+
*
|
|
209
|
+
* @returns {HTMLDivElement}
|
|
210
|
+
* @private
|
|
211
|
+
*/
|
|
212
|
+
drawView(): HTMLDivElement {
|
|
213
|
+
const div = document.createElement('DIV');
|
|
214
|
+
|
|
215
|
+
div.classList.add(this._CSS.wrapper, this._CSS.block);
|
|
216
|
+
div.contentEditable = 'false';
|
|
217
|
+
div.dataset.placeholderActive = this.api.i18n.t(this._placeholder);
|
|
218
|
+
|
|
219
|
+
if (this._data.text) {
|
|
220
|
+
div.innerHTML = this._data.text;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (!this.readOnly) {
|
|
224
|
+
div.contentEditable = 'true';
|
|
225
|
+
div.addEventListener('keyup', this.onKeyUp);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* bypass property 'align' required in html div element
|
|
230
|
+
*/
|
|
231
|
+
return div as HTMLDivElement;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Return Tool's view
|
|
236
|
+
*
|
|
237
|
+
* @returns {HTMLDivElement}
|
|
238
|
+
*/
|
|
239
|
+
render(): HTMLDivElement {
|
|
240
|
+
this._element = this.drawView();
|
|
241
|
+
|
|
242
|
+
return this._element;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Method that specified how to merge two Text blocks.
|
|
249
|
+
* Called by Editor.js by backspace at the beginning of the Block
|
|
250
|
+
*
|
|
251
|
+
* @param {ParagraphData} data
|
|
252
|
+
* @public
|
|
253
|
+
*/
|
|
254
|
+
merge(data: ParagraphData): void {
|
|
255
|
+
if (!this._element) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
this._data.text += data.text;
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* We use appendChild instead of innerHTML to keep the links of the existing nodes
|
|
263
|
+
* (for example, shadow caret)
|
|
264
|
+
*/
|
|
265
|
+
const fragment = makeFragment(data.text);
|
|
266
|
+
|
|
267
|
+
this._element.appendChild(fragment);
|
|
268
|
+
|
|
269
|
+
this._element.normalize();
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Validate Paragraph block data:
|
|
274
|
+
* - check for emptiness
|
|
275
|
+
*
|
|
276
|
+
* @param {ParagraphData} savedData — data received after saving
|
|
277
|
+
* @returns {boolean} false if saved data is not correct, otherwise true
|
|
278
|
+
* @public
|
|
279
|
+
*/
|
|
280
|
+
validate(savedData: ParagraphData): boolean {
|
|
281
|
+
if (savedData.text.trim() === '' && !this._preserveBlank) {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Extract Tool's data from the view
|
|
290
|
+
*
|
|
291
|
+
* @param {HTMLDivElement} toolsContent - Paragraph tools rendered view
|
|
292
|
+
* @returns {ParagraphData} - saved data
|
|
293
|
+
* @public
|
|
294
|
+
*/
|
|
295
|
+
save(toolsContent: HTMLDivElement): ParagraphData {
|
|
296
|
+
return {
|
|
297
|
+
text: toolsContent.innerHTML,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* On paste callback fired from Editor.
|
|
303
|
+
*
|
|
304
|
+
* @param {HTMLPasteEvent} event - event with pasted data
|
|
305
|
+
*/
|
|
306
|
+
onPaste(event: HTMLPasteEvent): void {
|
|
307
|
+
const data = {
|
|
308
|
+
text: event.detail.data.innerHTML,
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
this._data = data;
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* We use requestAnimationFrame for performance purposes
|
|
315
|
+
*/
|
|
316
|
+
window.requestAnimationFrame(() => {
|
|
317
|
+
if (!this._element) {
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
this._element.innerHTML = this._data.text || '';
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Enable Conversion Toolbar. Paragraph can be converted to/from other tools
|
|
326
|
+
* @returns {ConversionConfig}
|
|
327
|
+
*/
|
|
328
|
+
static get conversionConfig(): ConversionConfig {
|
|
329
|
+
return {
|
|
330
|
+
export: 'text', // to convert Paragraph to other block, use 'text' property of saved data
|
|
331
|
+
import: 'text', // to covert other block's exported string to Paragraph, fill 'text' property of tool data
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Sanitizer rules
|
|
337
|
+
* @returns {SanitizerConfig} - Edtior.js sanitizer config
|
|
338
|
+
*/
|
|
339
|
+
static get sanitize(): SanitizerConfig {
|
|
340
|
+
return {
|
|
341
|
+
text: {
|
|
342
|
+
br: true,
|
|
343
|
+
},
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Returns true to notify the core that read-only mode is supported
|
|
349
|
+
*
|
|
350
|
+
* @returns {boolean}
|
|
351
|
+
*/
|
|
352
|
+
static get isReadOnlySupported(): boolean {
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Used by Editor paste handling API.
|
|
358
|
+
* Provides configuration to handle P tags.
|
|
359
|
+
*
|
|
360
|
+
* @returns {PasteConfig} - Paragraph Paste Setting
|
|
361
|
+
*/
|
|
362
|
+
static get pasteConfig(): PasteConfig {
|
|
363
|
+
return {
|
|
364
|
+
tags: ['P'],
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Icon and title for displaying at the Toolbox
|
|
370
|
+
*
|
|
371
|
+
* @returns {ToolboxConfig} - Paragraph Toolbox Setting
|
|
372
|
+
*/
|
|
373
|
+
static get toolbox(): ToolboxConfig {
|
|
374
|
+
return {
|
|
375
|
+
icon: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="M8 9V7.2C8 7.08954 8.08954 7 8.2 7L12 7M16 9V7.2C16 7.08954 15.9105 7 15.8 7L12 7M12 7L12 17M12 17H10M12 17H14"/></svg>`,
|
|
376
|
+
title: 'Text',
|
|
377
|
+
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
}
|
|
@@ -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,26 @@
|
|
|
1
|
+
|
|
2
|
+
.cdx-quote {
|
|
3
|
+
overflow: hidden;
|
|
4
|
+
overflow-wrap: break-word;
|
|
5
|
+
margin: 0;
|
|
6
|
+
box-sizing: border-box;
|
|
7
|
+
word-wrap: break-word;
|
|
8
|
+
word-break: break-all;
|
|
9
|
+
}
|
|
10
|
+
.cdx-quote blockquote{
|
|
11
|
+
margin: 0;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
.cdx-block-quote{
|
|
18
|
+
border: solid #acacac;
|
|
19
|
+
border-width: 0 0 0 3px;
|
|
20
|
+
box-shadow: none;
|
|
21
|
+
color: #666;
|
|
22
|
+
line-height: 1.6em;
|
|
23
|
+
padding: 1px 0 0 12px;
|
|
24
|
+
text-align: left;
|
|
25
|
+
}
|
|
26
|
+
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import "./index.css";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import { make } from "@editorjs/dom";
|
|
5
|
+
import type { API, BlockAPI, BlockTool } from "@ebl-vue/editorjs";
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
const IconQuote = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
9
|
+
<path d="M10 10.8182L9 10.8182C8.80222 10.8182 8.60888 10.7649 8.44443 10.665C8.27998 10.5651 8.15181 10.4231 8.07612 10.257C8.00043 10.0909 7.98063 9.90808 8.01922 9.73174C8.0578 9.55539 8.15304 9.39341 8.29289 9.26627C8.43275 9.13913 8.61093 9.05255 8.80491 9.01747C8.99889 8.98239 9.19996 9.00039 9.38268 9.0692C9.56541 9.13801 9.72159 9.25453 9.83147 9.40403C9.94135 9.55353 10 9.72929 10 9.90909L10 12.1818C10 12.664 9.78929 13.1265 9.41421 13.4675C9.03914 13.8084 8.53043 14 8 14" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
10
|
+
<path d="M16 10.8182L15 10.8182C14.8022 10.8182 14.6089 10.7649 14.4444 10.665C14.28 10.5651 14.1518 10.4231 14.0761 10.257C14.0004 10.0909 13.9806 9.90808 14.0192 9.73174C14.0578 9.55539 14.153 9.39341 14.2929 9.26627C14.4327 9.13913 14.6109 9.05255 14.8049 9.01747C14.9989 8.98239 15.2 9.00039 15.3827 9.0692C15.5654 9.13801 15.7216 9.25453 15.8315 9.40403C15.9414 9.55353 16 9.72929 16 9.90909L16 12.1818C16 12.664 15.7893 13.1265 15.4142 13.4675C15.0391 13.8084 14.5304 14 14 14" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
11
|
+
</svg>`;
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Data structure for the Quote block.
|
|
17
|
+
*/
|
|
18
|
+
export interface QuoteData {
|
|
19
|
+
text: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Parameters for the Quote constructor.
|
|
24
|
+
*/
|
|
25
|
+
interface QuoteParams {
|
|
26
|
+
data: QuoteData;
|
|
27
|
+
api: API;
|
|
28
|
+
readOnly: boolean;
|
|
29
|
+
block: BlockAPI;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* CSS class names used in the Quote block.
|
|
34
|
+
*/
|
|
35
|
+
interface QuoteCSS {
|
|
36
|
+
baseClass: string;
|
|
37
|
+
wrapper: string;
|
|
38
|
+
input: string;
|
|
39
|
+
text: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Quote class representing a customizable quote block for Editor.js.
|
|
46
|
+
*/
|
|
47
|
+
export default class Quote implements BlockTool {
|
|
48
|
+
api: API;
|
|
49
|
+
readOnly: boolean;
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
private _data: QuoteData;
|
|
53
|
+
private _CSS: QuoteCSS;
|
|
54
|
+
private _quoteElement!: HTMLElement;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Creates an instance of the Quote block.
|
|
58
|
+
* @param params - The parameters for the Quote block.
|
|
59
|
+
*/
|
|
60
|
+
constructor({ data, api, readOnly }: QuoteParams) {
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
this.api = api;
|
|
64
|
+
this.readOnly = readOnly;
|
|
65
|
+
|
|
66
|
+
this._data = {
|
|
67
|
+
text: data.text || ""
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
this._CSS = {
|
|
71
|
+
baseClass: this.api.styles.block,
|
|
72
|
+
wrapper: "cdx-quote",
|
|
73
|
+
text: "cdx-quote__text",
|
|
74
|
+
input: this.api.styles.input,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
static get isReadOnlySupported(): boolean {
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
static get toolbox() {
|
|
84
|
+
return {
|
|
85
|
+
icon: IconQuote,
|
|
86
|
+
title: "Quote",
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
static get contentless(): boolean {
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
static get enableLineBreaks(): boolean {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
static get conversionConfig() {
|
|
101
|
+
return {
|
|
102
|
+
import: "text",
|
|
103
|
+
|
|
104
|
+
export: function (quoteData: QuoteData): string {
|
|
105
|
+
return quoteData.text;
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
get CSS(): QuoteCSS {
|
|
112
|
+
return {
|
|
113
|
+
baseClass: this.api.styles.block,
|
|
114
|
+
wrapper: "cdx-quote",
|
|
115
|
+
text: "cdx-quote__text",
|
|
116
|
+
input: this.api.styles.input,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
render(): HTMLElement {
|
|
123
|
+
const container = make("div", [this._CSS.baseClass, this._CSS.wrapper]);
|
|
124
|
+
this._quoteElement = make(
|
|
125
|
+
"blockquote",
|
|
126
|
+
[this._CSS.input, this._CSS.text, "cdx-block-quote"],
|
|
127
|
+
{
|
|
128
|
+
contentEditable: !this.readOnly,
|
|
129
|
+
innerHTML: this._data.text,
|
|
130
|
+
}
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
this._quoteElement.addEventListener("keydown", (event: KeyboardEvent) =>
|
|
134
|
+
this.handleKeydown(event)
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
container.appendChild(this._quoteElement);
|
|
138
|
+
return container;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
get currentItem(): HTMLElement {
|
|
143
|
+
let currentNode = window.getSelection()!.anchorNode;
|
|
144
|
+
|
|
145
|
+
if (currentNode!.nodeType !== Node.ELEMENT_NODE) {
|
|
146
|
+
currentNode = currentNode!.parentNode;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return (currentNode as Element).closest(`.${this.CSS.text}`) as HTMLElement;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
handleKeydown(event: KeyboardEvent) {
|
|
154
|
+
const currentItem = this._quoteElement;
|
|
155
|
+
|
|
156
|
+
if (event.key === "Enter") {
|
|
157
|
+
if (!event.shiftKey) {
|
|
158
|
+
event.preventDefault();
|
|
159
|
+
this.getOutOfQuote();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (
|
|
164
|
+
event.key === "Backspace" &&
|
|
165
|
+
currentItem.textContent?.trim().length === 0
|
|
166
|
+
) {
|
|
167
|
+
event.preventDefault();
|
|
168
|
+
|
|
169
|
+
const currentBlockIndex = this.api.blocks.getCurrentBlockIndex();
|
|
170
|
+
this.api.blocks.delete(currentBlockIndex);
|
|
171
|
+
this.api.blocks.insert("paragraph", { text: "" });
|
|
172
|
+
this.api.caret.setToBlock(currentBlockIndex);
|
|
173
|
+
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
getOutOfQuote() {
|
|
180
|
+
this.api.blocks.insert();
|
|
181
|
+
this.api.caret.setToBlock(this.api.blocks.getCurrentBlockIndex());
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
save(quoteElement: HTMLDivElement): QuoteData {
|
|
188
|
+
const text = quoteElement.querySelector(`.${this._CSS.text}`);
|
|
189
|
+
|
|
190
|
+
return Object.assign(this._data, {
|
|
191
|
+
text: text?.innerHTML ?? "",
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
static get sanitize() {
|
|
196
|
+
return {
|
|
197
|
+
text: {
|
|
198
|
+
br: true,
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
}
|