@oix1987/yjd 1.0.3 → 2.1.0

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 (73) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +223 -142
  3. package/core.js +82 -0
  4. package/dist/core.esm.js +2 -0
  5. package/dist/core.esm.js.map +1 -0
  6. package/dist/rich-editor.esm.js +1 -1
  7. package/dist/rich-editor.esm.js.map +1 -1
  8. package/dist/rich-editor.min.js +1 -1
  9. package/dist/rich-editor.min.js.map +1 -1
  10. package/index.d.ts +230 -103
  11. package/index.js +297 -0
  12. package/lib/core/editor.js +1885 -0
  13. package/lib/core/format.js +540 -0
  14. package/lib/core/module.js +81 -0
  15. package/lib/core/registry.js +158 -0
  16. package/lib/formats/background.js +213 -0
  17. package/lib/formats/bold.js +49 -0
  18. package/lib/formats/capitalization.js +579 -0
  19. package/lib/formats/color.js +183 -0
  20. package/lib/formats/emoji.js +282 -0
  21. package/lib/formats/font-family.js +548 -0
  22. package/lib/formats/heading.js +502 -0
  23. package/lib/formats/image.js +341 -0
  24. package/lib/formats/import.js +385 -0
  25. package/lib/formats/indent.js +297 -0
  26. package/lib/formats/italic.js +27 -0
  27. package/lib/formats/line-height.js +562 -0
  28. package/lib/formats/link.js +251 -0
  29. package/lib/formats/list.js +635 -0
  30. package/lib/formats/strike.js +31 -0
  31. package/lib/formats/subscript.js +40 -0
  32. package/lib/formats/superscript.js +39 -0
  33. package/lib/formats/table.js +293 -0
  34. package/lib/formats/tag.js +304 -0
  35. package/lib/formats/text-align.js +422 -0
  36. package/lib/formats/text-size.js +498 -0
  37. package/lib/formats/underline.js +30 -0
  38. package/lib/formats/video.js +381 -0
  39. package/lib/modules/block-toolbar.js +639 -0
  40. package/lib/modules/code-view.js +447 -0
  41. package/lib/modules/find-replace.js +273 -0
  42. package/lib/modules/history.js +425 -0
  43. package/lib/modules/mention.js +200 -0
  44. package/lib/modules/resize-handles.js +701 -0
  45. package/lib/modules/slash-menu.js +183 -0
  46. package/lib/modules/table-toolbar.js +635 -0
  47. package/lib/modules/toolbar.js +607 -0
  48. package/lib/serialize.js +241 -0
  49. package/lib/static.js +28 -0
  50. package/lib/styles-loader.js +142 -0
  51. package/{dist → lib}/styles.css +1392 -35
  52. package/lib/styles.css.js +2 -0
  53. package/lib/styles.min.css +1 -0
  54. package/lib/ui/color-picker.js +296 -0
  55. package/lib/ui/customselect.js +351 -0
  56. package/lib/ui/emoji-picker.js +196 -0
  57. package/lib/ui/icons.js +145 -0
  58. package/lib/ui/image-popup.js +435 -0
  59. package/lib/ui/import-popup.js +288 -0
  60. package/lib/ui/link-popup.js +139 -0
  61. package/lib/ui/list-picker.js +307 -0
  62. package/lib/ui/select-button.js +68 -0
  63. package/lib/ui/table-popup.js +171 -0
  64. package/lib/ui/tag-popup.js +249 -0
  65. package/lib/ui/text-align-picker.js +278 -0
  66. package/lib/ui/video-popup.js +413 -0
  67. package/lib/utils/exec-command.js +72 -0
  68. package/lib/utils/history-helper.js +50 -0
  69. package/lib/utils/popup-helper.js +219 -0
  70. package/lib/utils/popup-positioning.js +234 -0
  71. package/lib/utils/sanitize.js +164 -0
  72. package/package.json +51 -32
  73. package/umd-entry.js +19 -0
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Registry system - Inspired by Quill's registration system
3
+ * Manages registration and retrieval of modules, formats, themes, and UI components
4
+ */
5
+ class Registry {
6
+ constructor() {
7
+ this.modules = new Map();
8
+ this.formats = new Map();
9
+ this.themes = new Map();
10
+ this.ui = new Map();
11
+ }
12
+
13
+ /**
14
+ * Register a module, format, theme, or UI component
15
+ * @param {string|object} path - Registration path or object with multiple registrations
16
+ * @param {*} def - Definition to register
17
+ * @param {boolean} suppressWarning - Suppress overwrite warnings
18
+ */
19
+ register(path, def, suppressWarning = false) {
20
+ if (typeof path === 'object') {
21
+ // Bulk registration
22
+ Object.entries(path).forEach(([key, value]) => {
23
+ this.register(key, value, suppressWarning);
24
+ });
25
+ return;
26
+ }
27
+
28
+ const [type, name] = path.split('/');
29
+
30
+ // Only warn when genuinely replacing one definition with a DIFFERENT one.
31
+ // Re-registering the same class (common when several editors share the
32
+ // singleton registry) is a no-op, not a mistake.
33
+ if (!suppressWarning) {
34
+ const existing = this.get(path);
35
+ if (existing && existing !== def) {
36
+ console.warn(`Overwriting ${path}`);
37
+ }
38
+ }
39
+
40
+ switch (type) {
41
+ case 'modules':
42
+ this.modules.set(name, def);
43
+ break;
44
+ case 'formats':
45
+ this.formats.set(name, def);
46
+ break;
47
+ case 'themes':
48
+ this.themes.set(name, def);
49
+ break;
50
+ case 'ui':
51
+ this.ui.set(name, def);
52
+ break;
53
+ default:
54
+ console.warn(`Unknown registry type: ${type}`);
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Get a registered item
60
+ * @param {string} path - Registration path
61
+ * @returns {*}
62
+ */
63
+ get(path) {
64
+ const [type, name] = path.split('/');
65
+
66
+ switch (type) {
67
+ case 'modules':
68
+ return this.modules.get(name);
69
+ case 'formats':
70
+ return this.formats.get(name);
71
+ case 'themes':
72
+ return this.themes.get(name);
73
+ case 'ui':
74
+ return this.ui.get(name);
75
+ default:
76
+ return null;
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Check if an item is registered
82
+ * @param {string} path - Registration path
83
+ * @returns {boolean}
84
+ */
85
+ has(path) {
86
+ return this.get(path) !== null && this.get(path) !== undefined;
87
+ }
88
+
89
+ /**
90
+ * Get all registered items of a type
91
+ * @param {string} type - Type to get (modules, formats, themes, ui)
92
+ * @returns {Map}
93
+ */
94
+ getAll(type) {
95
+ switch (type) {
96
+ case 'modules':
97
+ return new Map(this.modules);
98
+ case 'formats':
99
+ return new Map(this.formats);
100
+ case 'themes':
101
+ return new Map(this.themes);
102
+ case 'ui':
103
+ return new Map(this.ui);
104
+ default:
105
+ return new Map();
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Unregister an item
111
+ * @param {string} path - Registration path
112
+ */
113
+ unregister(path) {
114
+ const [type, name] = path.split('/');
115
+
116
+ switch (type) {
117
+ case 'modules':
118
+ this.modules.delete(name);
119
+ break;
120
+ case 'formats':
121
+ this.formats.delete(name);
122
+ break;
123
+ case 'themes':
124
+ this.themes.delete(name);
125
+ break;
126
+ case 'ui':
127
+ this.ui.delete(name);
128
+ break;
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Clear all registrations
134
+ */
135
+ clear() {
136
+ this.modules.clear();
137
+ this.formats.clear();
138
+ this.themes.clear();
139
+ this.ui.clear();
140
+ }
141
+
142
+ /**
143
+ * Get all registered items for debugging
144
+ */
145
+ getAllItems() {
146
+ const items = {};
147
+ items.modules = Array.from(this.modules.keys());
148
+ items.formats = Array.from(this.formats.keys());
149
+ items.themes = Array.from(this.themes.keys());
150
+ items.ui = Array.from(this.ui.keys());
151
+ return items;
152
+ }
153
+ }
154
+
155
+ // Create singleton instance
156
+ const registry = new Registry();
157
+
158
+ export default registry;
@@ -0,0 +1,213 @@
1
+ import { InlineFormat } from '../core/format.js';
2
+ import ColorPicker from '../ui/color-picker.js';
3
+ import { saveBeforeFormat } from '../utils/history-helper.js';
4
+ import Editor from '../core/editor.js';
5
+ import { execFormat, setStyleWithCSS } from '../utils/exec-command.js';
6
+
7
+ /**
8
+ * Background Color Format - Handles text background color formatting
9
+ * Now supports multiple editor instances with separate popup instances
10
+ */
11
+ class Background extends InlineFormat {
12
+ static formatName = 'background';
13
+ static tagName = 'SPAN';
14
+ static attribute = 'background-color';
15
+
16
+ // Selection saved when the picker opens, restored before applying (see Color).
17
+ static savedRanges = new Map();
18
+
19
+ constructor() {
20
+ super();
21
+
22
+ // Get current editor instance
23
+ const currentEditor = Editor.getCurrentInstance();
24
+ if (!currentEditor) {
25
+ console.warn('No editor instance found for Background format');
26
+ return;
27
+ }
28
+
29
+ this.editorId = currentEditor.instanceId;
30
+
31
+ // Check if this editor already has a background color picker instance
32
+ let colorPicker = currentEditor.getPopupInstance('background');
33
+
34
+ if (!colorPicker) {
35
+ // Create new color picker instance for this editor
36
+ colorPicker = new ColorPicker({
37
+ onColorSelect: (color) => {
38
+ Background.applyBackgroundToCurrentSelection(color, this.editorId);
39
+ },
40
+ editor: currentEditor,
41
+ editorId: this.editorId
42
+ });
43
+
44
+ // Store popup instance in editor
45
+ currentEditor.setPopupInstance('background', colorPicker);
46
+ }
47
+
48
+ this.colorPicker = colorPicker;
49
+ }
50
+
51
+ /**
52
+ * Create a new Background format instance for a specific editor
53
+ * @param {string} editorId - Editor instance ID
54
+ * @returns {Background} Background format instance
55
+ */
56
+ static createForEditor(editorId) {
57
+ const editor = Editor.getInstanceById(editorId);
58
+ if (!editor) {
59
+ console.warn('No editor instance found for ID:', editorId);
60
+ return null;
61
+ }
62
+
63
+ // Temporarily set as current instance
64
+ const originalCurrent = Editor.currentInstance;
65
+ Editor.currentInstance = editor;
66
+
67
+ // Create format instance
68
+ const format = new Background();
69
+
70
+ // Restore original current instance
71
+ Editor.currentInstance = originalCurrent;
72
+
73
+ return format;
74
+ }
75
+
76
+ /**
77
+ * Static method to apply background color to current selection
78
+ * @param {string} color - Background color value
79
+ * @param {string} editorId - Editor instance ID
80
+ */
81
+ static applyBackgroundToCurrentSelection(color, editorId = null) {
82
+ // Get the correct editor instance
83
+ let editor = null;
84
+ if (editorId) {
85
+ editor = Editor.getInstanceById(editorId);
86
+ } else {
87
+ editor = Editor.getCurrentInstance();
88
+ }
89
+
90
+ if (!editor) {
91
+ console.warn('No editor instance found for background color application');
92
+ return;
93
+ }
94
+
95
+ const selection = window.getSelection();
96
+ // Restore the selection captured when the picker opened (a tap on the
97
+ // picker may have collapsed the live selection, especially on mobile).
98
+ const saved = editorId != null ? Background.savedRanges.get(editorId) : null;
99
+ if (saved) {
100
+ selection.removeAllRanges();
101
+ selection.addRange(saved);
102
+ }
103
+ if (editorId != null) Background.savedRanges.delete(editorId);
104
+
105
+ if (!selection || !selection.rangeCount || selection.isCollapsed) return;
106
+
107
+ // Save state before applying format
108
+ saveBeforeFormat();
109
+
110
+ setStyleWithCSS(true);
111
+ execFormat('backColor', color === 'transparent' ? 'inherit' : color);
112
+
113
+ // Refresh toolbar state (active highlight + swatch) and notify listeners.
114
+ setTimeout(() => {
115
+ if (editor) {
116
+ if (typeof editor.updateToolbarButtonStates === 'function') {
117
+ editor.updateToolbarButtonStates();
118
+ }
119
+ if (typeof editor.onContentChange === 'function') {
120
+ editor.onContentChange();
121
+ }
122
+ }
123
+ }, 0);
124
+ }
125
+
126
+ /**
127
+ * Toggle background color formatting - shows/hides color picker
128
+ */
129
+ toggle() {
130
+ if (this.colorPicker.isVisible) {
131
+ this.colorPicker.hide();
132
+ } else {
133
+ this.showColorPicker();
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Show color picker positioned relative to background button on toolbar
139
+ */
140
+ showColorPicker() {
141
+ // Find background button in the current editor's toolbar
142
+ const editor = Editor.getInstanceById(this.editorId);
143
+ if (!editor) return;
144
+
145
+ // Capture the selection so the colour applies even if a tap clears it.
146
+ // Fall back to the editor's last non-collapsed range (mobile touch clears it).
147
+ const sel = window.getSelection();
148
+ if (sel && sel.rangeCount && !sel.isCollapsed) {
149
+ Background.savedRanges.set(this.editorId, sel.getRangeAt(0).cloneRange());
150
+ } else if (editor._lastRange) {
151
+ Background.savedRanges.set(this.editorId, editor._lastRange.cloneRange());
152
+ }
153
+
154
+ const toolbar = editor.getModule('toolbar');
155
+ let backgroundButton = null;
156
+
157
+ if (toolbar) {
158
+ backgroundButton = toolbar.getButton('background');
159
+ }
160
+
161
+ // Fallback: find button by class in the current editor's toolbar
162
+ if (!backgroundButton) {
163
+ const toolbarContainer = toolbar?.getContainer();
164
+ if (toolbarContainer) {
165
+ backgroundButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.background-btn');
166
+ }
167
+ }
168
+
169
+ // Final fallback: find any background button in the current editor's wrapper
170
+ if (!backgroundButton) {
171
+ backgroundButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.background-btn');
172
+ }
173
+
174
+ if (!backgroundButton) {
175
+ console.warn('Background button not found for editor:', this.editorId);
176
+ return;
177
+ }
178
+
179
+ this.colorPicker.show(backgroundButton);
180
+ }
181
+
182
+ /**
183
+ * Check if background color formatting is active in current selection
184
+ */
185
+ isActive() {
186
+ return !!Background.getCurrentColor();
187
+ }
188
+
189
+ /**
190
+ * Return the explicit background colour applied at the current selection, or
191
+ * null when none is set. Looks for an explicit inline background (the span
192
+ * the editor inserts) instead of comparing the computed colour to hardcoded
193
+ * white/transparent — which misfired against themed backgrounds.
194
+ */
195
+ static getCurrentColor() {
196
+ const selection = window.getSelection();
197
+ if (!selection || !selection.rangeCount) return null;
198
+
199
+ let node = selection.getRangeAt(0).startContainer;
200
+ if (node.nodeType === Node.TEXT_NODE) node = node.parentNode;
201
+
202
+ while (node && node.nodeType === Node.ELEMENT_NODE) {
203
+ if (node.classList && node.classList.contains('rich-editor-area')) break;
204
+ if (node.style && node.style.backgroundColor && node.style.backgroundColor !== 'inherit') {
205
+ return node.style.backgroundColor;
206
+ }
207
+ node = node.parentNode;
208
+ }
209
+ return null;
210
+ }
211
+ }
212
+
213
+ export default Background;
@@ -0,0 +1,49 @@
1
+ import { InlineFormat } from '../core/format.js';
2
+ import { saveBeforeFormat } from '../utils/history-helper.js';
3
+ import { execFormat, queryFormatState } from '../utils/exec-command.js';
4
+
5
+ /**
6
+ * Bold Format - Handles bold text formatting
7
+ */
8
+ class Bold extends InlineFormat {
9
+ static formatName = 'bold';
10
+ static tagName = 'B';
11
+ static alternativeTagNames = ['STRONG'];
12
+
13
+ /**
14
+ * Apply bold formatting
15
+ */
16
+ apply() {
17
+ // Save state before applying format
18
+ saveBeforeFormat();
19
+ execFormat('bold');
20
+ }
21
+
22
+ /**
23
+ * Remove bold formatting
24
+ */
25
+ remove() {
26
+ execFormat('bold');
27
+ }
28
+
29
+ /**
30
+ * Toggle bold formatting
31
+ */
32
+ toggle() {
33
+ // Save state before applying format
34
+ saveBeforeFormat();
35
+ execFormat('bold');
36
+ }
37
+
38
+ /**
39
+ * Check if bold formatting is active
40
+ */
41
+ isActive() {
42
+ return queryFormatState('bold');
43
+ }
44
+
45
+
46
+ }
47
+
48
+
49
+ export default Bold;