@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.
- package/LICENSE +15 -0
- package/README.md +223 -142
- package/core.js +82 -0
- package/dist/core.esm.js +2 -0
- package/dist/core.esm.js.map +1 -0
- package/dist/rich-editor.esm.js +1 -1
- package/dist/rich-editor.esm.js.map +1 -1
- package/dist/rich-editor.min.js +1 -1
- package/dist/rich-editor.min.js.map +1 -1
- package/index.d.ts +230 -103
- package/index.js +297 -0
- package/lib/core/editor.js +1885 -0
- package/lib/core/format.js +540 -0
- package/lib/core/module.js +81 -0
- package/lib/core/registry.js +158 -0
- package/lib/formats/background.js +213 -0
- package/lib/formats/bold.js +49 -0
- package/lib/formats/capitalization.js +579 -0
- package/lib/formats/color.js +183 -0
- package/lib/formats/emoji.js +282 -0
- package/lib/formats/font-family.js +548 -0
- package/lib/formats/heading.js +502 -0
- package/lib/formats/image.js +341 -0
- package/lib/formats/import.js +385 -0
- package/lib/formats/indent.js +297 -0
- package/lib/formats/italic.js +27 -0
- package/lib/formats/line-height.js +562 -0
- package/lib/formats/link.js +251 -0
- package/lib/formats/list.js +635 -0
- package/lib/formats/strike.js +31 -0
- package/lib/formats/subscript.js +40 -0
- package/lib/formats/superscript.js +39 -0
- package/lib/formats/table.js +293 -0
- package/lib/formats/tag.js +304 -0
- package/lib/formats/text-align.js +422 -0
- package/lib/formats/text-size.js +498 -0
- package/lib/formats/underline.js +30 -0
- package/lib/formats/video.js +381 -0
- package/lib/modules/block-toolbar.js +639 -0
- package/lib/modules/code-view.js +447 -0
- package/lib/modules/find-replace.js +273 -0
- package/lib/modules/history.js +425 -0
- package/lib/modules/mention.js +200 -0
- package/lib/modules/resize-handles.js +701 -0
- package/lib/modules/slash-menu.js +183 -0
- package/lib/modules/table-toolbar.js +635 -0
- package/lib/modules/toolbar.js +607 -0
- package/lib/serialize.js +241 -0
- package/lib/static.js +28 -0
- package/lib/styles-loader.js +142 -0
- package/{dist → lib}/styles.css +1392 -35
- package/lib/styles.css.js +2 -0
- package/lib/styles.min.css +1 -0
- package/lib/ui/color-picker.js +296 -0
- package/lib/ui/customselect.js +351 -0
- package/lib/ui/emoji-picker.js +196 -0
- package/lib/ui/icons.js +145 -0
- package/lib/ui/image-popup.js +435 -0
- package/lib/ui/import-popup.js +288 -0
- package/lib/ui/link-popup.js +139 -0
- package/lib/ui/list-picker.js +307 -0
- package/lib/ui/select-button.js +68 -0
- package/lib/ui/table-popup.js +171 -0
- package/lib/ui/tag-popup.js +249 -0
- package/lib/ui/text-align-picker.js +278 -0
- package/lib/ui/video-popup.js +413 -0
- package/lib/utils/exec-command.js +72 -0
- package/lib/utils/history-helper.js +50 -0
- package/lib/utils/popup-helper.js +219 -0
- package/lib/utils/popup-positioning.js +234 -0
- package/lib/utils/sanitize.js +164 -0
- package/package.json +51 -32
- 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;
|