@oix1987/yjd 1.0.0 → 1.0.2
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/README.md +73 -22
- package/dist/rich-editor.esm.js +2 -0
- package/dist/rich-editor.esm.js.map +1 -0
- package/dist/rich-editor.min.js +2 -0
- package/dist/rich-editor.min.js.map +1 -0
- package/package.json +12 -7
- package/index.js +0 -221
- package/lib/core/editor.js +0 -1175
- package/lib/core/format.js +0 -542
- package/lib/core/module.js +0 -81
- package/lib/core/registry.js +0 -152
- package/lib/formats/background.js +0 -212
- package/lib/formats/bold.js +0 -67
- package/lib/formats/capitalization.js +0 -563
- package/lib/formats/color.js +0 -165
- package/lib/formats/emoji.js +0 -282
- package/lib/formats/font-family.js +0 -547
- package/lib/formats/heading.js +0 -502
- package/lib/formats/image.js +0 -344
- package/lib/formats/import.js +0 -385
- package/lib/formats/indent.js +0 -297
- package/lib/formats/italic.js +0 -27
- package/lib/formats/line-height.js +0 -558
- package/lib/formats/link.js +0 -251
- package/lib/formats/list.js +0 -635
- package/lib/formats/strike.js +0 -31
- package/lib/formats/subscript.js +0 -36
- package/lib/formats/superscript.js +0 -35
- package/lib/formats/table.js +0 -288
- package/lib/formats/tag.js +0 -304
- package/lib/formats/text-align.js +0 -421
- package/lib/formats/text-size.js +0 -497
- package/lib/formats/underline.js +0 -30
- package/lib/formats/video.js +0 -372
- package/lib/modules/block-toolbar.js +0 -628
- package/lib/modules/code-view.js +0 -434
- package/lib/modules/history.js +0 -410
- package/lib/modules/resize-handles.js +0 -677
- package/lib/modules/table-toolbar.js +0 -618
- package/lib/modules/toolbar.js +0 -424
- package/lib/styles-loader.js +0 -144
- package/lib/styles.css +0 -2123
- package/lib/ui/color-picker.js +0 -296
- package/lib/ui/customselect.js +0 -319
- package/lib/ui/emoji-picker.js +0 -196
- package/lib/ui/icons.js +0 -413
- package/lib/ui/image-popup.js +0 -444
- package/lib/ui/import-popup.js +0 -288
- package/lib/ui/link-popup.js +0 -191
- package/lib/ui/list-picker.js +0 -307
- package/lib/ui/select-button.js +0 -61
- package/lib/ui/table-popup.js +0 -171
- package/lib/ui/tag-popup.js +0 -249
- package/lib/ui/text-align-picker.js +0 -281
- package/lib/ui/video-popup.js +0 -422
- package/lib/utils/history-helper.js +0 -50
- package/lib/utils/popup-helper.js +0 -219
- package/lib/utils/popup-positioning.js +0 -231
package/lib/formats/image.js
DELETED
|
@@ -1,344 +0,0 @@
|
|
|
1
|
-
import { InlineFormat } from '../core/format.js';
|
|
2
|
-
import ImagePopup from '../ui/image-popup.js';
|
|
3
|
-
import Editor from '../core/editor.js';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Image Format - Handles image insertion
|
|
7
|
-
* Now supports multiple editor instances with separate popup instances
|
|
8
|
-
*/
|
|
9
|
-
class Image extends InlineFormat {
|
|
10
|
-
static formatName = 'image';
|
|
11
|
-
static tagName = 'IMG';
|
|
12
|
-
static className = 'inserted-image';
|
|
13
|
-
|
|
14
|
-
constructor() {
|
|
15
|
-
super();
|
|
16
|
-
|
|
17
|
-
// Get current editor instance
|
|
18
|
-
const currentEditor = Editor.getCurrentInstance();
|
|
19
|
-
if (!currentEditor) {
|
|
20
|
-
console.warn('No editor instance found for Image format');
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
this.editorId = currentEditor.instanceId;
|
|
25
|
-
|
|
26
|
-
// Check if this editor already has an image popup instance
|
|
27
|
-
let imagePopup = currentEditor.getPopupInstance('image');
|
|
28
|
-
|
|
29
|
-
if (!imagePopup) {
|
|
30
|
-
// Create new image popup instance for this editor
|
|
31
|
-
imagePopup = new ImagePopup({
|
|
32
|
-
onImageInsert: (src, alt) => {
|
|
33
|
-
Image.insertImageAtCurrentPosition(src, alt, this.editorId);
|
|
34
|
-
},
|
|
35
|
-
editor: currentEditor,
|
|
36
|
-
editorId: this.editorId
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
// Store popup instance in editor
|
|
40
|
-
currentEditor.setPopupInstance('image', imagePopup);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
this.imagePopup = imagePopup;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Create a new Image format instance for a specific editor
|
|
48
|
-
* @param {string} editorId - Editor instance ID
|
|
49
|
-
* @returns {Image} Image format instance
|
|
50
|
-
*/
|
|
51
|
-
static createForEditor(editorId) {
|
|
52
|
-
const editor = Editor.getInstanceById(editorId);
|
|
53
|
-
if (!editor) {
|
|
54
|
-
console.warn('No editor instance found for ID:', editorId);
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Temporarily set as current instance
|
|
59
|
-
const originalCurrent = Editor.currentInstance;
|
|
60
|
-
Editor.currentInstance = editor;
|
|
61
|
-
|
|
62
|
-
// Create format instance
|
|
63
|
-
const format = new Image();
|
|
64
|
-
|
|
65
|
-
// Restore original current instance
|
|
66
|
-
Editor.currentInstance = originalCurrent;
|
|
67
|
-
|
|
68
|
-
return format;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Create image element
|
|
73
|
-
* @param {string} src - Image source URL
|
|
74
|
-
* @param {string} alt - Alt text
|
|
75
|
-
* @returns {HTMLElement}
|
|
76
|
-
*/
|
|
77
|
-
static create(src, alt = '') {
|
|
78
|
-
const img = document.createElement('IMG');
|
|
79
|
-
img.src = src;
|
|
80
|
-
img.alt = alt || 'Inserted image';
|
|
81
|
-
img.className = 'inserted-image';
|
|
82
|
-
img.style.maxWidth = '100%';
|
|
83
|
-
img.style.height = 'auto';
|
|
84
|
-
img.setAttribute('contenteditable', 'false');
|
|
85
|
-
return img;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Insert image at current cursor position
|
|
90
|
-
* @param {string} src - Image source URL
|
|
91
|
-
* @param {string} alt - Alt text
|
|
92
|
-
* @param {string} editorId - Editor instance ID
|
|
93
|
-
*/
|
|
94
|
-
static insertImageAtCurrentPosition(src, alt = '', editorId = null) {
|
|
95
|
-
// Get the correct editor instance
|
|
96
|
-
let editor = null;
|
|
97
|
-
if (editorId) {
|
|
98
|
-
editor = Editor.getInstanceById(editorId);
|
|
99
|
-
} else {
|
|
100
|
-
editor = Editor.getCurrentInstance();
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (!editor) {
|
|
104
|
-
console.warn('No editor instance found for image insertion');
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const selection = window.getSelection();
|
|
109
|
-
if (!selection || !selection.rangeCount) {
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
try {
|
|
114
|
-
const range = selection.getRangeAt(0);
|
|
115
|
-
// Create image element
|
|
116
|
-
const imageElement = Image.create(src, alt);
|
|
117
|
-
// Insert image at cursor position
|
|
118
|
-
range.deleteContents();
|
|
119
|
-
range.insertNode(imageElement);
|
|
120
|
-
// Add a space after the image for easier editing
|
|
121
|
-
const spaceNode = document.createTextNode(' ');
|
|
122
|
-
range.setStartAfter(imageElement);
|
|
123
|
-
range.insertNode(spaceNode);
|
|
124
|
-
// Position cursor after the space
|
|
125
|
-
range.setStartAfter(spaceNode);
|
|
126
|
-
range.collapse(true);
|
|
127
|
-
selection.removeAllRanges();
|
|
128
|
-
selection.addRange(range);
|
|
129
|
-
|
|
130
|
-
// Trigger content change event
|
|
131
|
-
if (editor && typeof editor.onContentChange === 'function') {
|
|
132
|
-
editor.onContentChange();
|
|
133
|
-
}
|
|
134
|
-
} catch (error) {
|
|
135
|
-
console.error('Error inserting image:', error);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Trigger content change after applying format
|
|
139
|
-
setTimeout(() => {
|
|
140
|
-
const currentEditor = Editor.getCurrentInstance();
|
|
141
|
-
if (currentEditor && typeof currentEditor.onContentChange === 'function') {
|
|
142
|
-
currentEditor.onContentChange();
|
|
143
|
-
}
|
|
144
|
-
}, 0);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Apply image formatting - shows image popup
|
|
149
|
-
*/
|
|
150
|
-
apply(src, alt) {
|
|
151
|
-
if (src) {
|
|
152
|
-
Image.insertImageAtCurrentPosition(src, alt, this.editorId);
|
|
153
|
-
} else {
|
|
154
|
-
this.showImagePopup();
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Remove image formatting
|
|
160
|
-
*/
|
|
161
|
-
remove() {
|
|
162
|
-
const selection = window.getSelection();
|
|
163
|
-
if (!selection || !selection.rangeCount) return;
|
|
164
|
-
|
|
165
|
-
const range = selection.getRangeAt(0);
|
|
166
|
-
const imageElement = this.getImageElement(range);
|
|
167
|
-
|
|
168
|
-
if (imageElement) {
|
|
169
|
-
imageElement.remove();
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Toggle image formatting - shows image popup
|
|
175
|
-
*/
|
|
176
|
-
toggle() {
|
|
177
|
-
if (this.imagePopup.isVisible) {
|
|
178
|
-
this.imagePopup.hide();
|
|
179
|
-
} else {
|
|
180
|
-
this.showImagePopup();
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Show image popup
|
|
186
|
-
*/
|
|
187
|
-
showImagePopup() {
|
|
188
|
-
// Find image button in the current editor's toolbar
|
|
189
|
-
const editor = Editor.getInstanceById(this.editorId);
|
|
190
|
-
if (!editor) return;
|
|
191
|
-
|
|
192
|
-
const toolbar = editor.getModule('toolbar');
|
|
193
|
-
let imageButton = null;
|
|
194
|
-
|
|
195
|
-
if (toolbar) {
|
|
196
|
-
imageButton = toolbar.getButton('image');
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Fallback: find button by class in the current editor's toolbar
|
|
200
|
-
if (!imageButton) {
|
|
201
|
-
const toolbarContainer = toolbar?.getContainer();
|
|
202
|
-
if (toolbarContainer) {
|
|
203
|
-
imageButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.image-btn');
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Final fallback: find any image button in the current editor's wrapper
|
|
208
|
-
if (!imageButton) {
|
|
209
|
-
imageButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.image-btn');
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
if (!imageButton) {
|
|
213
|
-
console.warn('Image button not found for editor:', this.editorId);
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
this.imagePopup.show(imageButton);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Check if image formatting is active
|
|
222
|
-
*/
|
|
223
|
-
isActive() {
|
|
224
|
-
const selection = window.getSelection();
|
|
225
|
-
if (!selection || !selection.rangeCount) return false;
|
|
226
|
-
|
|
227
|
-
const range = selection.getRangeAt(0);
|
|
228
|
-
const imageElement = this.getImageElement(range);
|
|
229
|
-
|
|
230
|
-
return imageElement !== null;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Get image element from selection
|
|
235
|
-
* @param {Range} range - Selection range
|
|
236
|
-
* @returns {HTMLElement|null}
|
|
237
|
-
*/
|
|
238
|
-
getImageElement(range) {
|
|
239
|
-
let node = range.commonAncestorContainer;
|
|
240
|
-
|
|
241
|
-
// If it's a text node, get its parent
|
|
242
|
-
if (node.nodeType === Node.TEXT_NODE) {
|
|
243
|
-
node = node.parentNode;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// Check if current node is an image
|
|
247
|
-
if (node.tagName === 'IMG' && node.classList && node.classList.contains('inserted-image')) {
|
|
248
|
-
return node;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// Check if selection contains an image
|
|
252
|
-
const imageInSelection = range.cloneContents().querySelector('.inserted-image');
|
|
253
|
-
if (imageInSelection) {
|
|
254
|
-
return imageInSelection;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
return null;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Handle file upload
|
|
262
|
-
* @param {File} file - Image file
|
|
263
|
-
* @returns {Promise<string>} - Promise that resolves to image URL
|
|
264
|
-
*/
|
|
265
|
-
static async handleFileUpload(file) {
|
|
266
|
-
return new Promise((resolve, reject) => {
|
|
267
|
-
if (!file || !file.type.startsWith('image/')) {
|
|
268
|
-
reject(new Error('Please select a valid image file'));
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
const reader = new FileReader();
|
|
273
|
-
reader.onload = (e) => {
|
|
274
|
-
resolve(e.target.result);
|
|
275
|
-
};
|
|
276
|
-
reader.onerror = () => {
|
|
277
|
-
reject(new Error('Failed to read file'));
|
|
278
|
-
};
|
|
279
|
-
reader.readAsDataURL(file);
|
|
280
|
-
});
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/**
|
|
284
|
-
* Validate image URL
|
|
285
|
-
* @param {string} url - Image URL
|
|
286
|
-
* @returns {Promise<boolean>} - Promise that resolves to validation result
|
|
287
|
-
*/
|
|
288
|
-
static validateImageUrl(url) {
|
|
289
|
-
return new Promise((resolve) => {
|
|
290
|
-
// Check if it's a valid image URL format
|
|
291
|
-
const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'];
|
|
292
|
-
const hasValidExtension = imageExtensions.some(ext =>
|
|
293
|
-
url.toLowerCase().includes(`.${ext}`)
|
|
294
|
-
);
|
|
295
|
-
|
|
296
|
-
// Check if it's a data URL
|
|
297
|
-
if (url.startsWith('data:image/')) {
|
|
298
|
-
resolve(true);
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// Check if it's a valid HTTP(S) URL
|
|
303
|
-
if (!/^https?:\/\//.test(url)) {
|
|
304
|
-
resolve(false);
|
|
305
|
-
return;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// If it has a valid extension, assume it's valid
|
|
309
|
-
if (hasValidExtension) {
|
|
310
|
-
resolve(true);
|
|
311
|
-
return;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// Try to load the image (fallback)
|
|
315
|
-
const img = new Image();
|
|
316
|
-
img.onload = () => {
|
|
317
|
-
resolve(true);
|
|
318
|
-
};
|
|
319
|
-
img.onerror = () => {
|
|
320
|
-
// If loading fails, but URL looks like an image, still allow it
|
|
321
|
-
// This handles cases where CORS blocks loading but the URL is valid
|
|
322
|
-
if (url.includes('imgur.com') || url.includes('drive.google.com') || hasValidExtension) {
|
|
323
|
-
resolve(true);
|
|
324
|
-
} else {
|
|
325
|
-
resolve(false);
|
|
326
|
-
}
|
|
327
|
-
};
|
|
328
|
-
|
|
329
|
-
// Set timeout to avoid hanging
|
|
330
|
-
setTimeout(() => {
|
|
331
|
-
// If no response after 5 seconds, still allow if URL looks valid
|
|
332
|
-
if (hasValidExtension || url.includes('imgur.com') || url.includes('drive.google.com')) {
|
|
333
|
-
resolve(true);
|
|
334
|
-
} else {
|
|
335
|
-
resolve(false);
|
|
336
|
-
}
|
|
337
|
-
}, 5000);
|
|
338
|
-
|
|
339
|
-
img.src = url;
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
export default Image;
|