leksy-editor 1.3.1 → 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
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 } from './utilities';
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)
@@ -609,11 +611,6 @@ class LeksyEditor {
609
611
  contentEditableDiv.spellcheck = !!options.spellcheck
610
612
  contentEditableDiv.innerHTML = core.html;
611
613
  contentEditableDiv.className = 'content-editable';
612
- if (options.autoHeight) contentEditableDiv.style.height = 'auto';
613
- else contentEditableDiv.style.height = options.minHeight ?? options.height ?? '250px';
614
- contentEditableDiv.style.minHeight = options.minHeight ?? options.height ?? '250px';
615
- if (options.maxHeight) contentEditableDiv.style.maxHeight = options.maxHeight;
616
- if (options.autoHeight) iframe.contentDocument.body.style.overflow = 'hidden';
617
614
 
618
615
  contentEditableDiv.oninput = (e) => {
619
616
  core.onChange(e.target.innerHTML)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leksy-editor",
3
- "version": "1.3.1",
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();
@@ -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)
770
881
 
771
- const altLabel = document.createElement("label");
772
- altLabel.innerText = "Alternative Text";
882
+ // other fields
883
+ const span = document.createElement('div');
884
+ span.className = 'warning';
885
+
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;
@@ -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;
@@ -599,6 +621,37 @@ button.leksy-editor-popover-tab.active {
599
621
  animation: leksy-editor-dash 1.5s ease-in-out infinite;
600
622
  }
601
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
+
602
655
  @keyframes leksy-editor-rotate {
603
656
  100% {
604
657
  transform: rotate(360deg);
package/utilities.js CHANGED
@@ -197,11 +197,38 @@ const makeToolbarButton = (_plugin, options, core) => {
197
197
  return pluginButton
198
198
  }
199
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
+
200
227
  const makeToolbarColor = (_plugin, options, core) => {
201
228
  const pluginContainer = document.createElement('div');
202
229
  pluginContainer.className = `${options.classPrefix}${CLASSES.TOOLBAR_ITEM} ${CLASSES.TOOLBAR_ITEM_COLOR}`
203
- pluginContainer.setAttribute('data-title', _plugin.title)
204
- pluginContainer.setAttribute('data-type', 'color')
230
+ pluginContainer.dataset.title = _plugin.title
231
+ pluginContainer.dataset.type = 'color'
205
232
 
206
233
  const pluginButton = document.createElement('input');
207
234
  pluginButton.type = "color"
@@ -531,23 +558,29 @@ const makeToolbarSelect = (_plugin, options, core) => {
531
558
  }
532
559
 
533
560
  const selectPlugin = document.createElement('div');
534
- selectPlugin.setAttribute('data-type', 'select')
561
+ selectPlugin.dataset.type = 'select'
535
562
 
536
563
  const dropdownButton = document.createElement('button');
537
564
  dropdownButton.type = "button"
538
- dropdownButton.className = `${options.classPrefix}${CLASSES.TOOLBAR_ITEM} ${CLASSES.TOOLBAR_ITEM_SELECT}`
539
- 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
540
567
 
541
568
  const pluginIcon = document.createElement('div');
542
569
  pluginIcon.style.width = _plugin.width
543
570
  pluginIcon.style.textAlign = 'left'
544
571
  pluginIcon.style.display = 'flex'
545
572
  pluginIcon.style.justifyContent = 'space-between'
573
+
546
574
  const iconName = document.createElement('span');
547
575
  iconName.innerHTML = _plugin.icon;
576
+
548
577
  const arrow = document.createElement('span');
549
- arrow.innerHTML = SVG.ARROW_DOWN;
550
- 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
+ }
551
584
  pluginIcon.append(iconName, arrow)
552
585
 
553
586
  dropdownButton.append(pluginIcon)
@@ -908,7 +941,7 @@ const makeEditToolbar = (options, core, { type, td, image, updateImage, updateTa
908
941
  title.textContent = plugin.title;
909
942
 
910
943
  const input = document.createElement('input');
911
- input.type = "text";
944
+ input.type = "number";
912
945
  input.placeholder = "auto";
913
946
  input.style.width = '60px';
914
947
  input.style.padding = '2px';
@@ -1128,7 +1161,11 @@ const initImageResizer = (type, image, options, core) => {
1128
1161
  src = href;
1129
1162
  }
1130
1163
  }
1131
- 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);
1132
1169
  });
1133
1170
 
1134
1171
  resizer.appendChild(fullscreenButton);
@@ -2018,6 +2055,11 @@ const changeToolbarStatByPlugin = (plugin, action, value) => {
2018
2055
  } else if (plugin.getAttribute('data-type') === 'color') {
2019
2056
  plugin.childNodes[0].disabled = true
2020
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
+ })
2021
2063
  }
2022
2064
  break;
2023
2065
  case 'enabled':
@@ -2030,6 +2072,11 @@ const changeToolbarStatByPlugin = (plugin, action, value) => {
2030
2072
  } else if (plugin.getAttribute('data-type') === 'color') {
2031
2073
  plugin.childNodes[0].disabled = false
2032
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
+ })
2033
2080
  }
2034
2081
  break;
2035
2082
  case 'active':
@@ -2039,6 +2086,8 @@ const changeToolbarStatByPlugin = (plugin, action, value) => {
2039
2086
  plugin.childNodes[0].classList.add('active')
2040
2087
  } else if (plugin.getAttribute('data-type') === 'color') {
2041
2088
  plugin.classList.add('active')
2089
+ } else if (plugin.getAttribute('data-type') === 'button-select') {
2090
+ plugin.querySelector('button[data-type="button"]')?.classList.add('active')
2042
2091
  }
2043
2092
  break;
2044
2093
  case 'inactive':
@@ -2048,6 +2097,8 @@ const changeToolbarStatByPlugin = (plugin, action, value) => {
2048
2097
  plugin.childNodes[0].classList.remove('active')
2049
2098
  } else if (plugin.getAttribute('data-type') === 'color') {
2050
2099
  plugin.classList.remove('active')
2100
+ } else if (plugin.getAttribute('data-type') === 'button-select') {
2101
+ plugin.querySelector('button[data-type="button"]')?.classList.remove('active')
2051
2102
  }
2052
2103
  break;
2053
2104
  case 'font':
@@ -3073,6 +3124,65 @@ const makeSublist = (event, core) => {
3073
3124
  }
3074
3125
  };
3075
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
+ }
3076
3186
  export {
3077
3187
  cleanHTML,
3078
3188
  debounce,
@@ -3114,4 +3224,6 @@ export {
3114
3224
  makeStrikethrough,
3115
3225
  makeCodeBlock,
3116
3226
  makeSublist,
3227
+ transformTextStyle,
3228
+ makeToolbarButtonSelect,
3117
3229
  }