@oix1987/yjd 1.0.1 → 1.0.3

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 +9 -1
  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 +13 -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/ui/color-picker.js +0 -296
  43. package/lib/ui/customselect.js +0 -319
  44. package/lib/ui/emoji-picker.js +0 -196
  45. package/lib/ui/icons.js +0 -413
  46. package/lib/ui/image-popup.js +0 -444
  47. package/lib/ui/import-popup.js +0 -288
  48. package/lib/ui/link-popup.js +0 -191
  49. package/lib/ui/list-picker.js +0 -307
  50. package/lib/ui/select-button.js +0 -61
  51. package/lib/ui/table-popup.js +0 -171
  52. package/lib/ui/tag-popup.js +0 -249
  53. package/lib/ui/text-align-picker.js +0 -281
  54. package/lib/ui/video-popup.js +0 -422
  55. package/lib/utils/history-helper.js +0 -50
  56. package/lib/utils/popup-helper.js +0 -219
  57. package/lib/utils/popup-positioning.js +0 -231
  58. /package/{lib → dist}/styles.css +0 -0
@@ -1,424 +0,0 @@
1
- import Module from '../core/module.js';
2
- import ColorPicker from '../ui/color-picker.js';
3
- import IconUtils from '../ui/icons.js';
4
- import createCustomButton from '../ui/select-button.js';
5
-
6
- /**
7
- * Toolbar Module - Pure UI component with dual toolbar support
8
- * Only handles toolbar creation and event emission
9
- * No business logic or state management
10
- */
11
- class Toolbar extends Module {
12
- static DEFAULTS = {
13
- container: null,
14
- toolbar1: [
15
- { group: 'text-format', items: ['bold', 'italic', 'underline', 'strike'] },
16
- { group: 'script', items: ['subscript', 'superscript'] },
17
- { group: 'colors', items: ['color', 'background'] },
18
- { group: 'link', items: ['link'] },
19
- { group: 'structure', items: ['heading', 'text-size'] },
20
- { group: 'table', items: ['table'] },
21
- { group: 'alignment', items: ['text-align'] },
22
- { group: 'actions', items: ['undo', 'redo'] },
23
- { group: 'more', items: ['more'] }
24
- ],
25
- toolbar2: [
26
- { group: 'structure', items: ['list'] },
27
- { group: 'indent', items: ['indent-increase', 'indent-decrease'] },
28
- { group: 'font-family', items: ['font-family'] },
29
- { group: 'line-height', items: ['line-height'] },
30
- { group: 'capitalization', items: ['capitalization'] },
31
- { group: 'media', items: ['emoji', 'image', 'video'] },
32
- { group: 'content', items: ['tag'] },
33
- { group: 'view', items: ['code-view'] },
34
-
35
- ]
36
- };
37
-
38
- constructor(editor, options = {}) {
39
- super(editor, options);
40
- this.buttons = new Map();
41
- this.toolbar2Visible = false;
42
- this.events = new Map(); // Add event system
43
-
44
-
45
- // Handle toolbar configuration
46
- if (Array.isArray(options.toolbar)) {
47
- // If toolbar array is provided, use only those items - COMPLETELY OVERRIDE DEFAULTS
48
- this.options = {
49
- container: null,
50
- toolbar1: [
51
- { group: 'text-format', items: options.toolbar }
52
- ],
53
- toolbar2: []
54
- };
55
- } else if (options.toolbar1 || options.toolbar2) {
56
- // If specific toolbar1/toolbar2 config is provided, use it - COMPLETELY OVERRIDE DEFAULTS
57
- this.options = {
58
- container: null,
59
- toolbar1: options.toolbar1 || [],
60
- toolbar2: options.toolbar2 || []
61
- };
62
- } else {
63
- // Use full default configuration
64
- this.options = { ...Toolbar.DEFAULTS, ...options };
65
- }
66
-
67
-
68
- this.init();
69
- this.preloadIcons();
70
- }
71
-
72
- init() {
73
- this.container = this.createToolbarContainer();
74
- }
75
-
76
- /**
77
- * Preload icons for better performance
78
- */
79
- async preloadIcons() {
80
- // Icons are now inline, no need to preload
81
- // This method is kept for backward compatibility
82
- }
83
-
84
- /**
85
- * Create main toolbar container with both toolbars
86
- */
87
- createToolbarContainer() {
88
- const container = document.createElement('div');
89
- container.className = 'rich-editor-toolbar-container';
90
-
91
- // Prevent toolbar from taking focus away from editor
92
- this.editor.preventFocusLoss(container);
93
-
94
- // Create toolbar 1
95
- this.toolbar1 = this.createToolbar('rich-editor-toolbar-1', this.options.toolbar1);
96
- container.appendChild(this.toolbar1);
97
-
98
- // Create toolbar 2 (initially hidden)
99
- this.toolbar2 = this.createToolbar('rich-editor-toolbar-2', this.options.toolbar2);
100
- this.toolbar2.style.display = 'none';
101
- container.appendChild(this.toolbar2);
102
-
103
- return container;
104
- }
105
-
106
- /**
107
- * Create toolbar element
108
- */
109
- createToolbar(className, toolbarItems) {
110
- const toolbar = document.createElement('div');
111
- toolbar.className = className;
112
-
113
- // Create button groups based on toolbar config
114
- if (Array.isArray(toolbarItems)) {
115
- toolbarItems.forEach(group => {
116
- if (group && group.group && Array.isArray(group.items)) {
117
- // Create group container
118
- const groupContainer = document.createElement('div');
119
- groupContainer.className = `toolbar-group toolbar-group-${group.group}`;
120
-
121
- // Add buttons to group
122
- group.items.forEach(item => {
123
- if (typeof item === 'string') {
124
- this.addButton(groupContainer, item);
125
- }
126
- });
127
-
128
- toolbar.appendChild(groupContainer);
129
- }
130
- });
131
- }
132
-
133
- return toolbar;
134
- }
135
-
136
- /**
137
- * Add button to toolbar
138
- */
139
- addButton(container, format) {
140
- // Special handling for more button
141
- if (format === 'more') {
142
- return this.addMoreButton(container);
143
- }
144
-
145
- // Custom buttons with dropdowns
146
- const customButtons = {
147
- 'heading': { text: 'Paragraph', width: '110px', title: 'Format (Headings & Paragraphs)' },
148
- 'font-family': { text: 'Font Family', width: '150px', title: 'Font Family' },
149
- 'line-height': { text: 'Line Height', width: '110px', title: 'Line Height' },
150
- 'capitalization': { text: 'Capitalization', width: '130px', title: 'Text Capitalization' },
151
- 'text-size': { text: 'Text Size', width: '100px', title: 'Text Size' }
152
- };
153
-
154
- if (customButtons[format]) {
155
- const config = customButtons[format];
156
- const customButton = createCustomButton(config.text, { width: config.width });
157
- customButton.dataset.command = format;
158
- customButton.classList.add('rich-editor-toolbar-btn', `${format}-btn`);
159
- customButton.title = config.title;
160
-
161
- customButton.addEventListener('click', (e) => {
162
- e.preventDefault();
163
- this.emit('toolbar-click', { command: format, button: customButton });
164
- // Maintain editor focus after button click
165
- setTimeout(() => {
166
- this.editor.focus();
167
- }, 0);
168
- });
169
-
170
- this.buttons.set(format, customButton);
171
- container.appendChild(customButton);
172
- return customButton;
173
- }
174
-
175
- // Icon buttons with popups
176
- const iconButtons = {
177
- 'text-align': { icon: 'align-left', title: 'Align Left' },
178
- 'list': { icon: 'list', title: 'List' }
179
- };
180
-
181
- if (iconButtons[format]) {
182
- const config = iconButtons[format];
183
- const button = document.createElement('button');
184
- button.type = 'button';
185
- button.className = `rich-editor-toolbar-btn ${format}-btn`;
186
- button.dataset.command = format;
187
- button.title = config.title;
188
-
189
- const svgContent = IconUtils.getIcon(config.icon);
190
- if (svgContent) {
191
- button.innerHTML = `<span class="icon">${svgContent}</span>`;
192
- } else {
193
- button.textContent = format === 'text-align' ? '≡' : '•';
194
- }
195
-
196
- button.addEventListener('click', (e) => {
197
- e.preventDefault();
198
- this.emit('toolbar-click', { command: format, button: button });
199
- // Maintain editor focus after button click
200
- setTimeout(() => {
201
- this.editor.focus();
202
- }, 0);
203
- });
204
-
205
- this.buttons.set(format, button);
206
- container.appendChild(button);
207
- return button;
208
- }
209
-
210
- // Regular icon buttons
211
- const button = document.createElement('button');
212
- button.type = 'button';
213
- button.className = `rich-editor-toolbar-btn ${format}-btn`;
214
- button.dataset.command = format;
215
-
216
- // Add icon
217
- const iconElement = IconUtils.createIconElement(format, {
218
- width: '16px',
219
- height: '16px'
220
- });
221
- button.appendChild(iconElement);
222
-
223
- // Set title based on format
224
- const titles = {
225
- 'bold': 'Bold (Ctrl+B)',
226
- 'italic': 'Italic (Ctrl+I)',
227
- 'underline': 'Underline (Ctrl+U)',
228
- 'strike': 'Strikethrough',
229
- 'subscript': 'Subscript',
230
- 'superscript': 'Superscript',
231
- 'color': 'Text Color',
232
- 'background': 'Background Color',
233
- 'link': 'Insert/Edit Link',
234
- 'table': 'Insert Table',
235
- 'undo': 'Undo (Ctrl+Z)',
236
- 'redo': 'Redo (Ctrl+Y)',
237
- 'indent-increase': 'Increase Indent',
238
- 'indent-decrease': 'Decrease Indent',
239
- 'emoji': 'Insert Emoji',
240
- 'image': 'Insert Image',
241
- 'video': 'Insert Video',
242
- 'tag': 'Insert Tag',
243
-
244
- 'import': 'Import Files',
245
- 'code-view': 'Switch to HTML Editor',
246
-
247
- };
248
-
249
- button.title = titles[format] || format;
250
-
251
- // Add fallback for code-view
252
- if (format === 'code-view') {
253
- setTimeout(() => {
254
- if (!iconElement.innerHTML.trim()) {
255
- iconElement.innerHTML = '&lt;/&gt;';
256
- iconElement.style.fontSize = '12px';
257
- iconElement.style.fontWeight = 'bold';
258
- }
259
- }, 1000);
260
- }
261
-
262
- button.addEventListener('click', (e) => {
263
- e.preventDefault();
264
- this.emit('toolbar-click', { command: format, button });
265
- // Maintain editor focus after button click
266
- setTimeout(() => {
267
- this.editor.focus();
268
- }, 0);
269
- });
270
-
271
- this.buttons.set(format, button);
272
- container.appendChild(button);
273
- return button;
274
- }
275
-
276
- /**
277
- * Add more button to toggle toolbar 2
278
- */
279
- addMoreButton(container) {
280
- const button = document.createElement('button');
281
- button.type = 'button';
282
- button.className = 'rich-editor-toolbar-btn more-btn';
283
- button.dataset.command = 'more';
284
-
285
- const iconElement = IconUtils.createIconElement('more', {
286
- width: '16px',
287
- height: '16px'
288
- });
289
- button.appendChild(iconElement);
290
- button.title = 'More Options';
291
-
292
- button.addEventListener('click', (e) => {
293
- e.preventDefault();
294
- this.toggleToolbar2();
295
- // Maintain editor focus after button click
296
- setTimeout(() => {
297
- this.editor.focus();
298
- }, 0);
299
- });
300
-
301
- this.buttons.set('more', button);
302
- container.appendChild(button);
303
- return button;
304
- }
305
-
306
- /**
307
- * Toggle toolbar 2 visibility
308
- */
309
- toggleToolbar2() {
310
- this.toolbar2Visible = !this.toolbar2Visible;
311
-
312
- if (this.toolbar2Visible) {
313
- this.toolbar2.style.display = 'flex';
314
- this.toolbar2.style.borderTop = '1px solid #d1d5db';
315
- } else {
316
- this.toolbar2.style.display = 'none';
317
- }
318
-
319
- // Update more button appearance
320
- const moreButton = this.buttons.get('more');
321
- if (moreButton) {
322
- if (this.toolbar2Visible) {
323
- moreButton.classList.add('active');
324
- moreButton.title = 'Hide More Options';
325
- } else {
326
- moreButton.classList.remove('active');
327
- moreButton.title = 'More Options';
328
- }
329
- }
330
- }
331
-
332
- /**
333
- * Get toolbar container element
334
- */
335
- getContainer() {
336
- return this.container;
337
- }
338
-
339
- /**
340
- * Get button by command
341
- */
342
- getButton(command) {
343
- return this.buttons.get(command);
344
- }
345
-
346
- /**
347
- * Set button active state
348
- */
349
- setButtonActive(command, isActive) {
350
- const button = this.buttons.get(command);
351
- if (button && button.classList) {
352
- if (isActive) {
353
- button.classList.add('active');
354
- } else {
355
- button.classList.remove('active');
356
- }
357
- }
358
- }
359
-
360
- /**
361
- * Set button disabled state
362
- */
363
- setButtonDisabled(command, isDisabled) {
364
- const button = this.buttons.get(command);
365
- if (button) {
366
- button.disabled = isDisabled;
367
- button.style.opacity = isDisabled ? '0.5' : '1';
368
- button.style.cursor = isDisabled ? 'not-allowed' : 'pointer';
369
- }
370
- }
371
-
372
- /**
373
- * Set button title
374
- */
375
- setButtonTitle(command, title) {
376
- const button = this.buttons.get(command);
377
- if (button) {
378
- button.title = title;
379
- }
380
- }
381
-
382
- /**
383
- * Check if toolbar 2 is visible
384
- */
385
- isToolbar2Visible() {
386
- return this.toolbar2Visible;
387
- }
388
-
389
- /**
390
- * Event system methods
391
- */
392
- on(event, callback) {
393
- if (!this.events.has(event)) {
394
- this.events.set(event, []);
395
- }
396
- this.events.get(event).push(callback);
397
- }
398
-
399
- emit(event, data) {
400
- const callbacks = this.events.get(event);
401
- if (callbacks) {
402
- callbacks.forEach(callback => {
403
- try {
404
- callback(data);
405
- } catch (error) {
406
- console.error(`Error in toolbar event ${event}:`, error);
407
- }
408
- });
409
- }
410
- }
411
-
412
- /**
413
- * Destroy toolbar
414
- */
415
- destroy() {
416
- if (this.container && this.container.parentNode) {
417
- this.container.parentNode.removeChild(this.container);
418
- }
419
- this.buttons.clear();
420
- this.events.clear();
421
- }
422
- }
423
-
424
- export default Toolbar;
@@ -1,144 +0,0 @@
1
- /**
2
- * CSS Loader - Load và inject CSS styles vào DOM
3
- * Thay thế cho việc sử dụng inline styles
4
- */
5
- class StylesLoader {
6
- static loaded = false;
7
- static styleElement = null;
8
-
9
- /**
10
- * Load CSS từ file hoặc string
11
- */
12
- static async loadStyles() {
13
- if (this.loaded) return;
14
-
15
- try {
16
- // Tạo style element
17
- this.styleElement = document.createElement('style');
18
- this.styleElement.id = 'rich-editor-styles';
19
-
20
- // Load CSS từ file
21
- const response = await fetch(new URL('./styles.css', import.meta.url));
22
- const cssText = await response.text();
23
-
24
- this.styleElement.textContent = cssText;
25
-
26
- // Inject vào head
27
- document.head.appendChild(this.styleElement);
28
-
29
- this.loaded = true;
30
-
31
- } catch (error) {
32
-
33
- // Fallback: load minimal styles
34
- this.loadFallbackStyles();
35
- }
36
- }
37
-
38
- /**
39
- * Load minimal fallback styles nếu không thể load từ file
40
- */
41
- static loadFallbackStyles() {
42
- const fallbackCSS = `
43
- .yjd-rich-editor {
44
- position: relative;
45
- background: #fff;
46
- border: 1px solid #ddd;
47
- border-radius: 4px;
48
- display: flex;
49
- flex-direction: column;
50
- font-family: system-ui, sans-serif;
51
- }
52
- .yjd-rich-editor .rich-editor-area {
53
- flex: 1;
54
- padding: 20px;
55
- outline: none;
56
- min-height: 100px;
57
- }
58
- .yjd-rich-editor .rich-editor-toolbar {
59
- display: flex;
60
- gap: 4px;
61
- padding: 8px;
62
- border-bottom: 1px solid #ddd;
63
- background: #f9f9f9;
64
- }
65
- .yjd-rich-editor .rich-editor-toolbar-btn {
66
- padding: 4px 8px;
67
- border: 1px solid #ccc;
68
- border-radius: 3px;
69
- background: #fff;
70
- cursor: pointer;
71
- }
72
- .yjd-rich-editor .table-grid-selector {
73
- position: absolute;
74
- background: white;
75
- border: 1px solid #ccc;
76
- border-radius: 4px;
77
- padding: 10px;
78
- box-shadow: 0 2px 8px rgba(0,0,0,0.15);
79
- z-index: 1000;
80
- display: none;
81
- }
82
- .yjd-rich-editor .table-grid-cell {
83
- width: 20px;
84
- height: 20px;
85
- border: 1px solid #ddd;
86
- cursor: pointer;
87
- background: white;
88
- }
89
- `;
90
-
91
- this.styleElement = document.createElement('style');
92
- this.styleElement.id = 'rich-editor-styles-fallback';
93
- this.styleElement.textContent = fallbackCSS;
94
- document.head.appendChild(this.styleElement);
95
-
96
- this.loaded = true;
97
- }
98
-
99
- /**
100
- * Unload styles
101
- */
102
- static unloadStyles() {
103
- if (this.styleElement && this.styleElement.parentNode) {
104
- this.styleElement.parentNode.removeChild(this.styleElement);
105
- this.styleElement = null;
106
- this.loaded = false;
107
- }
108
- }
109
-
110
- /**
111
- * Check if styles are loaded
112
- */
113
- static isLoaded() {
114
- return this.loaded;
115
- }
116
-
117
- /**
118
- * Reload styles
119
- */
120
- static async reloadStyles() {
121
- this.unloadStyles();
122
- await this.loadStyles();
123
- }
124
-
125
- /**
126
- * Add custom CSS
127
- */
128
- static addCustomCSS(css, id = 'rich-editor-custom') {
129
- // Remove existing custom styles
130
- const existing = document.getElementById(id);
131
- if (existing) {
132
- existing.remove();
133
- }
134
-
135
- // Add new custom styles
136
- const style = document.createElement('style');
137
- style.id = id;
138
- style.textContent = css;
139
- document.head.appendChild(style);
140
-
141
- }
142
- }
143
-
144
- export default StylesLoader;