@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,251 @@
1
+ import { InlineFormat } from '../core/format.js';
2
+ import LinkPopup from '../ui/link-popup.js';
3
+ import Editor from '../core/editor.js';
4
+ import { isSafeUrl } from '../utils/sanitize.js';
5
+
6
+ /**
7
+ * Link Format - Simple link insertion
8
+ * Now supports multiple editor instances with separate popup instances
9
+ */
10
+ class Link extends InlineFormat {
11
+ static formatName = 'link';
12
+ static tagName = 'A';
13
+
14
+ // Map to store saved ranges for each editor instance
15
+ static savedRanges = new Map();
16
+
17
+ constructor() {
18
+ super();
19
+
20
+ // Get current editor instance
21
+ const currentEditor = Editor.getCurrentInstance();
22
+ if (!currentEditor) {
23
+ console.warn('No editor instance found for Link format');
24
+ return;
25
+ }
26
+
27
+ this.editorId = currentEditor.instanceId;
28
+
29
+ // Check if this editor already has a link popup instance
30
+ let linkPopup = currentEditor.getPopupInstance('link');
31
+
32
+ if (!linkPopup) {
33
+ // Create new popup instance for this editor
34
+ linkPopup = new LinkPopup({
35
+ onLinkSelect: (linkData) => {
36
+ Link.insertLink(linkData, this.editorId);
37
+ },
38
+ editor: currentEditor,
39
+ editorId: this.editorId
40
+ });
41
+
42
+ // Store popup instance in editor
43
+ currentEditor.setPopupInstance('link', linkPopup);
44
+ }
45
+
46
+ this.linkPopup = linkPopup;
47
+ }
48
+
49
+ /**
50
+ * Create a new Link format instance for a specific editor
51
+ * @param {string} editorId - Editor instance ID
52
+ * @returns {Link} Link format instance
53
+ */
54
+ static createForEditor(editorId) {
55
+ const editor = Editor.getInstanceById(editorId);
56
+ if (!editor) {
57
+ console.warn('No editor instance found for ID:', editorId);
58
+ return null;
59
+ }
60
+
61
+ // Temporarily set as current instance
62
+ const originalCurrent = Editor.currentInstance;
63
+ Editor.currentInstance = editor;
64
+
65
+ // Create format instance
66
+ const format = new Link();
67
+
68
+ // Restore original current instance
69
+ Editor.currentInstance = originalCurrent;
70
+
71
+ return format;
72
+ }
73
+
74
+ /**
75
+ * Insert link at saved cursor position
76
+ * @param {Object} linkData - Link data with url and text
77
+ * @param {string} editorId - Editor instance ID
78
+ */
79
+ static insertLink(linkData, editorId = null) {
80
+ // Get the correct editor instance
81
+ let editor = null;
82
+ if (editorId) {
83
+ editor = Editor.getInstanceById(editorId);
84
+ } else {
85
+ editor = Editor.getCurrentInstance();
86
+ }
87
+
88
+ if (!editor) {
89
+ console.warn('No editor instance found for link insertion');
90
+ return;
91
+ }
92
+
93
+ // Block unsafe URL schemes (javascript:, data:text/html, vbscript:, ...)
94
+ if (!isSafeUrl(linkData.url)) {
95
+ console.warn('Blocked unsafe link URL:', linkData.url);
96
+ return;
97
+ }
98
+
99
+ // Get saved range for this specific editor
100
+ const savedRange = Link.savedRanges.get(editorId);
101
+ if (!savedRange) {
102
+ console.warn('No saved range found for editor:', editorId);
103
+ return;
104
+ }
105
+
106
+ const selection = window.getSelection();
107
+ selection.removeAllRanges();
108
+ selection.addRange(savedRange);
109
+
110
+ const range = selection.getRangeAt(0);
111
+
112
+ if (range.collapsed) {
113
+ // No selection - insert link at cursor
114
+ const linkElement = document.createElement('A');
115
+ linkElement.href = linkData.url;
116
+ linkElement.target = '_blank';
117
+ linkElement.rel = 'noopener noreferrer';
118
+ linkElement.textContent = linkData.text || linkData.url;
119
+ range.insertNode(linkElement);
120
+ } else {
121
+ // Has selection - wrap existing content with link while preserving styles
122
+ const fragment = range.extractContents();
123
+ const linkElement = document.createElement('A');
124
+ linkElement.href = linkData.url;
125
+ linkElement.target = '_blank';
126
+ linkElement.rel = 'noopener noreferrer';
127
+
128
+ // Move all nodes from fragment to link element
129
+ while (fragment.firstChild) {
130
+ linkElement.appendChild(fragment.firstChild);
131
+ }
132
+
133
+ range.insertNode(linkElement);
134
+ }
135
+
136
+ // Position cursor after link
137
+ const newRange = document.createRange();
138
+ newRange.setStartAfter(range.endContainer);
139
+ newRange.collapse(true);
140
+ selection.removeAllRanges();
141
+ selection.addRange(newRange);
142
+
143
+ // Trigger content change after applying format
144
+ setTimeout(() => {
145
+ if (editor && typeof editor.onContentChange === 'function') {
146
+ editor.onContentChange();
147
+ }
148
+ }, 0);
149
+
150
+ // Clear saved range for this editor
151
+ Link.savedRanges.delete(editorId);
152
+
153
+ // Trigger content change event
154
+ if (editor && typeof editor.onContentChange === 'function') {
155
+ editor.onContentChange();
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Toggle link popup
161
+ */
162
+ toggle() {
163
+ if (this.linkPopup.isVisible) {
164
+ this.linkPopup.hide();
165
+ } else {
166
+ this.showPopup();
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Show link popup
172
+ */
173
+ showPopup() {
174
+ const editor = Editor.getInstanceById(this.editorId);
175
+ if (!editor) {
176
+ console.warn('No editor found for ID:', this.editorId);
177
+ return;
178
+ }
179
+
180
+ // Save the current range and anchor the popup right at the selected text.
181
+ const selection = window.getSelection();
182
+ let rect = null;
183
+ if (selection && selection.rangeCount > 0) {
184
+ const range = selection.getRangeAt(0).cloneRange();
185
+ Link.savedRanges.set(this.editorId, range);
186
+ rect = range.getBoundingClientRect();
187
+ // A collapsed caret can report an empty rect — fall back to its element.
188
+ if (!rect || (rect.width === 0 && rect.height === 0)) {
189
+ const node = range.startContainer.nodeType === Node.TEXT_NODE
190
+ ? range.startContainer.parentElement
191
+ : range.startContainer;
192
+ if (node && node.getBoundingClientRect) rect = node.getBoundingClientRect();
193
+ }
194
+ }
195
+
196
+ const existingLink = this.getCurrentLink();
197
+ let selectedText = '';
198
+ if (selection && !selection.isCollapsed) {
199
+ selectedText = selection.toString().trim();
200
+ }
201
+
202
+ // Anchor at the selection rect (virtual anchor); fall back to the toolbar
203
+ // button when there's no usable rect.
204
+ let anchor = null;
205
+ if (rect && (rect.width || rect.height)) {
206
+ anchor = { getBoundingClientRect: () => rect };
207
+ } else {
208
+ const toolbar = editor.getModule('toolbar');
209
+ anchor = toolbar && (toolbar.getButton('link')
210
+ || editor.wrapper.querySelector('.rich-editor-toolbar-btn[data-command="link"]'));
211
+ }
212
+ if (!anchor) {
213
+ console.warn('No anchor for link popup, editor:', this.editorId);
214
+ return;
215
+ }
216
+
217
+ this.linkPopup.show(anchor, existingLink, selectedText);
218
+ }
219
+
220
+ /**
221
+ * Get current link if cursor is in one
222
+ */
223
+ getCurrentLink() {
224
+ const selection = window.getSelection();
225
+ if (!selection || !selection.rangeCount) return null;
226
+
227
+ let node = selection.getRangeAt(0).startContainer;
228
+
229
+ // Find parent link element
230
+ while (node && node !== document.body) {
231
+ if (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'A') {
232
+ return {
233
+ url: node.href || '',
234
+ text: node.textContent || ''
235
+ };
236
+ }
237
+ node = node.parentNode;
238
+ }
239
+
240
+ return null;
241
+ }
242
+
243
+ /**
244
+ * Check if cursor is in a link
245
+ */
246
+ isActive() {
247
+ return this.getCurrentLink() !== null;
248
+ }
249
+ }
250
+
251
+ export default Link;