@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.
- package/.postcssrc.yml +33 -0
- package/dist/index.d.ts +1 -2
- package/dist/index.mjs +183 -182
- package/dist/index.mjs.map +1 -0
- package/package.json +1 -1
- package/postcss.config.js +15 -0
- package/src/components/Editor/Editor.vue +293 -0
- package/src/components/index.ts +27 -0
- package/src/constants/index.ts +1 -0
- package/src/i18n/zh-cn.ts +160 -0
- package/src/icons/index.ts +93 -0
- package/src/index.ts +21 -0
- package/src/installer.ts +21 -0
- package/src/plugins/alert/index.ts +455 -0
- package/src/plugins/block-alignment/index.ts +117 -0
- package/src/plugins/block-alignment/readme.md +1 -0
- package/src/plugins/code/LICENSE +21 -0
- package/src/plugins/code/index.ts +619 -0
- package/src/plugins/code/utils/string.ts +34 -0
- package/src/plugins/color-picker/index.ts +132 -0
- package/src/plugins/delimiter/index.ts +121 -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 +404 -0
- package/src/plugins/header/H2.ts +403 -0
- package/src/plugins/header/H3.ts +404 -0
- package/src/plugins/header/H4.ts +404 -0
- package/src/plugins/header/H5.ts +403 -0
- package/src/plugins/header/H6.ts +404 -0
- package/src/plugins/header/index.ts +15 -0
- package/src/plugins/header/types.d.ts +46 -0
- package/src/plugins/imageResizeCrop/ImageTune.ts +635 -0
- package/src/plugins/imageResizeCrop/index.css +230 -0
- package/src/plugins/imageResizeCrop/index.ts +5 -0
- package/src/plugins/imageResizeCrop/types.d.ts +23 -0
- package/src/plugins/imageTool/index.ts +510 -0
- package/src/plugins/imageTool/types/codexteam__ajax.d.ts +89 -0
- package/src/plugins/imageTool/types/types.ts +236 -0
- package/src/plugins/imageTool/ui.ts +313 -0
- package/src/plugins/imageTool/uploader.ts +287 -0
- package/src/plugins/imageTool/utils/dom.ts +24 -0
- package/src/plugins/imageTool/utils/index.ts +73 -0
- package/src/plugins/imageTool/utils/isPromise.ts +10 -0
- package/src/plugins/indent/index.ts +695 -0
- package/src/plugins/inline-code/index.ts +203 -0
- package/src/plugins/list/ListRenderer/ChecklistRenderer.ts +208 -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 +488 -0
- package/src/plugins/list/styles/CssPrefix.ts +4 -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 +83 -0
- package/src/plugins/list/utils/removeChildWrapperIfEmpty.ts +31 -0
- package/src/plugins/list/utils/renderToolboxInput.ts +113 -0
- package/src/plugins/list/utils/stripNumbers.ts +7 -0
- package/src/plugins/list/utils/type-guards.ts +8 -0
- package/src/plugins/marker/index.ts +199 -0
- package/src/plugins/outline/index.ts +62 -0
- package/src/plugins/outline/outline.css +52 -0
- package/src/plugins/paragraph/index.ts +384 -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.ts +203 -0
- package/src/plugins/table/index.ts +4 -0
- package/src/plugins/table/plugin.ts +255 -0
- package/src/plugins/table/table.ts +1202 -0
- package/src/plugins/table/toolbox.ts +166 -0
- package/src/plugins/table/utils/dom.ts +130 -0
- package/src/plugins/table/utils/popover.ts +185 -0
- package/src/plugins/table/utils/throttled.ts +22 -0
- package/src/plugins/underline/index.ts +214 -0
- package/src/plugins/undo/index.ts +526 -0
- package/src/plugins/undo/observer.ts +101 -0
- package/src/plugins/undo/vanilla-caret-js.ts +102 -0
- package/src/style.css +139 -0
- package/src/types.ts +3 -0
- package/src/utils/AxiosService.ts +87 -0
- package/src/utils/index.ts +15 -0
- package/src/utils/install.ts +19 -0
- package/tsconfig.json +37 -0
- 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,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
|
+
}
|