leksy-editor 1.3.0 → 1.4.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/README.md CHANGED
@@ -105,6 +105,7 @@ const app = createApp({
105
105
  | `minHeight` | Minimum height for the editor. |
106
106
  | `iframeStyle` | To design inside iframe, pass your css here |
107
107
  | `disabled` | To disabled editor |
108
+ | `onFullScreen` | To open custom preview |
108
109
 
109
110
  ### CSS Customization
110
111
 
package/constant.js CHANGED
@@ -27,6 +27,7 @@ const CLASSES = {
27
27
  TOOLBAR_TABLE: '-toolbar-table',
28
28
  TOOLBAR_ITEM: '-toolbar-item',
29
29
  TOOLBAR_ITEM_SELECT: 'select',
30
+ TOOLBAR_ITEM_BUTTON_SELECT: 'button-select',
30
31
  TOOLBAR_ITEM_COLOR: 'color',
31
32
  TOOLBAR_ITEMS: '-toolbar-items',
32
33
  PLACEHOLDER: '-placeholder',
@@ -66,6 +67,13 @@ const SVG = {
66
67
  ATTACHMENT: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8.38 15.68"><g><path d="M15.23,6h1v9.78a3.88,3.88,0,0,1-1.31,2.45,4,4,0,0,1-6.57-2.45V7A3,3,0,0,1,9.2,4.89a3,3,0,0,1,5,2.09v8.31a1.92,1.92,0,0,1-.58,1.39,2,2,0,0,1-1.39.58,1.92,1.92,0,0,1-1.39-.58,2,2,0,0,1-.58-1.39V8h1v7.32a1,1,0,0,0,.29.69,1,1,0,0,0,.69.28A.9.9,0,0,0,13,16a1,1,0,0,0,.29-.69V7a1.92,1.92,0,0,0-.58-1.39A2,2,0,0,0,11.27,5a1.92,1.92,0,0,0-1.39.58A2,2,0,0,0,9.33,7v8.31a3,3,0,1,0,5.9,0V6Z" transform="translate(-8.08 -3.78)"/></g></svg>',
67
68
  LIST_BULLETS: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15.74 12.37"><g><path d="M7.77,16.12a1.59,1.59,0,0,0-.49-1.18,1.62,1.62,0,0,0-1.19-.49,1.68,1.68,0,1,0,0,3.36,1.67,1.67,0,0,0,1.68-1.69Zm0-4.48A1.67,1.67,0,0,0,6.09,10,1.68,1.68,0,0,0,4.9,12.82a1.62,1.62,0,0,0,1.19.49,1.67,1.67,0,0,0,1.68-1.67Zm12.38,3.64a.27.27,0,0,0-.08-.19.28.28,0,0,0-.2-.09H9.19a.28.28,0,0,0-.2.08.29.29,0,0,0-.08.19V17a.27.27,0,0,0,.28.28H19.87a.27.27,0,0,0,.19-.08.24.24,0,0,0,.08-.2V15.28ZM7.77,7.13a1.63,1.63,0,0,0-.49-1.2,1.61,1.61,0,0,0-1.19-.49,1.61,1.61,0,0,0-1.19.49,1.71,1.71,0,0,0,0,2.4,1.62,1.62,0,0,0,1.19.49,1.61,1.61,0,0,0,1.19-.49,1.63,1.63,0,0,0,.49-1.2Zm12.38,3.66a.28.28,0,0,0-.08-.2.29.29,0,0,0-.19-.08H9.19a.27.27,0,0,0-.28.28v1.69a.27.27,0,0,0,.08.19.24.24,0,0,0,.2.08H19.87a.27.27,0,0,0,.19-.08.25.25,0,0,0,.08-.19V10.79Zm0-4.5a.27.27,0,0,0-.08-.19A.25.25,0,0,0,19.88,6H9.19A.28.28,0,0,0,9,6.1a.26.26,0,0,0-.08.19V8A.27.27,0,0,0,9,8.17a.24.24,0,0,0,.2.08H19.87a.27.27,0,0,0,.19-.08A.25.25,0,0,0,20.14,8V6.29Z" transform="translate(-4.41 -5.44)"/></g></svg>',
68
69
  LIST_NUMBER: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15.69 15.74"><g><path d="M7.66,18a1.24,1.24,0,0,0-.26-.78,1.17,1.17,0,0,0-.72-.42l.85-1V15H4.58v1.34h.94v-.46l.85,0h0c-.11.11-.22.23-.32.35s-.23.27-.37.47L5.39,17l.23.51c.61-.05.92.11.92.49a.42.42,0,0,1-.18.37.79.79,0,0,1-.45.12A1.41,1.41,0,0,1,5,18.15l-.51.77A2.06,2.06,0,0,0,6,19.5a1.8,1.8,0,0,0,1.2-.41A1.38,1.38,0,0,0,7.66,18Zm0-5.54H6.75V13H5.63A.72.72,0,0,1,6,12.51a5.45,5.45,0,0,1,.66-.45,2.71,2.71,0,0,0,.67-.57,1.19,1.19,0,0,0,.31-.81,1.29,1.29,0,0,0-.45-1,1.86,1.86,0,0,0-2-.11,1.51,1.51,0,0,0-.62.7l.74.52A.87.87,0,0,1,6,10.28a.51.51,0,0,1,.35.12.42.42,0,0,1,.13.33.55.55,0,0,1-.21.4,3,3,0,0,1-.5.38c-.19.13-.39.27-.58.42a2,2,0,0,0-.5.6,1.63,1.63,0,0,0-.21.81,3.89,3.89,0,0,0,.05.48h3.2V12.44Zm12.45,2.82a.27.27,0,0,0-.08-.19.28.28,0,0,0-.21-.08H9.1a.32.32,0,0,0-.21.08.24.24,0,0,0-.08.2V17a.27.27,0,0,0,.08.19.3.3,0,0,0,.21.08H19.83a.32.32,0,0,0,.21-.08.25.25,0,0,0,.08-.19V15.26ZM7.69,7.32h-1V3.76H5.8L4.6,4.88l.63.68a1.85,1.85,0,0,0,.43-.48h0l0,2.24H4.74V8.2h3V7.32Zm12.43,3.42a.27.27,0,0,0-.08-.19.28.28,0,0,0-.21-.08H9.1a.32.32,0,0,0-.21.08.24.24,0,0,0-.08.2v1.71a.27.27,0,0,0,.08.19.3.3,0,0,0,.21.08H19.83a.32.32,0,0,0,.21-.08.25.25,0,0,0,.08-.19V10.74Zm0-4.52A.27.27,0,0,0,20,6,.28.28,0,0,0,19.83,6H9.1A.32.32,0,0,0,8.89,6a.24.24,0,0,0-.08.19V7.93a.27.27,0,0,0,.08.19.32.32,0,0,0,.21.08H19.83A.32.32,0,0,0,20,8.12a.26.26,0,0,0,.08-.2V6.22Z" transform="translate(-4.43 -3.76)"/></g></svg>',
70
+ NEW_LIST_NUMBER: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path d="M144,48V208a8,8,0,0,1-16,0V62.13L100.12,78.86a8,8,0,1,1-8.24-13.72l40-24A8,8,0,0,1,144,48Z"></path></svg>',
71
+ LIST_SQUARE: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path d="M224,48V208a16,16,0,0,1-16,16H48a16,16,0,0,1-16-16V48A16,16,0,0,1,48,32H208A16,16,0,0,1,224,48Z"></path></svg>',
72
+ LIST_DISC: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><rect width="256" height="256" fill="none"/><rect x="32" y="32" width="192" height="192" rx="16"/></svg>',
73
+ LIST_CIRCLE: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><rect width="256" height="256" fill="none"/><circle cx="128" cy="128" r="96" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/></svg>',
74
+ LIST_LOWER_ALPHA: '<svg viewBox="-6 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg"><title>a</title><path d="M15.969 12.094v-2.938h2.25v16.438h-2.25v-2.313c-1.563 1.719-3.875 2.844-6.438 2.844-4.75 0-8.906-3.75-8.906-8.438s4.156-8.438 8.906-8.438c2.563 0 4.875 1.125 6.438 2.844zM15.969 17.875v-0.375c-0.125-3.438-2.969-6.188-6.469-6.188-3.594 0-6.719 2.844-6.719 6.375s3.125 6.375 6.719 6.375c3.5 0 6.344-2.75 6.469-6.188z"></path></svg>',
75
+ LIST_LOWER_ROMAN: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M272 112C272 85.5 293.5 64 320 64C346.5 64 368 85.5 368 112C368 138.5 346.5 160 320 160C293.5 160 272 138.5 272 112zM224 256C224 238.3 238.3 224 256 224L320 224C337.7 224 352 238.3 352 256L352 512L384 512C401.7 512 416 526.3 416 544C416 561.7 401.7 576 384 576L256 576C238.3 576 224 561.7 224 544C224 526.3 238.3 512 256 512L288 512L288 288L256 288C238.3 288 224 273.7 224 256z"/></svg>',
76
+ LIST_UPPER_ALPHA: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M349.5 115.7C344.6 103.8 332.9 96 320 96C307.1 96 295.4 103.8 290.5 115.7C197.2 339.7 143.8 467.7 130.5 499.7C123.7 516 131.4 534.7 147.7 541.5C164 548.3 182.7 540.6 189.5 524.3L221.3 448L418.6 448L450.4 524.3C457.2 540.6 475.9 548.3 492.2 541.5C508.5 534.7 516.2 516 509.4 499.7C496.1 467.7 442.7 339.7 349.4 115.7zM392 384L248 384L320 211.2L392 384z"/></svg>',
69
77
  SUBSCRIPT: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15.75 14.61"><g><path d="M15.38,4.33H12.74L11.19,7c-.28.46-.51.87-.69,1.21L10.07,9h0l-.44-.8c-.22-.4-.45-.81-.71-1.23L7.34,4.33H4.68L8.26,10,4.4,16.08H7.1l1.69-2.83c.38-.63.72-1.22,1-1.78l.25-.46h0l.49.92c.24.45.48.89.74,1.32L13,16.08h2.61L11.84,10l1.77-2.84,1.77-2.85Zm4.77,13.75H17v-.15c0-.4.05-.64.16-.72a4.42,4.42,0,0,1,1.16-.31,3.3,3.3,0,0,0,1.54-.56A1.84,1.84,0,0,0,20.15,15a1.78,1.78,0,0,0-.44-1.41A2.8,2.8,0,0,0,18,13.25a2.71,2.71,0,0,0-1.69.37,1.83,1.83,0,0,0-.44,1.43v.23H17v-.23q0-.63.18-.78a1.62,1.62,0,0,1,.88-.15,1.59,1.59,0,0,1,.88.15q.18.15.18.75t-.18.75a3.58,3.58,0,0,1-1.18.33,3.33,3.33,0,0,0-1.52.51,1.57,1.57,0,0,0-.32,1.18v1.15h4.27v-.86Z" transform="translate(-4.4 -4.33)"/></g></svg>',
70
78
  SUPERSCRIPT: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15.75 15.42"><g><path d="M12,13.14l3.61-5.81H12.94L11.33,10c-.28.46-.51.88-.69,1.25l-.45.83h0l-.45-.85c-.22-.41-.45-.82-.71-1.24L7.4,7.33H4.68l3.66,5.81L4.4,19.33H7.14l1.74-2.87q.58-1,1-1.83l.25-.48h0l.51.94.75,1.37,1.72,2.87h2.67l-1.92-3.09c-1.12-1.8-1.76-2.83-1.92-3.1Zm4.84-4.41h0l0,.15h3.27v.86H15.77V8.58a1.66,1.66,0,0,1,.33-1.22,3.51,3.51,0,0,1,1.56-.51,3.68,3.68,0,0,0,1.21-.34c.13-.1.19-.36.19-.77S19,5.07,18.87,5A1.63,1.63,0,0,0,18,4.8a1.58,1.58,0,0,0-.91.17c-.13.11-.19.38-.19.8V6H15.78V5.76a1.87,1.87,0,0,1,.45-1.47A2.84,2.84,0,0,1,18,3.91a2.8,2.8,0,0,1,1.72.38,1.84,1.84,0,0,1,.45,1.44,1.91,1.91,0,0,1-.34,1.35,3.24,3.24,0,0,1-1.58.57A3.69,3.69,0,0,0,17,8c-.12.1-.17.35-.17.76Z" transform="translate(-4.4 -3.91)"/></g></svg>',
71
79
  INDENT: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15.74 12.36"><g><path d="M4.68,14.45a.27.27,0,0,1-.19-.08.3.3,0,0,1-.08-.21V9.1a.27.27,0,0,1,.08-.19.28.28,0,0,1,.2-.08.25.25,0,0,1,.19.07l2.54,2.54a.29.29,0,0,1,0,.4L4.88,14.36a.24.24,0,0,1-.2.09Zm15.19,1.12a.27.27,0,0,1,.19.08.25.25,0,0,1,.08.19v1.69a.27.27,0,0,1-.08.19.25.25,0,0,1-.19.08H4.68a.27.27,0,0,1-.19-.08.25.25,0,0,1-.08-.19V15.84a.27.27,0,0,1,.27-.27H19.87Zm0-3.38a.27.27,0,0,1,.19.08.28.28,0,0,1,.08.21v1.68a.32.32,0,0,1-.08.21.25.25,0,0,1-.19.08H10.31a.27.27,0,0,1-.19-.08.3.3,0,0,1-.08-.21V12.48a.32.32,0,0,1,.08-.21.24.24,0,0,1,.19-.08h9.56Zm0-3.37a.27.27,0,0,1,.19.08.25.25,0,0,1,.08.19v1.69a.27.27,0,0,1-.08.19.25.25,0,0,1-.19.08H10.31a.27.27,0,0,1-.27-.27V9.1a.27.27,0,0,1,.27-.27h9.56Zm.2-3.29a.28.28,0,0,1,.08.2V7.41a.32.32,0,0,1-.08.21.25.25,0,0,1-.19.08H4.68a.27.27,0,0,1-.19-.08.3.3,0,0,1-.08-.21V5.73a.32.32,0,0,1,.08-.21.25.25,0,0,1,.19-.08H19.87a.28.28,0,0,1,.2.09Z" transform="translate(-4.41 -5.44)"/></g></svg>',
@@ -88,6 +96,7 @@ const SVG = {
88
96
  REVERT: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15.76 14.69"><g><path d="M18.26,15V12.3l1.89-2V15a2.58,2.58,0,0,1-.24,1c-.2.58-.75.92-1.65,1H7.56v2L4.41,15.63,7.56,13v2h10.7ZM6.3,8.28V11L4.41,13V8.28a2.58,2.58,0,0,1,.24-1c.2-.58.75-.92,1.65-1H17v-2l3.15,3.34L17,10.3v-2H6.3Z" transform="translate(-4.4 -4.28)"/></g></svg>',
89
97
  AUTO_SIZE: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15.74 15.74"><g><path d="M6.71,17.19,6.89,16l1.21-.15A6,6,0,0,1,6.81,13.9a5.78,5.78,0,0,1-.45-2.27A6,6,0,0,1,8.1,7.45a5.83,5.83,0,0,1,4.17-1.73l1-1-1-1A7.89,7.89,0,0,0,5,14.64a7.73,7.73,0,0,0,1.71,2.55Zm5.57,2.31h0A7.86,7.86,0,0,0,17.85,6.07L17.67,7.3l-1.21.15a5.9,5.9,0,0,1,1.29,1.92,5.81,5.81,0,0,1,.45,2.26,5.91,5.91,0,0,1-5.9,5.9l-1,1,.49.49.47.5Z" transform="translate(-4.41 -3.76)"/></g></svg>',
90
98
  ARROW_DOWN: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15.73 8.67"><g><path d="M18.79,7.52a.8.8,0,0,1,.56-.23.82.82,0,0,1,.79.79.8.8,0,0,1-.23.56l-7.07,7.07a.79.79,0,0,1-.57.25.77.77,0,0,1-.57-.25h0L4.64,8.65a.8.8,0,0,1-.23-.57.82.82,0,0,1,.79-.79.8.8,0,0,1,.56.23L12.28,14l3.26-3.26,3.25-3.26Z" transform="translate(-4.41 -7.29)"/></g></svg>',
99
+ ARROW_DROP_DOWN_FILL: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" ><path d="M12 14L8 10H16L12 14Z"></path></svg>',
91
100
  GIPHY_SVG: '<svg xmlns="http://www.w3.org/2000/svg" width="12px" viewBox="0 0 28 35"><g fill-rule="evenodd" clip-rule="evenodd"><path fill="#00ff99" d="M0 3h4v29H0z"></path><path fill="#9933ff" d="M24 11h4v21h-4z"></path><path fill="#00ccff" d="M0 31h28v4H0z"></path><path fill="#fff35c" d="M0 0h16v4H0z"></path><path fill="#ff6666" d="M24 8V4h-4V0h-4v12h12V8"></path><path class="shadow" d="M24 16v-4h4M16 0v4h-4"></path></g></svg>',
92
101
  PEXELS: '<svg viewBox="0 0 17 22" fill="none" xmlns="http://www.w3.org/2000/svg"><g> <path fill-rule="evenodd" clip-rule="evenodd" d="M12 5C12.7111 5 13.3875 5.14845 14 5.41604C15.7659 6.1876 17 7.94968 17 10C17 12.0503 15.7659 13.8124 14 14.584C13.3875 14.8516 12.7111 15 12 15V19H6V5H12ZM8 7V17H10V13H12L12.0032 12.9988C13.6427 13.0303 15.0746 11.6934 15.0443 9.95469L15.0375 9.56529C15.0121 8.10183 13.7882 6.94549 12.3257 7.00299L12.0203 7.00762L12 7H8Z" fill="#07a081"></path> </g></svg>',
93
102
  EMOJI: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M10.5199 19.8634C10.5955 18.6615 10.8833 17.5172 11.3463 16.4676C9.81124 16.3252 8.41864 15.6867 7.33309 14.7151L8.66691 13.2248C9.55217 14.0172 10.7188 14.4978 12 14.4978C12.1763 14.4978 12.3501 14.4887 12.5211 14.471C14.227 12.2169 16.8661 10.7083 19.8634 10.5199C19.1692 6.80877 15.9126 4 12 4C7.58172 4 4 7.58172 4 12C4 15.9126 6.80877 19.1692 10.5199 19.8634ZM19.0233 12.636C15.7891 13.2396 13.2396 15.7891 12.636 19.0233L19.0233 12.636ZM22 12C22 12.1677 21.9959 12.3344 21.9877 12.5L12.5 21.9877C12.3344 21.9959 12.1677 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12ZM10 10C10 10.8284 9.32843 11.5 8.5 11.5C7.67157 11.5 7 10.8284 7 10C7 9.17157 7.67157 8.5 8.5 8.5C9.32843 8.5 10 9.17157 10 10ZM17 10C17 10.8284 16.3284 11.5 15.5 11.5C14.6716 11.5 14 10.8284 14 10C14 9.17157 14.6716 8.5 15.5 8.5C16.3284 8.5 17 9.17157 17 10Z"></path></svg>',
@@ -230,7 +239,8 @@ const CSS = {
230
239
  `,
231
240
  IFRAME_EDITOR: `
232
241
  .content-editable {
233
- height: 100vh;
242
+ min-height: 100vh;
243
+ height: 100%;
234
244
  outline: 0;
235
245
  padding: 4px;
236
246
  box-sizing: border-box;
@@ -651,6 +661,19 @@ const TAB_CATEGORIES = {
651
661
  }
652
662
  };
653
663
 
664
+ const ORDERED_LIST_OPTIONS = {
665
+ '1': `${SVG.NEW_LIST_NUMBER} Number`,
666
+ 'a': `${SVG.LIST_LOWER_ALPHA} Lower Alpha`,
667
+ 'i': `${SVG.LIST_LOWER_ROMAN} Lower Roman`,
668
+ 'A': `${SVG.LIST_UPPER_ALPHA} Upper Alpha`,
669
+ }
670
+
671
+ const UNORDERED_LIST_OPTIONS = {
672
+ 'Disc': `${SVG.LIST_DISC} Disc`,
673
+ 'Circle': `${SVG.LIST_CIRCLE} Circle`,
674
+ 'Square': `${SVG.LIST_SQUARE} Square`,
675
+ }
676
+
654
677
  const RESIZE_MARGIN = 10;
655
678
 
656
679
  const LIST_STYLES_BY_LEVEL = ['1', 'a', 'i', 'A'];
@@ -681,4 +704,6 @@ export {
681
704
  RESIZE_MARGIN,
682
705
  LIST_STYLES_BY_LEVEL,
683
706
  UNORDERED_LIST_STYLES_BY_LEVEL,
707
+ UNORDERED_LIST_OPTIONS,
708
+ ORDERED_LIST_OPTIONS,
684
709
  }
package/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import './style.css'
2
2
  import { CLASSES, CSS, CSS_VARIABLES, ERRORS, REGEX, SVG } from "./constant"
3
- import PLUGINS, { applyOrderList, applyTextFormat, applyUnorderedList } from './plugin';
4
- import { showAnchorPopover, changeAllToolbarState, changeToolbarStateByName, changeToolbarValueByName, cleanHTML, debounce, destroyImageResizer, destroyTableEditPlugin, initImageResizer, initTableEditPlugin, makeToolbarButton, makeToolbarColor, makeToolbarDropdown, makeToolbarSelect, rgbToHex, updateTableResizerPosition, destroyAnchorPopover, changeToolbarHtmlByName, showRemoteCursor, syncRemoteChangesDebounce, applyRemoteChanges, updateCursorPositionDebounce, buildTributeValues, updateHeight, getTableGrid, getCellPosition, makeUnorderedList, makeOrderedList, makeHeading, makeBlockQuote, makeStrikethrough, makeCodeBlock, makeSublist } from './utilities';
3
+ import PLUGINS, { applyTextFormat } from './plugin';
4
+ import { showAnchorPopover, changeAllToolbarState, changeToolbarStateByName, changeToolbarValueByName, cleanHTML, debounce, destroyImageResizer, destroyTableEditPlugin, initImageResizer, initTableEditPlugin, makeToolbarButton, makeToolbarColor, makeToolbarDropdown, makeToolbarSelect, rgbToHex, updateTableResizerPosition, destroyAnchorPopover, changeToolbarHtmlByName, showRemoteCursor, syncRemoteChangesDebounce, applyRemoteChanges, updateCursorPositionDebounce, buildTributeValues, updateHeight, getTableGrid, getCellPosition, makeUnorderedList, makeOrderedList, makeHeading, makeBlockQuote, makeStrikethrough, makeCodeBlock, makeSublist, showTooltip, makeToolbarButtonSelect } from './utilities';
5
5
 
6
6
  class LeksyEditor {
7
7
 
@@ -27,6 +27,7 @@ class LeksyEditor {
27
27
  * @property {String} minHeight
28
28
  * @property {String} iframeStyle
29
29
  * @property {Boolean} disabled
30
+ * @property {Function} onFullScreen
30
31
  */
31
32
  /**
32
33
  *
@@ -263,7 +264,6 @@ class LeksyEditor {
263
264
  getCore: () => core,
264
265
  updateHeight: (height) => {
265
266
  core.elements.iframeContainer.style.setProperty('height', height);
266
- core.elements.editor.style.setProperty('height', height);
267
267
  },
268
268
  setDisabled: (disabled) => {
269
269
  options.disabled = disabled;
@@ -371,6 +371,8 @@ class LeksyEditor {
371
371
  toolbarPlugin = makeToolbarSelect(_plugin, options, core)
372
372
  } else if (_plugin.type === 'color') {
373
373
  toolbarPlugin = makeToolbarColor(_plugin, options, core)
374
+ } else if (_plugin.type === 'button-select') {
375
+ toolbarPlugin = makeToolbarButtonSelect(_plugin, options, core)
374
376
  }
375
377
  if (core.elements.toolbar[plugin])
376
378
  core.elements.toolbar[plugin].push(toolbarPlugin)
@@ -386,6 +388,7 @@ class LeksyEditor {
386
388
  })
387
389
 
388
390
  if (options.disabled) changeAllToolbarState(core, 'disabled')
391
+ showTooltip(options)
389
392
 
390
393
  core.elements.toolbarContainer = toolbarContainer;
391
394
  core.elements.base.appendChild(toolbarContainer);
@@ -608,11 +611,6 @@ class LeksyEditor {
608
611
  contentEditableDiv.spellcheck = !!options.spellcheck
609
612
  contentEditableDiv.innerHTML = core.html;
610
613
  contentEditableDiv.className = 'content-editable';
611
- if (options.autoHeight) contentEditableDiv.style.height = 'auto';
612
- else contentEditableDiv.style.height = options.minHeight ?? options.height ?? '250px';
613
- contentEditableDiv.style.minHeight = options.minHeight ?? options.height ?? '250px';
614
- if (options.maxHeight) contentEditableDiv.style.maxHeight = options.maxHeight;
615
- if (options.autoHeight) iframe.contentDocument.body.style.overflow = 'hidden';
616
614
 
617
615
  contentEditableDiv.oninput = (e) => {
618
616
  core.onChange(e.target.innerHTML)
@@ -819,7 +817,7 @@ class LeksyEditor {
819
817
 
820
818
  if (e.target.closest("a")?.tagName === 'A') {
821
819
  const anchor = e.target.closest("a");
822
- if (anchor) {
820
+ if (anchor && element.tagName !== 'IMG') {
823
821
  core.elements.selectedElement = anchor;
824
822
  showAnchorPopover(anchor, e.pageX, e.pageY, options, core);
825
823
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leksy-editor",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
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/plugin.js CHANGED
@@ -1,6 +1,6 @@
1
- import { EMOJI_CATEGORIES, FONT_SIZE_OPTIONS, FONTS, FORMAT_OPTIONS, MIMETYPE, REGEX, SPECIAL_CHARACTERS, SVG } from "./constant"
1
+ import { EMOJI_CATEGORIES, FONT_SIZE_OPTIONS, FONTS, FORMAT_OPTIONS, MIMETYPE, REGEX, SPECIAL_CHARACTERS, SVG, UNORDERED_LIST_OPTIONS, ORDERED_LIST_OPTIONS } from "./constant"
2
2
  import { giphy, pexels, tenor } from "./gallery";
3
- import { formatLink, changeAllToolbarState, changeToolbarHtmlByName, changeToolbarStateByName, changeToolbarValueByName, cleanHTML, constructEmbedUrl, extractSocialMediaId, isLinkValid, openModal } from "./utilities";
3
+ import { formatLink, changeAllToolbarState, changeToolbarHtmlByName, changeToolbarStateByName, changeToolbarValueByName, cleanHTML, constructEmbedUrl, extractSocialMediaId, isLinkValid, openModal, transformTextStyle } from "./utilities";
4
4
 
5
5
  const applyTextFormat = (core, command) => {
6
6
  core.elements.editor.focus();
@@ -233,7 +233,7 @@ const PLUGINS = {
233
233
  }
234
234
  },
235
235
  'font-size': {
236
- title: 'Font',
236
+ title: 'Font Size',
237
237
  icon: 'Font Size',
238
238
  type: 'select',
239
239
  options: Object.keys(FONT_SIZE_OPTIONS).map(size => ({
@@ -267,23 +267,62 @@ const PLUGINS = {
267
267
  'ordered_list': {
268
268
  title: "Ordered List",
269
269
  icon: SVG.LIST_NUMBER,
270
- type: 'button',
271
- click: (event, core, options) => {
270
+ type: 'button-select',
271
+ options: Object.keys(ORDERED_LIST_OPTIONS).map(list => ({
272
+ label: ORDERED_LIST_OPTIONS[list], value: list
273
+ })),
274
+ click: (value, core, options) => {
272
275
  core.elements.editor.focus();
273
- core.elements.iframeWindow.execCommand('insertOrderedList');
274
- core.updateCaretPosition()
275
-
276
276
  const isActive = core.elements.iframeWindow.queryCommandState('insertOrderedList');
277
- if (isActive) changeToolbarStateByName(core, 'active', ['ordered_list'])
278
- else changeToolbarStateByName(core, 'inactive', ['ordered_list'])
277
+ if (!isActive) {
278
+ core.elements.iframeWindow.execCommand('insertOrderedList');
279
+ }
280
+ const selection = core.elements.iframeWindow.getSelection();
281
+ if (selection.rangeCount > 0) {
282
+ let node = selection.anchorNode;
283
+ while (node && node.nodeName !== 'OL' && node !== core.elements.editor) {
284
+ node = node.parentNode;
285
+ }
286
+ if (node?.nodeName === 'OL') {
287
+ node.setAttribute('type', value);
288
+ }
289
+ }
290
+ core.updateCaretPosition()
291
+ changeToolbarStateByName(core, 'active', ['ordered_list'])
279
292
  changeToolbarStateByName(core, 'inactive', ['unordered_list'])
293
+ },
294
+ mainClick: (event, core, options) => {
295
+ applyOrderList(core)
280
296
  }
281
297
  },
282
298
  'unordered_list': {
283
299
  title: "Unordered List",
284
300
  icon: SVG.LIST_BULLETS,
285
- type: 'button',
286
- click: (event, core, options) => {
301
+ type: 'button-select',
302
+ options: Object.keys(UNORDERED_LIST_OPTIONS).map(list => ({
303
+ label: UNORDERED_LIST_OPTIONS[list], value: list
304
+ })),
305
+ click: (value, core, options) => {
306
+ core.elements.editor.focus();
307
+ const isActive = core.elements.iframeWindow.queryCommandState('insertUnorderedList');
308
+ if (!isActive) {
309
+ core.elements.iframeWindow.execCommand('insertUnorderedList');
310
+ }
311
+ const selection = core.elements.iframeWindow.getSelection();
312
+ if (selection.rangeCount > 0) {
313
+ let node = selection.anchorNode;
314
+ while (node && node.nodeName !== 'UL' && node !== core.elements.editor) {
315
+ node = node.parentNode;
316
+ }
317
+ if (node?.nodeName === 'UL') {
318
+ node.style.listStyleType = value;
319
+ }
320
+ }
321
+ core.updateCaretPosition()
322
+ changeToolbarStateByName(core, 'active', ['unordered_list'])
323
+ changeToolbarStateByName(core, 'inactive', ['ordered_list'])
324
+ },
325
+ mainClick: (event, core, options) => {
287
326
  applyUnorderedList(core)
288
327
  }
289
328
  },
@@ -388,7 +427,12 @@ const PLUGINS = {
388
427
  { label: 'Block Quote', value: 'blockquote' },
389
428
  { label: 'Code', value: 'code' },
390
429
  { label: 'Translucent', value: 'translucent' },
391
- { label: 'Shadow', value: 'shadow' }
430
+ { label: 'Shadow', value: 'shadow' },
431
+ { label: 'Upper Case', value: 'upper_case' },
432
+ { label: 'Lower Case', value: 'lower_case' },
433
+ { label: 'Title Case', value: 'title_case' },
434
+ { label: 'Button (Green)', value: 'button_green' },
435
+ { label: 'Button (Black)', value: 'button_black' }
392
436
  ],
393
437
  click: (event, core, options) => {
394
438
  if (!core.state.range) return
@@ -423,6 +467,50 @@ const PLUGINS = {
423
467
  core.state.range.insertNode(code);
424
468
  break;
425
469
  }
470
+ case 'upper_case': {
471
+ transformTextStyle(core, 'upper_case');
472
+ break;
473
+ }
474
+ case 'lower_case': {
475
+ transformTextStyle(core, 'lower_case');
476
+ break;
477
+ }
478
+ case 'title_case': {
479
+ transformTextStyle(core, 'title_case');
480
+ break;
481
+ }
482
+ case 'button_green': {
483
+ core.updateCaretPosition()
484
+ const span = document.createElement('span');
485
+ span.textContent = core.state.range.toString();
486
+ span.style.cssText = 'background-color: #28a745; color: white; font-weight: bold; padding: 8px; border-radius: 4px; text-decoration: none; display: inline-block; cursor: pointer;';
487
+ span.contentEditable = 'false';
488
+ let element = core.state.range.commonAncestorContainer;
489
+ if (element.nodeType === Node.TEXT_NODE) element = element.parentElement;
490
+ if (element.nodeName === 'A') {
491
+ element.style.cssText = 'background-color: #28a745; color: white; font-weight: bold; padding: 8px; border-radius: 4px; text-decoration: none; display: inline-block; cursor: pointer;';
492
+ } else if (span.textContent && !['TR', 'TBODY', 'THEAD', 'TABLE'].includes(element.nodeName)) {
493
+ core.state.range.deleteContents();
494
+ core.state.range.insertNode(span);
495
+ }
496
+ break;
497
+ }
498
+ case 'button_black': {
499
+ core.updateCaretPosition()
500
+ const span = document.createElement('span');
501
+ span.textContent = core.state.range.toString();
502
+ span.style.cssText = 'background-color: black; color: white; font-weight: bold; padding: 8px; border-radius: 4px; text-decoration: none; display: inline-block; cursor: pointer;';
503
+ span.contentEditable = 'false';
504
+ let element = core.state.range.commonAncestorContainer;
505
+ if (element.nodeType === Node.TEXT_NODE) element = element.parentElement;
506
+ if (element.nodeName === 'A') {
507
+ element.style.cssText = 'background-color: black; color: white; font-weight: bold; padding: 8px; border-radius: 4px; text-decoration: none; display: inline-block; cursor: pointer;';
508
+ } else if (span.textContent && !['TR', 'TBODY', 'THEAD', 'TABLE'].includes(element.nodeName)) {
509
+ core.state.range.deleteContents();
510
+ core.state.range.insertNode(span);
511
+ }
512
+ break;
513
+ }
426
514
  }
427
515
  core.elements.editor.focus();
428
516
  core.updateCaretPosition()
@@ -742,53 +830,85 @@ const PLUGINS = {
742
830
  button.click();
743
831
  }
744
832
  };
745
- const body = document.createElement('div');
746
833
 
834
+ // Top
835
+ const top = document.createElement('div');
836
+ top.style.display = 'flex';
837
+ top.style.gap = '16px';
838
+
839
+ // Top - left
840
+ const left = document.createElement('div');
841
+ left.style.display = 'flex';
842
+ left.style.flexDirection = 'column';
843
+ left.style.gap = '16px';
844
+ left.style.flex = '1';
845
+
846
+ const uploadBox = document.createElement('div');
847
+ uploadBox.className = `${options.classPrefix}-upload-img-box`;
848
+ uploadBox.innerHTML = `
849
+ <div style="text-align: center; color: #6b7280;">
850
+ <div style="font-size: 32px; font-weight: 500;">+</div>
851
+ <div style="font-size: 14px;">Click to browse files</div>
852
+ </div>
853
+ `;
747
854
 
748
855
  const fileInput = document.createElement('input');
749
856
  fileInput.type = 'file';
750
857
  fileInput.accept = MIMETYPE.IMAGE;
751
858
  fileInput.style.display = 'none';
859
+ uploadBox.addEventListener('click', () => fileInput.click());
752
860
 
753
- // Create a label or button to trigger file input
754
- const uploadBtn = document.createElement('button');
755
- uploadBtn.innerText = 'Choose Image';
756
- uploadBtn.type = 'button';
757
- uploadBtn.style.border = '1px solid grey';
758
- uploadBtn.style.padding = '4px';
759
- uploadBtn.style.borderRadius = '6px';
861
+ left.append(uploadBox, fileInput);
760
862
 
863
+ // Top - right
864
+ const right = document.createElement('div');
865
+ right.style.display = 'flex';
866
+ right.style.flexDirection = 'column';
867
+ right.style.flex = '1';
761
868
 
762
- // Span to show selected file name
763
- const fileNameSpan = document.createElement('span');
764
- fileNameSpan.style.marginLeft = '10px';
765
- fileNameSpan.innerText = 'No file selected';
869
+ const previewBox = document.createElement('div');
870
+ previewBox.className = `${options.classPrefix}-upload-img-preview-box`;
766
871
 
767
- uploadBtn.addEventListener('click', () => {
768
- fileInput.click(); // Trigger file input
769
- });
872
+ const imgPreview = document.createElement('img');
873
+ imgPreview.className = `${options.classPrefix}-upload-img-preview`;
874
+ imgPreview.style.display = 'none';
875
+
876
+ previewBox.append(imgPreview);
877
+ right.append(previewBox);
878
+
879
+ // Complete top
880
+ top.append(left, right)
881
+
882
+ // other fields
883
+ const span = document.createElement('div');
884
+ span.className = 'warning';
770
885
 
771
- const altLabel = document.createElement("label");
772
- altLabel.innerText = "Alternative Text";
886
+ const altLabel = document.createElement('label');
887
+ altLabel.innerText = 'Image Description (Alt Text)';
773
888
  altLabel.style.display = "block";
774
889
  altLabel.style.paddingLeft = "4px";
775
- altLabel.style.marginTop = '10px';
890
+ altLabel.style.marginTop = '4px';
891
+ altLabel.style.marginBottom = '4px';
776
892
 
777
893
  const altInput = document.createElement('input');
778
894
  altInput.type = 'text';
779
895
  altInput.placeholder = 'Alternative Text';
780
896
  altInput.addEventListener('keydown', onInputKeydown);
781
897
 
782
- const span = document.createElement('span');
783
- span.className = 'warning';
784
-
785
- body.append(fileInput, uploadBtn, fileNameSpan, altLabel, altInput, span);
898
+ const body = document.createElement('div');
899
+ body.append(top, span, altLabel, altInput);
786
900
 
901
+ // Footer
787
902
  const footer = document.createElement('div');
903
+ footer.style.display = 'flex';
904
+ footer.style.justifyContent = 'space-between';
905
+ footer.style.alignItems = 'center';
906
+
788
907
  const button = document.createElement('button');
789
908
  button.type = 'button';
790
909
  button.className = 'submit';
791
910
  button.innerText = 'Add';
911
+
792
912
  footer.append(button);
793
913
 
794
914
  const modal = openModal(
@@ -800,33 +920,35 @@ const PLUGINS = {
800
920
  core,
801
921
  options
802
922
  );
803
- let base64String = ""
923
+ let base64String = '';
804
924
 
805
925
  fileInput.addEventListener('change', (event) => {
806
926
  const file = event.target.files[0];
807
-
808
927
  if (file && MIMETYPE.IMAGE.includes(file.type)) {
809
928
  const reader = new FileReader();
810
-
811
- reader.onload = async function (e) {
812
- base64String = e.target.result;
929
+ reader.onload = () => {
930
+ base64String = reader.result;
931
+ imgPreview.src = base64String;
932
+ imgPreview.style.display = 'block';
813
933
  };
814
-
815
934
  reader.readAsDataURL(file);
816
- fileNameSpan.innerText = file.name;
817
- span.innerText = ""; // Clear warning if file is selected
935
+ span.innerText = "";
818
936
  }
819
937
  });
938
+
820
939
  fileInput.addEventListener('click', async () => {
821
940
  let fileFromNative = await core.handleFilePicker(MIMETYPE.IMAGE.split(','), 'image');
822
941
 
823
942
  if (fileFromNative) {
824
943
  base64String = fileFromNative.data;
825
944
  fileNameSpan.innerText = fileFromNative.name;
945
+ imgPreview.src = base64String;
946
+ imgPreview.style.display = 'block';
826
947
  span.innerText = ""; // Clear warning if file is selected
827
948
 
828
949
  }
829
950
  });
951
+
830
952
  button.onclick = async () => {
831
953
  if (!base64String) {
832
954
  span.innerText = 'Image not selected';
@@ -843,6 +965,7 @@ const PLUGINS = {
843
965
  core.insertNode(img);
844
966
  modal.close();
845
967
  };
968
+
846
969
  }
847
970
  else if (event.target.getAttribute('data-value') === 'link') {
848
971
  const onInputKeydown = (event) => {
@@ -1204,6 +1327,7 @@ const PLUGINS = {
1204
1327
  core.updateCaretPosition()
1205
1328
  }
1206
1329
  }
1330
+
1207
1331
  }
1208
1332
 
1209
1333
  export default PLUGINS
package/style.css CHANGED
@@ -119,6 +119,23 @@
119
119
  width: unset;
120
120
  }
121
121
 
122
+ .leksy-editor-toolbar-item.button-select {
123
+ border-radius: 4px 0 0 4px;
124
+ margin-right: 0;
125
+ }
126
+
127
+ .leksy-editor-toolbar-item.select.button-select {
128
+ padding: 0;
129
+ border-radius: 0 4px 4px 0;
130
+ margin-left: 0;
131
+ margin-right: 1px;
132
+ }
133
+
134
+ .leksy-editor-toolbar-item.select.button-select svg {
135
+ width: 18px;
136
+ height: 18px;
137
+ }
138
+
122
139
  .leksy-editor-toolbar-item.reset-color {
123
140
  width: unset;
124
141
  padding: 4px 8px;
@@ -152,7 +169,9 @@
152
169
 
153
170
  .leksy-editor-toolbar-item::after {
154
171
  content: attr(data-title);
155
- position: absolute;
172
+ position: fixed;
173
+ top: var(--leksy-editor-top, 0);
174
+ left: var(--leksy-editor-left, 0);
156
175
  opacity: 0;
157
176
  visibility: hidden;
158
177
  transition: opacity 0.3s, visibility 0.3s;
@@ -161,8 +180,6 @@
161
180
  color: black;
162
181
  padding: 5px 10px;
163
182
  border-radius: 5px;
164
- bottom: -90%;
165
- left: 60%;
166
183
  transform: translateX(-50%);
167
184
  white-space: nowrap;
168
185
  z-index: 1;
@@ -277,6 +294,11 @@
277
294
  border-radius: 6px;
278
295
  }
279
296
 
297
+ .leksy-editor-dropdown-content>button svg {
298
+ width: 10px;
299
+ margin-right: 2px;
300
+ }
301
+
280
302
  .leksy-editor-dropdown-content>button:hover {
281
303
  background-color: #28a745;
282
304
  color: #fff;
@@ -516,7 +538,6 @@ button.leksy-editor-popover-tab.active {
516
538
  height: 2rem;
517
539
  padding: 0.75rem 1rem;
518
540
  background-color: hsl(160, 100%, 35%);
519
- border-color: hsl(160, 75%, 50%);
520
541
  display: flex;
521
542
  align-items: center;
522
543
  justify-content: center;
@@ -530,7 +551,6 @@ button.leksy-editor-popover-tab.active {
530
551
  white-space: nowrap;
531
552
  gap: 0.5rem;
532
553
  transition: 500ms;
533
- text-transform: uppercase;
534
554
  font-size: 1rem;
535
555
  }
536
556
 
@@ -601,6 +621,37 @@ button.leksy-editor-popover-tab.active {
601
621
  animation: leksy-editor-dash 1.5s ease-in-out infinite;
602
622
  }
603
623
 
624
+ .leksy-editor-upload-img-box {
625
+ height: 180px;
626
+ border: 2px dashed #d1d5db;
627
+ border-radius: 12px;
628
+ background: #f9fafb;
629
+ display: flex;
630
+ align-items: center;
631
+ justify-content: center;
632
+ cursor: pointer;
633
+ transition: border-color 0.2s ease;
634
+ }
635
+
636
+ .leksy-editor-upload-img-box:hover {
637
+ border-color: gray;
638
+ }
639
+
640
+ .leksy-editor-upload-img-preview-box {
641
+ height: 180px;
642
+ border: 2px solid grey;
643
+ margin-bottom: 8px;
644
+ border-radius: 8px;
645
+ }
646
+
647
+ .leksy-editor-upload-img-preview {
648
+ width: 100%;
649
+ height: 100%;
650
+ border-radius: 8px;
651
+ object-fit: cover;
652
+ object-position: top;
653
+ }
654
+
604
655
  @keyframes leksy-editor-rotate {
605
656
  100% {
606
657
  transform: rotate(360deg);
package/utilities.js CHANGED
@@ -161,6 +161,24 @@ function getScrollParent(element) {
161
161
  return document.body;
162
162
  }
163
163
 
164
+ const showTooltip = (options) => {
165
+ document.addEventListener('mouseover', (e) => {
166
+ const el = e.target.closest(`.${options.classPrefix}${CLASSES.TOOLBAR_ITEM}`);
167
+ if (!el) return;
168
+
169
+ const rect = el.getBoundingClientRect();
170
+
171
+ let top = rect.bottom + 2;
172
+ let left = rect.left + rect.width / 2;
173
+
174
+ left = Math.max(68, Math.min(left, window.innerWidth - 68));
175
+
176
+ el.style.setProperty('--leksy-editor-top', `${top}px`);
177
+ el.style.setProperty('--leksy-editor-left', `${left}px`);
178
+ });
179
+ };
180
+
181
+
164
182
  const makeToolbarButton = (_plugin, options, core) => {
165
183
  const pluginButton = document.createElement('button');
166
184
  pluginButton.type = "button"
@@ -179,11 +197,38 @@ const makeToolbarButton = (_plugin, options, core) => {
179
197
  return pluginButton
180
198
  }
181
199
 
200
+
201
+ const makeToolbarButtonSelect = (_plugin, options, core) => {
202
+ const pluginButton = document.createElement('button');
203
+ pluginButton.type = "button"
204
+ pluginButton.className = `${options.classPrefix}${CLASSES.TOOLBAR_ITEM} ${_plugin.type === 'button-select' ? CLASSES.TOOLBAR_ITEM_BUTTON_SELECT : ''}`
205
+
206
+ pluginButton.dataset.title = _plugin.title
207
+ pluginButton.dataset.type = 'button'
208
+ pluginButton.onclick = (e) => {
209
+ e.preventDefault();
210
+ _plugin.mainClick(e, core, options);
211
+ };
212
+
213
+ const pluginIcon = document.createElement('div');
214
+ pluginIcon.innerHTML = _plugin.icon;
215
+ pluginButton.appendChild(pluginIcon)
216
+ const pluginSelect = makeToolbarSelect({ ..._plugin, icon: '' }, options, core);
217
+
218
+ const pluginButtonSelect = document.createElement('div');
219
+ pluginButtonSelect.dataset.type = 'button-select'
220
+ pluginButtonSelect.style.display = 'flex'
221
+ pluginButtonSelect.appendChild(pluginButton);
222
+ pluginButtonSelect.appendChild(pluginSelect);
223
+ return pluginButtonSelect
224
+ }
225
+
226
+
182
227
  const makeToolbarColor = (_plugin, options, core) => {
183
228
  const pluginContainer = document.createElement('div');
184
229
  pluginContainer.className = `${options.classPrefix}${CLASSES.TOOLBAR_ITEM} ${CLASSES.TOOLBAR_ITEM_COLOR}`
185
- pluginContainer.setAttribute('data-title', _plugin.title)
186
- pluginContainer.setAttribute('data-type', 'color')
230
+ pluginContainer.dataset.title = _plugin.title
231
+ pluginContainer.dataset.type = 'color'
187
232
 
188
233
  const pluginButton = document.createElement('input');
189
234
  pluginButton.type = "color"
@@ -513,23 +558,29 @@ const makeToolbarSelect = (_plugin, options, core) => {
513
558
  }
514
559
 
515
560
  const selectPlugin = document.createElement('div');
516
- selectPlugin.setAttribute('data-type', 'select')
561
+ selectPlugin.dataset.type = 'select'
517
562
 
518
563
  const dropdownButton = document.createElement('button');
519
564
  dropdownButton.type = "button"
520
- dropdownButton.className = `${options.classPrefix}${CLASSES.TOOLBAR_ITEM} ${CLASSES.TOOLBAR_ITEM_SELECT}`
521
- dropdownButton.setAttribute('data-title', _plugin.title)
565
+ dropdownButton.className = `${options.classPrefix}${CLASSES.TOOLBAR_ITEM} ${CLASSES.TOOLBAR_ITEM_SELECT} ${_plugin.type === 'button-select' ? CLASSES.TOOLBAR_ITEM_BUTTON_SELECT : ''}`
566
+ dropdownButton.dataset.title = _plugin.type == 'button-select' ? `Options` : _plugin.title
522
567
 
523
568
  const pluginIcon = document.createElement('div');
524
569
  pluginIcon.style.width = _plugin.width
525
570
  pluginIcon.style.textAlign = 'left'
526
571
  pluginIcon.style.display = 'flex'
527
572
  pluginIcon.style.justifyContent = 'space-between'
573
+
528
574
  const iconName = document.createElement('span');
529
575
  iconName.innerHTML = _plugin.icon;
576
+
530
577
  const arrow = document.createElement('span');
531
- arrow.innerHTML = SVG.ARROW_DOWN;
532
- arrow.style.paddingLeft = '8px';
578
+ if (_plugin.type == 'button-select') {
579
+ arrow.innerHTML = SVG.ARROW_DROP_DOWN_FILL;
580
+ } else {
581
+ arrow.innerHTML = SVG.ARROW_DOWN;
582
+ arrow.style.paddingLeft = '8px';
583
+ }
533
584
  pluginIcon.append(iconName, arrow)
534
585
 
535
586
  dropdownButton.append(pluginIcon)
@@ -890,7 +941,7 @@ const makeEditToolbar = (options, core, { type, td, image, updateImage, updateTa
890
941
  title.textContent = plugin.title;
891
942
 
892
943
  const input = document.createElement('input');
893
- input.type = "text";
944
+ input.type = "number";
894
945
  input.placeholder = "auto";
895
946
  input.style.width = '60px';
896
947
  input.style.padding = '2px';
@@ -1110,7 +1161,11 @@ const initImageResizer = (type, image, options, core) => {
1110
1161
  src = href;
1111
1162
  }
1112
1163
  }
1113
- createPreviewModal(src, core, options, isVideo);
1164
+ if (options.onFullScreen instanceof Function) {
1165
+ const type = isVideo ? 'video/mp4' : 'image/png';
1166
+ options.onFullScreen(src, type);
1167
+ }
1168
+ else createPreviewModal(src, core, options, isVideo);
1114
1169
  });
1115
1170
 
1116
1171
  resizer.appendChild(fullscreenButton);
@@ -2000,6 +2055,11 @@ const changeToolbarStatByPlugin = (plugin, action, value) => {
2000
2055
  } else if (plugin.getAttribute('data-type') === 'color') {
2001
2056
  plugin.childNodes[0].disabled = true
2002
2057
  plugin.classList.add('disabled')
2058
+ } else if (plugin.getAttribute('data-type') === 'button-select') {
2059
+ plugin.querySelectorAll('button').forEach(btn => {
2060
+ btn.disabled = true
2061
+ btn.classList.add('disabled')
2062
+ })
2003
2063
  }
2004
2064
  break;
2005
2065
  case 'enabled':
@@ -2012,6 +2072,11 @@ const changeToolbarStatByPlugin = (plugin, action, value) => {
2012
2072
  } else if (plugin.getAttribute('data-type') === 'color') {
2013
2073
  plugin.childNodes[0].disabled = false
2014
2074
  plugin.classList.remove('disabled')
2075
+ } else if (plugin.getAttribute('data-type') === 'button-select') {
2076
+ plugin.querySelectorAll('button').forEach(btn => {
2077
+ btn.disabled = false
2078
+ btn.classList.remove('disabled')
2079
+ })
2015
2080
  }
2016
2081
  break;
2017
2082
  case 'active':
@@ -2021,6 +2086,8 @@ const changeToolbarStatByPlugin = (plugin, action, value) => {
2021
2086
  plugin.childNodes[0].classList.add('active')
2022
2087
  } else if (plugin.getAttribute('data-type') === 'color') {
2023
2088
  plugin.classList.add('active')
2089
+ } else if (plugin.getAttribute('data-type') === 'button-select') {
2090
+ plugin.querySelector('button[data-type="button"]')?.classList.add('active')
2024
2091
  }
2025
2092
  break;
2026
2093
  case 'inactive':
@@ -2030,6 +2097,8 @@ const changeToolbarStatByPlugin = (plugin, action, value) => {
2030
2097
  plugin.childNodes[0].classList.remove('active')
2031
2098
  } else if (plugin.getAttribute('data-type') === 'color') {
2032
2099
  plugin.classList.remove('active')
2100
+ } else if (plugin.getAttribute('data-type') === 'button-select') {
2101
+ plugin.querySelector('button[data-type="button"]')?.classList.remove('active')
2033
2102
  }
2034
2103
  break;
2035
2104
  case 'font':
@@ -3055,6 +3124,65 @@ const makeSublist = (event, core) => {
3055
3124
  }
3056
3125
  };
3057
3126
 
3127
+ const transformCase = (text, type) => {
3128
+ if (type === 'upper_case') return text.toUpperCase();
3129
+ if (type === 'lower_case') return text.toLowerCase();
3130
+ if (type === 'title_case') return text.toLowerCase().replace(/\b\w/g, s => s.toUpperCase());
3131
+ return text;
3132
+ };
3133
+
3134
+ const transformTextStyle = (core, type) => {
3135
+ const range = core.state.range;
3136
+ if (!range) return;
3137
+ const startContainer = range.startContainer;
3138
+ const startOffset = range.startOffset;
3139
+ const endContainer = range.endContainer;
3140
+ let endOffset = range.endOffset;
3141
+
3142
+ const processTextNode = (node, start, end) => {
3143
+ const text = node.nodeValue;
3144
+ const transformedText = transformCase(text.slice(start, end), type);
3145
+ node.nodeValue = text.slice(0, start) + transformedText + text.slice(end);
3146
+ if (node === endContainer) endOffset = start + transformedText.length;
3147
+ };
3148
+
3149
+ if (range.commonAncestorContainer.nodeType === Node.TEXT_NODE) {
3150
+ processTextNode(range.commonAncestorContainer, range.startOffset, range.endOffset);
3151
+ } else {
3152
+ const iterator = core.elements.iframeWindow.createNodeIterator(
3153
+ range.commonAncestorContainer,
3154
+ NodeFilter.SHOW_TEXT,
3155
+ {
3156
+ acceptNode: (node) => {
3157
+ return range.intersectsNode(node) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
3158
+ }
3159
+ }
3160
+ );
3161
+
3162
+ const nodes = [];
3163
+ let node;
3164
+ while ((node = iterator.nextNode())) {
3165
+ nodes.push(node);
3166
+ }
3167
+
3168
+ nodes.forEach(node => {
3169
+ let start = 0;
3170
+ let end = node.nodeValue.length;
3171
+
3172
+ if (node === range.startContainer) start = range.startOffset;
3173
+ if (node === range.endContainer) end = range.endOffset;
3174
+
3175
+ processTextNode(node, start, end);
3176
+ });
3177
+ }
3178
+
3179
+ const newRange = core.elements.iframeWindow.createRange();
3180
+ newRange.setStart(startContainer, startOffset);
3181
+ newRange.setEnd(endContainer, endOffset);
3182
+ const selection = core.elements.iframeWindow.getSelection();
3183
+ selection.removeAllRanges();
3184
+ selection.addRange(newRange);
3185
+ }
3058
3186
  export {
3059
3187
  cleanHTML,
3060
3188
  debounce,
@@ -3090,9 +3218,12 @@ export {
3090
3218
  getCellPosition,
3091
3219
  makeUnorderedList,
3092
3220
  makeOrderedList,
3221
+ showTooltip,
3093
3222
  makeHeading,
3094
3223
  makeBlockQuote,
3095
3224
  makeStrikethrough,
3096
3225
  makeCodeBlock,
3097
3226
  makeSublist,
3227
+ transformTextStyle,
3228
+ makeToolbarButtonSelect,
3098
3229
  }