@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,410 +0,0 @@
1
- import Module from '../core/module.js';
2
-
3
- /**
4
- * History Module - Handles undo/redo functionality
5
- * Extracted from FormatManager.js and ToolbarManager.js logic
6
- */
7
- class History extends Module {
8
- static DEFAULTS = {
9
- delay: 1000, // Delay between history saves
10
- maxStack: 100, // Maximum number of undo states
11
- userOnly: false // Only save user-initiated changes
12
- };
13
-
14
- constructor(editor, options = {}) {
15
- super(editor, options);
16
- this.stack = [];
17
- this.index = -1;
18
- this.lastSave = 0;
19
- this.savedSelection = null;
20
-
21
- this.init();
22
- }
23
-
24
- init() {
25
- this.setupEventListeners();
26
- this.saveState(); // Save initial state
27
- }
28
-
29
- /**
30
- * Setup event listeners for automatic history saving
31
- */
32
- setupEventListeners() {
33
- // Save state on input with debouncing
34
- this.editor.editor.addEventListener('input', () => {
35
- this.handleInput();
36
- });
37
-
38
- // Save state on specific commands
39
- this.editor.editor.addEventListener('keydown', (e) => {
40
- // Save state before destructive operations
41
- if (e.key === 'Enter' || e.key === 'Backspace' || e.key === 'Delete') {
42
- this.saveState();
43
- }
44
- });
45
-
46
- // Listen for DOM changes to catch all formatting operations
47
- this.setupMutationObserver();
48
-
49
- // Listen for toolbar clicks to save state before formatting
50
- this.editor.wrapper.addEventListener('click', (e) => {
51
- if (e.target.closest('.rich-editor-toolbar-btn')) {
52
- // Save state before applying format
53
- setTimeout(() => {
54
- this.saveState();
55
- }, 0);
56
- }
57
- });
58
-
59
- // Handle undo/redo shortcuts - only when editor is focused
60
- this.editor.editor.addEventListener('keydown', (e) => {
61
- if ((e.ctrlKey || e.metaKey) && !e.shiftKey && e.key === 'z') {
62
- e.preventDefault();
63
- this.undo();
64
- } else if (((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'z') ||
65
- ((e.ctrlKey || e.metaKey) && e.key === 'y')) {
66
- e.preventDefault();
67
- this.redo();
68
- }
69
- });
70
- }
71
-
72
- /**
73
- * Setup mutation observer to watch for DOM changes
74
- */
75
- setupMutationObserver() {
76
- this.mutationObserver = new MutationObserver((mutations) => {
77
- let shouldSave = false;
78
-
79
- for (const mutation of mutations) {
80
- // Check if the mutation is relevant (not just attribute changes on non-content elements)
81
- if (mutation.type === 'childList' ||
82
- (mutation.type === 'attributes' &&
83
- (mutation.target.nodeType === Node.TEXT_NODE ||
84
- ['P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'PRE', 'UL', 'OL', 'LI', 'SPAN', 'STRONG', 'EM', 'U', 'S', 'SUB', 'SUP', 'A', 'IMG', 'VIDEO', 'TABLE', 'TR', 'TD', 'TH'].includes(mutation.target.tagName)))) {
85
- shouldSave = true;
86
- break;
87
- }
88
- }
89
-
90
- if (shouldSave) {
91
- // Debounce the save to avoid too many saves
92
- clearTimeout(this.mutationTimeout);
93
- this.mutationTimeout = setTimeout(() => {
94
- this.saveState();
95
- }, 100);
96
- }
97
- });
98
-
99
- // Start observing
100
- this.mutationObserver.observe(this.editor.editor, {
101
- childList: true,
102
- subtree: true,
103
- attributes: true,
104
- attributeFilter: ['style', 'class', 'href', 'src', 'alt', 'title']
105
- });
106
- }
107
-
108
- /**
109
- * Handle input event with debouncing
110
- */
111
- handleInput() {
112
- const now = Date.now();
113
- if (now - this.lastSave > this.options.delay) {
114
- this.saveState();
115
- this.lastSave = now;
116
- }
117
- }
118
-
119
- /**
120
- * Save current editor state
121
- */
122
- saveState() {
123
- const content = this.editor.getContent();
124
- const selection = this.saveSelection();
125
-
126
- // Don't save if content hasn't changed
127
- if (this.stack.length > 0 && this.stack[this.index]?.content === content) {
128
- return;
129
- }
130
-
131
- // Don't save if it's too soon after last save (debouncing)
132
- const now = Date.now();
133
- if (this.lastSave && now - this.lastSave < 50) {
134
- return;
135
- }
136
-
137
- // Remove any redo states if we're not at the end
138
- if (this.index < this.stack.length - 1) {
139
- this.stack.splice(this.index + 1);
140
- }
141
-
142
- // Add new state
143
- this.stack.push({
144
- content,
145
- selection,
146
- timestamp: now
147
- });
148
-
149
- // Limit stack size
150
- if (this.stack.length > this.options.maxStack) {
151
- this.stack.shift();
152
- } else {
153
- this.index++;
154
- }
155
-
156
- this.lastSave = now;
157
- }
158
-
159
- /**
160
- * Undo last change
161
- */
162
- undo() {
163
- if (!this.canUndo()) return false;
164
-
165
- this.index--;
166
- const state = this.stack[this.index];
167
-
168
- this.restoreState(state);
169
- this.onHistoryChange('undo');
170
-
171
- return true;
172
- }
173
-
174
- /**
175
- * Redo last undone change
176
- */
177
- redo() {
178
- if (!this.canRedo()) return false;
179
-
180
- this.index++;
181
- const state = this.stack[this.index];
182
-
183
- this.restoreState(state);
184
- this.onHistoryChange('redo');
185
-
186
- return true;
187
- }
188
-
189
- /**
190
- * Check if undo is possible
191
- */
192
- canUndo() {
193
- return this.index > 0;
194
- }
195
-
196
- /**
197
- * Check if redo is possible
198
- */
199
- canRedo() {
200
- return this.index < this.stack.length - 1;
201
- }
202
-
203
- /**
204
- * Restore editor state
205
- * @param {object} state - State to restore
206
- */
207
- restoreState(state) {
208
- if (!state) return;
209
-
210
- // Restore content
211
- this.editor.setContent(state.content);
212
-
213
- // Restore selection
214
- if (state.selection) {
215
- setTimeout(() => {
216
- this.restoreSelection(state.selection);
217
- }, 10);
218
- }
219
- }
220
-
221
- /**
222
- * Save current selection
223
- */
224
- saveSelection() {
225
- const selection = window.getSelection();
226
- if (!selection || !selection.rangeCount) return null;
227
-
228
- const range = selection.getRangeAt(0);
229
- const editorEl = this.editor.editor;
230
-
231
- // Calculate offset relative to editor
232
- const startOffset = this.getOffsetInEditor(range.startContainer, range.startOffset, editorEl);
233
- const endOffset = this.getOffsetInEditor(range.endContainer, range.endOffset, editorEl);
234
-
235
- return {
236
- startOffset,
237
- endOffset,
238
- collapsed: range.collapsed
239
- };
240
- }
241
-
242
- /**
243
- * Restore selection
244
- * @param {object} selectionState - Selection state to restore
245
- */
246
- restoreSelection(selectionState) {
247
- if (!selectionState) return;
248
-
249
- const editorEl = this.editor.editor;
250
- const range = document.createRange();
251
- const selection = window.getSelection();
252
-
253
- try {
254
- const startNode = this.getNodeAtOffset(editorEl, selectionState.startOffset);
255
- const endNode = this.getNodeAtOffset(editorEl, selectionState.endOffset);
256
-
257
- if (startNode && endNode) {
258
- range.setStart(startNode.node, startNode.offset);
259
- range.setEnd(endNode.node, endNode.offset);
260
-
261
- selection.removeAllRanges();
262
- selection.addRange(range);
263
- }
264
- } catch (error) {
265
- console.warn('Could not restore selection:', error);
266
- // Fallback: focus editor
267
- this.editor.focus();
268
- }
269
- }
270
-
271
- /**
272
- * Get offset of a position within editor
273
- * @param {Node} node - DOM node
274
- * @param {number} offset - Offset within node
275
- * @param {Element} root - Root element (editor)
276
- */
277
- getOffsetInEditor(node, offset, root) {
278
- let totalOffset = 0;
279
- const walker = document.createTreeWalker(
280
- root,
281
- NodeFilter.SHOW_TEXT,
282
- null,
283
- false
284
- );
285
-
286
- let currentNode;
287
- while (currentNode = walker.nextNode()) {
288
- if (currentNode === node) {
289
- return totalOffset + offset;
290
- }
291
- totalOffset += currentNode.textContent.length;
292
- }
293
-
294
- return totalOffset;
295
- }
296
-
297
- /**
298
- * Get node at specific offset within editor
299
- * @param {Element} root - Root element (editor)
300
- * @param {number} targetOffset - Target offset
301
- */
302
- getNodeAtOffset(root, targetOffset) {
303
- let currentOffset = 0;
304
- const walker = document.createTreeWalker(
305
- root,
306
- NodeFilter.SHOW_TEXT,
307
- null,
308
- false
309
- );
310
-
311
- let currentNode;
312
- while (currentNode = walker.nextNode()) {
313
- const nodeLength = currentNode.textContent.length;
314
- if (currentOffset + nodeLength >= targetOffset) {
315
- return {
316
- node: currentNode,
317
- offset: targetOffset - currentOffset
318
- };
319
- }
320
- currentOffset += nodeLength;
321
- }
322
-
323
- // Fallback: return last node
324
- return {
325
- node: root.lastChild || root,
326
- offset: 0
327
- };
328
- }
329
-
330
- /**
331
- * Clear history
332
- */
333
- clear() {
334
- this.stack = [];
335
- this.index = -1;
336
- this.saveState(); // Save current state as first entry
337
- }
338
-
339
- /**
340
- * Get current history state info
341
- */
342
- getState() {
343
- return {
344
- canUndo: this.canUndo(),
345
- canRedo: this.canRedo(),
346
- stackLength: this.stack.length,
347
- currentIndex: this.index
348
- };
349
- }
350
-
351
- /**
352
- * Called when history changes (undo/redo)
353
- * @param {string} action - 'undo' or 'redo'
354
- */
355
- onHistoryChange(action) {
356
- // Notify other modules about history change
357
- this.editor.modules.forEach(module => {
358
- if (module !== this && typeof module.onHistoryChange === 'function') {
359
- module.onHistoryChange(action, this.getState());
360
- }
361
- });
362
-
363
- // Trigger custom event
364
- const event = new CustomEvent('historychange', {
365
- detail: { action, state: this.getState() }
366
- });
367
- this.editor.editor.dispatchEvent(event);
368
- }
369
-
370
- /**
371
- * Force save current state (useful before major operations)
372
- */
373
- forceSave() {
374
- // Temporarily disable debouncing for force save
375
- const originalLastSave = this.lastSave;
376
- this.lastSave = 0;
377
- this.saveState();
378
- this.lastSave = originalLastSave;
379
- }
380
-
381
- /**
382
- * Save state before applying format (called by editor)
383
- */
384
- saveBeforeFormat() {
385
- this.forceSave();
386
- }
387
-
388
- /**
389
- * Destroy module
390
- */
391
- destroy() {
392
- // Disconnect mutation observer
393
- if (this.mutationObserver) {
394
- this.mutationObserver.disconnect();
395
- this.mutationObserver = null;
396
- }
397
-
398
- // Clear timeout
399
- if (this.mutationTimeout) {
400
- clearTimeout(this.mutationTimeout);
401
- this.mutationTimeout = null;
402
- }
403
-
404
- this.stack = [];
405
- this.index = -1;
406
- this.savedSelection = null;
407
- }
408
- }
409
-
410
- export default History;