@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/import.js
DELETED
|
@@ -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;
|
package/lib/formats/indent.js
DELETED
|
@@ -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 };
|
package/lib/formats/italic.js
DELETED
|
@@ -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;
|