leksy-editor 1.2.1 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/constant.js +6 -0
- package/index.js +7 -3
- package/package.json +1 -1
- package/plugin.js +3 -1
- package/style.css +3 -5
- package/utilities.js +157 -9
package/constant.js
CHANGED
|
@@ -653,6 +653,10 @@ const TAB_CATEGORIES = {
|
|
|
653
653
|
|
|
654
654
|
const RESIZE_MARGIN = 10;
|
|
655
655
|
|
|
656
|
+
const LIST_STYLES_BY_LEVEL = ['1', 'a', 'i', 'A'];
|
|
657
|
+
const UNORDERED_LIST_STYLES_BY_LEVEL = ['disc', 'circle', 'square'];
|
|
658
|
+
|
|
659
|
+
|
|
656
660
|
export {
|
|
657
661
|
ERRORS,
|
|
658
662
|
CLASSES,
|
|
@@ -675,4 +679,6 @@ export {
|
|
|
675
679
|
CSS_VARIABLES,
|
|
676
680
|
TAB_CATEGORIES,
|
|
677
681
|
RESIZE_MARGIN,
|
|
682
|
+
LIST_STYLES_BY_LEVEL,
|
|
683
|
+
UNORDERED_LIST_STYLES_BY_LEVEL,
|
|
678
684
|
}
|
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, {
|
|
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 } 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 } from './utilities';
|
|
5
5
|
|
|
6
6
|
class LeksyEditor {
|
|
7
7
|
|
|
@@ -386,6 +386,7 @@ class LeksyEditor {
|
|
|
386
386
|
})
|
|
387
387
|
|
|
388
388
|
if (options.disabled) changeAllToolbarState(core, 'disabled')
|
|
389
|
+
showTooltip(options)
|
|
389
390
|
|
|
390
391
|
core.elements.toolbarContainer = toolbarContainer;
|
|
391
392
|
core.elements.base.appendChild(toolbarContainer);
|
|
@@ -725,6 +726,9 @@ class LeksyEditor {
|
|
|
725
726
|
}
|
|
726
727
|
|
|
727
728
|
if (event.key === 'Tab') {
|
|
729
|
+
makeSublist(event, core);
|
|
730
|
+
if (event.defaultPrevented) return;
|
|
731
|
+
|
|
728
732
|
event.preventDefault();
|
|
729
733
|
|
|
730
734
|
const tabNode = document.createTextNode("\u00a0\u00a0\u00a0\u00a0");
|
|
@@ -816,7 +820,7 @@ class LeksyEditor {
|
|
|
816
820
|
|
|
817
821
|
if (e.target.closest("a")?.tagName === 'A') {
|
|
818
822
|
const anchor = e.target.closest("a");
|
|
819
|
-
if (anchor) {
|
|
823
|
+
if (anchor && element.tagName !== 'IMG') {
|
|
820
824
|
core.elements.selectedElement = anchor;
|
|
821
825
|
showAnchorPopover(anchor, e.pageX, e.pageY, options, core);
|
|
822
826
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "leksy-editor",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.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
|
@@ -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 => ({
|
|
@@ -930,6 +930,7 @@ const PLUGINS = {
|
|
|
930
930
|
if (url) {
|
|
931
931
|
const a = document.createElement('a');
|
|
932
932
|
a.href = url.videoUrl
|
|
933
|
+
a.dataset.leksyPreview = 'video-url'
|
|
933
934
|
a.target = '_blank'
|
|
934
935
|
const img = document.createElement('img');
|
|
935
936
|
img.src = url.thumbnailUrl
|
|
@@ -946,6 +947,7 @@ const PLUGINS = {
|
|
|
946
947
|
if (url) {
|
|
947
948
|
const a = document.createElement('a');
|
|
948
949
|
a.href = url.videoUrl
|
|
950
|
+
a.dataset.leksyPreview = 'video-url'
|
|
949
951
|
a.target = '_blank'
|
|
950
952
|
const img = document.createElement('img');
|
|
951
953
|
img.src = url.thumbnailUrl
|
package/style.css
CHANGED
|
@@ -152,7 +152,9 @@
|
|
|
152
152
|
|
|
153
153
|
.leksy-editor-toolbar-item::after {
|
|
154
154
|
content: attr(data-title);
|
|
155
|
-
position:
|
|
155
|
+
position: fixed;
|
|
156
|
+
top: var(--leksy-editor-top, 0);
|
|
157
|
+
left: var(--leksy-editor-left, 0);
|
|
156
158
|
opacity: 0;
|
|
157
159
|
visibility: hidden;
|
|
158
160
|
transition: opacity 0.3s, visibility 0.3s;
|
|
@@ -161,8 +163,6 @@
|
|
|
161
163
|
color: black;
|
|
162
164
|
padding: 5px 10px;
|
|
163
165
|
border-radius: 5px;
|
|
164
|
-
bottom: -90%;
|
|
165
|
-
left: 60%;
|
|
166
166
|
transform: translateX(-50%);
|
|
167
167
|
white-space: nowrap;
|
|
168
168
|
z-index: 1;
|
|
@@ -516,7 +516,6 @@ button.leksy-editor-popover-tab.active {
|
|
|
516
516
|
height: 2rem;
|
|
517
517
|
padding: 0.75rem 1rem;
|
|
518
518
|
background-color: hsl(160, 100%, 35%);
|
|
519
|
-
border-color: hsl(160, 75%, 50%);
|
|
520
519
|
display: flex;
|
|
521
520
|
align-items: center;
|
|
522
521
|
justify-content: center;
|
|
@@ -530,7 +529,6 @@ button.leksy-editor-popover-tab.active {
|
|
|
530
529
|
white-space: nowrap;
|
|
531
530
|
gap: 0.5rem;
|
|
532
531
|
transition: 500ms;
|
|
533
|
-
text-transform: uppercase;
|
|
534
532
|
font-size: 1rem;
|
|
535
533
|
}
|
|
536
534
|
|
package/utilities.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CLASSES, FONT_SIZES, FONTS, FORMATS, GIPHY_POWERED_IMAGE, REGEX, RESIZER_PLUGINS, SOCIAL_MEDIA_BASEURLS, SOCIAL_MEDIA_PATTERNS, SVG, TABLE_PLUGINS, TAB_CATEGORIES, RESIZE_MARGIN } from "./constant";
|
|
1
|
+
import { CLASSES, FONT_SIZES, FONTS, FORMATS, GIPHY_POWERED_IMAGE, REGEX, RESIZER_PLUGINS, SOCIAL_MEDIA_BASEURLS, SOCIAL_MEDIA_PATTERNS, SVG, TABLE_PLUGINS, TAB_CATEGORIES, RESIZE_MARGIN, LIST_STYLES_BY_LEVEL, UNORDERED_LIST_STYLES_BY_LEVEL } from "./constant";
|
|
2
2
|
import { DiffDOM } from 'diff-dom';
|
|
3
3
|
import { applyUnorderedList, applyOrderList } from "./plugin";
|
|
4
4
|
|
|
@@ -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"
|
|
@@ -939,6 +957,7 @@ const makeEditToolbar = (options, core, { type, td, image, updateImage, updateTa
|
|
|
939
957
|
|
|
940
958
|
Object.keys(TAB_CATEGORIES).forEach((categoryName, index) => {
|
|
941
959
|
const tabButton = document.createElement('button');
|
|
960
|
+
tabButton.type = 'button'
|
|
942
961
|
tabButton.textContent = categoryName;
|
|
943
962
|
tabButton.className = `${options.classPrefix}${CLASSES.POPOVER_TAB}`
|
|
944
963
|
|
|
@@ -1098,7 +1117,18 @@ const initImageResizer = (type, image, options, core) => {
|
|
|
1098
1117
|
fullscreenButton.style.justifyContent = 'center';
|
|
1099
1118
|
|
|
1100
1119
|
fullscreenButton.addEventListener('click', () => {
|
|
1101
|
-
|
|
1120
|
+
const anchor = image.closest('a');
|
|
1121
|
+
let isVideo = false;
|
|
1122
|
+
let src = image.src;
|
|
1123
|
+
|
|
1124
|
+
if (anchor?.dataset?.leksyPreview === 'video-url') {
|
|
1125
|
+
const href = anchor.getAttribute('href');
|
|
1126
|
+
if (href) {
|
|
1127
|
+
isVideo = true;
|
|
1128
|
+
src = href;
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
createPreviewModal(src, core, options, isVideo);
|
|
1102
1132
|
});
|
|
1103
1133
|
|
|
1104
1134
|
resizer.appendChild(fullscreenButton);
|
|
@@ -1318,7 +1348,7 @@ const initImageResizer = (type, image, options, core) => {
|
|
|
1318
1348
|
core.elements.iframeWindow.addEventListener('keydown', core.deleteImageHandler)
|
|
1319
1349
|
}
|
|
1320
1350
|
|
|
1321
|
-
const createPreviewModal = (
|
|
1351
|
+
const createPreviewModal = (src, core, options, isVideo = false) => {
|
|
1322
1352
|
// Create overlay
|
|
1323
1353
|
const overlay = document.createElement('div');
|
|
1324
1354
|
overlay.className = `${options.classPrefix}${CLASSES.PREVIEW_MODAL_OVERLAY} `
|
|
@@ -1337,12 +1367,22 @@ const createPreviewModal = (imageSrc, core, options) => {
|
|
|
1337
1367
|
const modalBody = document.createElement('div');
|
|
1338
1368
|
modalBody.className = `${options.classPrefix}${CLASSES.PREVIEW_MODAL_BODY} `
|
|
1339
1369
|
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1370
|
+
let element;
|
|
1371
|
+
if (isVideo) {
|
|
1372
|
+
element = document.createElement('video');
|
|
1373
|
+
element.src = src;
|
|
1374
|
+
element.controls = true;
|
|
1375
|
+
element.style.maxWidth = '100%';
|
|
1376
|
+
element.style.maxHeight = '100%';
|
|
1377
|
+
element.setAttribute('controlsList', 'nodownload');
|
|
1378
|
+
element.setAttribute('disablePictureInPicture', true);
|
|
1379
|
+
} else {
|
|
1380
|
+
element = document.createElement('img');
|
|
1381
|
+
element.src = src;
|
|
1382
|
+
element.alt = 'Preview Image';
|
|
1383
|
+
}
|
|
1344
1384
|
|
|
1345
|
-
modalBody.appendChild(
|
|
1385
|
+
modalBody.appendChild(element);
|
|
1346
1386
|
modalContent.append(closeButton, modalBody);
|
|
1347
1387
|
overlay.appendChild(modalContent);
|
|
1348
1388
|
|
|
@@ -1350,7 +1390,7 @@ const createPreviewModal = (imageSrc, core, options) => {
|
|
|
1350
1390
|
document.body.appendChild(overlay);
|
|
1351
1391
|
|
|
1352
1392
|
overlay.addEventListener('click', (event) => {
|
|
1353
|
-
if (event.target !==
|
|
1393
|
+
if (event.target !== element) {
|
|
1354
1394
|
overlay.remove();
|
|
1355
1395
|
}
|
|
1356
1396
|
});
|
|
@@ -2927,6 +2967,112 @@ const makeCodeBlock = (event, core) => {
|
|
|
2927
2967
|
core.updateCaretPosition();
|
|
2928
2968
|
}
|
|
2929
2969
|
|
|
2970
|
+
const getListLevel = (li) => {
|
|
2971
|
+
let level = 0;
|
|
2972
|
+
let parent = li.parentElement;
|
|
2973
|
+
const tagName = parent.tagName;
|
|
2974
|
+
|
|
2975
|
+
while (parent && parent.tagName === tagName) {
|
|
2976
|
+
level++;
|
|
2977
|
+
parent = parent.parentElement?.closest(tagName);
|
|
2978
|
+
}
|
|
2979
|
+
|
|
2980
|
+
return level;
|
|
2981
|
+
};
|
|
2982
|
+
|
|
2983
|
+
const applyListStyle = (list, level) => {
|
|
2984
|
+
let type;
|
|
2985
|
+
if (list.tagName === 'OL') {
|
|
2986
|
+
type = LIST_STYLES_BY_LEVEL[(level - 1) % LIST_STYLES_BY_LEVEL.length];
|
|
2987
|
+
} else if (list.tagName === 'UL') {
|
|
2988
|
+
type = UNORDERED_LIST_STYLES_BY_LEVEL[(level - 1) % UNORDERED_LIST_STYLES_BY_LEVEL.length];
|
|
2989
|
+
}
|
|
2990
|
+
if (type) list.setAttribute('type', type);
|
|
2991
|
+
};
|
|
2992
|
+
|
|
2993
|
+
const indentListItem = (li, core) => {
|
|
2994
|
+
const prevLi = li.previousElementSibling;
|
|
2995
|
+
if (!prevLi) return;
|
|
2996
|
+
|
|
2997
|
+
const parentTag = li.parentElement.tagName;
|
|
2998
|
+
let subList = prevLi.querySelector(parentTag);
|
|
2999
|
+
|
|
3000
|
+
if (!subList) {
|
|
3001
|
+
subList = document.createElement(parentTag);
|
|
3002
|
+
prevLi.appendChild(subList);
|
|
3003
|
+
}
|
|
3004
|
+
|
|
3005
|
+
subList.appendChild(li);
|
|
3006
|
+
const selection = core.elements.iframeWindow.getSelection();
|
|
3007
|
+
const range = document.createRange();
|
|
3008
|
+
range.selectNodeContents(li);
|
|
3009
|
+
range.collapse(false);
|
|
3010
|
+
selection.removeAllRanges();
|
|
3011
|
+
selection.addRange(range);
|
|
3012
|
+
core.elements.editor.focus();
|
|
3013
|
+
core.updateCaretPosition();
|
|
3014
|
+
|
|
3015
|
+
if (parentTag === 'OL' || parentTag === 'UL') {
|
|
3016
|
+
const level = getListLevel(li);
|
|
3017
|
+
applyListStyle(subList, level);
|
|
3018
|
+
}
|
|
3019
|
+
};
|
|
3020
|
+
|
|
3021
|
+
const outdentListItem = (li, core) => {
|
|
3022
|
+
const parentOl = li.parentElement;
|
|
3023
|
+
const parentLi = parentOl.closest('li');
|
|
3024
|
+
if (!parentLi) return;
|
|
3025
|
+
|
|
3026
|
+
parentLi.after(li);
|
|
3027
|
+
|
|
3028
|
+
const selection = core.elements.iframeWindow.getSelection();
|
|
3029
|
+
const range = document.createRange();
|
|
3030
|
+
range.selectNodeContents(li);
|
|
3031
|
+
range.collapse(false);
|
|
3032
|
+
selection.removeAllRanges();
|
|
3033
|
+
selection.addRange(range);
|
|
3034
|
+
core.elements.editor.focus();
|
|
3035
|
+
core.updateCaretPosition();
|
|
3036
|
+
|
|
3037
|
+
|
|
3038
|
+
if (parentOl.children.length === 0) {
|
|
3039
|
+
parentOl.remove();
|
|
3040
|
+
}
|
|
3041
|
+
|
|
3042
|
+
if (li.parentElement.tagName === 'OL' || li.parentElement.tagName === 'UL') {
|
|
3043
|
+
const level = getListLevel(li);
|
|
3044
|
+
applyListStyle(li.parentElement, level);
|
|
3045
|
+
}
|
|
3046
|
+
};
|
|
3047
|
+
|
|
3048
|
+
const makeSublist = (event, core) => {
|
|
3049
|
+
if (event.key !== 'Tab') return;
|
|
3050
|
+
|
|
3051
|
+
const selection = core.elements.iframeWindow.getSelection();
|
|
3052
|
+
if (!selection.rangeCount) return;
|
|
3053
|
+
|
|
3054
|
+
let node = selection.anchorNode;
|
|
3055
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
3056
|
+
node = node.parentElement;
|
|
3057
|
+
}
|
|
3058
|
+
|
|
3059
|
+
const li = node.closest('li');
|
|
3060
|
+
if (!li) return;
|
|
3061
|
+
|
|
3062
|
+
const clone = li.cloneNode(true);
|
|
3063
|
+
clone.querySelectorAll('ul, ol').forEach(l => l.remove());
|
|
3064
|
+
const hasContent = clone.textContent.replace(/\u00A0/g, '').length > 0 || clone.querySelector('img, video, audio, table, hr');
|
|
3065
|
+
if (hasContent) return;
|
|
3066
|
+
|
|
3067
|
+
event.preventDefault();
|
|
3068
|
+
|
|
3069
|
+
if (event.shiftKey) {
|
|
3070
|
+
outdentListItem(li, core);
|
|
3071
|
+
} else {
|
|
3072
|
+
indentListItem(li, core);
|
|
3073
|
+
}
|
|
3074
|
+
};
|
|
3075
|
+
|
|
2930
3076
|
export {
|
|
2931
3077
|
cleanHTML,
|
|
2932
3078
|
debounce,
|
|
@@ -2962,8 +3108,10 @@ export {
|
|
|
2962
3108
|
getCellPosition,
|
|
2963
3109
|
makeUnorderedList,
|
|
2964
3110
|
makeOrderedList,
|
|
3111
|
+
showTooltip,
|
|
2965
3112
|
makeHeading,
|
|
2966
3113
|
makeBlockQuote,
|
|
2967
3114
|
makeStrikethrough,
|
|
2968
3115
|
makeCodeBlock,
|
|
3116
|
+
makeSublist,
|
|
2969
3117
|
}
|