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