leksy-editor 1.3.1 → 1.4.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/README.md CHANGED
@@ -99,12 +99,14 @@ const app = createApp({
99
99
  | `tenorApiKey` | API key for Tenor integration. |
100
100
  | `cssVariables` | Custom CSS styling variables for multiple themes and modes. |
101
101
  | `disablePastedColorStyles` | Remove color-related CSS while pasting. |
102
+ | `removeColorsWhilePasting` | Remove all color and bg color CSS while pasting. |
102
103
  | `autoHeight` | Automatically adjusts height based on content. |
103
104
  | `height` | Height for the editor. |
104
105
  | `maxHeight` | Maximum height for the editor. |
105
106
  | `minHeight` | Minimum height for the editor. |
106
107
  | `iframeStyle` | To design inside iframe, pass your css here |
107
108
  | `disabled` | To disabled editor |
109
+ | `onFullScreen` | To open custom preview |
108
110
 
109
111
  ### CSS Customization
110
112
 
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',
@@ -49,6 +50,9 @@ const CLASSES = {
49
50
  PREVIEW_MODAL_CONTENT: '-preview-modal-content',
50
51
  PREVIEW_MODAL_CLOSE: '-preview-modal-close',
51
52
  PREVIEW_MODAL_BODY: '-preview-modal-body',
53
+ UPLOAD_IMG_BOX: '-upload-img-box',
54
+ UPLOAD_IMG_PREVIEW_BOX: '-upload-img-preview-box',
55
+ UPLOAD_IMG_PREVIEW: '-upload-img-preview',
52
56
  }
53
57
 
54
58
  const SVG = {
@@ -66,6 +70,13 @@ const SVG = {
66
70
  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
71
  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
72
  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>',
73
+ 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>',
74
+ 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>',
75
+ 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>',
76
+ 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>',
77
+ 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>',
78
+ 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>',
79
+ 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
80
  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
81
  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
82
  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 +99,7 @@ const SVG = {
88
99
  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
100
  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
101
  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>',
102
+ 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
103
  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
104
  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
105
  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>',
@@ -130,10 +142,8 @@ const SVG = {
130
142
  FULLSCREEN_IMAGE: '<svg xmlns="http://www.w3.org/2000/svg" width="20px" viewBox="0 0 24 24" fill="currentColor"><path d="M8 3V5H4V9H2V3H8ZM2 21V15H4V19H8V21H2ZM22 21H16V19H20V15H22V21ZM22 9H20V5H16V3H22V9Z"></path></svg>',
131
143
  CLOSE: '<svg xmlns="http://www.w3.org/2000/svg" width="16px" viewBox="0 0 24 24" fill="currentColor"><path d="M10.5859 12L2.79297 4.20706L4.20718 2.79285L12.0001 10.5857L19.793 2.79285L21.2072 4.20706L13.4143 12L21.2072 19.7928L19.793 21.2071L12.0001 13.4142L4.20718 21.2071L2.79297 19.7928L10.5859 12Z"></path></svg>',
132
144
  SPINNER: '<svg class="leksy-editor-spinner" width="16" height="16" viewBox="0 0 50 50"><circle class="leksy-editor-path" cx="25" cy="25" r="20" fill="none" stroke-width="4"></circle></svg>',
133
-
134
145
  }
135
146
 
136
-
137
147
  const MIMETYPE = {
138
148
  IMAGE: 'image/png,image/jpg,image/jpeg,image/webp',
139
149
  VIDEO: 'video/mp4,video/quicktime',
@@ -141,7 +151,6 @@ const MIMETYPE = {
141
151
 
142
152
  const SOCIAL_MEDIA_PATTERNS = {
143
153
  YOUTUBE: /(?:youtube\.com\/(?:.*[?&]v=|embed\/|v\/|watch\?v=|live\/)|youtu\.be\/)([^"&?\/\s]{11})/,
144
- // TWITTER: /(?:twitter\.com\/(?:#!\/)?(?:\w+)\/status\/|x\.com\/(?:#!\/)?(?:\w+)\/status\/)(\d+)/,
145
154
  REDDIT: /(?:reddit\.com\/r\/[\w-]+\/comments\/|reddit\.com\/comments\/)([\w-]+)/,
146
155
  INSTAGRAM: /(?:instagram\.com\/(?:p|reel|tv|stories\/[\w-]+|[\w-]+\/posts|share\/p)\/|instagr\.am\/p\/)([\w-]+)/,
147
156
  THREADS: /(?:threads\.net\/@[\w-]+\/post\/)([\w-]+)/,
@@ -154,7 +163,6 @@ const SOCIAL_MEDIA_PATTERNS = {
154
163
 
155
164
  const SOCIAL_MEDIA_BASEURLS = {
156
165
  YOUTUBE: "https://www.youtube.com/embed/",
157
- // TWITTER: "https://twitframe.com/show?url=https://twitter.com/user/status/",
158
166
  INSTAGRAM: "https://www.instagram.com/p/",
159
167
  REDDIT: "https://www.redditmedia.com/r/all/comments/",
160
168
  PINTEREST: "https://assets.pinterest.com/ext/embed.html?id=",
@@ -164,6 +172,7 @@ const SOCIAL_MEDIA_BASEURLS = {
164
172
  TWITCH_CHANNEL: 'https://player.twitch.tv/?channel=',
165
173
  TIKTOK: 'https://www.tiktok.com/embed/',
166
174
  };
175
+
167
176
  const CSS_VARIABLES = {
168
177
  primary: "hsl(160, 100%, 40%)",
169
178
  midDarker: "hsl(223, 5%, 76%)",
@@ -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
 
@@ -21,12 +21,14 @@ class LeksyEditor {
21
21
  * @property {String} tenorApiKey
22
22
  * @property {Object} cssVariables
23
23
  * @property {Boolean} disablePastedColorStyles
24
+ * @property {Boolean} removeColorsWhilePasting
24
25
  * @property {Boolean} autoHeight
25
26
  * @property {String} height
26
27
  * @property {String} maxHeight
27
28
  * @property {String} minHeight
28
29
  * @property {String} iframeStyle
29
30
  * @property {Boolean} disabled
31
+ * @property {Function} onFullScreen
30
32
  */
31
33
  /**
32
34
  *
@@ -263,7 +265,6 @@ class LeksyEditor {
263
265
  getCore: () => core,
264
266
  updateHeight: (height) => {
265
267
  core.elements.iframeContainer.style.setProperty('height', height);
266
- core.elements.editor.style.setProperty('height', height);
267
268
  },
268
269
  setDisabled: (disabled) => {
269
270
  options.disabled = disabled;
@@ -371,6 +372,8 @@ class LeksyEditor {
371
372
  toolbarPlugin = makeToolbarSelect(_plugin, options, core)
372
373
  } else if (_plugin.type === 'color') {
373
374
  toolbarPlugin = makeToolbarColor(_plugin, options, core)
375
+ } else if (_plugin.type === 'button-select') {
376
+ toolbarPlugin = makeToolbarButtonSelect(_plugin, options, core)
374
377
  }
375
378
  if (core.elements.toolbar[plugin])
376
379
  core.elements.toolbar[plugin].push(toolbarPlugin)
@@ -609,11 +612,6 @@ class LeksyEditor {
609
612
  contentEditableDiv.spellcheck = !!options.spellcheck
610
613
  contentEditableDiv.innerHTML = core.html;
611
614
  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
615
 
618
616
  contentEditableDiv.oninput = (e) => {
619
617
  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.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/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, CLASSES } 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}${CLASSES.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}${CLASSES.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}${CLASSES.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
@@ -45,6 +45,10 @@ const traverseAndClean = (element, options, core) => {
45
45
  if (bodyBg === bgColor) node.style.removeProperty('background-color');
46
46
  if (bodyBg === textColor) node.style.removeProperty('color');
47
47
  }
48
+ if (options?.removeColorsWhilePasting) {
49
+ node.style.removeProperty('background-color');
50
+ node.style.removeProperty('color');
51
+ }
48
52
  attributesToRemove.forEach(attr => node.removeAttribute(attr));
49
53
 
50
54
  // Process child nodes
@@ -197,11 +201,38 @@ const makeToolbarButton = (_plugin, options, core) => {
197
201
  return pluginButton
198
202
  }
199
203
 
204
+
205
+ const makeToolbarButtonSelect = (_plugin, options, core) => {
206
+ const pluginButton = document.createElement('button');
207
+ pluginButton.type = "button"
208
+ pluginButton.className = `${options.classPrefix}${CLASSES.TOOLBAR_ITEM} ${_plugin.type === 'button-select' ? CLASSES.TOOLBAR_ITEM_BUTTON_SELECT : ''}`
209
+
210
+ pluginButton.dataset.title = _plugin.title
211
+ pluginButton.dataset.type = 'button'
212
+ pluginButton.onclick = (e) => {
213
+ e.preventDefault();
214
+ _plugin.mainClick(e, core, options);
215
+ };
216
+
217
+ const pluginIcon = document.createElement('div');
218
+ pluginIcon.innerHTML = _plugin.icon;
219
+ pluginButton.appendChild(pluginIcon)
220
+ const pluginSelect = makeToolbarSelect({ ..._plugin, icon: '' }, options, core);
221
+
222
+ const pluginButtonSelect = document.createElement('div');
223
+ pluginButtonSelect.dataset.type = 'button-select'
224
+ pluginButtonSelect.style.display = 'flex'
225
+ pluginButtonSelect.appendChild(pluginButton);
226
+ pluginButtonSelect.appendChild(pluginSelect);
227
+ return pluginButtonSelect
228
+ }
229
+
230
+
200
231
  const makeToolbarColor = (_plugin, options, core) => {
201
232
  const pluginContainer = document.createElement('div');
202
233
  pluginContainer.className = `${options.classPrefix}${CLASSES.TOOLBAR_ITEM} ${CLASSES.TOOLBAR_ITEM_COLOR}`
203
- pluginContainer.setAttribute('data-title', _plugin.title)
204
- pluginContainer.setAttribute('data-type', 'color')
234
+ pluginContainer.dataset.title = _plugin.title
235
+ pluginContainer.dataset.type = 'color'
205
236
 
206
237
  const pluginButton = document.createElement('input');
207
238
  pluginButton.type = "color"
@@ -531,23 +562,29 @@ const makeToolbarSelect = (_plugin, options, core) => {
531
562
  }
532
563
 
533
564
  const selectPlugin = document.createElement('div');
534
- selectPlugin.setAttribute('data-type', 'select')
565
+ selectPlugin.dataset.type = 'select'
535
566
 
536
567
  const dropdownButton = document.createElement('button');
537
568
  dropdownButton.type = "button"
538
- dropdownButton.className = `${options.classPrefix}${CLASSES.TOOLBAR_ITEM} ${CLASSES.TOOLBAR_ITEM_SELECT}`
539
- dropdownButton.setAttribute('data-title', _plugin.title)
569
+ dropdownButton.className = `${options.classPrefix}${CLASSES.TOOLBAR_ITEM} ${CLASSES.TOOLBAR_ITEM_SELECT} ${_plugin.type === 'button-select' ? CLASSES.TOOLBAR_ITEM_BUTTON_SELECT : ''}`
570
+ dropdownButton.dataset.title = _plugin.type == 'button-select' ? `Options` : _plugin.title
540
571
 
541
572
  const pluginIcon = document.createElement('div');
542
573
  pluginIcon.style.width = _plugin.width
543
574
  pluginIcon.style.textAlign = 'left'
544
575
  pluginIcon.style.display = 'flex'
545
576
  pluginIcon.style.justifyContent = 'space-between'
577
+
546
578
  const iconName = document.createElement('span');
547
579
  iconName.innerHTML = _plugin.icon;
580
+
548
581
  const arrow = document.createElement('span');
549
- arrow.innerHTML = SVG.ARROW_DOWN;
550
- arrow.style.paddingLeft = '8px';
582
+ if (_plugin.type == 'button-select') {
583
+ arrow.innerHTML = SVG.ARROW_DROP_DOWN_FILL;
584
+ } else {
585
+ arrow.innerHTML = SVG.ARROW_DOWN;
586
+ arrow.style.paddingLeft = '8px';
587
+ }
551
588
  pluginIcon.append(iconName, arrow)
552
589
 
553
590
  dropdownButton.append(pluginIcon)
@@ -908,7 +945,7 @@ const makeEditToolbar = (options, core, { type, td, image, updateImage, updateTa
908
945
  title.textContent = plugin.title;
909
946
 
910
947
  const input = document.createElement('input');
911
- input.type = "text";
948
+ input.type = "number";
912
949
  input.placeholder = "auto";
913
950
  input.style.width = '60px';
914
951
  input.style.padding = '2px';
@@ -1128,7 +1165,11 @@ const initImageResizer = (type, image, options, core) => {
1128
1165
  src = href;
1129
1166
  }
1130
1167
  }
1131
- createPreviewModal(src, core, options, isVideo);
1168
+ if (options.onFullScreen instanceof Function) {
1169
+ const type = isVideo ? 'video/mp4' : 'image/png';
1170
+ options.onFullScreen(src, type);
1171
+ }
1172
+ else createPreviewModal(src, core, options, isVideo);
1132
1173
  });
1133
1174
 
1134
1175
  resizer.appendChild(fullscreenButton);
@@ -2018,6 +2059,11 @@ const changeToolbarStatByPlugin = (plugin, action, value) => {
2018
2059
  } else if (plugin.getAttribute('data-type') === 'color') {
2019
2060
  plugin.childNodes[0].disabled = true
2020
2061
  plugin.classList.add('disabled')
2062
+ } else if (plugin.getAttribute('data-type') === 'button-select') {
2063
+ plugin.querySelectorAll('button').forEach(btn => {
2064
+ btn.disabled = true
2065
+ btn.classList.add('disabled')
2066
+ })
2021
2067
  }
2022
2068
  break;
2023
2069
  case 'enabled':
@@ -2030,6 +2076,11 @@ const changeToolbarStatByPlugin = (plugin, action, value) => {
2030
2076
  } else if (plugin.getAttribute('data-type') === 'color') {
2031
2077
  plugin.childNodes[0].disabled = false
2032
2078
  plugin.classList.remove('disabled')
2079
+ } else if (plugin.getAttribute('data-type') === 'button-select') {
2080
+ plugin.querySelectorAll('button').forEach(btn => {
2081
+ btn.disabled = false
2082
+ btn.classList.remove('disabled')
2083
+ })
2033
2084
  }
2034
2085
  break;
2035
2086
  case 'active':
@@ -2039,6 +2090,8 @@ const changeToolbarStatByPlugin = (plugin, action, value) => {
2039
2090
  plugin.childNodes[0].classList.add('active')
2040
2091
  } else if (plugin.getAttribute('data-type') === 'color') {
2041
2092
  plugin.classList.add('active')
2093
+ } else if (plugin.getAttribute('data-type') === 'button-select') {
2094
+ plugin.querySelector('button[data-type="button"]')?.classList.add('active')
2042
2095
  }
2043
2096
  break;
2044
2097
  case 'inactive':
@@ -2048,6 +2101,8 @@ const changeToolbarStatByPlugin = (plugin, action, value) => {
2048
2101
  plugin.childNodes[0].classList.remove('active')
2049
2102
  } else if (plugin.getAttribute('data-type') === 'color') {
2050
2103
  plugin.classList.remove('active')
2104
+ } else if (plugin.getAttribute('data-type') === 'button-select') {
2105
+ plugin.querySelector('button[data-type="button"]')?.classList.remove('active')
2051
2106
  }
2052
2107
  break;
2053
2108
  case 'font':
@@ -3073,6 +3128,65 @@ const makeSublist = (event, core) => {
3073
3128
  }
3074
3129
  };
3075
3130
 
3131
+ const transformCase = (text, type) => {
3132
+ if (type === 'upper_case') return text.toUpperCase();
3133
+ if (type === 'lower_case') return text.toLowerCase();
3134
+ if (type === 'title_case') return text.toLowerCase().replace(/\b\w/g, s => s.toUpperCase());
3135
+ return text;
3136
+ };
3137
+
3138
+ const transformTextStyle = (core, type) => {
3139
+ const range = core.state.range;
3140
+ if (!range) return;
3141
+ const startContainer = range.startContainer;
3142
+ const startOffset = range.startOffset;
3143
+ const endContainer = range.endContainer;
3144
+ let endOffset = range.endOffset;
3145
+
3146
+ const processTextNode = (node, start, end) => {
3147
+ const text = node.nodeValue;
3148
+ const transformedText = transformCase(text.slice(start, end), type);
3149
+ node.nodeValue = text.slice(0, start) + transformedText + text.slice(end);
3150
+ if (node === endContainer) endOffset = start + transformedText.length;
3151
+ };
3152
+
3153
+ if (range.commonAncestorContainer.nodeType === Node.TEXT_NODE) {
3154
+ processTextNode(range.commonAncestorContainer, range.startOffset, range.endOffset);
3155
+ } else {
3156
+ const iterator = core.elements.iframeWindow.createNodeIterator(
3157
+ range.commonAncestorContainer,
3158
+ NodeFilter.SHOW_TEXT,
3159
+ {
3160
+ acceptNode: (node) => {
3161
+ return range.intersectsNode(node) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
3162
+ }
3163
+ }
3164
+ );
3165
+
3166
+ const nodes = [];
3167
+ let node;
3168
+ while ((node = iterator.nextNode())) {
3169
+ nodes.push(node);
3170
+ }
3171
+
3172
+ nodes.forEach(node => {
3173
+ let start = 0;
3174
+ let end = node.nodeValue.length;
3175
+
3176
+ if (node === range.startContainer) start = range.startOffset;
3177
+ if (node === range.endContainer) end = range.endOffset;
3178
+
3179
+ processTextNode(node, start, end);
3180
+ });
3181
+ }
3182
+
3183
+ const newRange = core.elements.iframeWindow.createRange();
3184
+ newRange.setStart(startContainer, startOffset);
3185
+ newRange.setEnd(endContainer, endOffset);
3186
+ const selection = core.elements.iframeWindow.getSelection();
3187
+ selection.removeAllRanges();
3188
+ selection.addRange(newRange);
3189
+ }
3076
3190
  export {
3077
3191
  cleanHTML,
3078
3192
  debounce,
@@ -3114,4 +3228,6 @@ export {
3114
3228
  makeStrikethrough,
3115
3229
  makeCodeBlock,
3116
3230
  makeSublist,
3231
+ transformTextStyle,
3232
+ makeToolbarButtonSelect,
3117
3233
  }