@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/color.js
DELETED
|
@@ -1,165 +0,0 @@
|
|
|
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
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Color Format - Handles text color formatting
|
|
8
|
-
*/
|
|
9
|
-
class Color extends InlineFormat {
|
|
10
|
-
static formatName = 'color';
|
|
11
|
-
static tagName = 'SPAN';
|
|
12
|
-
static attribute = 'color';
|
|
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 Color format');
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
this.editorId = currentEditor.instanceId;
|
|
25
|
-
|
|
26
|
-
// Check if this editor already has a color picker instance
|
|
27
|
-
let colorPicker = currentEditor.getPopupInstance('color');
|
|
28
|
-
|
|
29
|
-
if (!colorPicker) {
|
|
30
|
-
// Create new color picker instance for this editor
|
|
31
|
-
colorPicker = new ColorPicker({
|
|
32
|
-
onColorSelect: (color) => {
|
|
33
|
-
Color.applyColorToCurrentSelection(color);
|
|
34
|
-
},
|
|
35
|
-
editor: Editor.getCurrentInstance()
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
// Store popup instance in editor
|
|
39
|
-
currentEditor.setPopupInstance('color', colorPicker);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
this.colorPicker = colorPicker;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Static method to apply color to current selection
|
|
47
|
-
*/
|
|
48
|
-
static applyColorToCurrentSelection(color) {
|
|
49
|
-
const selection = window.getSelection();
|
|
50
|
-
if (!selection || !selection.rangeCount || selection.isCollapsed) return;
|
|
51
|
-
|
|
52
|
-
// Save state before applying format
|
|
53
|
-
saveBeforeFormat();
|
|
54
|
-
const colorbutton = document.querySelector('.rich-editor-toolbar-btn.color-btn');
|
|
55
|
-
|
|
56
|
-
try {
|
|
57
|
-
|
|
58
|
-
document.execCommand('styleWithCSS', false, true);
|
|
59
|
-
if(color === 'transparent') {
|
|
60
|
-
document.execCommand('foreColor', false, '#2c3e50');
|
|
61
|
-
colorbutton.classList.remove('active');
|
|
62
|
-
|
|
63
|
-
} else {
|
|
64
|
-
document.execCommand('foreColor', false, color);
|
|
65
|
-
colorbutton.classList.add('active');
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
} catch (error) {
|
|
69
|
-
console.error('Error applying color format:', error);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Trigger content change after applying format
|
|
73
|
-
setTimeout(() => {
|
|
74
|
-
const currentEditor = Editor.getCurrentInstance();
|
|
75
|
-
if (currentEditor && typeof currentEditor.onContentChange === 'function') {
|
|
76
|
-
currentEditor.onContentChange();
|
|
77
|
-
}
|
|
78
|
-
}, 0);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Toggle color formatting - shows/hides color picker
|
|
83
|
-
*/
|
|
84
|
-
toggle() {
|
|
85
|
-
if (this.colorPicker.isVisible) {
|
|
86
|
-
this.colorPicker.hide();
|
|
87
|
-
} else {
|
|
88
|
-
this.showColorPicker();
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Show color picker positioned relative to color button on toolbar
|
|
94
|
-
*/
|
|
95
|
-
showColorPicker() {
|
|
96
|
-
// Find color button in the current editor's toolbar
|
|
97
|
-
const editor = Editor.getInstanceById(this.editorId);
|
|
98
|
-
if (!editor) return;
|
|
99
|
-
|
|
100
|
-
const toolbar = editor.getModule('toolbar');
|
|
101
|
-
let colorButton = null;
|
|
102
|
-
|
|
103
|
-
if (toolbar) {
|
|
104
|
-
colorButton = toolbar.getButton('color');
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Fallback: find button by class in the current editor's toolbar
|
|
108
|
-
if (!colorButton) {
|
|
109
|
-
const toolbarContainer = toolbar?.getContainer();
|
|
110
|
-
if (toolbarContainer) {
|
|
111
|
-
colorButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.color-btn');
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Final fallback: find any color button in the current editor's wrapper
|
|
116
|
-
if (!colorButton) {
|
|
117
|
-
colorButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.color-btn');
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (!colorButton) {
|
|
121
|
-
console.warn('Color button not found for editor:', this.editorId);
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
this.colorPicker.show(colorButton);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Check if color formatting is active in current selection
|
|
130
|
-
*/
|
|
131
|
-
isActive() {
|
|
132
|
-
const selection = window.getSelection();
|
|
133
|
-
if (!selection || !selection.rangeCount) return false;
|
|
134
|
-
|
|
135
|
-
const range = selection.getRangeAt(0);
|
|
136
|
-
let node = range.startContainer;
|
|
137
|
-
|
|
138
|
-
// Nếu là text node thì lấy parent element
|
|
139
|
-
if (node.nodeType === Node.TEXT_NODE) {
|
|
140
|
-
node = node.parentNode;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
while (node && node !== document.body) {
|
|
144
|
-
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
145
|
-
const color = window.getComputedStyle(node).color;
|
|
146
|
-
|
|
147
|
-
// Nếu gặp đúng màu mặc định -> dừng lại, coi như chưa đổi màu
|
|
148
|
-
if (color === 'rgb(44, 62, 80)') {
|
|
149
|
-
return false;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Nếu gặp màu khác mặc định (và không phải transparent)
|
|
153
|
-
if (color && color !== 'rgba(0, 0, 0, 0)') {
|
|
154
|
-
return true;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
node = node.parentNode;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
return false;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
export default Color;
|
package/lib/formats/emoji.js
DELETED
|
@@ -1,282 +0,0 @@
|
|
|
1
|
-
import { InlineFormat } from '../core/format.js';
|
|
2
|
-
import EmojiPicker from '../ui/emoji-picker.js';
|
|
3
|
-
import Editor from '../core/editor.js';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Emoji Format - Handles emoji insertion
|
|
7
|
-
* Now supports multiple editor instances with separate popup instances
|
|
8
|
-
*/
|
|
9
|
-
class Emoji extends InlineFormat {
|
|
10
|
-
static formatName = 'emoji';
|
|
11
|
-
static tagName = 'SPAN';
|
|
12
|
-
static className = 'emoji';
|
|
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 Emoji format');
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
this.editorId = currentEditor.instanceId;
|
|
25
|
-
|
|
26
|
-
// Check if this editor already has an emoji picker instance
|
|
27
|
-
let emojiPicker = currentEditor.getPopupInstance('emoji');
|
|
28
|
-
|
|
29
|
-
if (!emojiPicker) {
|
|
30
|
-
// Create new emoji picker instance for this editor
|
|
31
|
-
emojiPicker = new EmojiPicker({
|
|
32
|
-
onEmojiSelect: (emoji) => {
|
|
33
|
-
Emoji.insertEmojiAtCurrentPosition(emoji, this.editorId);
|
|
34
|
-
},
|
|
35
|
-
editor: currentEditor,
|
|
36
|
-
editorId: this.editorId
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
// Store popup instance in editor
|
|
40
|
-
currentEditor.setPopupInstance('emoji', emojiPicker);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
this.emojiPicker = emojiPicker;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Create a new Emoji format instance for a specific editor
|
|
48
|
-
* @param {string} editorId - Editor instance ID
|
|
49
|
-
* @returns {Emoji} Emoji 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 Emoji();
|
|
64
|
-
|
|
65
|
-
// Restore original current instance
|
|
66
|
-
Editor.currentInstance = originalCurrent;
|
|
67
|
-
|
|
68
|
-
return format;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Create emoji element
|
|
73
|
-
* @param {string} value - Emoji character
|
|
74
|
-
* @returns {HTMLElement}
|
|
75
|
-
*/
|
|
76
|
-
static create(value) {
|
|
77
|
-
const span = document.createElement('SPAN');
|
|
78
|
-
span.className = 'emoji';
|
|
79
|
-
span.textContent = value;
|
|
80
|
-
span.setAttribute('data-emoji', value);
|
|
81
|
-
return span;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Insert emoji at current cursor position
|
|
86
|
-
* @param {string} emoji - Emoji character to insert
|
|
87
|
-
* @param {string} editorId - Editor instance ID
|
|
88
|
-
*/
|
|
89
|
-
static insertEmojiAtCurrentPosition(emoji, editorId = null) {
|
|
90
|
-
// Get the correct editor instance
|
|
91
|
-
let editor = null;
|
|
92
|
-
if (editorId) {
|
|
93
|
-
editor = Editor.getInstanceById(editorId);
|
|
94
|
-
} else {
|
|
95
|
-
editor = Editor.getCurrentInstance();
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (!editor) {
|
|
99
|
-
console.warn('No editor instance found for emoji insertion');
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const selection = window.getSelection();
|
|
104
|
-
if (!selection || !selection.rangeCount) return;
|
|
105
|
-
|
|
106
|
-
try {
|
|
107
|
-
const range = selection.getRangeAt(0);
|
|
108
|
-
|
|
109
|
-
// Check if cursor is inside an existing emoji span
|
|
110
|
-
let currentNode = range.startContainer;
|
|
111
|
-
let emojiParent = null;
|
|
112
|
-
|
|
113
|
-
// If cursor is in a text node, check its parent
|
|
114
|
-
if (currentNode.nodeType === Node.TEXT_NODE) {
|
|
115
|
-
currentNode = currentNode.parentNode;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Find if we're inside an emoji span
|
|
119
|
-
while (currentNode && currentNode !== editor.element) {
|
|
120
|
-
if (currentNode.classList && currentNode.classList.contains('emoji')) {
|
|
121
|
-
emojiParent = currentNode;
|
|
122
|
-
break;
|
|
123
|
-
}
|
|
124
|
-
currentNode = currentNode.parentNode;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// If cursor is inside an emoji span, move it outside
|
|
128
|
-
if (emojiParent) {
|
|
129
|
-
// Move cursor after the emoji span
|
|
130
|
-
range.setStartAfter(emojiParent);
|
|
131
|
-
range.collapse(true);
|
|
132
|
-
selection.removeAllRanges();
|
|
133
|
-
selection.addRange(range);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Create emoji element
|
|
137
|
-
const emojiElement = Emoji.create(emoji);
|
|
138
|
-
|
|
139
|
-
// Insert emoji at cursor position
|
|
140
|
-
range.deleteContents();
|
|
141
|
-
range.insertNode(emojiElement);
|
|
142
|
-
|
|
143
|
-
// Create a zero-width space character after the emoji
|
|
144
|
-
const zeroWidthSpace = document.createTextNode('\u200B'); // Zero-width space
|
|
145
|
-
|
|
146
|
-
// Insert zero-width space after emoji
|
|
147
|
-
range.setStartAfter(emojiElement);
|
|
148
|
-
range.insertNode(zeroWidthSpace);
|
|
149
|
-
|
|
150
|
-
// Position cursor after the zero-width space
|
|
151
|
-
range.setStartAfter(zeroWidthSpace);
|
|
152
|
-
range.collapse(true);
|
|
153
|
-
selection.removeAllRanges();
|
|
154
|
-
selection.addRange(range);
|
|
155
|
-
|
|
156
|
-
// Trigger content change event
|
|
157
|
-
if (editor && typeof editor.onContentChange === 'function') {
|
|
158
|
-
editor.onContentChange();
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
} catch (error) {
|
|
162
|
-
console.error('Error inserting emoji:', error);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Apply emoji formatting - shows emoji picker
|
|
168
|
-
*/
|
|
169
|
-
apply(value) {
|
|
170
|
-
if (value) {
|
|
171
|
-
Emoji.insertEmojiAtCurrentPosition(value, this.editorId);
|
|
172
|
-
} else {
|
|
173
|
-
this.showEmojiPicker();
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Remove emoji formatting
|
|
179
|
-
*/
|
|
180
|
-
remove() {
|
|
181
|
-
const selection = window.getSelection();
|
|
182
|
-
if (!selection || !selection.rangeCount) return;
|
|
183
|
-
|
|
184
|
-
const range = selection.getRangeAt(0);
|
|
185
|
-
const emojiElement = this.getEmojiElement(range);
|
|
186
|
-
|
|
187
|
-
if (emojiElement) {
|
|
188
|
-
// Replace emoji element with its text content
|
|
189
|
-
const textNode = document.createTextNode(emojiElement.textContent);
|
|
190
|
-
emojiElement.parentNode.replaceChild(textNode, emojiElement);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Toggle emoji formatting - shows emoji picker
|
|
196
|
-
*/
|
|
197
|
-
toggle() {
|
|
198
|
-
if (this.emojiPicker.isVisible) {
|
|
199
|
-
this.emojiPicker.hide();
|
|
200
|
-
} else {
|
|
201
|
-
this.showEmojiPicker();
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Show emoji picker popup
|
|
207
|
-
*/
|
|
208
|
-
showEmojiPicker() {
|
|
209
|
-
// Find emoji button in the current editor's toolbar
|
|
210
|
-
const editor = Editor.getInstanceById(this.editorId);
|
|
211
|
-
if (!editor) return;
|
|
212
|
-
|
|
213
|
-
const toolbar = editor.getModule('toolbar');
|
|
214
|
-
let emojiButton = null;
|
|
215
|
-
|
|
216
|
-
if (toolbar) {
|
|
217
|
-
emojiButton = toolbar.getButton('emoji');
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Fallback: find button by class in the current editor's toolbar
|
|
221
|
-
if (!emojiButton) {
|
|
222
|
-
const toolbarContainer = toolbar?.getContainer();
|
|
223
|
-
if (toolbarContainer) {
|
|
224
|
-
emojiButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.emoji-btn');
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// Final fallback: find any emoji button in the current editor's wrapper
|
|
229
|
-
if (!emojiButton) {
|
|
230
|
-
emojiButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.emoji-btn');
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
if (!emojiButton) {
|
|
234
|
-
console.warn('Emoji button not found for editor:', this.editorId);
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
this.emojiPicker.show(emojiButton);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Check if emoji formatting is active
|
|
243
|
-
*/
|
|
244
|
-
isActive() {
|
|
245
|
-
const selection = window.getSelection();
|
|
246
|
-
if (!selection || !selection.rangeCount) return false;
|
|
247
|
-
|
|
248
|
-
const range = selection.getRangeAt(0);
|
|
249
|
-
const emojiElement = this.getEmojiElement(range);
|
|
250
|
-
|
|
251
|
-
return emojiElement !== null;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* Get emoji element from selection
|
|
256
|
-
* @param {Range} range - Selection range
|
|
257
|
-
* @returns {HTMLElement|null}
|
|
258
|
-
*/
|
|
259
|
-
getEmojiElement(range) {
|
|
260
|
-
let node = range.commonAncestorContainer;
|
|
261
|
-
|
|
262
|
-
// If it's a text node, get its parent
|
|
263
|
-
if (node.nodeType === Node.TEXT_NODE) {
|
|
264
|
-
node = node.parentNode;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// Check if current node is an emoji
|
|
268
|
-
if (node.classList && node.classList.contains('emoji')) {
|
|
269
|
-
return node;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// Check if selection contains an emoji
|
|
273
|
-
const emojiInSelection = range.cloneContents().querySelector('.emoji');
|
|
274
|
-
if (emojiInSelection) {
|
|
275
|
-
return emojiInSelection;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
return null;
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
export default Emoji;
|