@oix1987/yjd 1.0.3 → 2.0.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 (70) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +146 -142
  3. package/core.js +77 -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 +134 -103
  11. package/index.js +227 -0
  12. package/lib/core/editor.js +1806 -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 +347 -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/resize-handles.js +701 -0
  44. package/lib/modules/slash-menu.js +183 -0
  45. package/lib/modules/table-toolbar.js +635 -0
  46. package/lib/modules/toolbar.js +607 -0
  47. package/lib/styles-loader.js +142 -0
  48. package/{dist → lib}/styles.css +1285 -35
  49. package/lib/styles.css.js +2 -0
  50. package/lib/styles.min.css +1 -0
  51. package/lib/ui/color-picker.js +296 -0
  52. package/lib/ui/customselect.js +351 -0
  53. package/lib/ui/emoji-picker.js +196 -0
  54. package/lib/ui/icons.js +145 -0
  55. package/lib/ui/image-popup.js +435 -0
  56. package/lib/ui/import-popup.js +288 -0
  57. package/lib/ui/link-popup.js +139 -0
  58. package/lib/ui/list-picker.js +307 -0
  59. package/lib/ui/select-button.js +68 -0
  60. package/lib/ui/table-popup.js +171 -0
  61. package/lib/ui/tag-popup.js +249 -0
  62. package/lib/ui/text-align-picker.js +278 -0
  63. package/lib/ui/video-popup.js +413 -0
  64. package/lib/utils/exec-command.js +72 -0
  65. package/lib/utils/history-helper.js +50 -0
  66. package/lib/utils/popup-helper.js +219 -0
  67. package/lib/utils/popup-positioning.js +234 -0
  68. package/lib/utils/sanitize.js +164 -0
  69. package/package.json +51 -32
  70. package/umd-entry.js +18 -0
