leksy-editor 2.6.0 → 3.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.
package/tab.js DELETED
@@ -1,709 +0,0 @@
1
- import { v4 as uuidv4 } from 'uuid';
2
- import { SVG } from "./constant";
3
- import { destroyAnchorPopover, destroyImageResizer, destroyTableEditPlugin, insertOutlinesItems } from './utilities';
4
-
5
- const initTabs = (core, options) => {
6
- if (!options.showTabs) return;
7
-
8
- const tabsContainer = document.createElement('div');
9
- tabsContainer.className = `tabs-container`;
10
- core.elements.tabsContainer = tabsContainer;
11
- core.elements.placeholder.style.visibility = 'hidden';
12
- core.elements.iframeWindow.body.style.display = 'flex';
13
- core.elements.iframeWindow.body.prepend(tabsContainer);
14
- renderTabs(core, options);
15
- }
16
-
17
- const findTabLocation = (list, tabId, parent = null) => {
18
- for (let i = 0; i < list.length; i++) {
19
- if (list[i].tabId === tabId) {
20
- return { list, index: i, parent };
21
- }
22
- if (list[i].children) {
23
- const result = findTabLocation(list[i].children, tabId, list[i]);
24
- if (result) return result;
25
- }
26
- }
27
- return null;
28
- };
29
-
30
- const findTab = (tabs, tabId) => {
31
- for (const tab of tabs) {
32
- if (tab.tabId === tabId) return tab;
33
- if (tab.children) {
34
- const found = findTab(tab.children, tabId);
35
- if (found) return found;
36
- }
37
- }
38
- return null;
39
- };
40
-
41
- const isDescendant = (tabs, parentId, childId) => {
42
- const parent = findTab(tabs, parentId);
43
- if (!parent?.children) return false;
44
- return findTab(parent.children, childId) !== null;
45
- };
46
-
47
- const renderTabs = (core, options) => {
48
- if (!core.elements.tabsContainer) return;
49
-
50
- core.onChange(core.html);
51
- core.elements.tabsContainer.innerHTML = '';
52
-
53
- const renderLevel = (tabs, container, level) => {
54
- tabs.forEach(tab => {
55
- const tabItem = document.createElement('div');
56
- tabItem.className = `tab-item`;
57
- tabItem.classList.toggle('active', tab.tabId === core.state.currentTabId)
58
-
59
- tabItem.draggable = true;
60
-
61
- tabItem.ondragstart = (e) => {
62
- e.stopPropagation();
63
- core.state.draggedTabId = tab.tabId;
64
- e.dataTransfer.effectAllowed = 'move';
65
- tabItem.style.opacity = '0.5';
66
- };
67
-
68
- tabItem.ondragend = (e) => {
69
- e.stopPropagation();
70
- delete core.state.draggedTabId;
71
- tabItem.style.opacity = '1';
72
- renderTabs(core, options);
73
- };
74
-
75
- tabItem.ondragover = (e) => {
76
- e.preventDefault();
77
- e.stopPropagation();
78
- const draggedId = core.state.draggedTabId;
79
- if (!draggedId || draggedId === tab.tabId) return;
80
- if (isDescendant(core.state.tabs, draggedId, tab.tabId)) return;
81
-
82
- const rect = tabItem.getBoundingClientRect();
83
- const relY = e.clientY - rect.top;
84
- const height = rect.height;
85
-
86
- tabItem.style.borderTop = '';
87
- tabItem.style.borderBottom = '1px solid #eee';
88
- tabItem.style.background = '';
89
-
90
- if (relY < height * 0.25) {
91
- tabItem.style.borderTop = '2px solid var(--primary)';
92
- } else if (relY > height * 0.75) {
93
- tabItem.style.borderBottom = '2px solid var(--primary)';
94
- } else {
95
- tabItem.style.background = 'rgba(0,0,0,0.05)';
96
- }
97
- };
98
-
99
- tabItem.ondragleave = (e) => {
100
- e.stopPropagation();
101
- tabItem.style.borderTop = '';
102
- tabItem.style.borderBottom = '1px solid #eee';
103
- tabItem.style.background = '';
104
- };
105
-
106
- tabItem.ondrop = (e) => {
107
- e.preventDefault();
108
- e.stopPropagation();
109
- const draggedId = core.state.draggedTabId;
110
- if (!draggedId || draggedId === tab.tabId) return;
111
- if (isDescendant(core.state.tabs, draggedId, tab.tabId) || level > 1) return;
112
-
113
- const rect = tabItem.getBoundingClientRect();
114
- const relY = e.clientY - rect.top;
115
- const height = rect.height;
116
-
117
- if (relY < height * 0.25) {
118
- moveTabRelative(core, draggedId, tab.tabId, 'before', options);
119
- } else if (relY > height * 0.75) {
120
- moveTabRelative(core, draggedId, tab.tabId, 'after', options);
121
- } else {
122
- moveTabInto(core, draggedId, tab.tabId, options);
123
- }
124
- };
125
-
126
- let clickTimer;
127
-
128
- tabItem.onclick = () => {
129
- clearTimeout(clickTimer);
130
- clickTimer = setTimeout(() => {
131
- if (tab.tabId === core.state.currentTabId) {
132
- tab.showOutlines = !tab.showOutlines;
133
- renderTabs(core, options);
134
- }
135
- switchTab(core, tab.tabId, options);
136
- }, 250);
137
- };
138
-
139
- tabItem.ondblclick = () => {
140
- if (options.disabled) return
141
- clearTimeout(clickTimer);
142
- renameTab(core, tab.tabId, titleInput, options);
143
- };
144
-
145
- const leftSection = document.createElement('div');
146
- leftSection.className = 'left'
147
- leftSection.style.paddingLeft = `${level * 12}px`
148
-
149
- const hasChildren = tab.children && tab.children.length > 0;
150
- const arrow = document.createElement('span');
151
- arrow.style.width = '20px'
152
- arrow.style.height = '20px';
153
- arrow.style.marginRight = '4px';
154
- arrow.style.cursor = 'pointer';
155
- arrow.style.transition = 'transform 0.2s';
156
-
157
- if (hasChildren) {
158
- arrow.innerHTML = SVG.ARROW_DROP_DOWN_FILL;
159
- arrow.style.transform = tab.isExpanded ? 'rotate(0deg)' : 'rotate(-90deg)';
160
- arrow.onclick = (e) => {
161
- e.stopPropagation();
162
- tab.isExpanded = !tab.isExpanded;
163
- renderTabs(core, options);
164
- };
165
- }
166
-
167
- const titleInput = document.createElement('input');
168
- titleInput.value = tab.tabTitle || 'Untitled Tab';
169
- titleInput.disabled = true;
170
- titleInput.className = 'tab-input';
171
-
172
- const menuBtn = document.createElement('span');
173
- menuBtn.className = 'tab-icons tab-dropdown';
174
- menuBtn.innerHTML = SVG.MORE;
175
-
176
- menuBtn.onclick = (e) => {
177
- e.stopPropagation();
178
- showContextMenu(core, tab, e, level, options);
179
- };
180
-
181
-
182
- const span = document.createElement('span');
183
- span.className = 'tab-icons';
184
- span.innerHTML = SVG.TABLE_OF_CONTENT;
185
-
186
- leftSection.appendChild(arrow);
187
- leftSection.appendChild(span);
188
- leftSection.appendChild(titleInput);
189
- tabItem.appendChild(leftSection);
190
- if (!options.disabled) tabItem.appendChild(menuBtn);
191
-
192
- const tabItemContainer = document.createElement('div');
193
- tabItemContainer.id = tab.tabId;
194
- tabItemContainer.appendChild(tabItem);
195
-
196
- const outlineContainer = document.createElement('div');
197
- outlineContainer.className = 'outline-items';
198
- outlineContainer.style.paddingLeft = `${32 + level * 12}px`
199
- tabItemContainer.appendChild(outlineContainer);
200
- insertOutlinesItems(core, options, tab, outlineContainer)
201
- container.appendChild(tabItemContainer);
202
- core.elements.tabs[tab.tabId] = tabItemContainer
203
-
204
- if (tab.children && tab.children.length > 0 && tab.isExpanded) {
205
- const childrenContainer = document.createElement('div');
206
- renderLevel(tab.children, childrenContainer, level + 1);
207
- container.appendChild(childrenContainer);
208
- }
209
- });
210
- };
211
-
212
- const tabListContainer = document.createElement('div');
213
- tabListContainer.className = 'tab-items'
214
- tabListContainer.addEventListener('scroll', () => {
215
- core.state.tabItemsScrollHeight = tabListContainer.scrollTop;
216
- });
217
- renderLevel(core.state.tabs, tabListContainer, 0);
218
- core.elements.tabsContainer.appendChild(tabListContainer);
219
- tabListContainer.scrollTop = core.state.tabItemsScrollHeight || 0;
220
-
221
- const tabHeader = document.createElement('span');
222
- tabHeader.className = 'tab-header';
223
-
224
- const headerTitle = document.createElement('span');
225
- headerTitle.innerHTML = "Tabs";
226
-
227
- const rightDiv = document.createElement('div')
228
- const addBtn = document.createElement('button');
229
- addBtn.className = 'tab-add-button'
230
- addBtn.type = 'button';
231
- addBtn.innerHTML = SVG.PLUS;
232
- addBtn.onclick = () => createTab(core, options);
233
-
234
- const expandBtn = document.createElement('button');
235
- expandBtn.className = 'tab-expand-button'
236
- expandBtn.type = 'button';
237
- expandBtn.innerHTML = SVG.ARROW_DOWN;
238
- expandBtn.onclick = () => {
239
- core.state.isTabExpandInResponsive = !core.state.isTabExpandInResponsive
240
- expandBtn.classList.toggle('show')
241
- tabListContainer.classList.toggle('show')
242
- }
243
- requestAnimationFrame(() => {
244
- tabListContainer.classList.add('animate');
245
- });
246
- if (core.state.isTabExpandInResponsive) {
247
- expandBtn.classList.add('show')
248
- tabListContainer.classList.add('show')
249
- }
250
-
251
- if (!options.disabled) rightDiv.appendChild(addBtn);
252
- rightDiv.appendChild(expandBtn);
253
-
254
- tabHeader.appendChild(headerTitle);
255
- tabHeader.appendChild(rightDiv);
256
- core.elements.tabsContainer.prepend(tabHeader);
257
- }
258
-
259
- const showContextMenu = (core, tab, event, level, options) => {
260
- const existingMenu = core.elements.iframeWindow.getElementById('leksy-editor-tab-context-menu');
261
- if (existingMenu) existingMenu.remove();
262
-
263
- const menu = document.createElement('div');
264
- menu.id = 'leksy-editor-tab-context-menu';
265
-
266
- // temporary visibility to measure
267
- menu.style.visibility = 'hidden';
268
- menu.style.display = 'block';
269
-
270
- const closeMenu = (e) => {
271
- if (!menu.contains(e.target)) {
272
- menu.remove();
273
- core.elements.iframeWindow.removeEventListener('click', closeMenu);
274
- }
275
- };
276
-
277
- const createOption = (text, icon, onClick) => {
278
- const item = document.createElement('div');
279
- item.innerHTML = icon + text;
280
- item.className = 'tab-menu-item';
281
- item.onclick = (e) => {
282
- e.stopPropagation();
283
- menu.remove();
284
- core.elements.iframeWindow.removeEventListener('click', closeMenu);
285
- onClick();
286
- };
287
- return item;
288
- };
289
-
290
- const createSubmenuOption = (text, items) => {
291
- const item = document.createElement('div');
292
- item.className = 'tab-menu-item';
293
- item.style.position = 'relative';
294
-
295
- const span = document.createElement('span');
296
- span.innerHTML = SVG.PLAY_LIST + text;
297
- const arrow = document.createElement('span');
298
- arrow.innerHTML = SVG.ARROW_DROP_RIGHT;
299
- arrow.style.width = '20px';
300
- arrow.style.height = '20px';
301
- arrow.style.marginLeft = '4px';
302
- arrow.style.display = 'flex';
303
- arrow.style.alignItems = 'center';
304
-
305
- item.appendChild(span);
306
- item.appendChild(arrow);
307
-
308
- let submenu;
309
-
310
- item.onmouseenter = () => {
311
- submenu = document.createElement('div');
312
- submenu.className = 'tab-submenu';
313
- submenu.style.position = 'absolute';
314
- submenu.style.left = '100%';
315
- submenu.style.top = '0';
316
- submenu.style.visibility = 'hidden';
317
-
318
- items.forEach(subItem => {
319
- const subOption = createOption(subItem.tabTitle, SVG.TABLE_OF_CONTENT, () => {
320
- moveTabInto(core, tab.tabId, subItem.tabId, options);
321
- });
322
-
323
- if (subItem.level > 0) {
324
- subOption.style.paddingLeft = `${16 + subItem.level * 10}px`;
325
- }
326
-
327
- submenu.appendChild(subOption);
328
- });
329
-
330
- item.appendChild(submenu);
331
-
332
- const rect = submenu.getBoundingClientRect();
333
- const itemRect = item.getBoundingClientRect();
334
-
335
- const viewportWidth = core.elements.iframeWindow.documentElement.clientWidth;
336
- const viewportHeight = core.elements.iframeWindow.documentElement.clientHeight;
337
-
338
- // if no space on right → open left
339
- if (rect.right > viewportWidth) {
340
- submenu.style.left = 'auto';
341
- submenu.style.right = '100%';
342
- }
343
-
344
- // if no space bottom → shift up
345
- if (rect.bottom > viewportHeight) {
346
- submenu.style.top = `${viewportHeight - itemRect.top - rect.height}px`;
347
- }
348
-
349
- submenu.style.visibility = 'visible';
350
- };
351
-
352
- item.onmouseleave = () => {
353
- if (submenu) submenu.remove();
354
- submenu = null;
355
- };
356
-
357
- return item;
358
- };
359
-
360
- const location = findTabLocation(core.state.tabs, tab.tabId);
361
-
362
- if (level < 2) {
363
- menu.appendChild(createOption('Add subtab', SVG.PLUS, () => addSublist(core, tab, options)));
364
- }
365
-
366
- menu.appendChild(createOption('Duplicate', SVG.COPY, () => duplicateTab(core, tab, options)));
367
-
368
- menu.appendChild(createOption('Rename', SVG.PENCIL_LINE, () => {
369
- const tabItem = event.target.closest(`.tab-item`);
370
- if (tabItem) {
371
- const input = tabItem.querySelector('input');
372
- if (input) renameTab(core, tab.tabId, input, options);
373
- }
374
- }));
375
-
376
- menu.appendChild(createOption('Delete', SVG.DELETE, () => deleteTab(core, tab.tabId, options)));
377
-
378
- menu.appendChild(createOption('Move up', SVG.ARROW_UP, () => moveTab(core, tab.tabId, 'up', options)));
379
- menu.appendChild(createOption('Move down', SVG.ARROW_DOWN_LONG, () => moveTab(core, tab.tabId, 'down', options)));
380
-
381
- const getTargets = (list, currentTabId, level = 0) => {
382
- let targets = [];
383
- for (const t of list) {
384
- if (t.tabId === currentTabId || level > 1) continue;
385
- targets.push({ tabId: t.tabId, tabTitle: t.tabTitle, level });
386
- if (t.children) targets = targets.concat(getTargets(t.children, currentTabId, level + 1));
387
- }
388
- return targets;
389
- };
390
- const targets = getTargets(core.state.tabs, tab.tabId);
391
- if (targets.length > 0) {
392
- menu.appendChild(createSubmenuOption('Move into', targets));
393
- }
394
-
395
- if (location?.parent) {
396
- menu.appendChild(createOption('Move out', SVG.DELETE, () => outdentTab(core, tab.tabId, options)));
397
- }
398
-
399
- menu.appendChild(createOption(tab.showOutlines ? 'Hide outlines' : 'Show outlines', SVG.LIST_BULLETS, () => {
400
- tab.showOutlines = !tab.showOutlines;
401
- renderTabs(core, options);
402
- if (tab.showOutlines) switchTab(core, tab.tabId, options);
403
- }));
404
-
405
- core.elements.iframeWindow.body.appendChild(menu);
406
- core.elements.iframeWindow.addEventListener('click', closeMenu);
407
-
408
- const rect = menu.getBoundingClientRect();
409
- const menuWidth = rect.width;
410
- const menuHeight = rect.height;
411
-
412
- const viewportWidth = core.elements.iframeWindow.documentElement.clientWidth;
413
- const viewportHeight = core.elements.iframeWindow.documentElement.clientHeight;
414
-
415
- let left = event.clientX;
416
- let top = event.clientY;
417
-
418
- // if not enough space on right → open to left
419
- if (left + menuWidth > viewportWidth) {
420
- left = event.clientX - menuWidth;
421
- }
422
-
423
- // if not enough space on bottom → open above
424
- if (top + menuHeight > viewportHeight) {
425
- top = event.clientY - menuHeight;
426
- }
427
-
428
- menu.style.left = `${left}px`;
429
- menu.style.top = `${top}px`;
430
- menu.style.visibility = 'visible';
431
-
432
-
433
- };
434
-
435
- const createTab = (core, options) => {
436
- if (options.disabled) return
437
- const newTab = {
438
- tabTitle: `Tab ${core.state.tabs.length + 1}`,
439
- tabId: uuidv4(),
440
- content: '<div><br></div>',
441
- children: []
442
- };
443
-
444
- core.state.tabs.push(newTab);
445
- core.history.tabs[newTab.tabId] = { stack: [], currentIndex: -1 };
446
-
447
- switchTab(core, newTab.tabId, options);
448
- }
449
-
450
- const switchTab = (core, tabId, options) => {
451
- if (core.state.currentTabId === tabId) return;
452
-
453
- destroyImageResizer(options, core)
454
- destroyTableEditPlugin(options, core)
455
- destroyAnchorPopover(core)
456
-
457
- const currentTab = findTab(core.state.tabs, core.state.currentTabId);
458
- if (currentTab) {
459
- currentTab.content = core.elements.editor.innerHTML;
460
- }
461
-
462
- const nextTab = findTab(core.state.tabs, tabId);
463
- if (nextTab) {
464
- core.state.currentTabId = nextTab.tabId;
465
- core.elements.editor.innerHTML = nextTab.content;
466
- renderTabs(core, options);
467
-
468
- core.updateCaretPosition();
469
- core.elements.editor.focus();
470
- }
471
- }
472
-
473
- const deleteTab = (core, tabId, options) => {
474
- const deleteFromList = (list) => {
475
- const index = list.findIndex(t => t.tabId === tabId);
476
- if (index > -1) {
477
- list.splice(index, 1);
478
- return true;
479
- }
480
- for (const tab of list) {
481
- if (tab.children && deleteFromList(tab.children)) return true;
482
- }
483
- return false;
484
- };
485
-
486
- const tabToDelete = findTab(core.state.tabs, tabId);
487
- if (!tabToDelete) return;
488
-
489
- const isCurrentTabAffected = (tab) => {
490
- if (tab.tabId === core.state.currentTabId) return true;
491
- if (tab.children) {
492
- return tab.children.some(child => isCurrentTabAffected(child));
493
- }
494
- return false;
495
- };
496
-
497
- const shouldSwitch = isCurrentTabAffected(tabToDelete);
498
-
499
- deleteFromList(core.state.tabs);
500
- delete core.history.tabs[tabId];
501
-
502
- if (shouldSwitch) {
503
- if (core.state.tabs.length > 0) {
504
- switchTab(core, core.state.tabs[0].tabId, options);
505
- } else {
506
- createTab(core, options);
507
- }
508
- } else {
509
- renderTabs(core, options);
510
- }
511
- }
512
-
513
- const duplicateTab = (core, tab, options) => {
514
- const newTab = structuredClone(tab);
515
- newTab.tabId = uuidv4();
516
- newTab.tabTitle = `${newTab.tabTitle} (Copy)`;
517
-
518
- const updateIds = (t) => {
519
- t.tabId = uuidv4();
520
- core.history.tabs[t.tabId] = { stack: [], currentIndex: -1 };
521
- if (t.children) t.children.forEach(updateIds);
522
- };
523
- if (newTab.children) newTab.children.forEach(updateIds);
524
-
525
- core.history.tabs[newTab.tabId] = { stack: [], currentIndex: -1 };
526
-
527
- const insertIntoList = (list) => {
528
- const index = list.findIndex(t => t.tabId === tab.tabId);
529
- if (index > -1) {
530
- list.splice(index + 1, 0, newTab);
531
- return true;
532
- }
533
- for (const t of list) {
534
- if (t.children && insertIntoList(t.children)) return true;
535
- }
536
- return false;
537
- };
538
-
539
- insertIntoList(core.state.tabs);
540
- renderTabs(core, options);
541
- };
542
-
543
- const addSublist = (core, parentTab, options) => {
544
- const newTab = {
545
- tabTitle: `Subtab ${(parentTab.children?.length || 0) + 1}`,
546
- tabId: uuidv4(),
547
- content: '<div><br></div>',
548
- children: []
549
- };
550
- core.history.tabs[newTab.tabId] = { stack: [], currentIndex: -1 };
551
-
552
- if (!parentTab.children) parentTab.children = [];
553
- parentTab.children.push(newTab);
554
- parentTab.isExpanded = true;
555
-
556
- renderTabs(core, options);
557
- switchTab(core, newTab.tabId, options);
558
- };
559
-
560
- const moveTab = (core, tabId, direction, options) => {
561
- const moveInList = (list) => {
562
- const index = list.findIndex(t => t.tabId === tabId);
563
- if (index > -1) {
564
- if (direction === 'up' && index > 0) {
565
- [list[index - 1], list[index]] = [list[index], list[index - 1]];
566
- return true;
567
- }
568
- if (direction === 'down' && index < list.length - 1) {
569
- [list[index], list[index + 1]] = [list[index + 1], list[index]];
570
- return true;
571
- }
572
- return false;
573
- }
574
- for (const tab of list) {
575
- if (tab.children && moveInList(tab.children)) return true;
576
- }
577
- return false;
578
- };
579
-
580
- if (moveInList(core.state.tabs)) {
581
- renderTabs(core, options);
582
- }
583
- };
584
-
585
- const moveTabRelative = (core, tabId, targetTabId, position, options) => {
586
- const sourceLoc = findTabLocation(core.state.tabs, tabId);
587
- if (!sourceLoc) return;
588
-
589
- const [tab] = sourceLoc.list.splice(sourceLoc.index, 1);
590
- const targetLoc = findTabLocation(core.state.tabs, targetTabId);
591
-
592
- if (targetLoc) {
593
- if (position === 'before') {
594
- targetLoc.list.splice(targetLoc.index, 0, tab);
595
- } else {
596
- targetLoc.list.splice(targetLoc.index + 1, 0, tab);
597
- }
598
- } else {
599
- sourceLoc.list.splice(sourceLoc.index, 0, tab);
600
- }
601
- renderTabs(core, options);
602
- };
603
-
604
- const moveTabInto = (core, tabId, targetTabId, options) => {
605
- const sourceLoc = findTabLocation(core.state.tabs, tabId);
606
- if (!sourceLoc) return;
607
-
608
- const [tab] = sourceLoc.list.splice(sourceLoc.index, 1);
609
- const targetTab = findTab(core.state.tabs, targetTabId);
610
-
611
- if (targetTab) {
612
- if (!targetTab.children) targetTab.children = [];
613
- targetTab.children.push(tab);
614
- targetTab.isExpanded = true;
615
- }
616
- renderTabs(core, options);
617
- };
618
-
619
- const outdentTab = (core, tabId, options) => {
620
- const location = findTabLocation(core.state.tabs, tabId);
621
- if (!location) return;
622
- const { list, index, parent } = location;
623
-
624
- if (parent) {
625
- const parentLocation = findTabLocation(core.state.tabs, parent.tabId);
626
- if (!parentLocation) return;
627
-
628
- const { list: parentList, index: parentIndex } = parentLocation;
629
- const tab = list[index];
630
-
631
- list.splice(index, 1);
632
- parentList.splice(parentIndex + 1, 0, tab);
633
-
634
- renderTabs(core, options);
635
- }
636
- };
637
-
638
- const renameTab = (core, tabId, titleInput, options) => {
639
- const tab = findTab(core.state.tabs, tabId);
640
- if (!tab) return;
641
-
642
- titleInput.disabled = false;
643
- titleInput.style.pointerEvents = 'auto';
644
- titleInput.style.outline = '1px solid gray';
645
-
646
- titleInput.onclick = (e) => e.stopPropagation();
647
-
648
- let saved = false;
649
- const save = () => {
650
- if (saved) return;
651
- saved = true;
652
- if (titleInput.value.trim()) {
653
- tab.tabTitle = titleInput.value.trim();
654
- }
655
- renderTabs(core, options);
656
- };
657
-
658
- titleInput.onblur = save;
659
- titleInput.onkeydown = (e) => {
660
- if (e.key === 'Enter') {
661
- save();
662
- }
663
- };
664
-
665
- titleInput.focus();
666
- titleInput.select();
667
- }
668
-
669
- const validateAndFixTabId = (tabs) => {
670
- const ids = new Set();
671
- const traverse = (list) => {
672
- list.forEach(tab => {
673
- if (!tab.tabId || ids.has(tab.tabId)) {
674
- tab.tabId = uuidv4();
675
- }
676
- if (!tab.content) tab.content = '<div><br></div>';
677
- if (!tab.tabTitle) tab.tabTitle = 'Untitled Tab';
678
-
679
- ids.add(tab.tabId);
680
- if (tab.children && tab.children.length > 0) {
681
- traverse(tab.children);
682
- }
683
- });
684
- };
685
- traverse(tabs);
686
- }
687
-
688
- const prepareTabs = (value) => {
689
- let initialTabs = [];
690
- if (Array.isArray(value) && value.length) {
691
- validateAndFixTabId(value);
692
- initialTabs = value;
693
- } else {
694
- initialTabs = [{
695
- tabTitle: 'Tab 1',
696
- tabId: uuidv4(),
697
- content: typeof value === 'string' ? value : '<div><br></div>',
698
- children: []
699
- }];
700
- }
701
- return initialTabs
702
- }
703
-
704
- export {
705
- initTabs,
706
- renderTabs,
707
- findTab,
708
- prepareTabs,
709
- }