@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,385 +0,0 @@
1
- import { InlineFormat } from '../core/format.js';
2
- import ImportPopup from '../ui/import-popup.js';
3
- import Editor from '../core/editor.js';
4
-
5
- /**
6
- * Import Format - Handles importing various file types
7
- * Now supports multiple editor instances with separate popup instances
8
- */
9
- class Import extends InlineFormat {
10
- static formatName = 'import';
11
- static tagName = 'DIV';
12
- static className = 'imported-content';
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 Import format');
21
- return;
22
- }
23
-
24
- this.editorId = currentEditor.instanceId;
25
-
26
- // Check if this editor already has an import popup instance
27
- let importPopup = currentEditor.getPopupInstance('import');
28
-
29
- if (!importPopup) {
30
- // Create new import popup instance for this editor
31
- importPopup = new ImportPopup({
32
- onImport: (content, fileType) => {
33
- Import.insertImportedContent(content, fileType, this.editorId);
34
- },
35
- editor: currentEditor,
36
- editorId: this.editorId
37
- });
38
-
39
- // Store popup instance in editor
40
- currentEditor.setPopupInstance('import', importPopup);
41
- }
42
-
43
- this.importPopup = importPopup;
44
- }
45
-
46
- /**
47
- * Create a new Import format instance for a specific editor
48
- * @param {string} editorId - Editor instance ID
49
- * @returns {Import} Import 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 Import();
64
-
65
- // Restore original current instance
66
- Editor.currentInstance = originalCurrent;
67
-
68
- return format;
69
- }
70
-
71
- /**
72
- * Insert imported content at current cursor position
73
- * @param {string} content - Imported content
74
- * @param {string} fileType - Type of imported file
75
- * @param {string} editorId - Editor instance ID
76
- */
77
- static insertImportedContent(content, fileType, editorId = null) {
78
- // Get the correct editor instance
79
- let editor = null;
80
- if (editorId) {
81
- editor = Editor.getInstanceById(editorId);
82
- } else {
83
- editor = Editor.getCurrentInstance();
84
- }
85
-
86
- if (!editor) {
87
- console.warn('No editor instance found for content insertion');
88
- return;
89
- }
90
-
91
- const selection = window.getSelection();
92
- if (!selection || !selection.rangeCount) return;
93
-
94
- try {
95
- const range = selection.getRangeAt(0);
96
-
97
- // Create content element based on file type
98
- let contentElement;
99
-
100
- if (fileType === 'html') {
101
- contentElement = Import.processHtmlContent(content);
102
- } else if (fileType === 'excel') {
103
- contentElement = Import.processExcelContent(content);
104
- } else if (fileType === 'pdf' || fileType === 'word') {
105
- contentElement = Import.processTextContent(content);
106
- } else {
107
- contentElement = Import.processTextContent(content);
108
- }
109
-
110
- // Insert content at cursor position
111
- range.deleteContents();
112
- range.insertNode(contentElement);
113
-
114
- // Position cursor after the content
115
- range.setStartAfter(contentElement);
116
- range.collapse(true);
117
- selection.removeAllRanges();
118
- selection.addRange(range);
119
-
120
- // Trigger content change event
121
- if (editor && typeof editor.onContentChange === 'function') {
122
- editor.onContentChange();
123
- }
124
-
125
- } catch (error) {
126
- console.error('Error inserting imported content:', error);
127
- }
128
- }
129
-
130
- /**
131
- * Process HTML content
132
- * @param {string} htmlContent - HTML content to process
133
- * @returns {HTMLElement}
134
- */
135
- static processHtmlContent(htmlContent) {
136
- const container = document.createElement('div');
137
- container.className = 'imported-content html-content';
138
-
139
- // Create a temporary element to parse HTML safely
140
- const temp = document.createElement('div');
141
- temp.innerHTML = htmlContent;
142
-
143
- // Clean and transfer content
144
- Import.cleanHtmlContent(temp);
145
- container.appendChild(temp);
146
-
147
- return container;
148
- }
149
-
150
- /**
151
- * Process Excel content (CSV-like data)
152
- * @param {Array} data - Excel data as array of arrays
153
- * @returns {HTMLElement}
154
- */
155
- static processExcelContent(data) {
156
- const container = document.createElement('div');
157
- container.className = 'imported-content excel-content';
158
-
159
- if (!Array.isArray(data) || data.length === 0) {
160
- container.textContent = 'No data to import';
161
- return container;
162
- }
163
-
164
- // Create table
165
- const table = document.createElement('table');
166
- table.className = 'imported-table';
167
-
168
- // Add header row if available
169
- if (data.length > 0) {
170
- const thead = document.createElement('thead');
171
- const headerRow = document.createElement('tr');
172
-
173
- data[0].forEach(cellData => {
174
- const th = document.createElement('th');
175
- th.textContent = cellData || '';
176
- headerRow.appendChild(th);
177
- });
178
-
179
- thead.appendChild(headerRow);
180
- table.appendChild(thead);
181
- }
182
-
183
- // Add data rows
184
- if (data.length > 1) {
185
- const tbody = document.createElement('tbody');
186
-
187
- for (let i = 1; i < data.length; i++) {
188
- const row = document.createElement('tr');
189
-
190
- data[i].forEach(cellData => {
191
- const td = document.createElement('td');
192
- td.textContent = cellData || '';
193
- row.appendChild(td);
194
- });
195
-
196
- tbody.appendChild(row);
197
- }
198
-
199
- table.appendChild(tbody);
200
- }
201
-
202
- container.appendChild(table);
203
- return container;
204
- }
205
-
206
- /**
207
- * Process plain text content (PDF, Word)
208
- * @param {string} textContent - Text content to process
209
- * @returns {HTMLElement}
210
- */
211
- static processTextContent(textContent) {
212
- const container = document.createElement('div');
213
- container.className = 'imported-content text-content';
214
-
215
- // Split into paragraphs and process
216
- const paragraphs = textContent.split(/\n\s*\n/);
217
-
218
- paragraphs.forEach(paragraph => {
219
- if (paragraph.trim()) {
220
- const p = document.createElement('p');
221
- p.textContent = paragraph.trim();
222
- container.appendChild(p);
223
- }
224
- });
225
-
226
- return container;
227
- }
228
-
229
- /**
230
- * Clean HTML content by removing dangerous elements and attributes
231
- * @param {HTMLElement} element - Element to clean
232
- */
233
- static cleanHtmlContent(element) {
234
- const allowedTags = ['p', 'div', 'span', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
235
- 'strong', 'b', 'em', 'i', 'u', 'ul', 'ol', 'li', 'br',
236
- 'table', 'thead', 'tbody', 'tr', 'th', 'td'];
237
-
238
- const allowedAttrs = ['class', 'style'];
239
-
240
- const walker = document.createTreeWalker(
241
- element,
242
- NodeFilter.SHOW_ELEMENT,
243
- null,
244
- false
245
- );
246
-
247
- const elementsToRemove = [];
248
-
249
- while (walker.nextNode()) {
250
- const node = walker.currentNode;
251
-
252
- // Remove dangerous tags
253
- if (!allowedTags.includes(node.tagName.toLowerCase())) {
254
- elementsToRemove.push(node);
255
- continue;
256
- }
257
-
258
- // Clean attributes
259
- const attrs = Array.from(node.attributes);
260
- attrs.forEach(attr => {
261
- if (!allowedAttrs.includes(attr.name.toLowerCase())) {
262
- node.removeAttribute(attr.name);
263
- }
264
- });
265
- }
266
-
267
- // Remove dangerous elements
268
- elementsToRemove.forEach(el => {
269
- if (el.parentNode) {
270
- el.parentNode.removeChild(el);
271
- }
272
- });
273
- }
274
-
275
- /**
276
- * Parse CSV content
277
- * @param {string} csvContent - CSV content
278
- * @returns {Array} - Array of arrays representing CSV data
279
- */
280
- static parseCSV(csvContent) {
281
- const lines = csvContent.split('\n');
282
- const result = [];
283
-
284
- lines.forEach(line => {
285
- if (line.trim()) {
286
- // Simple CSV parsing (doesn't handle quoted values with commas)
287
- const cells = line.split(',').map(cell => cell.trim().replace(/^["']|["']$/g, ''));
288
- result.push(cells);
289
- }
290
- });
291
-
292
- return result;
293
- }
294
-
295
- /**
296
- * Apply import formatting - shows import popup
297
- */
298
- apply() {
299
- this.showImportPopup();
300
- }
301
-
302
- /**
303
- * Toggle import formatting - shows import popup
304
- */
305
- toggle() {
306
- if (this.importPopup.isVisible) {
307
- this.importPopup.hide();
308
- } else {
309
- this.showImportPopup();
310
- }
311
- }
312
-
313
- /**
314
- * Show import popup
315
- */
316
- showImportPopup() {
317
- // Find import button in the current editor's toolbar
318
- const editor = Editor.getInstanceById(this.editorId);
319
- if (!editor) return;
320
-
321
- const toolbar = editor.getModule('toolbar');
322
- let importButton = null;
323
-
324
- if (toolbar) {
325
- importButton = toolbar.getButton('import');
326
- }
327
-
328
- // Fallback: find button by class in the current editor's toolbar
329
- if (!importButton) {
330
- const toolbarContainer = toolbar?.getContainer();
331
- if (toolbarContainer) {
332
- importButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.import-btn');
333
- }
334
- }
335
-
336
- // Final fallback: find any import button in the current editor's wrapper
337
- if (!importButton) {
338
- importButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.import-btn');
339
- }
340
-
341
- if (!importButton) {
342
- console.warn('Import button not found for editor:', this.editorId);
343
- return;
344
- }
345
-
346
- this.importPopup.show(importButton);
347
- }
348
-
349
- /**
350
- * Check if import formatting is active
351
- */
352
- isActive() {
353
- return false; // Import doesn't have an "active" state
354
- }
355
-
356
- /**
357
- * Get supported file types
358
- */
359
- static getSupportedTypes() {
360
- return {
361
- html: {
362
- extensions: ['.html', '.htm'],
363
- mimeTypes: ['text/html'],
364
- name: 'HTML Files'
365
- },
366
- excel: {
367
- extensions: ['.csv', '.xlsx', '.xls'],
368
- mimeTypes: ['text/csv', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'],
369
- name: 'Excel Files'
370
- },
371
- pdf: {
372
- extensions: ['.pdf'],
373
- mimeTypes: ['application/pdf'],
374
- name: 'PDF Files'
375
- },
376
- word: {
377
- extensions: ['.doc', '.docx'],
378
- mimeTypes: ['application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'],
379
- name: 'Word Documents'
380
- }
381
- };
382
- }
383
- }
384
-
385
- export default Import;
@@ -1,297 +0,0 @@
1
- import { BlockFormat } from '../core/format.js';
2
- import { saveBeforeFormat } from '../utils/history-helper.js';
3
- import Editor from '../core/editor.js';
4
-
5
- /**
6
- * Indent Format - Handles text indentation (increase/decrease)
7
- */
8
- class Indent extends BlockFormat {
9
- static formatName = 'indent';
10
- static tagName = 'DIV';
11
- static attribute = 'style';
12
-
13
- constructor() {
14
- super();
15
- }
16
-
17
- /**
18
- * Create element with indentation
19
- * @param {string} value - Indent level (e.g., '20px', '40px')
20
- * @returns {HTMLElement}
21
- */
22
- static create(value) {
23
- const node = document.createElement('DIV');
24
- if (value) {
25
- node.style.paddingLeft = value;
26
- }
27
- return node;
28
- }
29
-
30
- /**
31
- * Apply indent to current selection
32
- * @param {string} direction - 'increase' or 'decrease'
33
- */
34
- static applyIndentToCurrentSelection(direction) {
35
- const selection = window.getSelection();
36
- if (!selection || !selection.rangeCount) return;
37
-
38
- // Save state before applying format
39
- saveBeforeFormat();
40
-
41
- try {
42
- const range = selection.getRangeAt(0);
43
- const blockElements = Indent.getSelectedBlockElements(range);
44
-
45
- if (blockElements.length === 0) {
46
- // If no block elements found, create one and apply indent
47
- const newBlock = document.createElement('DIV');
48
- newBlock.style.paddingLeft = direction === 'increase' ? '20px' : '0px';
49
-
50
- // Get selected text or use default
51
- const selectedText = range.toString() || '';
52
- if (selectedText) {
53
- newBlock.textContent = selectedText;
54
- range.deleteContents();
55
- range.insertNode(newBlock);
56
- } else {
57
- // Insert at cursor position
58
- range.insertNode(newBlock);
59
- newBlock.innerHTML = '<br>'; // Make it editable
60
- }
61
-
62
- // Position cursor in the new block
63
- const newRange = document.createRange();
64
- newRange.selectNodeContents(newBlock);
65
- newRange.collapse(false);
66
- selection.removeAllRanges();
67
- selection.addRange(newRange);
68
- } else {
69
- // Apply indent to existing blocks
70
- blockElements.forEach(block => {
71
- Indent.applyIndentToBlock(block, direction);
72
- });
73
- }
74
- } catch (error) {
75
- console.error('Error applying indent:', error);
76
- }
77
-
78
- // Trigger content change after applying format
79
- setTimeout(() => {
80
- const currentEditor = Editor.getCurrentInstance();
81
- if (currentEditor && typeof currentEditor.onContentChange === 'function') {
82
- currentEditor.onContentChange();
83
- }
84
- }, 0);
85
- }
86
-
87
- /**
88
- * Apply indent to a specific block element
89
- * @param {HTMLElement} block - Block element to indent
90
- * @param {string} direction - 'increase' or 'decrease'
91
- */
92
- static applyIndentToBlock(block, direction) {
93
- if (!block || !block.style) return;
94
-
95
- const currentIndent = parseInt(block.style.paddingLeft) || 0;
96
- let newIndent;
97
-
98
- if (direction === 'increase') {
99
- newIndent = currentIndent + 20; // Increase by 20px
100
- } else {
101
- newIndent = Math.max(0, currentIndent - 20); // Decrease by 20px, minimum 0
102
- }
103
-
104
- if (newIndent === 0) {
105
- block.style.paddingLeft = '';
106
- } else {
107
- block.style.paddingLeft = newIndent + 'px';
108
- }
109
- }
110
-
111
- /**
112
- * Get all selected block elements
113
- */
114
- static getSelectedBlockElements(range) {
115
- const blocks = [];
116
- const startContainer = range.startContainer;
117
- const endContainer = range.endContainer;
118
-
119
- // Get start block
120
- const startBlock = Indent.getBlockElement(startContainer);
121
- if (startBlock) blocks.push(startBlock);
122
-
123
- // If selection spans multiple blocks, get all blocks in between
124
- if (startContainer !== endContainer) {
125
- let currentNode = startBlock;
126
- while (currentNode && currentNode !== endContainer) {
127
- const nextBlock = Indent.getNextBlockElement(currentNode);
128
- if (nextBlock && !blocks.includes(nextBlock)) {
129
- blocks.push(nextBlock);
130
- currentNode = nextBlock;
131
- } else {
132
- break;
133
- }
134
- }
135
-
136
- // Get end block
137
- const endBlock = Indent.getBlockElement(endContainer);
138
- if (endBlock && !blocks.includes(endBlock)) {
139
- blocks.push(endBlock);
140
- }
141
- }
142
-
143
- return blocks;
144
- }
145
-
146
- /**
147
- * Get the block element containing the given node
148
- */
149
- static getBlockElement(node) {
150
- if (!node) return null;
151
-
152
- let currentNode = node;
153
- while (currentNode && currentNode !== document.body) {
154
- if (currentNode.nodeType === Node.ELEMENT_NODE) {
155
- const tagName = currentNode.tagName;
156
- if (['P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'LI'].includes(tagName)) {
157
- return currentNode;
158
- }
159
- }
160
- currentNode = currentNode.parentNode;
161
- }
162
- return null;
163
- }
164
-
165
- /**
166
- * Get next block element in document order
167
- */
168
- static getNextBlockElement(element) {
169
- let currentNode = element.nextSibling;
170
-
171
- while (currentNode) {
172
- if (currentNode.nodeType === Node.ELEMENT_NODE) {
173
- const tagName = currentNode.tagName;
174
- if (['P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'LI'].includes(tagName)) {
175
- return currentNode;
176
- }
177
- }
178
- currentNode = currentNode.nextSibling;
179
- }
180
-
181
- return null;
182
- }
183
-
184
- /**
185
- * Apply indent formatting
186
- * @param {string} value - Direction ('increase' or 'decrease')
187
- */
188
- apply(value = 'increase') {
189
- Indent.applyIndentToCurrentSelection(value);
190
- }
191
-
192
- /**
193
- * Remove indent formatting (reset to 0)
194
- */
195
- remove() {
196
- Indent.applyIndentToCurrentSelection('remove');
197
- }
198
-
199
- /**
200
- * Check if indent formatting is active
201
- * @returns {boolean}
202
- */
203
- isActive() {
204
- const selection = window.getSelection();
205
- if (!selection || !selection.rangeCount) return false;
206
-
207
- const range = selection.getRangeAt(0);
208
- const blockElement = Indent.getBlockElement(range.commonAncestorContainer);
209
-
210
- if (!blockElement) return false;
211
-
212
- const paddingLeft = parseInt(blockElement.style.paddingLeft) || 0;
213
- return paddingLeft > 0;
214
- }
215
-
216
- /**
217
- * Get current indent level
218
- * @returns {number}
219
- */
220
- static getCurrentIndentLevel() {
221
- const selection = window.getSelection();
222
- if (!selection || !selection.rangeCount) return 0;
223
-
224
- const range = selection.getRangeAt(0);
225
- const blockElement = Indent.getBlockElement(range.commonAncestorContainer);
226
-
227
- if (!blockElement) return 0;
228
-
229
- return parseInt(blockElement.style.paddingLeft) || 0;
230
- }
231
-
232
- /**
233
- * Get current indent level (instance method)
234
- * @returns {number}
235
- */
236
- getCurrentIndentLevel() {
237
- return Indent.getCurrentIndentLevel();
238
- }
239
- }
240
-
241
- /**
242
- * Indent Increase Format - Handles increasing indentation
243
- */
244
- class IndentIncrease extends Indent {
245
- static formatName = 'indent-increase';
246
-
247
- /**
248
- * Apply increase indent formatting
249
- */
250
- apply() {
251
- Indent.applyIndentToCurrentSelection('increase');
252
- }
253
-
254
- /**
255
- * Toggle increase indent - always increases
256
- */
257
- toggle() {
258
- this.apply();
259
- }
260
-
261
- /**
262
- * Never active - this is an action button
263
- */
264
- isActive() {
265
- return false;
266
- }
267
- }
268
-
269
- /**
270
- * Indent Decrease Format - Handles decreasing indentation
271
- */
272
- class IndentDecrease extends Indent {
273
- static formatName = 'indent-decrease';
274
-
275
- /**
276
- * Apply decrease indent formatting
277
- */
278
- apply() {
279
- Indent.applyIndentToCurrentSelection('decrease');
280
- }
281
-
282
- /**
283
- * Toggle decrease indent - always decreases
284
- */
285
- toggle() {
286
- this.apply();
287
- }
288
-
289
- /**
290
- * Never active - this is an action button
291
- */
292
- isActive() {
293
- return false;
294
- }
295
- }
296
-
297
- export { Indent as default, IndentIncrease, IndentDecrease };
@@ -1,27 +0,0 @@
1
- import { InlineFormat } from '../core/format.js';
2
- import { saveBeforeFormat } from '../utils/history-helper.js';
3
-
4
- /**
5
- * Italic Format - Handles italic text formatting
6
- */
7
- class Italic extends InlineFormat {
8
- static formatName = 'italic';
9
- static tagName = 'I';
10
- static alternativeTagNames = ['EM'];
11
-
12
- /**
13
- * Toggle italic formatting
14
- */
15
- toggle() {
16
- // Save state before applying format
17
- saveBeforeFormat();
18
-
19
- if (this.isActive()) {
20
- this.remove();
21
- } else {
22
- this.apply();
23
- }
24
- }
25
- }
26
-
27
- export default Italic;