@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,39 @@
1
+ import { InlineFormat } from '../core/format.js';
2
+ import { saveBeforeFormat } from '../utils/history-helper.js';
3
+ import registry from '../core/registry.js';
4
+ /**
5
+ * Superscript Format - Handles superscript text formatting
6
+ * Creates <sup> elements for superscript text
7
+ */
8
+ class Superscript extends InlineFormat {
9
+ static formatName = 'superscript';
10
+ static tagName = 'SUP';
11
+
12
+ /**
13
+ * Toggle superscript formatting
14
+ */
15
+ removeSubscriptBeforeApply() {
16
+ // Resolved via registry (not a static import) to avoid a circular
17
+ // dependency between superscript.js and subscript.js.
18
+ const Subscript = registry.get('formats/subscript');
19
+ if (!Subscript) return;
20
+ const subscript = new Subscript();
21
+ if (subscript.isActive()) {
22
+ subscript.remove();
23
+ }
24
+ }
25
+ toggle() {
26
+ // Save state before applying format
27
+ saveBeforeFormat();
28
+
29
+ if (this.isActive()) {
30
+ this.remove();
31
+ } else {
32
+ // Ensure mutual exclusivity: remove subscript before applying superscript
33
+ this.removeSubscriptBeforeApply();
34
+ this.apply();
35
+ }
36
+ }
37
+ }
38
+
39
+ export default Superscript;
@@ -0,0 +1,293 @@
1
+ import { BlockFormat } from '../core/format.js';
2
+ import TablePopup from '../ui/table-popup.js';
3
+ import Editor from '../core/editor.js';
4
+
5
+ /**
6
+ * Table Format - HTML table insertion
7
+ * Now supports multiple editor instances with separate popup instances
8
+ */
9
+ class Table extends BlockFormat {
10
+ static formatName = 'table';
11
+ static tagName = 'TABLE';
12
+ static savedRanges = new Map(); // Map to store saved ranges for each editor
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 Table format');
21
+ return;
22
+ }
23
+
24
+ this.editorId = currentEditor.instanceId;
25
+
26
+ // Check if this editor already has a table popup instance
27
+ let tablePopup = currentEditor.getPopupInstance('table');
28
+
29
+ if (!tablePopup) {
30
+ // Create new table popup instance for this editor
31
+ tablePopup = new TablePopup({
32
+ onTableSelect: (tableData) => {
33
+ Table.insertTable(tableData, this.editorId);
34
+ },
35
+ editor: currentEditor,
36
+ editorId: this.editorId
37
+ });
38
+
39
+ // Store popup instance in editor
40
+ currentEditor.setPopupInstance('table', tablePopup);
41
+ }
42
+
43
+ this.tablePopup = tablePopup;
44
+ }
45
+
46
+ /**
47
+ * Create a new Table format instance for a specific editor
48
+ * @param {string} editorId - Editor instance ID
49
+ * @returns {Table} Table 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 Table();
64
+
65
+ // Restore original current instance
66
+ Editor.currentInstance = originalCurrent;
67
+
68
+ return format;
69
+ }
70
+
71
+ /**
72
+ * Insert table at saved cursor position
73
+ * @param {Object} tableData - Table data with rows and cols
74
+ * @param {string} editorId - Editor instance ID
75
+ */
76
+ static insertTable(tableData, editorId = null) {
77
+ // Get the correct editor instance
78
+ let editor = null;
79
+ if (editorId) {
80
+ editor = Editor.getInstanceById(editorId);
81
+ } else {
82
+ editor = Editor.getCurrentInstance();
83
+ }
84
+
85
+ if (!editor) {
86
+ console.warn('No editor instance found for table insertion');
87
+ return;
88
+ }
89
+
90
+ // Get saved range for this editor
91
+ const savedRange = Table.savedRanges.get(editorId);
92
+ if (!savedRange) return;
93
+
94
+ const selection = window.getSelection();
95
+ selection.removeAllRanges();
96
+ selection.addRange(savedRange);
97
+
98
+ const range = selection.getRangeAt(0);
99
+
100
+ // Create table HTML
101
+ const tableElement = Table.createTableElement(tableData.rows, tableData.cols);
102
+
103
+ // Clear any selected content
104
+ if (!range.collapsed) {
105
+ range.deleteContents();
106
+ }
107
+
108
+ // Insert the table as a top-level block (not nested inside a heading or
109
+ // inline formatting tags, which would produce invalid HTML).
110
+ if (typeof editor.insertBlock === 'function') {
111
+ editor.insertBlock(tableElement);
112
+ } else {
113
+ range.insertNode(tableElement);
114
+ }
115
+
116
+ // Position cursor in first cell
117
+ const firstCell = tableElement.querySelector('td');
118
+ if (firstCell) {
119
+ const newRange = document.createRange();
120
+ newRange.setStart(firstCell, 0);
121
+ newRange.collapse(true);
122
+ selection.removeAllRanges();
123
+ selection.addRange(newRange);
124
+ }
125
+
126
+ // Clear saved range for this editor
127
+ Table.savedRanges.delete(editorId);
128
+
129
+ // Trigger content change event
130
+ if (editor && typeof editor.onContentChange === 'function') {
131
+ editor.onContentChange();
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Create table element
137
+ */
138
+ static createTableElement(rows, cols) {
139
+ const table = document.createElement('table');
140
+ table.className = 'rich-editor-table';
141
+ table.cellSpacing = '0';
142
+ table.cellPadding = '0';
143
+ table.border = '1';
144
+
145
+ const tbody = document.createElement('tbody');
146
+
147
+ for (let r = 0; r < rows; r++) {
148
+ const row = document.createElement('tr');
149
+
150
+ for (let c = 0; c < cols; c++) {
151
+ const cell = document.createElement('td');
152
+ cell.innerHTML = '<br>'; // empty placeholder (keeps height, not counted as content)
153
+ cell.style.minWidth = '50px';
154
+ cell.style.minHeight = '24px';
155
+ cell.style.padding = '4px 8px';
156
+ cell.style.border = '1px solid #ddd';
157
+ cell.style.verticalAlign = 'top';
158
+
159
+ // Make cells editable
160
+ cell.contentEditable = 'true';
161
+
162
+ row.appendChild(cell);
163
+ }
164
+
165
+ tbody.appendChild(row);
166
+ }
167
+
168
+ table.appendChild(tbody);
169
+
170
+ // Add table styles
171
+ table.style.borderCollapse = 'collapse';
172
+ table.style.width = '100%';
173
+ table.style.margin = '10px 0';
174
+
175
+ return table;
176
+ }
177
+
178
+ /**
179
+ * Toggle table popup
180
+ */
181
+ toggle() {
182
+ if (this.tablePopup.isVisible) {
183
+ this.tablePopup.hide();
184
+ } else {
185
+ this.showPopup();
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Show table popup
191
+ */
192
+ showPopup() {
193
+ // Lưu vị trí con trỏ hiện tại cho editor này
194
+ const selection = window.getSelection();
195
+ if (selection && selection.rangeCount > 0) {
196
+ Table.savedRanges.set(this.editorId, selection.getRangeAt(0).cloneRange());
197
+ }
198
+
199
+ // Find table button in the current editor's toolbar
200
+ const editor = Editor.getInstanceById(this.editorId);
201
+ if (!editor) return;
202
+
203
+ const toolbar = editor.getModule('toolbar');
204
+ let tableButton = null;
205
+
206
+ if (toolbar) {
207
+ tableButton = toolbar.getButton('table');
208
+ }
209
+
210
+ // Fallback: find button by class in the current editor's toolbar
211
+ if (!tableButton) {
212
+ const toolbarContainer = toolbar?.getContainer();
213
+ if (toolbarContainer) {
214
+ tableButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.table-btn');
215
+ }
216
+ }
217
+
218
+ // Final fallback: find any table button in the current editor's wrapper
219
+ if (!tableButton) {
220
+ tableButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.table-btn');
221
+ }
222
+
223
+ if (!tableButton) {
224
+ console.warn('Table button not found for editor:', this.editorId);
225
+ return;
226
+ }
227
+
228
+ this.tablePopup.show(tableButton);
229
+ }
230
+
231
+ /**
232
+ * Check if cursor is in a table
233
+ */
234
+ isActive() {
235
+ const selection = window.getSelection();
236
+ if (!selection || !selection.rangeCount) return false;
237
+
238
+ let node = selection.getRangeAt(0).startContainer;
239
+
240
+ // Find parent table element
241
+ while (node && node !== document.body) {
242
+ if (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'TABLE') {
243
+ return true;
244
+ }
245
+ node = node.parentNode;
246
+ }
247
+
248
+ return false;
249
+ }
250
+
251
+ /**
252
+ * Get current table if cursor is in one
253
+ */
254
+ getCurrentTable() {
255
+ const selection = window.getSelection();
256
+ if (!selection || !selection.rangeCount) return null;
257
+
258
+ let node = selection.getRangeAt(0).startContainer;
259
+
260
+ // Find parent table element
261
+ while (node && node !== document.body) {
262
+ if (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'TABLE') {
263
+ return node;
264
+ }
265
+ node = node.parentNode;
266
+ }
267
+
268
+ return null;
269
+ }
270
+
271
+ /**
272
+ * Apply table formatting (not applicable for this format)
273
+ */
274
+ apply() {
275
+ this.showPopup();
276
+ }
277
+
278
+ /**
279
+ * Remove table formatting
280
+ */
281
+ remove() {
282
+ const table = this.getCurrentTable();
283
+ if (table) {
284
+ // Hide resize handles before removing table
285
+ if (window.richEditor && window.richEditor.resizeHandles) {
286
+ window.richEditor.resizeHandles.hideHandles();
287
+ }
288
+ table.parentNode.removeChild(table);
289
+ }
290
+ }
291
+ }
292
+
293
+ export default Table;
@@ -0,0 +1,304 @@
1
+ import { InlineFormat } from '../core/format.js';
2
+ import TagPopup from '../ui/tag-popup.js';
3
+ import Editor from '../core/editor.js';
4
+
5
+ /**
6
+ * Tag Format - Handles custom tag insertion
7
+ * Now supports multiple editor instances with separate popup instances
8
+ */
9
+ class Tag extends InlineFormat {
10
+ static formatName = 'tag';
11
+ static tagName = 'SPAN';
12
+ static className = 'custom-tag';
13
+ static savedRanges = new Map(); // Map to store saved ranges for each editor
14
+
15
+ constructor() {
16
+ super();
17
+
18
+ // Get current editor instance
19
+ const currentEditor = Editor.getCurrentInstance();
20
+ if (!currentEditor) {
21
+ console.warn('No editor instance found for Tag format');
22
+ return;
23
+ }
24
+
25
+ this.editorId = currentEditor.instanceId;
26
+
27
+ // Check if this editor already has a tag popup instance
28
+ let tagPopup = currentEditor.getPopupInstance('tag');
29
+
30
+ if (!tagPopup) {
31
+ // Create new tag popup instance for this editor
32
+ tagPopup = new TagPopup({
33
+ onTagInsert: (tagType, tagContent) => {
34
+ Tag.insertTagAtCurrentPosition(tagType, tagContent, this.editorId);
35
+ },
36
+ editor: currentEditor,
37
+ editorId: this.editorId
38
+ });
39
+
40
+ // Store popup instance in editor
41
+ currentEditor.setPopupInstance('tag', tagPopup);
42
+ }
43
+
44
+ this.tagPopup = tagPopup;
45
+ }
46
+
47
+ /**
48
+ * Create a new Tag format instance for a specific editor
49
+ * @param {string} editorId - Editor instance ID
50
+ * @returns {Tag} Tag format instance
51
+ */
52
+ static createForEditor(editorId) {
53
+ const editor = Editor.getInstanceById(editorId);
54
+ if (!editor) {
55
+ console.warn('No editor instance found for ID:', editorId);
56
+ return null;
57
+ }
58
+
59
+ // Temporarily set as current instance
60
+ const originalCurrent = Editor.currentInstance;
61
+ Editor.currentInstance = editor;
62
+
63
+ // Create format instance
64
+ const format = new Tag();
65
+
66
+ // Restore original current instance
67
+ Editor.currentInstance = originalCurrent;
68
+
69
+ return format;
70
+ }
71
+
72
+ /**
73
+ * Create tag element
74
+ * @param {string} tagType - Type of tag (@, #, custom)
75
+ * @param {string} content - Tag content
76
+ * @returns {HTMLElement}
77
+ */
78
+ static create(tagType, content) {
79
+ const span = document.createElement('SPAN');
80
+ span.className = `custom-tag tag-${tagType}`;
81
+
82
+ let displayText = content;
83
+ if (tagType === 'mention') {
84
+ displayText = `@${content}`;
85
+ } else if (tagType === 'hashtag') {
86
+ displayText = `#${content}`;
87
+ } else if (tagType === 'custom') {
88
+ displayText = `<${content}>`;
89
+ }
90
+
91
+ span.textContent = displayText;
92
+ span.setAttribute('data-tag-type', tagType);
93
+ span.setAttribute('data-tag-content', content);
94
+ span.setAttribute('contenteditable', 'false');
95
+
96
+ return span;
97
+ }
98
+
99
+ /**
100
+ * Insert tag at current cursor position
101
+ * @param {string} tagType - Type of tag
102
+ * @param {string} content - Tag content
103
+ * @param {string} editorId - Editor instance ID
104
+ */
105
+ static insertTagAtCurrentPosition(tagType, content, editorId = null) {
106
+ // Get the correct editor instance
107
+ let editor = null;
108
+ if (editorId) {
109
+ editor = Editor.getInstanceById(editorId);
110
+ } else {
111
+ editor = Editor.getCurrentInstance();
112
+ }
113
+
114
+ if (!editor) {
115
+ console.warn('No editor instance found for tag insertion');
116
+ return;
117
+ }
118
+
119
+ // Use saved range if available, otherwise get current selection
120
+ const selection = window.getSelection();
121
+ if (!selection) return;
122
+
123
+ try {
124
+ // Restore saved range if exists for this editor
125
+ const savedRange = Tag.savedRanges.get(editorId);
126
+ if (savedRange) {
127
+ selection.removeAllRanges();
128
+ selection.addRange(savedRange);
129
+ Tag.savedRanges.delete(editorId);
130
+ } else if (!selection.rangeCount) {
131
+ return;
132
+ }
133
+
134
+ const range = selection.getRangeAt(0);
135
+
136
+ // Create tag element
137
+ const tagElement = Tag.create(tagType, content);
138
+
139
+ // Insert tag at cursor position
140
+ range.deleteContents();
141
+ range.insertNode(tagElement);
142
+
143
+ // Add a space after the tag for easier editing
144
+ const spaceNode = document.createTextNode(' ');
145
+ range.setStartAfter(tagElement);
146
+ range.insertNode(spaceNode);
147
+
148
+ // Position cursor after the space
149
+ range.setStartAfter(spaceNode);
150
+ range.collapse(true);
151
+ selection.removeAllRanges();
152
+ selection.addRange(range);
153
+
154
+ // Focus back on editor
155
+ if (editor && editor.element) {
156
+ editor.element.focus();
157
+ }
158
+
159
+ // Trigger content change event
160
+ if (editor && typeof editor.onContentChange === 'function') {
161
+ editor.onContentChange();
162
+ }
163
+
164
+ } catch (error) {
165
+ console.error('Error inserting tag:', error);
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Apply tag formatting - shows tag popup
171
+ */
172
+ apply(tagType, content) {
173
+ if (tagType && content) {
174
+ Tag.insertTagAtCurrentPosition(tagType, content, this.editorId);
175
+ } else {
176
+ // Save current selection before showing popup
177
+ const selection = window.getSelection();
178
+ if (selection && selection.rangeCount > 0) {
179
+ Tag.savedRanges.set(this.editorId, selection.getRangeAt(0).cloneRange());
180
+ }
181
+ this.showTagPopup();
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Remove tag formatting
187
+ */
188
+ remove() {
189
+ const selection = window.getSelection();
190
+ if (!selection || !selection.rangeCount) return;
191
+
192
+ const range = selection.getRangeAt(0);
193
+ const tagElement = this.getTagElement(range);
194
+
195
+ if (tagElement) {
196
+ // Replace tag element with its text content
197
+ const textNode = document.createTextNode(tagElement.textContent);
198
+ tagElement.parentNode.replaceChild(textNode, tagElement);
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Toggle tag formatting - shows tag popup
204
+ */
205
+ toggle() {
206
+ if (this.tagPopup.isVisible) {
207
+ this.tagPopup.hide();
208
+ } else {
209
+ // Save current selection before showing popup
210
+ const selection = window.getSelection();
211
+ if (selection && selection.rangeCount > 0) {
212
+ Tag.savedRanges.set(this.editorId, selection.getRangeAt(0).cloneRange());
213
+ }
214
+ this.showTagPopup();
215
+ }
216
+ }
217
+
218
+ /**
219
+ * Show tag popup
220
+ */
221
+ showTagPopup() {
222
+ // Find tag button in the current editor's toolbar
223
+ const editor = Editor.getInstanceById(this.editorId);
224
+ if (!editor) return;
225
+
226
+ const toolbar = editor.getModule('toolbar');
227
+ let tagButton = null;
228
+
229
+ if (toolbar) {
230
+ tagButton = toolbar.getButton('tag');
231
+ }
232
+
233
+ // Fallback: find button by class in the current editor's toolbar
234
+ if (!tagButton) {
235
+ const toolbarContainer = toolbar?.getContainer();
236
+ if (toolbarContainer) {
237
+ tagButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.tag-btn');
238
+ }
239
+ }
240
+
241
+ // Final fallback: find any tag button in the current editor's wrapper
242
+ if (!tagButton) {
243
+ tagButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.tag-btn');
244
+ }
245
+
246
+ if (!tagButton) {
247
+ console.warn('Tag button not found for editor:', this.editorId);
248
+ return;
249
+ }
250
+
251
+ this.tagPopup.show(tagButton);
252
+ }
253
+
254
+ /**
255
+ * Check if tag formatting is active
256
+ */
257
+ isActive() {
258
+ const selection = window.getSelection();
259
+ if (!selection || !selection.rangeCount) return false;
260
+
261
+ const range = selection.getRangeAt(0);
262
+ return this.getTagElement(range) !== null;
263
+ }
264
+
265
+ /**
266
+ * Get tag element from selection
267
+ * @param {Range} range - Selection range
268
+ * @returns {HTMLElement|null}
269
+ */
270
+ getTagElement(range) {
271
+ let node = range.commonAncestorContainer;
272
+
273
+ // If it's a text node, get its parent
274
+ if (node.nodeType === Node.TEXT_NODE) {
275
+ node = node.parentNode;
276
+ }
277
+
278
+ // Check if current node is a tag
279
+ if (node.classList && node.classList.contains('custom-tag')) {
280
+ return node;
281
+ }
282
+
283
+ // Check if selection contains a tag
284
+ const tagInSelection = range.cloneContents().querySelector('.custom-tag');
285
+ return tagInSelection || null;
286
+ }
287
+
288
+ /**
289
+ * Get predefined tag suggestions
290
+ * @param {string} tagType - Type of tag
291
+ * @returns {Array} - Array of suggestions
292
+ */
293
+ static getSuggestions(tagType) {
294
+ const suggestions = {
295
+ mention: ['john', 'sarah', 'admin', 'team', 'support'],
296
+ hashtag: ['urgent', 'todo', 'done', 'review', 'important'],
297
+ custom: ['note', 'warning', 'tip', 'info', 'success']
298
+ };
299
+
300
+ return suggestions[tagType] || [];
301
+ }
302
+ }
303
+
304
+ export default Tag;