leksy-editor 2.0.0 → 2.1.1

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/constant.js CHANGED
@@ -390,6 +390,7 @@ const CSS = {
390
390
  overflow-y: auto;
391
391
  z-index: 9999;
392
392
  color: var(--text-color);
393
+ height: 100vh;
393
394
  }
394
395
 
395
396
  .tabs-container .tab-header {
@@ -401,14 +402,36 @@ const CSS = {
401
402
  align-items: center;
402
403
  border-bottom: 1px solid var(--base-white-dark);
403
404
  }
404
- .tabs-container .tab-header .tab-add-button {
405
+ .tabs-container .tab-header .tab-add-button,
406
+ .tabs-container .tab-header .tab-expand-button {
405
407
  cursor: pointer;
406
408
  border: none;
407
409
  background: transparent;
410
+ width: 24px;
411
+ height: 24px;
408
412
  }
409
- .tabs-container .tab-header .tab-add-button svg {
413
+ .tabs-container .tab-header .tab-expand-button {
414
+ display: none;
415
+ }
416
+ .tabs-container .tab-header .tab-add-button svg,
417
+ .tabs-container .tab-header .tab-expand-button svg {
410
418
  fill: var(--text-color);
411
419
  }
420
+ .tabs-container .tab-header .tab-expand-button svg {
421
+ margin-bottom: 4px;
422
+ rotate: 180deg;
423
+ }
424
+ .tabs-container .tab-header .tab-expand-button.show svg {
425
+ rotate: 0deg;
426
+ }
427
+
428
+ .tabs-container .tab-items {
429
+ overflow-y: auto;
430
+ }
431
+
432
+ .tabs-container .tab-items.animate {
433
+ transition: max-height 0.3s ease, opacity 0.2s ease;
434
+ }
412
435
 
413
436
  .tabs-container .tab-items .tab-item {
414
437
  padding: 8px;
@@ -515,6 +538,7 @@ const CSS = {
515
538
  box-shadow: 0 2px 5px var(--shadow);
516
539
  z-index: 10001;
517
540
  max-height: 200px;
541
+ overflow-y: auto;
518
542
  }
519
543
  #leksy-editor-tab-context-menu .tab-submenu .tab-menu-item {
520
544
  text-overflow: ellipsis;
@@ -529,7 +553,35 @@ const CSS = {
529
553
  height: 14px;
530
554
  margin-right: 4px;
531
555
  }
532
- `
556
+ `,
557
+ IFRAME_MEDIA_QUERY: `
558
+ @media only screen and (max-width: 991px) {
559
+ .tabs-container {
560
+ position: fixed;
561
+ outline: 1px solid grey;
562
+ width: 100%;
563
+ bottom: 0px;
564
+ border-radius: 16px 16px 0 0;
565
+ }
566
+ .tabs-container {
567
+ height: unset;
568
+ }
569
+ .tabs-container .tab-items {
570
+ max-height: 0;
571
+ opacity: 0;
572
+ }
573
+ .tabs-container .tab-header .tab-expand-button {
574
+ display: inline-block;
575
+ }
576
+ .tabs-container .tab-items.show {
577
+ max-height: 30vh;
578
+ opacity: 1;
579
+ }
580
+ .tabs-container .tab-items .tab-item .tab-dropdown {
581
+ visibility: visible;
582
+ }
583
+ }
584
+ `,
533
585
  }
534
586
 
535
587
  const RESIZER_PLUGINS = [
package/index.js CHANGED
@@ -641,6 +641,7 @@ class LeksyEditor {
641
641
  ${CSS.IFRAME_EDITOR}
642
642
  ${CSS.IFRAME_RESIZER}
643
643
  ${CSS.IFRAME_TAB}
644
+ ${CSS.IFRAME_MEDIA_QUERY}
644
645
  ${options.iframeStyle || ''}
645
646
  </style>
646
647
  </head>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leksy-editor",
3
- "version": "2.0.0",
3
+ "version": "2.1.1",
4
4
  "description": "Leksy Editor is an alternative to traditional WYSIWYG editors, designed primarily for creating mail templates, blogs, and documents without any content manipulation.",
5
5
  "main": "index.js",
6
6
  "directories": {
package/tab.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { v4 as uuidv4 } from 'uuid';
2
2
  import { SVG } from "./constant";
3
- import { insertOutlinesItems } from './utilities';
3
+ import { destroyAnchorPopover, destroyImageResizer, destroyTableEditPlugin, insertOutlinesItems } from './utilities';
4
4
 
5
5
  const initTabs = (core, options) => {
6
6
  if (!options.showTabs) return;
@@ -133,7 +133,7 @@ const renderTabs = (core, options) => {
133
133
  renderTabs(core, options);
134
134
  }
135
135
  switchTab(core, tab.tabId, options);
136
- }, 200);
136
+ }, 250);
137
137
  };
138
138
 
139
139
  tabItem.ondblclick = () => {
@@ -210,8 +210,12 @@ const renderTabs = (core, options) => {
210
210
 
211
211
  const tabListContainer = document.createElement('div');
212
212
  tabListContainer.className = 'tab-items'
213
+ tabListContainer.addEventListener('scroll', () => {
214
+ core.state.tabItemsScrollHeight = tabListContainer.scrollTop;
215
+ });
213
216
  renderLevel(core.state.tabs, tabListContainer, 0);
214
217
  core.elements.tabsContainer.appendChild(tabListContainer);
218
+ tabListContainer.scrollTop = core.state.tabItemsScrollHeight || 0;
215
219
 
216
220
  const tabHeader = document.createElement('span');
217
221
  tabHeader.className = 'tab-header';
@@ -219,13 +223,35 @@ const renderTabs = (core, options) => {
219
223
  const headerTitle = document.createElement('span');
220
224
  headerTitle.innerHTML = "Tabs";
221
225
 
226
+ const rightDiv = document.createElement('div')
222
227
  const addBtn = document.createElement('button');
223
228
  addBtn.className = 'tab-add-button'
224
229
  addBtn.type = 'button';
225
230
  addBtn.innerHTML = SVG.PLUS;
226
231
  addBtn.onclick = () => createTab(core, options);
232
+
233
+ const expandBtn = document.createElement('button');
234
+ expandBtn.className = 'tab-expand-button'
235
+ expandBtn.type = 'button';
236
+ expandBtn.innerHTML = SVG.ARROW_DOWN;
237
+ expandBtn.onclick = () => {
238
+ core.state.isTabExpandInResponsive = !core.state.isTabExpandInResponsive
239
+ expandBtn.classList.toggle('show')
240
+ tabListContainer.classList.toggle('show')
241
+ }
242
+ requestAnimationFrame(() => {
243
+ tabListContainer.classList.add('animate');
244
+ });
245
+ if (core.state.isTabExpandInResponsive) {
246
+ expandBtn.classList.add('show')
247
+ tabListContainer.classList.add('show')
248
+ }
249
+
250
+ rightDiv.appendChild(addBtn);
251
+ rightDiv.appendChild(expandBtn);
252
+
227
253
  tabHeader.appendChild(headerTitle);
228
- tabHeader.appendChild(addBtn);
254
+ tabHeader.appendChild(rightDiv);
229
255
  core.elements.tabsContainer.prepend(tabHeader);
230
256
  }
231
257
 
@@ -235,8 +261,10 @@ const showContextMenu = (core, tab, event, level, options) => {
235
261
 
236
262
  const menu = document.createElement('div');
237
263
  menu.id = 'leksy-editor-tab-context-menu';
238
- menu.style.left = `${event.clientX}px`;
239
- menu.style.top = `${event.clientY}px`;
264
+
265
+ // temporary visibility to measure
266
+ menu.style.visibility = 'hidden';
267
+ menu.style.display = 'block';
240
268
 
241
269
  const closeMenu = (e) => {
242
270
  if (!menu.contains(e.target)) {
@@ -281,17 +309,43 @@ const showContextMenu = (core, tab, event, level, options) => {
281
309
  item.onmouseenter = () => {
282
310
  submenu = document.createElement('div');
283
311
  submenu.className = 'tab-submenu';
312
+ submenu.style.position = 'absolute';
313
+ submenu.style.left = '100%';
314
+ submenu.style.top = '0';
315
+ submenu.style.visibility = 'hidden';
284
316
 
285
317
  items.forEach(subItem => {
286
318
  const subOption = createOption(subItem.tabTitle, SVG.TABLE_OF_CONTENT, () => {
287
- moveTabInto(core, tab.tabId, subItem.tabId);
319
+ moveTabInto(core, tab.tabId, subItem.tabId, options);
288
320
  });
321
+
289
322
  if (subItem.level > 0) {
290
323
  subOption.style.paddingLeft = `${16 + subItem.level * 10}px`;
291
324
  }
325
+
292
326
  submenu.appendChild(subOption);
293
327
  });
328
+
294
329
  item.appendChild(submenu);
330
+
331
+ const rect = submenu.getBoundingClientRect();
332
+ const itemRect = item.getBoundingClientRect();
333
+
334
+ const viewportWidth = core.elements.iframeWindow.documentElement.clientWidth;
335
+ const viewportHeight = core.elements.iframeWindow.documentElement.clientHeight;
336
+
337
+ // if no space on right → open left
338
+ if (rect.right > viewportWidth) {
339
+ submenu.style.left = 'auto';
340
+ submenu.style.right = '100%';
341
+ }
342
+
343
+ // if no space bottom → shift up
344
+ if (rect.bottom > viewportHeight) {
345
+ submenu.style.top = `${viewportHeight - itemRect.top - rect.height}px`;
346
+ }
347
+
348
+ submenu.style.visibility = 'visible';
295
349
  };
296
350
 
297
351
  item.onmouseleave = () => {
@@ -341,7 +395,7 @@ const showContextMenu = (core, tab, event, level, options) => {
341
395
  menu.appendChild(createOption('Move out', SVG.DELETE, () => outdentTab(core, tab.tabId, options)));
342
396
  }
343
397
 
344
- menu.appendChild(createOption(tab.showOutlines ? 'Hide outline' : 'Show outline', SVG.LIST_BULLETS, () => {
398
+ menu.appendChild(createOption(tab.showOutlines ? 'Hide outlines' : 'Show outlines', SVG.LIST_BULLETS, () => {
345
399
  tab.showOutlines = !tab.showOutlines;
346
400
  renderTabs(core, options);
347
401
  if (tab.showOutlines) switchTab(core, tab.tabId, options);
@@ -349,6 +403,32 @@ const showContextMenu = (core, tab, event, level, options) => {
349
403
 
350
404
  core.elements.iframeWindow.body.appendChild(menu);
351
405
  core.elements.iframeWindow.addEventListener('click', closeMenu);
406
+
407
+ const rect = menu.getBoundingClientRect();
408
+ const menuWidth = rect.width;
409
+ const menuHeight = rect.height;
410
+
411
+ const viewportWidth = core.elements.iframeWindow.documentElement.clientWidth;
412
+ const viewportHeight = core.elements.iframeWindow.documentElement.clientHeight;
413
+
414
+ let left = event.clientX;
415
+ let top = event.clientY;
416
+
417
+ // if not enough space on right → open to left
418
+ if (left + menuWidth > viewportWidth) {
419
+ left = event.clientX - menuWidth;
420
+ }
421
+
422
+ // if not enough space on bottom → open above
423
+ if (top + menuHeight > viewportHeight) {
424
+ top = event.clientY - menuHeight;
425
+ }
426
+
427
+ menu.style.left = `${left}px`;
428
+ menu.style.top = `${top}px`;
429
+ menu.style.visibility = 'visible';
430
+
431
+
352
432
  };
353
433
 
354
434
  const createTab = (core, options) => {
@@ -368,6 +448,10 @@ const createTab = (core, options) => {
368
448
  const switchTab = (core, tabId, options) => {
369
449
  if (core.state.currentTabId === tabId) return;
370
450
 
451
+ destroyImageResizer(options, core)
452
+ destroyTableEditPlugin(options, core)
453
+ destroyAnchorPopover(core)
454
+
371
455
  const currentTab = findTab(core.state.tabs, core.state.currentTabId);
372
456
  if (currentTab) {
373
457
  currentTab.content = core.elements.editor.innerHTML;