@ebl-vue/editor-full 1.0.12 → 1.0.13

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 (46) hide show
  1. package/dist/index.d.ts +5 -0
  2. package/dist/index.mjs +733 -440
  3. package/dist/index.mjs.map +1 -1
  4. package/package.json +3 -1
  5. package/src/components/Editor/Editor.vue +34 -10
  6. package/src/i18n/zh-cn.ts +6 -1
  7. package/src/icons/index.ts +15 -0
  8. package/src/installer.ts +4 -3
  9. package/src/plugins/alert/index.ts +19 -27
  10. package/src/plugins/block-alignment/index.ts +4 -3
  11. package/src/plugins/code/index.ts +3 -2
  12. package/src/plugins/color-picker/index.ts +3 -11
  13. package/src/plugins/delimiter/index.ts +5 -6
  14. package/src/plugins/header/H1.ts +1 -1
  15. package/src/plugins/header/H2.ts +1 -1
  16. package/src/plugins/header/H3.ts +1 -1
  17. package/src/plugins/header/H4.ts +1 -2
  18. package/src/plugins/header/H5.ts +1 -3
  19. package/src/plugins/header/H6.ts +1 -3
  20. package/src/plugins/imageTool/index.css +145 -0
  21. package/src/plugins/imageTool/index.ts +519 -0
  22. package/src/plugins/imageTool/types/codexteam__ajax.d.ts +89 -0
  23. package/src/plugins/imageTool/types/types.ts +234 -0
  24. package/src/plugins/imageTool/ui.ts +312 -0
  25. package/src/plugins/imageTool/uploader.ts +234 -0
  26. package/src/plugins/imageTool/utils/dom.ts +24 -0
  27. package/src/plugins/imageTool/utils/isPromise.ts +10 -0
  28. package/src/plugins/indent/index.ts +5 -7
  29. package/src/plugins/inline-code/index.ts +2 -5
  30. package/src/plugins/list/ListRenderer/ChecklistRenderer.ts +1 -4
  31. package/src/plugins/list/index.ts +20 -37
  32. package/src/plugins/list/types/OlCounterType.ts +1 -1
  33. package/src/plugins/marker/index.ts +28 -16
  34. package/src/plugins/paragraph/index.ts +3 -2
  35. package/src/plugins/quote/index.ts +1 -4
  36. package/src/plugins/table/plugin.ts +1 -1
  37. package/src/plugins/table/table.ts +40 -38
  38. package/src/plugins/table/toolbox.ts +5 -4
  39. package/src/plugins/table/utils/dom.ts +15 -14
  40. package/src/plugins/table/utils/popover.ts +28 -15
  41. package/src/plugins/underline/index.ts +2 -4
  42. package/src/plugins/undo/index.ts +48 -33
  43. package/src/plugins/undo/observer.ts +3 -3
  44. package/types/index.d.ts +5 -0
  45. package/vite.config.ts +3 -1
  46. package/src/plugins/list/styles/icons/index.ts +0 -10
