@ebl-vue/editor-full 1.0.11 → 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.
- package/dist/index.d.ts +5 -0
- package/dist/index.mjs +733 -440
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -1
- package/src/components/Editor/Editor.vue +34 -10
- package/src/i18n/zh-cn.ts +6 -1
- package/src/icons/index.ts +15 -0
- package/src/installer.ts +4 -3
- package/src/plugins/alert/index.ts +19 -27
- package/src/plugins/block-alignment/index.ts +4 -3
- package/src/plugins/code/index.ts +3 -2
- package/src/plugins/color-picker/index.ts +3 -11
- package/src/plugins/delimiter/index.ts +5 -6
- package/src/plugins/header/H1.ts +1 -1
- package/src/plugins/header/H2.ts +1 -1
- package/src/plugins/header/H3.ts +1 -1
- package/src/plugins/header/H4.ts +1 -2
- package/src/plugins/header/H5.ts +1 -3
- package/src/plugins/header/H6.ts +1 -3
- package/src/plugins/imageTool/index.css +145 -0
- package/src/plugins/imageTool/index.ts +519 -0
- package/src/plugins/imageTool/types/codexteam__ajax.d.ts +89 -0
- package/src/plugins/imageTool/types/types.ts +234 -0
- package/src/plugins/imageTool/ui.ts +312 -0
- package/src/plugins/imageTool/uploader.ts +234 -0
- package/src/plugins/imageTool/utils/dom.ts +24 -0
- package/src/plugins/imageTool/utils/isPromise.ts +10 -0
- package/src/plugins/indent/index.ts +5 -7
- package/src/plugins/inline-code/index.ts +2 -5
- package/src/plugins/list/ListRenderer/ChecklistRenderer.ts +1 -4
- package/src/plugins/list/index.ts +20 -37
- package/src/plugins/list/types/OlCounterType.ts +1 -1
- package/src/plugins/marker/index.ts +28 -16
- package/src/plugins/paragraph/index.ts +3 -2
- package/src/plugins/quote/index.ts +1 -4
- package/src/plugins/table/plugin.ts +1 -1
- package/src/plugins/table/table.ts +40 -38
- package/src/plugins/table/toolbox.ts +5 -4
- package/src/plugins/table/utils/dom.ts +15 -14
- package/src/plugins/table/utils/popover.ts +28 -15
- package/src/plugins/underline/index.ts +2 -4
- package/src/plugins/undo/index.ts +48 -33
- package/src/plugins/undo/observer.ts +3 -3
- package/src/style.css +6 -0
- package/types/index.d.ts +5 -0
- package/vite.config.ts +3 -1
- 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
|
+
}
|