@@ -0,0 +1,307 @@
1
+ import IconUtils from './icons.js';
2
+ import { appendPopup, calculatePopupPosition, setPopupPosition } from '../utils/popup-helper.js';
3
+
4
+ /**
5
+ * List Picker Component - Popup for selecting list types
6
+ */
7
+ class ListPicker {
8
+ constructor(options = {}) {
9
+ this.options = {
10
+ listTypes: [
11
+ { value: 'bullet', label: 'Bullet List', icon: 'list-bullet' },
12
+ { value: 'ordered', label: 'Numbered List', icon: 'list-ordered' },
13
+ { value: 'roman', label: 'Roman Numerals List', icon: 'list-roman' },
14
+ { value: 'alpha', label: 'Alphabetical List', icon: 'list-alpha' }
15
+ ],
16
+ onListSelect: null,
17
+ ...options
18
+ };
19
+
20
+ this.popup = null;
21
+ this.isVisible = false;
22
+ this.currentListType = null;
23
+ this.clickOutsideHandler = null;
24
+
25
+ this.createListPicker();
26
+ }
27
+
28
+ /**
29
+ * Create list picker popup
30
+ */
31
+ createListPicker() {
32
+ // Create popup
33
+ this.popup = document.createElement('div');
34
+ this.popup.className = 'list-picker-popup';
35
+
36
+ // Create list type buttons
37
+ this.createListTypeButtons();
38
+
39
+ // Add popup to container
40
+ appendPopup(this.popup);
41
+ }
42
+
43
+ /**
44
+ * Create list type buttons
45
+ */
46
+ async createListTypeButtons() {
47
+ const buttonContainer = document.createElement('div');
48
+ buttonContainer.className = 'list-button-container';
49
+
50
+ // Icons are now inline, no need to preload
51
+
52
+ // Create buttons
53
+ for (const listType of this.options.listTypes) {
54
+ const listButton = document.createElement('button');
55
+ listButton.type = 'button';
56
+ listButton.className = 'list-button';
57
+ listButton.dataset.listType = listType.value;
58
+ listButton.title = listType.label;
59
+
60
+ // Add icon
61
+ const iconSvg = IconUtils.getIcon(listType.icon);
62
+ if (iconSvg) {
63
+ listButton.innerHTML = iconSvg;
64
+ } else {
65
+ listButton.textContent = listType.label.charAt(0);
66
+ }
67
+
68
+ listButton.addEventListener('click', (e) => {
69
+ e.preventDefault();
70
+ e.stopPropagation();
71
+ this.selectListType(listType.value);
72
+ });
73
+
74
+ buttonContainer.appendChild(listButton);
75
+ }
76
+
77
+ this.popup.appendChild(buttonContainer);
78
+ }
79
+
80
+ /**
81
+ * Setup click outside handler
82
+ */
83
+ setupClickOutside() {
84
+ if (this.clickOutsideHandler) {
85
+ document.removeEventListener('click', this.clickOutsideHandler);
86
+ }
87
+
88
+ this.clickOutsideHandler = (e) => {
89
+ if (!this.popup.contains(e.target)) {
90
+ this.hide();
91
+ }
92
+ };
93
+
94
+ // Add slight delay to avoid immediate close
95
+ setTimeout(() => {
96
+ document.addEventListener('click', this.clickOutsideHandler);
97
+ }, 100);
98
+ }
99
+
100
+ /**
101
+ * Remove click outside handler
102
+ */
103
+ removeClickOutside() {
104
+ if (this.clickOutsideHandler) {
105
+ document.removeEventListener('click', this.clickOutsideHandler);
106
+ this.clickOutsideHandler = null;
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Show list picker popup
112
+ * @param {HTMLElement} anchor - Element to position popup relative to
113
+ */
114
+ show(anchor) {
115
+ if (!anchor) return;
116
+
117
+ // Ensure popup is in DOM
118
+ if (!document.body.contains(this.popup)) {
119
+ appendPopup(this.popup);
120
+ }
121
+
122
+ // Update current list type state
123
+ this.updateCurrentListType();
124
+
125
+ // Calculate and set popup position
126
+ const position = calculatePopupPosition(anchor, this.popup, {
127
+ offsetY: 5,
128
+ offsetX: 0
129
+ });
130
+ setPopupPosition(this.popup, position);
131
+
132
+ // Show popup by adding visible class
133
+ this.popup.classList.add('visible');
134
+ this.isVisible = true;
135
+
136
+ // Setup click outside handler
137
+ this.setupClickOutside();
138
+ }
139
+
140
+ /**
141
+ * Hide list picker popup
142
+ */
143
+ hide() {
144
+ this.popup.classList.remove('visible');
145
+ this.isVisible = false;
146
+ this.removeClickOutside();
147
+ }
148
+
149
+ /**
150
+ * Select list type and trigger callback
151
+ * @param {string} listType - Selected list type
152
+ */
153
+ selectListType(listType) {
154
+ this.currentListType = listType;
155
+
156
+ if (this.options.onListSelect) {
157
+ this.options.onListSelect(listType);
158
+ }
159
+
160
+ this.hide();
161
+ }
162
+
163
+ /**
164
+ * Update current list type state based on selection
165
+ */
166
+ updateCurrentListType() {
167
+ try {
168
+ const listType = this.getCurrentListType();
169
+ this.currentListType = listType;
170
+
171
+ // Update button states
172
+ this.updateButtonStates(listType);
173
+ } catch (error) {
174
+ console.warn('Error updating current list type:', error);
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Update button states based on current list type
180
+ * @param {string|null} currentListType - Current active list type
181
+ */
182
+ updateButtonStates(currentListType) {
183
+ const buttons = this.popup.querySelectorAll('.list-button');
184
+ buttons.forEach(button => {
185
+ button.classList.remove('active');
186
+ if (currentListType && button.dataset.listType === currentListType) {
187
+ button.classList.add('active');
188
+ }
189
+ });
190
+ }
191
+
192
+ /**
193
+ * Update toolbar button icon based on selection
194
+ * @param {string} listType - Current list type
195
+ */
196
+ updateToolbarButtonIcon(listType) {
197
+ const button = document.querySelector('.rich-editor-toolbar-btn.list-btn');
198
+ if (!button) return;
199
+
200
+ const iconMap = {
201
+ 'bullet': 'list-bullet',
202
+ 'ordered': 'list-ordered',
203
+ 'roman': 'list-roman',
204
+ 'alpha': 'list-alpha'
205
+ };
206
+
207
+ const titleMap = {
208
+ 'bullet': 'Bullet List',
209
+ 'ordered': 'Numbered List',
210
+ 'roman': 'Roman Numerals List',
211
+ 'alpha': 'Alphabetical List'
212
+ };
213
+
214
+ const iconName = iconMap[listType] || 'list-bullet';
215
+
216
+ // Update button title
217
+ button.title = titleMap[listType] || 'List';
218
+
219
+ // Update icon
220
+ const svgContent = IconUtils.getIcon(iconName);
221
+ if (svgContent) {
222
+ const iconSpan = button.querySelector('.icon');
223
+ if (iconSpan) {
224
+ iconSpan.innerHTML = svgContent;
225
+ } else {
226
+ button.innerHTML = `<span class="icon">${svgContent}</span>`;
227
+ }
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Get parent list of an element
233
+ */
234
+ getParentList(element) {
235
+ let current = element;
236
+ while (current && current !== document.body) {
237
+ if (current.tagName === 'UL' || current.tagName === 'OL') {
238
+ return current;
239
+ }
240
+ current = current.parentElement;
241
+ }
242
+ return null;
243
+ }
244
+
245
+ /**
246
+ * Get list type from list element
247
+ */
248
+ getListType(listElement) {
249
+ if (listElement.tagName === 'OL') {
250
+ const type = listElement.style.listStyleType;
251
+ if (type === 'upper-roman') return 'roman';
252
+ if (type === 'lower-alpha') return 'alpha';
253
+ return 'ordered';
254
+ }
255
+ return 'bullet';
256
+ }
257
+
258
+ /**
259
+ * Get block element containing the given node
260
+ */
261
+ getBlockElement(node) {
262
+ if (!node) return null;
263
+
264
+ let currentNode = node;
265
+ while (currentNode && currentNode !== document.body) {
266
+ if (currentNode.nodeType === Node.ELEMENT_NODE) {
267
+ const tagName = currentNode.tagName;
268
+ if (['P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'LI', 'UL', 'OL'].includes(tagName)) {
269
+ return currentNode;
270
+ }
271
+ }
272
+ currentNode = currentNode.parentNode;
273
+ }
274
+ return null;
275
+ }
276
+
277
+ /**
278
+ * Get current list type
279
+ * @returns {string|null}
280
+ */
281
+ getCurrentListType() {
282
+ const selection = window.getSelection();
283
+ if (!selection || !selection.rangeCount) return null;
284
+
285
+ const range = selection.getRangeAt(0);
286
+ const listElement = this.getParentList(range.commonAncestorContainer);
287
+
288
+ return listElement ? this.getListType(listElement) : null;
289
+ }
290
+
291
+ /**
292
+ * Destroy the list picker
293
+ */
294
+ destroy() {
295
+ this.removeClickOutside();
296
+
297
+ if (this.popup && this.popup.parentNode) {
298
+ this.popup.parentNode.removeChild(this.popup);
299
+ }
300
+
301
+ this.popup = null;
302
+ this.isVisible = false;
303
+ this.currentListType = null;
304
+ }
305
+ }
306
+
307
+ export default ListPicker;
@@ -0,0 +1,68 @@
1
+ import IconUtils from './icons.js';
2
+
3
+ /**
4
+ * Create Custom Button - Simple utility to create styled button
5
+ * @param {string} text - Button text content
6
+ * @param {Object} options - Button options
7
+ * @param {string} options.width - Button width (e.g., '120px', 'auto')
8
+ * @returns {HTMLElement} Button element
9
+ */
10
+ function createCustomButton(text = 'Button', options = {}) {
11
+ const { width = 'auto', icon = null } = options;
12
+
13
+ // Create button
14
+ const button = document.createElement('button');
15
+ button.type = 'button';
16
+ button.className = 'custom-select-button';
17
+
18
+ // Optional leading icon so the control is recognisable at a glance.
19
+ if (icon) {
20
+ const leadIcon = IconUtils.createIconElement(icon);
21
+ leadIcon.className = 'select-lead-icon';
22
+ button.appendChild(leadIcon);
23
+ }
24
+
25
+ // Create text span
26
+ const textSpan = document.createElement('span');
27
+ textSpan.textContent = text;
28
+ textSpan.className = 'button-text';
29
+
30
+ // Create dropdown icon
31
+ const dropdownIcon = IconUtils.createIconElement('dropdown');
32
+ dropdownIcon.className = 'dropdown-icon';
33
+
34
+ // Add text and icon to button
35
+ button.appendChild(textSpan);
36
+ button.appendChild(dropdownIcon);
37
+
38
+ // Apply styles
39
+ button.style.width = width;
40
+ button.style.padding = '0px 5px 0px 8px';
41
+ button.style.setProperty('height', '32px', 'important');
42
+ button.style.setProperty('borderRadius', '6px', 'important');
43
+ button.style.setProperty('alignItems', 'center', 'important');
44
+ button.style.fontSize = '14px';
45
+ button.style.fontWeight = '400';
46
+ button.style.color = '#374151';
47
+ button.style.background = '#FFFFFF';
48
+ button.style.cursor = 'pointer';
49
+ button.style.border = '1px solid #d1d5db';
50
+ button.style.display = 'flex';
51
+ button.style.justifyContent = 'space-between';
52
+ button.style.alignItems = 'center';
53
+
54
+ // Style the text span to take available space
55
+ textSpan.style.flex = '1';
56
+ textSpan.style.textAlign = 'left';
57
+
58
+ // Style the dropdown icon
59
+
60
+ // Add method to update button text
61
+ button.updateText = function(newText) {
62
+ textSpan.textContent = newText;
63
+ };
64
+
65
+ return button;
66
+ }
67
+
68
+ export default createCustomButton;
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Table Popup Component - Interactive table size picker
3
+ */
4
+ import { PopupPositioning } from '../utils/popup-positioning.js';
5
+ import { appendPopup, calculatePopupPosition, setPopupPosition } from '../utils/popup-helper.js';
6
+
7
+ class TablePopup {
8
+ constructor(options = {}) {
9
+ this.options = {
10
+ maxRows: 8,
11
+ maxCols: 8,
12
+ onTableSelect: null,
13
+ ...options
14
+ };
15
+
16
+ this.popup = null;
17
+ this.isVisible = false;
18
+ this.selectedRows = 1;
19
+ this.selectedCols = 1;
20
+ this.grid = null;
21
+ this.sizeDisplay = null;
22
+
23
+ this.createPopup();
24
+ }
25
+
26
+ createPopup() {
27
+ this.popup = document.createElement('div');
28
+ this.popup.className = 'table-popup';
29
+
30
+ const content = document.createElement('div');
31
+ content.className = 'table-popup-content';
32
+
33
+ // Create size display text
34
+ this.createSizeDisplay();
35
+
36
+ // Create grid selector
37
+ this.createGridSelector();
38
+
39
+ content.appendChild(this.grid);
40
+ content.appendChild(this.sizeDisplay);
41
+ this.popup.appendChild(content);
42
+ appendPopup(this.popup);
43
+ }
44
+
45
+ createSizeDisplay() {
46
+ this.sizeDisplay = document.createElement('div');
47
+ this.sizeDisplay.className = 'table-size-display';
48
+
49
+ }
50
+ createGridSelector() {
51
+ this.grid = document.createElement('div');
52
+ this.grid.className = 'table-grid-selector';
53
+
54
+ // Create grid of cells
55
+ for (let row = 1; row <= this.options.maxRows; row++) {
56
+ for (let col = 1; col <= this.options.maxCols; col++) {
57
+ const cell = document.createElement('div');
58
+ cell.className = 'table-grid-cell';
59
+ cell.dataset.row = row;
60
+ cell.dataset.col = col;
61
+
62
+ // Mouse events
63
+ cell.addEventListener('mouseenter', () => {
64
+ this.highlightGrid(row, col);
65
+ });
66
+
67
+ cell.addEventListener('click', () => {
68
+ this.selectSize(row, col);
69
+ this.handleInsert();
70
+ });
71
+
72
+ this.grid.appendChild(cell);
73
+ }
74
+ }
75
+
76
+ // Reset hover when leaving grid
77
+ this.grid.addEventListener('mouseleave', () => {
78
+ this.highlightGrid(1, 1);
79
+ });
80
+ }
81
+
82
+ highlightGrid(rows, cols) {
83
+ this.selectedRows = rows;
84
+ this.selectedCols = cols;
85
+
86
+ // Update size display text
87
+ this.updateSizeDisplay(rows, cols);
88
+
89
+ // Update grid visual
90
+ const cells = this.grid.querySelectorAll('.table-grid-cell');
91
+ cells.forEach(cell => {
92
+ const cellRow = parseInt(cell.dataset.row);
93
+ const cellCol = parseInt(cell.dataset.col);
94
+
95
+ if (cellRow <= rows && cellCol <= cols) {
96
+ cell.classList.add('highlighted');
97
+ } else {
98
+ cell.classList.remove('highlighted');
99
+ }
100
+ });
101
+ }
102
+
103
+ updateSizeDisplay(rows, cols) {
104
+ if (this.sizeDisplay) {
105
+ this.sizeDisplay.textContent = `${rows}x${cols}`;
106
+ }
107
+ }
108
+
109
+ selectSize(rows, cols) {
110
+ this.selectedRows = rows;
111
+ this.selectedCols = cols;
112
+ this.updateSizeDisplay(rows, cols);
113
+ }
114
+
115
+ handleInsert() {
116
+ if (this.options.onTableSelect) {
117
+ this.options.onTableSelect({
118
+ rows: this.selectedRows,
119
+ cols: this.selectedCols
120
+ });
121
+ }
122
+
123
+ this.hide();
124
+ }
125
+
126
+ show(anchor) {
127
+ if (!anchor) return;
128
+
129
+ // Reset selection
130
+ this.selectedRows = 1;
131
+ this.selectedCols = 1;
132
+ this.highlightGrid(1, 1);
133
+
134
+ // Calculate and set popup position
135
+ const position = calculatePopupPosition(anchor, this.popup, {
136
+ offsetY: 5,
137
+ offsetX: 0
138
+ });
139
+ setPopupPosition(this.popup, position);
140
+
141
+ // Show popup
142
+ this.popup.classList.add('visible');
143
+ this.isVisible = true;
144
+
145
+ // Click outside to close
146
+ setTimeout(() => {
147
+ document.addEventListener('click', this.closeOnClickOutside);
148
+ }, 100);
149
+ }
150
+
151
+ hide() {
152
+ this.popup.classList.remove('visible');
153
+ this.isVisible = false;
154
+ document.removeEventListener('click', this.closeOnClickOutside);
155
+ }
156
+
157
+ closeOnClickOutside = (e) => {
158
+ if (!this.popup.contains(e.target)) {
159
+ this.hide();
160
+ }
161
+ }
162
+
163
+ destroy() {
164
+ document.removeEventListener('click', this.closeOnClickOutside);
165
+ if (this.popup && this.popup.parentNode) {
166
+ this.popup.parentNode.removeChild(this.popup);
167
+ }
168
+ }
169
+ }
170
+
171
+ export default TablePopup;