@@ -0,0 +1,145 @@
1
+ .image-tool {
2
+ --bg-color: #cdd1e0;
3
+ --front-color: #388ae5;
4
+ --border-color: #e8e8eb
5
+ }
6
+
7
+
8
+ .image-tool__image {
9
+ border-radius: 3px;
10
+ overflow: hidden;
11
+ margin-bottom: 10px;
12
+ padding-bottom: 0
13
+ }
14
+
15
+ .image-tool__image-picture {
16
+ max-width: 100%;
17
+ vertical-align: bottom;
18
+ display: inline-block
19
+ }
20
+
21
+ .image-tool__image-preloader {
22
+ width: 50px;
23
+ height: 50px;
24
+ border-radius: 50%;
25
+ background-size: cover;
26
+ margin: auto;
27
+ position: relative;
28
+ background-color: var(--bg-color);
29
+ background-position: center center
30
+ }
31
+
32
+ .image-tool__image-preloader:after {
33
+ content: "";
34
+ position: absolute;
35
+ z-index: 3;
36
+ width: 60px;
37
+ height: 60px;
38
+ border-radius: 50%;
39
+ border: 2px solid var(--bg-color);
40
+ border-top-color: var(--front-color);
41
+ left: 50%;
42
+ top: 50%;
43
+ margin-top: -30px;
44
+ margin-left: -30px;
45
+ animation: image-preloader-spin 2s infinite linear;
46
+ box-sizing: border-box
47
+ }
48
+
49
+ .image-tool__caption {
50
+ visibility: hidden;
51
+ position: absolute;
52
+ bottom: 0;
53
+ left: 0;
54
+ margin-bottom: 10px
55
+ }
56
+
57
+ .image-tool__caption[contentEditable=true][data-placeholder]:before {
58
+ position: absolute !important;
59
+ content: attr(data-placeholder);
60
+ color: #707684;
61
+ font-weight: 400;
62
+ display: none
63
+ }
64
+
65
+ .image-tool__caption[contentEditable=true][data-placeholder]:empty:before {
66
+ display: block
67
+ }
68
+
69
+ .image-tool__caption[contentEditable=true][data-placeholder]:empty:focus:before {
70
+ display: none
71
+ }
72
+
73
+ .image-tool--empty .image-tool__image,
74
+ .image-tool--empty .image-tool__image-preloader {
75
+ display: none
76
+ }
77
+
78
+ .image-tool--empty .image-tool__caption,
79
+ .image-tool--uploading .image-tool__caption {
80
+ visibility: hidden !important
81
+ }
82
+
83
+ .image-tool .cdx-button {
84
+ display: flex;
85
+ align-items: center;
86
+ justify-content: center
87
+ }
88
+
89
+ .image-tool .cdx-button svg {
90
+ height: auto;
91
+ margin: 0 6px 0 0
92
+ }
93
+
94
+ .image-tool--filled .cdx-button,
95
+ .image-tool--filled .image-tool__image-preloader {
96
+ display: none
97
+ }
98
+
99
+ .image-tool--uploading .image-tool__image {
100
+ min-height: 200px;
101
+ display: flex;
102
+ border: 1px solid var(--border-color);
103
+ background-color: #fff
104
+ }
105
+
106
+ .image-tool--uploading .image-tool__image-picture,
107
+ .image-tool--uploading .cdx-button {
108
+ display: none
109
+ }
110
+
111
+ .image-tool--withBorder .image-tool__image {
112
+ border: 1px solid var(--border-color)
113
+ }
114
+
115
+ .image-tool--withBackground .image-tool__image {
116
+ padding: 15px;
117
+ background: var(--bg-color)
118
+ }
119
+
120
+ .image-tool--withBackground .image-tool__image-picture {
121
+ max-width: 60%;
122
+ margin: 0 auto
123
+ }
124
+
125
+ .image-tool--stretched .image-tool__image-picture {
126
+ width: 100%
127
+ }
128
+
129
+ .image-tool--caption .image-tool__caption {
130
+ visibility: visible
131
+ }
132
+
133
+ .image-tool--caption {
134
+ padding-bottom: 50px
135
+ }
136
+
137
+ @keyframes image-preloader-spin {
138
+ 0% {
139
+ transform: rotate(0)
140
+ }
141
+
142
+ to {
143
+ transform: rotate(360deg)
144
+ }
145
+ }
@@ -0,0 +1,519 @@
1
+ /**
2
+ * Image Tool for the Editor.js
3
+ * @author CodeX <team@codex.so>
4
+ * @license MIT
5
+ * @see {@link https://github.com/editor-js/image}
6
+ *
7
+ * To developers.
8
+ * To simplify Tool structure, we split it to 4 parts:
9
+ * 1) index.ts — main Tool's interface, public API and methods for working with data
10
+ * 2) uploader.ts — module that has methods for sending files via AJAX: from device, by URL or File pasting
11
+ * 3) ui.ts — module for UI manipulations: render, showing preloader, etc
12
+ *
13
+ * For debug purposes there is a testing server
14
+ * that can save uploaded files and return a Response {@link UploadResponseFormat}
15
+ *
16
+ * $ node dev/server.js
17
+ *
18
+ * It will expose 8008 port, so you can pass http://localhost:8008 with the Tools config:
19
+ *
20
+ * image: {
21
+ * class: ImageTool,
22
+ * config: {
23
+ * endpoints: {
24
+ * byFile: 'http://localhost:8008/uploadFile',
25
+ * byUrl: 'http://localhost:8008/fetchUrl',
26
+ * }
27
+ * },
28
+ * },
29
+ */
30
+
31
+ import type { TunesMenuConfig } from '@ebl-vue/editorjs/types/tools';
32
+ import type { API, ToolboxConfig, PasteConfig, BlockToolConstructorOptions, BlockTool, BlockAPI, PasteEvent, PatternPasteEventDetail, FilePasteEventDetail } from '@editorjs/editorjs';
33
+ import './index.css';
34
+
35
+ import Ui from './ui';
36
+ import Uploader from './uploader';
37
+
38
+ import { IconAddBorder, IconStretch, IconAddBackground, IconPicture, IconText } from '../../icons';
39
+ import type { ActionConfig, UploadResponseFormat, ImageToolData, ImageConfig, HTMLPasteEventDetailExtended, ImageSetterParam, FeaturesConfig } from './types/types';
40
+
41
+ type ImageToolConstructorOptions = BlockToolConstructorOptions<ImageToolData, ImageConfig>;
42
+
43
+ /**
44
+ * Implementation of ImageTool class
45
+ */
46
+ export default class ImageTool implements BlockTool {
47
+ /**
48
+ * Editor.js API instance
49
+ */
50
+ private api: API;
51
+
52
+ /**
53
+ * Current Block API instance
54
+ */
55
+ private block: BlockAPI;
56
+
57
+ /**
58
+ * Configuration for the ImageTool
59
+ */
60
+ private config: ImageConfig;
61
+
62
+ /**
63
+ * Uploader module instance
64
+ */
65
+ private uploader: any;
66
+
67
+ /**
68
+ * UI module instance
69
+ */
70
+ private ui: Ui;
71
+
72
+ /**
73
+ * Stores current block data internally
74
+ */
75
+ private _data: ImageToolData;
76
+
77
+ /**
78
+ * Caption enabled state
79
+ * Null when user has not toggled the caption tune
80
+ * True when user has toggled the caption tune
81
+ * False when user has toggled the caption tune
82
+ */
83
+ private isCaptionEnabled: boolean | null = null;
84
+
85
+ /**
86
+ * @param tool - tool properties got from editor.js
87
+ * @param tool.data - previously saved data
88
+ * @param tool.config - user config for Tool
89
+ * @param tool.api - Editor.js API
90
+ * @param tool.readOnly - read-only mode flag
91
+ * @param tool.block - current Block API
92
+ */
93
+ constructor({ data, config, api, readOnly, block }: ImageToolConstructorOptions) {
94
+ this.api = api;
95
+ this.block = block;
96
+ /**
97
+ * Tool's initial config
98
+ */
99
+ this.config = {
100
+ endpoints: config.endpoints,
101
+ additionalRequestData: config.additionalRequestData,
102
+ additionalRequestHeaders: config.additionalRequestHeaders,
103
+ field: config.field,
104
+ types: config.types,
105
+ captionPlaceholder: this.api.i18n.t(config.captionPlaceholder ?? 'Caption'),
106
+ buttonContent: config.buttonContent,
107
+ uploader: config.uploader,
108
+ actions: config.actions,
109
+ features: config.features || {},
110
+ };
111
+
112
+ /**
113
+ * Module for file uploading
114
+ */
115
+ this.uploader = new Uploader({
116
+ config: this.config,
117
+ onUpload: (response: UploadResponseFormat) => this.onUpload(response),
118
+ onError: (error: string) => this.uploadingFailed(error),
119
+ });
120
+
121
+ /**
122
+ * Module for working with UI
123
+ */
124
+ this.ui = new Ui({
125
+ api,
126
+ config: this.config,
127
+ onSelectFile: () => {
128
+ this.uploader.uploadSelectedFile({
129
+ onPreview: (src: string) => {
130
+ this.ui.showPreloader(src);
131
+ },
132
+ });
133
+ },
134
+ readOnly,
135
+ });
136
+
137
+ /**
138
+ * Set saved state
139
+ */
140
+ this._data = {
141
+ caption: '',
142
+ withBorder: false,
143
+ withBackground: false,
144
+ stretched: false,
145
+ file: {
146
+ url: '',
147
+ },
148
+ };
149
+ this.data = data;
150
+ }
151
+
152
+ /**
153
+ * Notify core that read-only mode is supported
154
+ */
155
+ public static get isReadOnlySupported(): boolean {
156
+ return true;
157
+ }
158
+
159
+ /**
160
+ * Get Tool toolbox settings
161
+ * icon - Tool icon's SVG
162
+ * title - title to show in toolbox
163
+ */
164
+ public static get toolbox(): ToolboxConfig {
165
+ return {
166
+ icon: IconPicture,
167
+ title: 'Image',
168
+ };
169
+ }
170
+
171
+ /**
172
+ * Available image tools
173
+ */
174
+ public static get tunes(): Array<ActionConfig> {
175
+ return [
176
+ {
177
+ name: 'withBorder',
178
+ icon: IconAddBorder,
179
+ title: 'With border',
180
+ toggle: true,
181
+ },
182
+ {
183
+ name: 'stretched',
184
+ icon: IconStretch,
185
+ title: 'Stretch image',
186
+ toggle: true,
187
+ },
188
+ {
189
+ name: 'withBackground',
190
+ icon: IconAddBackground,
191
+ title: 'With background',
192
+ toggle: true,
193
+ },
194
+ ];
195
+ }
196
+
197
+ /**
198
+ * Renders Block content
199
+ */
200
+ public render(): HTMLDivElement {
201
+ if (this.config.features?.caption === true || this.config.features?.caption === undefined || (this.config.features?.caption === 'optional' && this.data.caption)) {
202
+ this.isCaptionEnabled = true;
203
+ this.ui.applyTune('caption', true);
204
+ }
205
+
206
+ return this.ui.render() as HTMLDivElement;
207
+ }
208
+
209
+ /**
210
+ * Validate data: check if Image exists
211
+ * @param savedData — data received after saving
212
+ * @returns false if saved data is not correct, otherwise true
213
+ */
214
+ public validate(savedData: ImageToolData): boolean {
215
+ return !!savedData.file.url;
216
+ }
217
+
218
+ /**
219
+ * Return Block data
220
+ */
221
+ public save(): ImageToolData {
222
+ const caption = this.ui.nodes.caption;
223
+
224
+ this._data.caption = caption.innerHTML;
225
+
226
+ return this.data;
227
+ }
228
+
229
+ /**
230
+ * Returns configuration for block tunes: add background, add border, stretch image
231
+ * @returns TunesMenuConfig
232
+ */
233
+ public renderSettings(): TunesMenuConfig {
234
+ // Merge default tunes with the ones that might be added by user
235
+ // @see https://github.com/editor-js/image/pull/49
236
+ const tunes = ImageTool.tunes.concat(this.config.actions || []);
237
+ const featureTuneMap: Record<string, string> = {
238
+ border: 'withBorder',
239
+ background: 'withBackground',
240
+ stretch: 'stretched',
241
+ caption: 'caption',
242
+ };
243
+
244
+ if (this.config.features?.caption === 'optional') {
245
+ tunes.push({
246
+ name: 'caption',
247
+ icon: IconText,
248
+ title: 'With caption',
249
+ toggle: true,
250
+ });
251
+ }
252
+
253
+ const availableTunes = tunes.filter((tune) => {
254
+ const featureKey = Object.keys(featureTuneMap).find(key => featureTuneMap[key] === tune.name);
255
+
256
+ if (featureKey === 'caption') {
257
+ return this.config.features?.caption !== false;
258
+ }
259
+
260
+ return featureKey == null || this.config.features?.[featureKey as keyof FeaturesConfig] !== false;
261
+ });
262
+
263
+ /**
264
+ * Check if the tune is active
265
+ * @param tune - tune to check
266
+ */
267
+ const isActive = (tune: ActionConfig): boolean => {
268
+ let currentState = this.data[tune.name as keyof ImageToolData] as boolean;
269
+
270
+ if (tune.name === 'caption') {
271
+ currentState = this.isCaptionEnabled ?? currentState;
272
+ }
273
+
274
+ return currentState;
275
+ };
276
+
277
+ return availableTunes.map(tune => ({
278
+ icon: tune.icon,
279
+ label: this.api.i18n.t(tune.title),
280
+ name: tune.name,
281
+ toggle: tune.toggle,
282
+ isActive: isActive(tune),
283
+ onActivate: () => {
284
+ /** If it'a user defined tune, execute it's callback stored in action property */
285
+ if (typeof tune.action === 'function') {
286
+ tune.action(tune.name);
287
+
288
+ return;
289
+ }
290
+ let newState = !isActive(tune);
291
+
292
+ /**
293
+ * For the caption tune, we can't rely on the this._data
294
+ * because it can be manualy toggled by user
295
+ */
296
+ if (tune.name === 'caption') {
297
+ this.isCaptionEnabled = !(this.isCaptionEnabled ?? false);
298
+ newState = this.isCaptionEnabled;
299
+ }
300
+
301
+ this.tuneToggled(tune.name as keyof ImageToolData, newState);
302
+ },
303
+ }));
304
+ }
305
+
306
+ /**
307
+ * Fires after clicks on the Toolbox Image Icon
308
+ * Initiates click on the Select File button
309
+ */
310
+ public appendCallback(): void {
311
+ this.ui.nodes.fileButton.click();
312
+ }
313
+
314
+ /**
315
+ * Specify paste substitutes
316
+ * @see {@link https://github.com/codex-team/editor.js/blob/master/docs/tools.md#paste-handling}
317
+ */
318
+ public static get pasteConfig(): PasteConfig {
319
+ return {
320
+ /**
321
+ * Paste HTML into Editor
322
+ */
323
+ tags: [
324
+ {
325
+ img: { src: true },
326
+ },
327
+ ],
328
+ /**
329
+ * Paste URL of image into the Editor
330
+ */
331
+ patterns: {
332
+ image: /https?:\/\/\S+\.(gif|jpe?g|tiff|png|svg|webp)(\?[a-z0-9=]*)?$/i,
333
+ },
334
+
335
+ /**
336
+ * Drag n drop file from into the Editor
337
+ */
338
+ files: {
339
+ mimeTypes: ['image/*'],
340
+ },
341
+ };
342
+ }
343
+
344
+ /**
345
+ * Specify paste handlers
346
+ * @see {@link https://github.com/codex-team/editor.js/blob/master/docs/tools.md#paste-handling}
347
+ * @param event - editor.js custom paste event
348
+ * {@link https://github.com/codex-team/editor.js/blob/master/types/tools/paste-events.d.ts}
349
+ */
350
+ public async onPaste(event: PasteEvent): Promise<void> {
351
+ switch (event.type) {
352
+ case 'tag': {
353
+ const image = (event.detail as HTMLPasteEventDetailExtended).data;
354
+
355
+ /** Images from PDF */
356
+ if (/^blob:/.test(image.src)) {
357
+ const response = await fetch(image.src);
358
+
359
+ const file = await response.blob();
360
+
361
+ this.uploadFile(file);
362
+ break;
363
+ }
364
+
365
+ this.uploadUrl(image.src);
366
+ break;
367
+ }
368
+ case 'pattern': {
369
+ const url = (event.detail as PatternPasteEventDetail).data;
370
+
371
+ this.uploadUrl(url);
372
+ break;
373
+ }
374
+ case 'file': {
375
+ const file = (event.detail as FilePasteEventDetail).file;
376
+
377
+ this.uploadFile(file);
378
+ break;
379
+ }
380
+ }
381
+ }
382
+
383
+ /**
384
+ * Private methods
385
+ */
386
+
387
+ /**
388
+ * Stores all Tool's data
389
+ * @param data - data in Image Tool format
390
+ */
391
+ private set data(data: ImageToolData) {
392
+ this.image = data.file;
393
+
394
+ this._data.caption = data.caption || '';
395
+ this.ui.fillCaption(this._data.caption);
396
+
397
+ ImageTool.tunes.forEach(({ name: tune }) => {
398
+ const value = typeof data[tune as keyof ImageToolData] !== 'undefined' ? data[tune as keyof ImageToolData] === true || data[tune as keyof ImageToolData] === 'true' : false;
399
+
400
+ this.setTune(tune as keyof ImageToolData, value);
401
+ });
402
+
403
+ if (data.caption) {
404
+ this.setTune('caption', true);
405
+ } else if (this.config.features?.caption === true) {
406
+ this.setTune('caption', true);
407
+ }
408
+ }
409
+
410
+ /**
411
+ * Return Tool data
412
+ */
413
+ private get data(): ImageToolData {
414
+ return this._data;
415
+ }
416
+
417
+ /**
418
+ * Set new image file
419
+ * @param file - uploaded file data
420
+ */
421
+ private set image(file: ImageSetterParam | undefined) {
422
+ this._data.file = file || { url: '' };
423
+
424
+ if (file && file.url) {
425
+ this.ui.fillImage(file.url);
426
+ }
427
+ }
428
+
429
+ /**
430
+ * File uploading callback
431
+ * @param response - uploading server response
432
+ */
433
+ private onUpload(response: UploadResponseFormat): void {
434
+ if (response.success && Boolean(response.file)) {
435
+ this.image = response.file;
436
+ } else {
437
+ this.uploadingFailed('incorrect response: ' + JSON.stringify(response));
438
+ }
439
+ }
440
+
441
+ /**
442
+ * Handle uploader errors
443
+ * @param errorText - uploading error info
444
+ */
445
+ private uploadingFailed(errorText: string): void {
446
+ console.log('Image Tool: uploading failed because of', errorText);
447
+
448
+
449
+ this.api.notifier.show({
450
+ message: this.api.i18n.t('Couldn’t upload image. Please try another.'),
451
+ style: 'error',
452
+ });
453
+ this.ui.hidePreloader();
454
+ }
455
+
456
+ /**
457
+ * Callback fired when Block Tune is activated
458
+ * @param tuneName - tune that has been clicked
459
+ * @param state - new state
460
+ */
461
+ private tuneToggled(tuneName: keyof ImageToolData, state: boolean): void {
462
+ if (tuneName === 'caption') {
463
+ this.ui.applyTune(tuneName, state);
464
+
465
+ if (state == false) {
466
+ this._data.caption = '';
467
+ this.ui.fillCaption('');
468
+ }
469
+ } else {
470
+ /**
471
+ * Inverse tune state
472
+ */
473
+ this.setTune(tuneName, state);
474
+ }
475
+ }
476
+
477
+ /**
478
+ * Set one tune
479
+ * @param tuneName - {@link Tunes.tunes}
480
+ * @param value - tune state
481
+ */
482
+ private setTune(tuneName: keyof ImageToolData, value: boolean): void {
483
+ (this._data[tuneName] as boolean) = value;
484
+
485
+ this.ui.applyTune(tuneName, value);
486
+ if (tuneName === 'stretched') {
487
+ /**
488
+ * Wait until the API is ready
489
+ */
490
+ Promise.resolve().then(() => {
491
+ this.block.stretched = value;
492
+ })
493
+ .catch((err) => {
494
+ console.error(err);
495
+ });
496
+ }
497
+ }
498
+
499
+ /**
500
+ * Show preloader and upload image file
501
+ * @param file - file that is currently uploading (from paste)
502
+ */
503
+ private uploadFile(file: Blob): void {
504
+ this.uploader.uploadByFile(file, {
505
+ onPreview: (src: string) => {
506
+ this.ui.showPreloader(src);
507
+ },
508
+ });
509
+ }
510
+
511
+ /**
512
+ * Show preloader and upload image by target url
513
+ * @param url - url pasted
514
+ */
515
+ private uploadUrl(url: string): void {
516
+ this.ui.showPreloader(url);
517
+ this.uploader.uploadByUrl(url);
518
+ }
519
+ }