leksy-editor 2.2.0 → 2.2.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/package.json +1 -1
- package/utilities.js +189 -149
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "leksy-editor",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.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/utilities.js
CHANGED
|
@@ -3648,6 +3648,7 @@ const findAndReplace = (core, options, event) => {
|
|
|
3648
3648
|
const matchCase = matchCaseCheckbox.checked;
|
|
3649
3649
|
const useRegex = useRegexCheckbox.checked;
|
|
3650
3650
|
const wholeWord = wholeWordCheckbox.checked;
|
|
3651
|
+
|
|
3651
3652
|
span.innerText = "";
|
|
3652
3653
|
countSpan.innerText = "";
|
|
3653
3654
|
|
|
@@ -3656,129 +3657,172 @@ const findAndReplace = (core, options, event) => {
|
|
|
3656
3657
|
return;
|
|
3657
3658
|
}
|
|
3658
3659
|
|
|
3659
|
-
let textContent = editor.textContent;
|
|
3660
|
-
let matchIndex = -1;
|
|
3661
|
-
let matchLength = 0;
|
|
3662
|
-
|
|
3663
3660
|
let searchPattern = searchText;
|
|
3664
3661
|
|
|
3665
3662
|
if (!useRegex) {
|
|
3666
|
-
const
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3663
|
+
const cleaned = wholeWord ? searchText.trim() : searchText;
|
|
3664
|
+
const escaped = cleaned.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
3665
|
+
|
|
3666
|
+
searchPattern = wholeWord
|
|
3667
|
+
? `(?<!\\w)${escaped}(?!\\w)`
|
|
3668
|
+
: escaped;
|
|
3672
3669
|
}
|
|
3673
3670
|
|
|
3674
3671
|
const selection = iframeWindow.getSelection();
|
|
3675
3672
|
|
|
3673
|
+
let regex;
|
|
3676
3674
|
try {
|
|
3677
3675
|
const flags = matchCase ? 'g' : 'gi';
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
if (selection.rangeCount > 0) {
|
|
3684
|
-
const range = selection.getRangeAt(0);
|
|
3685
|
-
if (direction === 'next') {
|
|
3686
|
-
startIndex = getCharIndex(editor, range.endContainer, range.endOffset);
|
|
3687
|
-
} else {
|
|
3688
|
-
startIndex = getCharIndex(editor, range.startContainer, range.startOffset);
|
|
3689
|
-
}
|
|
3690
|
-
}
|
|
3676
|
+
regex = new RegExp(searchPattern, flags);
|
|
3677
|
+
} catch (error) {
|
|
3678
|
+
span.innerText = "Invalid regular expression";
|
|
3679
|
+
return;
|
|
3680
|
+
}
|
|
3691
3681
|
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
if (matches[i].index < startIndex) {
|
|
3699
|
-
match = matches[i];
|
|
3700
|
-
break;
|
|
3701
|
-
}
|
|
3702
|
-
}
|
|
3703
|
-
if (!match) match = matches[matches.length - 1];
|
|
3704
|
-
}
|
|
3682
|
+
// ✅ Collect matches (node-based + global index)
|
|
3683
|
+
const walker = core.elements.iframeWindow.createTreeWalker(
|
|
3684
|
+
editor,
|
|
3685
|
+
NodeFilter.SHOW_TEXT,
|
|
3686
|
+
null
|
|
3687
|
+
);
|
|
3705
3688
|
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3689
|
+
const matches = [];
|
|
3690
|
+
let node;
|
|
3691
|
+
let globalIndex = 0;
|
|
3692
|
+
|
|
3693
|
+
while ((node = walker.nextNode())) {
|
|
3694
|
+
const text = node.nodeValue;
|
|
3695
|
+
regex.lastIndex = 0;
|
|
3696
|
+
|
|
3697
|
+
let match;
|
|
3698
|
+
while ((match = regex.exec(text)) !== null) {
|
|
3699
|
+
matches.push({
|
|
3700
|
+
node,
|
|
3701
|
+
start: match.index,
|
|
3702
|
+
end: match.index + match[0].length,
|
|
3703
|
+
globalStart: globalIndex + match.index
|
|
3704
|
+
});
|
|
3711
3705
|
}
|
|
3712
|
-
|
|
3713
|
-
|
|
3706
|
+
|
|
3707
|
+
globalIndex += text.length;
|
|
3708
|
+
}
|
|
3709
|
+
|
|
3710
|
+
if (!matches.length) {
|
|
3711
|
+
countSpan.innerText = "0 of 0";
|
|
3714
3712
|
return;
|
|
3715
3713
|
}
|
|
3716
3714
|
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
const endNodeInfo = getNodeAndOffsetFromIndex(editor, matchIndex + matchLength);
|
|
3715
|
+
// ✅ Get current cursor global position
|
|
3716
|
+
let currentGlobal = 0;
|
|
3720
3717
|
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
newRange.setStart(startNodeInfo.node, startNodeInfo.offset);
|
|
3724
|
-
newRange.setEnd(endNodeInfo.node, endNodeInfo.offset);
|
|
3725
|
-
selection.removeAllRanges();
|
|
3726
|
-
selection.addRange(newRange);
|
|
3718
|
+
if (selection.rangeCount > 0) {
|
|
3719
|
+
const range = selection.getRangeAt(0);
|
|
3727
3720
|
|
|
3728
|
-
|
|
3721
|
+
const walker2 = core.elements.iframeWindow.createTreeWalker(
|
|
3722
|
+
editor,
|
|
3723
|
+
NodeFilter.SHOW_TEXT,
|
|
3724
|
+
null
|
|
3725
|
+
);
|
|
3726
|
+
|
|
3727
|
+
let n, count = 0;
|
|
3728
|
+
|
|
3729
|
+
while ((n = walker2.nextNode())) {
|
|
3730
|
+
if (n === range.startContainer) {
|
|
3731
|
+
currentGlobal = count + range.startOffset;
|
|
3732
|
+
break;
|
|
3733
|
+
}
|
|
3734
|
+
count += n.nodeValue.length;
|
|
3729
3735
|
}
|
|
3736
|
+
}
|
|
3737
|
+
|
|
3738
|
+
// ✅ Find next / prev
|
|
3739
|
+
let matchIndex = -1;
|
|
3740
|
+
|
|
3741
|
+
if (direction === 'next') {
|
|
3742
|
+
matchIndex = matches.findIndex(m => m.globalStart > currentGlobal);
|
|
3743
|
+
if (matchIndex === -1) matchIndex = 0;
|
|
3730
3744
|
} else {
|
|
3731
|
-
|
|
3745
|
+
for (let i = matches.length - 1; i >= 0; i--) {
|
|
3746
|
+
if (matches[i].globalStart < currentGlobal) {
|
|
3747
|
+
matchIndex = i;
|
|
3748
|
+
break;
|
|
3749
|
+
}
|
|
3750
|
+
}
|
|
3751
|
+
if (matchIndex === -1) matchIndex = matches.length - 1;
|
|
3732
3752
|
}
|
|
3753
|
+
|
|
3754
|
+
const match = matches[matchIndex];
|
|
3755
|
+
|
|
3756
|
+
// ✅ Select
|
|
3757
|
+
const newRange = core.elements.iframeWindow.createRange();
|
|
3758
|
+
newRange.setStart(match.node, match.start);
|
|
3759
|
+
newRange.setEnd(match.node, match.end);
|
|
3760
|
+
|
|
3761
|
+
selection.removeAllRanges();
|
|
3762
|
+
selection.addRange(newRange);
|
|
3763
|
+
|
|
3764
|
+
match.node.parentElement?.scrollIntoView({
|
|
3765
|
+
block: "center",
|
|
3766
|
+
behavior: "smooth"
|
|
3767
|
+
});
|
|
3768
|
+
|
|
3769
|
+
countSpan.innerText = `${matchIndex + 1} of ${matches.length}`;
|
|
3733
3770
|
};
|
|
3734
3771
|
|
|
3735
3772
|
const replace = () => {
|
|
3736
3773
|
const searchText = findInput.value;
|
|
3737
3774
|
const replaceText = replaceInput.value;
|
|
3738
|
-
const useRegex = useRegexCheckbox.checked;
|
|
3739
3775
|
const matchCase = matchCaseCheckbox.checked;
|
|
3776
|
+
const useRegex = useRegexCheckbox.checked;
|
|
3740
3777
|
const wholeWord = wholeWordCheckbox.checked;
|
|
3741
3778
|
|
|
3742
3779
|
if (!searchText) return;
|
|
3743
3780
|
|
|
3781
|
+
// 🔹 Build regex (same as find)
|
|
3782
|
+
let pattern = searchText;
|
|
3783
|
+
|
|
3784
|
+
if (!useRegex) {
|
|
3785
|
+
const cleaned = wholeWord ? searchText.trim() : searchText;
|
|
3786
|
+
const escaped = cleaned.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
3787
|
+
|
|
3788
|
+
pattern = wholeWord
|
|
3789
|
+
? `(?<!\\w)${escaped}(?!\\w)`
|
|
3790
|
+
: escaped;
|
|
3791
|
+
}
|
|
3792
|
+
|
|
3793
|
+
let regex;
|
|
3794
|
+
try {
|
|
3795
|
+
regex = new RegExp(pattern, matchCase ? '' : 'i');
|
|
3796
|
+
} catch {
|
|
3797
|
+
return;
|
|
3798
|
+
}
|
|
3799
|
+
|
|
3744
3800
|
const selection = iframeWindow.getSelection();
|
|
3745
|
-
if (selection.rangeCount > 0) {
|
|
3746
|
-
const range = selection.getRangeAt(0);
|
|
3747
|
-
const selectedText = range.toString();
|
|
3748
|
-
let shouldReplace = false;
|
|
3749
|
-
let newText = replaceText;
|
|
3750
|
-
|
|
3751
|
-
let searchPattern = searchText;
|
|
3752
|
-
let isRegexSearch = useRegex;
|
|
3753
|
-
|
|
3754
|
-
if (!useRegex && wholeWord) {
|
|
3755
|
-
const escaped = searchText.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
3756
|
-
searchPattern = `\\b${escaped}\\b`;
|
|
3757
|
-
isRegexSearch = true;
|
|
3758
|
-
}
|
|
3759
|
-
|
|
3760
|
-
if (isRegexSearch) {
|
|
3761
|
-
try {
|
|
3762
|
-
const regex = new RegExp(searchPattern, matchCase ? '' : 'i');
|
|
3763
|
-
const match = selectedText.match(regex);
|
|
3764
|
-
if (match && match[0] === selectedText) {
|
|
3765
|
-
shouldReplace = true;
|
|
3766
|
-
newText = selectedText.replace(regex, replaceText);
|
|
3767
|
-
}
|
|
3768
|
-
} catch (e) { }
|
|
3769
|
-
} else if (selectedText === searchText || (!matchCase && selectedText.toLowerCase() === searchText.toLowerCase())) {
|
|
3770
|
-
shouldReplace = true;
|
|
3771
|
-
}
|
|
3772
3801
|
|
|
3773
|
-
|
|
3774
|
-
iframeWindow.execCommand('insertText', false, newText);
|
|
3775
|
-
find('next');
|
|
3776
|
-
} else {
|
|
3777
|
-
find('next');
|
|
3778
|
-
}
|
|
3779
|
-
} else {
|
|
3802
|
+
if (!selection.rangeCount) {
|
|
3780
3803
|
find('next');
|
|
3804
|
+
return;
|
|
3781
3805
|
}
|
|
3806
|
+
|
|
3807
|
+
const range = selection.getRangeAt(0);
|
|
3808
|
+
const selectedText = range.toString();
|
|
3809
|
+
|
|
3810
|
+
const match = selectedText.match(regex);
|
|
3811
|
+
|
|
3812
|
+
if (match && match[0] === selectedText) {
|
|
3813
|
+
const newText = selectedText.replace(regex, replaceText);
|
|
3814
|
+
|
|
3815
|
+
// ✅ safe replace
|
|
3816
|
+
range.deleteContents();
|
|
3817
|
+
range.insertNode(core.elements.iframeWindow.createTextNode(newText));
|
|
3818
|
+
|
|
3819
|
+
// move cursor after replaced text
|
|
3820
|
+
range.setStart(range.endContainer, range.endOffset);
|
|
3821
|
+
selection.removeAllRanges();
|
|
3822
|
+
selection.addRange(range);
|
|
3823
|
+
}
|
|
3824
|
+
|
|
3825
|
+
find('next');
|
|
3782
3826
|
};
|
|
3783
3827
|
|
|
3784
3828
|
const replaceAll = () => {
|
|
@@ -3790,76 +3834,72 @@ const findAndReplace = (core, options, event) => {
|
|
|
3790
3834
|
|
|
3791
3835
|
if (!searchText) return;
|
|
3792
3836
|
|
|
3793
|
-
let
|
|
3794
|
-
const selection = iframeWindow.getSelection();
|
|
3837
|
+
let pattern = searchText;
|
|
3795
3838
|
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3839
|
+
if (!useRegex) {
|
|
3840
|
+
const cleaned = wholeWord ? searchText.trim() : searchText;
|
|
3841
|
+
const escaped = cleaned.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
3842
|
+
|
|
3843
|
+
pattern = wholeWord
|
|
3844
|
+
? `(?<!\\w)${escaped}(?!\\w)`
|
|
3845
|
+
: escaped;
|
|
3803
3846
|
}
|
|
3804
3847
|
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
}
|
|
3834
|
-
} else {
|
|
3835
|
-
let search = searchText;
|
|
3836
|
-
if (!matchCase) {
|
|
3837
|
-
currentContent = currentContent.toLowerCase();
|
|
3838
|
-
search = search.toLowerCase();
|
|
3839
|
-
}
|
|
3840
|
-
matchIndex = currentContent.indexOf(search, searchStart);
|
|
3841
|
-
matchLength = search.length;
|
|
3848
|
+
let regex;
|
|
3849
|
+
try {
|
|
3850
|
+
regex = new RegExp(pattern, matchCase ? 'g' : 'gi');
|
|
3851
|
+
} catch {
|
|
3852
|
+
return;
|
|
3853
|
+
}
|
|
3854
|
+
|
|
3855
|
+
// 🔹 Collect matches (same as find)
|
|
3856
|
+
const walker = core.elements.iframeWindow.createTreeWalker(
|
|
3857
|
+
editor,
|
|
3858
|
+
NodeFilter.SHOW_TEXT,
|
|
3859
|
+
null
|
|
3860
|
+
);
|
|
3861
|
+
|
|
3862
|
+
const matches = [];
|
|
3863
|
+
let node;
|
|
3864
|
+
|
|
3865
|
+
while ((node = walker.nextNode())) {
|
|
3866
|
+
const text = node.nodeValue;
|
|
3867
|
+
regex.lastIndex = 0;
|
|
3868
|
+
|
|
3869
|
+
let match;
|
|
3870
|
+
while ((match = regex.exec(text)) !== null) {
|
|
3871
|
+
matches.push({
|
|
3872
|
+
node,
|
|
3873
|
+
start: match.index,
|
|
3874
|
+
end: match.index + match[0].length,
|
|
3875
|
+
original: match[0]
|
|
3876
|
+
});
|
|
3842
3877
|
}
|
|
3878
|
+
}
|
|
3843
3879
|
|
|
3844
|
-
|
|
3880
|
+
// 🔹 Replace in reverse (important)
|
|
3881
|
+
let count = 0;
|
|
3845
3882
|
|
|
3846
|
-
|
|
3847
|
-
const
|
|
3883
|
+
for (let i = matches.length - 1; i >= 0; i--) {
|
|
3884
|
+
const m = matches[i];
|
|
3885
|
+
const text = m.node.nodeValue;
|
|
3848
3886
|
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
selection.removeAllRanges();
|
|
3854
|
-
selection.addRange(newRange);
|
|
3887
|
+
const newText = m.original.replace(
|
|
3888
|
+
new RegExp(pattern, matchCase ? '' : 'i'),
|
|
3889
|
+
replaceText
|
|
3890
|
+
);
|
|
3855
3891
|
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3892
|
+
m.node.nodeValue =
|
|
3893
|
+
text.slice(0, m.start) +
|
|
3894
|
+
newText +
|
|
3895
|
+
text.slice(m.end);
|
|
3896
|
+
|
|
3897
|
+
count++;
|
|
3861
3898
|
}
|
|
3899
|
+
|
|
3862
3900
|
span.innerText = `Replaced ${count} occurrences.`;
|
|
3901
|
+
|
|
3902
|
+
find('next');
|
|
3863
3903
|
};
|
|
3864
3904
|
|
|
3865
3905
|
prevBtn.onclick = () => find('prev');
|