@oix1987/yjd 1.0.1 → 1.0.3

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 +9 -1
  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 +13 -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/ui/color-picker.js +0 -296
  43. package/lib/ui/customselect.js +0 -319
  44. package/lib/ui/emoji-picker.js +0 -196
  45. package/lib/ui/icons.js +0 -413
  46. package/lib/ui/image-popup.js +0 -444
  47. package/lib/ui/import-popup.js +0 -288
  48. package/lib/ui/link-popup.js +0 -191
  49. package/lib/ui/list-picker.js +0 -307
  50. package/lib/ui/select-button.js +0 -61
  51. package/lib/ui/table-popup.js +0 -171
  52. package/lib/ui/tag-popup.js +0 -249
  53. package/lib/ui/text-align-picker.js +0 -281
  54. package/lib/ui/video-popup.js +0 -422
  55. package/lib/utils/history-helper.js +0 -50
  56. package/lib/utils/popup-helper.js +0 -219
  57. package/lib/utils/popup-positioning.js +0 -231
  58. /package/{lib → dist}/styles.css +0 -0
@@ -1,542 +0,0 @@
1
- /**
2
- * Base Format class - Inspired by Quill's architecture
3
- * All text formats should extend this class
4
- */
5
- export class Format {
6
- static formatName = '';
7
- static tagName = '';
8
- static className = '';
9
-
10
- constructor(domNode) {
11
- this.domNode = domNode;
12
- }
13
-
14
- /**
15
- * Create a new format node
16
- * @param {*} value - Format value
17
- * @returns {HTMLElement}
18
- */
19
- static create(value) {
20
- const node = document.createElement(this.tagName);
21
- if (this.className) {
22
- node.className = this.className;
23
- }
24
- return node;
25
- }
26
-
27
- getOffsetWithin(container, range) {
28
- let offset = 0;
29
- const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, null);
30
- let currentNode;
31
-
32
- while ((currentNode = walker.nextNode())) {
33
- if (currentNode === range.startContainer) {
34
- return offset + range.startOffset;
35
- }
36
- offset += currentNode.textContent.length;
37
- }
38
-
39
- return offset;
40
- }
41
-
42
- /**
43
- * Check if format is active at current selection
44
- * @returns {boolean}
45
- */
46
- }
47
-
48
- /**
49
- * Inline Format - for formats like bold, italic, underline
50
- * Handles inline formatting that wraps text within the same line/block
51
- */
52
- export class InlineFormat extends Format {
53
- /**
54
- * Create inline format element
55
- * @param {*} value - Format value
56
- * @returns {HTMLElement}
57
- */
58
- static create(value) {
59
- const node = super.create(value);
60
- return node;
61
- }
62
-
63
- /**
64
- * Apply inline format to selection
65
- * Wraps selected text or inserts format marker at cursor
66
- * @param {*} value - Format value
67
- */
68
- apply(value) {
69
- const selection = window.getSelection();
70
- if (!selection || !selection.rangeCount) return;
71
-
72
- const range = selection.getRangeAt(0);
73
- if (range.collapsed) {
74
- // No selection - insert format marker at cursor
75
- const formatNode = this.constructor.create(value);
76
- formatNode.appendChild(document.createTextNode('\u200B')); // Zero-width space
77
- range.insertNode(formatNode);
78
-
79
- // Position cursor inside the format node
80
- const newRange = document.createRange();
81
- newRange.setStart(formatNode.firstChild, 1);
82
- newRange.collapse(true);
83
- selection.removeAllRanges();
84
- selection.addRange(newRange);
85
- } else {
86
- // Has selection - wrap selected content
87
- const contents = range.extractContents();
88
- const formatNode = this.constructor.create(value);
89
- formatNode.appendChild(contents);
90
- range.insertNode(formatNode);
91
-
92
- // Select the formatted content
93
- const newRange = document.createRange();
94
- newRange.selectNodeContents(formatNode);
95
- selection.removeAllRanges();
96
- selection.addRange(newRange);
97
- }
98
- }
99
-
100
- /**
101
- * Remove inline format from selection
102
- * Unwraps formatted content or removes format at cursor
103
- */
104
- remove() {
105
- const selection = window.getSelection();
106
- if (!selection || !selection.rangeCount) return;
107
-
108
- const range = selection.getRangeAt(0);
109
-
110
- if (range.collapsed) {
111
- // Handle cursor position
112
- this.removeAtCursor(range, selection);
113
- } else {
114
- // Handle selection
115
- this.removeFromSelection(range, selection);
116
- }
117
- }
118
-
119
- /**
120
- * Remove format at cursor position
121
- * @param {Range} range - Current range
122
- * @param {Selection} selection - Current selection
123
- */
124
- removeAtCursor(range, selection) {
125
- const container = range.startContainer;
126
- const formatNode = this.findFormatNode(container);
127
-
128
- if (!formatNode || !formatNode.parentNode) return;
129
-
130
- const text = formatNode.textContent;
131
- const absoluteOffset = this.getOffsetWithin(formatNode, range);
132
-
133
- // Split the format node at cursor position
134
- const beforeText = text.slice(0, absoluteOffset);
135
- const afterText = text.slice(absoluteOffset);
136
-
137
- const fragment = document.createDocumentFragment();
138
-
139
- if (beforeText) {
140
- const beforeNode = formatNode.cloneNode(false);
141
- beforeNode.textContent = beforeText;
142
- fragment.appendChild(beforeNode);
143
- }
144
-
145
- // Insert zero-width space as marker
146
- const zwspNode = document.createTextNode('\u200B');
147
- fragment.appendChild(zwspNode);
148
-
149
- if (afterText) {
150
- const afterNode = formatNode.cloneNode(false);
151
- afterNode.textContent = afterText;
152
- fragment.appendChild(afterNode);
153
- }
154
-
155
- formatNode.replaceWith(fragment);
156
-
157
- // Position cursor after the marker
158
- const newRange = document.createRange();
159
- newRange.setStartAfter(zwspNode);
160
- newRange.collapse(true);
161
- selection.removeAllRanges();
162
- selection.addRange(newRange);
163
- }
164
-
165
- /**
166
- * Remove format from selection
167
- */
168
- removeFromSelection(range, selection) {
169
- const formatName = this.constructor.formatName;
170
- document.execCommand(formatName);
171
- if(formatName === 'strike'){
172
- document.execCommand("strikeThrough");
173
- }
174
- }
175
-
176
-
177
-
178
- /**
179
- * Find the format node containing the given node
180
- * @param {Node} node - DOM node
181
- * @returns {Element|null} Format node
182
- */
183
- findFormatNode(node) {
184
- while (node && node !== document.body) {
185
- if (node.nodeType === Node.ELEMENT_NODE &&
186
- node.tagName === this.constructor.tagName) {
187
- return node;
188
- }
189
- node = node.parentNode;
190
- }
191
- return null;
192
- }
193
-
194
- /**
195
- * Find all format nodes within a range
196
- * @param {Range} range - Selection range
197
- * @returns {Element[]} Array of format nodes
198
- */
199
- findFormatNodesInRange(range) {
200
- const nodes = [];
201
- const walker = document.createTreeWalker(
202
- range.commonAncestorContainer,
203
- NodeFilter.SHOW_ELEMENT,
204
- {
205
- acceptNode: (node) => {
206
- if (node.tagName === this.constructor.tagName &&
207
- range.intersectsNode(node)) {
208
- return NodeFilter.FILTER_ACCEPT;
209
- }
210
- return NodeFilter.FILTER_SKIP;
211
- }
212
- }
213
- );
214
-
215
- let node;
216
- while ((node = walker.nextNode())) {
217
- nodes.push(node);
218
- }
219
-
220
- return nodes;
221
- }
222
-
223
- /**
224
- * Check if inline format is active at current selection
225
- * @returns {boolean}
226
- */
227
- isActive() {
228
- const selection = window.getSelection();
229
- if (!selection || !selection.rangeCount) return false;
230
-
231
- const range = selection.getRangeAt(0);
232
- let node = range.startContainer;
233
-
234
- const tagName = this.constructor.tagName;
235
- const altTags = this.constructor.alternativeTagNames || [];
236
- const formatName = this.constructor.formatName;
237
-
238
- // Đặc biệt với một số lệnh hỗ trợ execCommand
239
- const commandSupported = ['bold', 'italic', 'underline'];
240
- if (commandSupported.includes(formatName?.toLowerCase())) {
241
- try {
242
- return document.queryCommandState(formatName);
243
- } catch (e) {
244
- // fallback nếu execCommand không được hỗ trợ
245
- }
246
- }
247
-
248
- // Kiểm tra DOM tag
249
- while (node && node !== document.body) {
250
- if (
251
- node.nodeType === Node.ELEMENT_NODE &&
252
- (node.tagName === tagName ||
253
- altTags.includes(node.tagName))
254
- ) {
255
- return true;
256
- }
257
- node = node.parentNode;
258
- }
259
-
260
- return false;
261
- }
262
-
263
- }
264
-
265
- /**
266
- * Block Format - for formats like headers, paragraphs, alignment
267
- * Handles block-level formatting that affects entire blocks/paragraphs
268
- */
269
- export class BlockFormat extends Format {
270
- /**
271
- * Create block format element
272
- * @param {*} value - Format value
273
- * @returns {HTMLElement}
274
- */
275
- static create(value) {
276
- const node = super.create(value);
277
- return node;
278
- }
279
-
280
- /**
281
- * Apply block format to selection
282
- * Converts current block(s) or creates new block
283
- * @param {*} value - Format value
284
- */
285
- apply(value) {
286
- const selection = window.getSelection();
287
- if (!selection || !selection.rangeCount) return;
288
-
289
- const range = selection.getRangeAt(0);
290
- const blocks = this.getBlockElements(range);
291
-
292
- if (blocks.length === 0) {
293
- // No block found - create new one
294
- this.createBlockAtCursor(range, value);
295
- } else {
296
- // Convert existing blocks
297
- blocks.forEach(block => {
298
- this.convertBlock(block, value);
299
- });
300
- }
301
- }
302
-
303
- /**
304
- * Remove block format from selection
305
- * Converts blocks back to default (paragraph) or removes formatting
306
- */
307
- remove() {
308
- const selection = window.getSelection();
309
- if (!selection || !selection.rangeCount) return;
310
-
311
- const range = selection.getRangeAt(0);
312
- const blocks = this.getBlockElements(range);
313
-
314
- blocks.forEach(block => {
315
- this.removeBlockFormat(block);
316
- });
317
- }
318
-
319
- /**
320
- * Create new block at cursor position
321
- * @param {Range} range - Current range
322
- * @param {*} value - Format value
323
- */
324
- createBlockAtCursor(range, value) {
325
- const blockNode = this.constructor.create(value);
326
-
327
- // Try to preserve style from existing block if cursor is inside one
328
- const existingBlock = this.getBlockElement(range.startContainer);
329
- if (existingBlock && existingBlock.style && existingBlock.style.cssText) {
330
- blockNode.style.cssText = existingBlock.style.cssText;
331
- }
332
-
333
- if (range.collapsed) {
334
- // No selection - create empty block
335
- blockNode.appendChild(document.createTextNode(''));
336
- range.insertNode(blockNode);
337
-
338
- // Position cursor inside the block
339
- const newRange = document.createRange();
340
- newRange.setStart(blockNode, 0);
341
- newRange.collapse(true);
342
- const selection = window.getSelection();
343
- selection.removeAllRanges();
344
- selection.addRange(newRange);
345
- } else {
346
- // Has selection - wrap in block
347
- const contents = range.extractContents();
348
- blockNode.appendChild(contents);
349
- range.insertNode(blockNode);
350
-
351
- // Select the content in the block
352
- const newRange = document.createRange();
353
- newRange.selectNodeContents(blockNode);
354
- const selection = window.getSelection();
355
- selection.removeAllRanges();
356
- selection.addRange(newRange);
357
- }
358
- }
359
-
360
- /**
361
- * Convert existing block to new format
362
- * @param {Element} block - Block element to convert
363
- * @param {*} value - Format value
364
- */
365
- convertBlock(block, value) {
366
- const newBlock = this.constructor.create(value);
367
-
368
- // Copy all child nodes
369
- while (block.firstChild) {
370
- newBlock.appendChild(block.firstChild);
371
- }
372
-
373
- // Copy relevant attributes
374
- if (block.className && this.shouldPreserveClass(block.className)) {
375
- newBlock.className = block.className;
376
- }
377
-
378
- // Copy style attributes to preserve formatting like text-align
379
- if (block.style && block.style.cssText) {
380
- newBlock.style.cssText = block.style.cssText;
381
- }
382
-
383
- // Replace the block
384
- block.parentNode.replaceChild(newBlock, block);
385
- }
386
-
387
- /**
388
- * Remove block format (convert to paragraph)
389
- * @param {Element} block - Block element
390
- */
391
- removeBlockFormat(block) {
392
- const paragraph = document.createElement('P');
393
-
394
- // Move all child nodes to paragraph
395
- while (block.firstChild) {
396
- paragraph.appendChild(block.firstChild);
397
- }
398
-
399
- // Copy style attributes to preserve formatting like text-align
400
- if (block.style && block.style.cssText) {
401
- paragraph.style.cssText = block.style.cssText;
402
- }
403
-
404
- // Replace the block
405
- block.parentNode.replaceChild(paragraph, block);
406
- }
407
-
408
- /**
409
- * Get block elements in range
410
- * @param {Range} range - Selection range
411
- * @returns {Element[]} Array of block elements
412
- */
413
- getBlockElements(range) {
414
- const blocks = [];
415
- let startBlock = this.getBlockElement(range.startContainer);
416
- let endBlock = this.getBlockElement(range.endContainer);
417
-
418
- // Nếu endBlock ngay sau startBlock và selection kết thúc ở vị trí 0 của endBlock
419
- if (startBlock && endBlock && startBlock.nextElementSibling === endBlock) {
420
- const endAtStartOfEndBlock =
421
- range.endContainer === endBlock &&
422
- range.endOffset === 0;
423
- if (endAtStartOfEndBlock) {
424
- endBlock = startBlock;
425
- }
426
- }
427
-
428
- if (startBlock === endBlock) {
429
- if (startBlock) blocks.push(startBlock);
430
- } else {
431
- // Multiple blocks
432
- let current = startBlock;
433
- while (current && current !== endBlock) {
434
- if (this.isBlockElement(current)) {
435
- blocks.push(current);
436
- }
437
- current = this.getNextBlockElement(current);
438
- }
439
- if (endBlock && this.isBlockElement(endBlock)) {
440
- blocks.push(endBlock);
441
- }
442
- }
443
-
444
- return blocks.filter((block, index, self) =>
445
- self.indexOf(block) === index
446
- );
447
- }
448
-
449
- /**
450
- * Get block element containing node
451
- * @param {Node} node - DOM node
452
- * @returns {Element|null} Block element
453
- */
454
- getBlockElement(node) {
455
- while (node && node.nodeType !== Node.ELEMENT_NODE) {
456
- node = node.parentNode;
457
- }
458
-
459
- while (node && node !== document.body) {
460
- if (this.isBlockElement(node)) {
461
- return node;
462
- }
463
- node = node.parentNode;
464
- }
465
-
466
- return null;
467
- }
468
-
469
- /**
470
- * Check if element is a block element
471
- * @param {Element} element - DOM element
472
- * @returns {boolean}
473
- */
474
- isBlockElement(element) {
475
- if (!element || element.nodeType !== Node.ELEMENT_NODE) return false;
476
-
477
- const blockTags = [
478
- 'P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6',
479
- 'BLOCKQUOTE', 'PRE', 'UL', 'OL', 'LI', 'SECTION', 'ARTICLE'
480
- ];
481
- return blockTags.includes(element.tagName.toUpperCase());
482
- }
483
-
484
- /**
485
- * Get next block element
486
- * @param {Element} element - Current element
487
- * @returns {Element|null} Next block element
488
- */
489
- getNextBlockElement(element) {
490
- let next = element.nextElementSibling;
491
- while (next) {
492
- if (this.isBlockElement(next)) {
493
- return next;
494
- }
495
- next = next.nextElementSibling;
496
- }
497
- return null;
498
- }
499
-
500
- /**
501
- * Check if block format is active at current selection
502
- * @param {*} value - Optional specific value to check
503
- * @returns {boolean}
504
- */
505
- isActive(value = null) {
506
- const selection = window.getSelection();
507
- if (!selection || !selection.rangeCount) return false;
508
-
509
- const range = selection.getRangeAt(0);
510
- const block = this.getBlockElement(range.startContainer);
511
-
512
- if (!block) return false;
513
-
514
- // Check if block matches our format
515
- if (block.tagName === this.constructor.tagName) {
516
- return value ? this.hasValue(block, value) : true;
517
- }
518
-
519
- return false;
520
- }
521
-
522
- /**
523
- * Check if block has specific value
524
- * Override in subclasses for specific value checking
525
- * @param {Element} block - Block element
526
- * @param {*} value - Value to check
527
- * @returns {boolean}
528
- */
529
- hasValue(block, value) {
530
- return true; // Default implementation
531
- }
532
-
533
- /**
534
- * Check if class should be preserved during conversion
535
- * Override in subclasses for specific class handling
536
- * @param {string} className - Class name
537
- * @returns {boolean}
538
- */
539
- shouldPreserveClass(className) {
540
- return false; // Default: don't preserve classes
541
- }
542
- }
@@ -1,81 +0,0 @@
1
- /**
2
- * Base Module class - Inspired by Quill's architecture
3
- * All editor modules should extend this class
4
- */
5
- export default class Module {
6
- static DEFAULTS = {};
7
-
8
- constructor(editor, options = {}) {
9
- this.editor = editor;
10
- this.options = { ...this.constructor.DEFAULTS, ...options };
11
- this.events = new Map();
12
- }
13
-
14
- /**
15
- * Add event listener
16
- * @param {string} event - Event name
17
- * @param {function} handler - Event handler
18
- */
19
- on(event, handler) {
20
- if (!this.events.has(event)) {
21
- this.events.set(event, []);
22
- }
23
- this.events.get(event).push(handler);
24
- }
25
-
26
- /**
27
- * Remove event listener
28
- * @param {string} event - Event name
29
- * @param {function} handler - Event handler
30
- */
31
- off(event, handler) {
32
- if (this.events.has(event)) {
33
- const handlers = this.events.get(event);
34
- const index = handlers.indexOf(handler);
35
- if (index > -1) {
36
- handlers.splice(index, 1);
37
- }
38
- }
39
- }
40
-
41
- /**
42
- * Emit event
43
- * @param {string} event - Event name
44
- * @param {*} data - Event data
45
- */
46
- emit(event, data) {
47
- if (this.events.has(event)) {
48
- this.events.get(event).forEach(handler => {
49
- try {
50
- handler(data);
51
- } catch (error) {
52
- console.error(`Error in event handler for ${event}:`, error);
53
- }
54
- });
55
- }
56
- }
57
-
58
- /**
59
- * Called when module is being destroyed
60
- * Override this method to cleanup resources
61
- */
62
- destroy() {
63
- this.events.clear();
64
- }
65
-
66
- /**
67
- * Called when editor content changes
68
- * Override this method to respond to content changes
69
- */
70
- onContentChange() {
71
- // Override in subclasses
72
- }
73
-
74
- /**
75
- * Called when selection changes
76
- * Override this method to respond to selection changes
77
- */
78
- onSelectionChange(range) {
79
- // Override in subclasses
80
- }
81
- }