@lukso/web-components 1.156.0 → 1.156.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/dist/components/lukso-icon/index.cjs +33 -11
- package/dist/components/lukso-icon/index.d.ts +4 -4
- package/dist/components/lukso-icon/index.d.ts.map +1 -1
- package/dist/components/lukso-icon/index.js +33 -11
- package/dist/components/lukso-icon/lukso-icon.stories.d.ts +15 -1
- package/dist/components/lukso-icon/lukso-icon.stories.d.ts.map +1 -1
- package/dist/components/lukso-markdown-editor/index.cjs +493 -79
- package/dist/components/lukso-markdown-editor/index.d.ts +40 -1
- package/dist/components/lukso-markdown-editor/index.d.ts.map +1 -1
- package/dist/components/lukso-markdown-editor/index.js +493 -79
- package/dist/components/lukso-markdown-editor/lukso-markdown-editor.stories.d.ts +53 -1
- package/dist/components/lukso-markdown-editor/lukso-markdown-editor.stories.d.ts.map +1 -1
- package/dist/vitest.config.d.ts.map +1 -1
- package/package.json +1 -1
- package/LICENSE +0 -21
- package/README.md +0 -146
|
@@ -48,9 +48,11 @@ let LuksoMarkdownEditor = class extends TailwindStyledElement(style) {
|
|
|
48
48
|
this.isHeadingDropdownOpen = false;
|
|
49
49
|
this.isColorDropdownOpen = false;
|
|
50
50
|
this.isListDropdownOpen = false;
|
|
51
|
+
this.isAlignmentDropdownOpen = false;
|
|
51
52
|
this.headingTriggerId = "heading-dropdown-trigger";
|
|
52
53
|
this.colorTriggerId = "color-dropdown-trigger";
|
|
53
54
|
this.listTriggerId = "list-dropdown-trigger";
|
|
55
|
+
this.alignmentTriggerId = "alignment-dropdown-trigger";
|
|
54
56
|
this.currentSelection = { start: 0, end: 0 };
|
|
55
57
|
this.savedSelection = null;
|
|
56
58
|
this.defaultColor = "#374151";
|
|
@@ -61,10 +63,12 @@ let LuksoMarkdownEditor = class extends TailwindStyledElement(style) {
|
|
|
61
63
|
h1: false,
|
|
62
64
|
h2: false,
|
|
63
65
|
h3: false,
|
|
66
|
+
h4: false,
|
|
64
67
|
color: false,
|
|
65
68
|
activeColor: this.defaultColor,
|
|
66
69
|
unorderedList: false,
|
|
67
|
-
orderedList: false
|
|
70
|
+
orderedList: false,
|
|
71
|
+
alignment: "left"
|
|
68
72
|
};
|
|
69
73
|
// Undo/Redo state
|
|
70
74
|
this.undoStack = [];
|
|
@@ -87,14 +91,19 @@ let LuksoMarkdownEditor = class extends TailwindStyledElement(style) {
|
|
|
87
91
|
if (this.isListDropdownOpen) {
|
|
88
92
|
this.isListDropdownOpen = false;
|
|
89
93
|
}
|
|
94
|
+
if (this.isAlignmentDropdownOpen) {
|
|
95
|
+
this.isAlignmentDropdownOpen = false;
|
|
96
|
+
}
|
|
90
97
|
return;
|
|
91
98
|
}
|
|
92
99
|
const isInsideHeadingDropdown = this.shadowRoot?.getElementById("headingDropdown")?.contains(target);
|
|
93
100
|
const isInsideColorDropdown = this.shadowRoot?.getElementById("colorDropdown")?.contains(target);
|
|
94
101
|
const isInsideListDropdown = this.shadowRoot?.getElementById("listDropdown")?.contains(target);
|
|
102
|
+
const isInsideAlignmentDropdown = this.shadowRoot?.getElementById("alignmentDropdown")?.contains(target);
|
|
95
103
|
const isHeadingTrigger = this.shadowRoot?.getElementById(this.headingTriggerId)?.contains(target);
|
|
96
104
|
const isColorTrigger = this.shadowRoot?.getElementById(this.colorTriggerId)?.contains(target);
|
|
97
105
|
const isListTrigger = this.shadowRoot?.getElementById(this.listTriggerId)?.contains(target);
|
|
106
|
+
const isAlignmentTrigger = this.shadowRoot?.getElementById(this.alignmentTriggerId)?.contains(target);
|
|
98
107
|
if (!isInsideHeadingDropdown && !isHeadingTrigger && this.isHeadingDropdownOpen) {
|
|
99
108
|
this.isHeadingDropdownOpen = false;
|
|
100
109
|
}
|
|
@@ -104,6 +113,9 @@ let LuksoMarkdownEditor = class extends TailwindStyledElement(style) {
|
|
|
104
113
|
if (!isInsideListDropdown && !isListTrigger && this.isListDropdownOpen) {
|
|
105
114
|
this.isListDropdownOpen = false;
|
|
106
115
|
}
|
|
116
|
+
if (!isInsideAlignmentDropdown && !isAlignmentTrigger && this.isAlignmentDropdownOpen) {
|
|
117
|
+
this.isAlignmentDropdownOpen = false;
|
|
118
|
+
}
|
|
107
119
|
};
|
|
108
120
|
this.styles = ce({
|
|
109
121
|
slots: {
|
|
@@ -116,6 +128,7 @@ let LuksoMarkdownEditor = class extends TailwindStyledElement(style) {
|
|
|
116
128
|
colorMenu: "relative",
|
|
117
129
|
headingMenu: "relative",
|
|
118
130
|
listMenu: "relative",
|
|
131
|
+
alignmentMenu: "relative",
|
|
119
132
|
label: "heading-inter-14-bold text-neutral-20",
|
|
120
133
|
description: "paragraph-inter-12-regular text-neutral-20",
|
|
121
134
|
divider: "w-[1px] h-4 bg-neutral-90"
|
|
@@ -328,7 +341,7 @@ let LuksoMarkdownEditor = class extends TailwindStyledElement(style) {
|
|
|
328
341
|
/**
|
|
329
342
|
* Apply or toggle heading formatting for the current line(s).
|
|
330
343
|
*
|
|
331
|
-
* @param level - 0 to remove heading, 1-
|
|
344
|
+
* @param level - 0 to remove heading, 1-4 for heading levels
|
|
332
345
|
*/
|
|
333
346
|
applyHeading(level) {
|
|
334
347
|
if (this.isReadonly || this.isDisabled) return;
|
|
@@ -519,6 +532,156 @@ let LuksoMarkdownEditor = class extends TailwindStyledElement(style) {
|
|
|
519
532
|
if (this.activeFormats.orderedList) return "ordered";
|
|
520
533
|
return "none";
|
|
521
534
|
}
|
|
535
|
+
/**
|
|
536
|
+
* Get the current alignment icon name based on activeFormats.
|
|
537
|
+
*/
|
|
538
|
+
getAlignmentIcon() {
|
|
539
|
+
switch (this.activeFormats.alignment) {
|
|
540
|
+
case "center":
|
|
541
|
+
return "textalign-center";
|
|
542
|
+
case "right":
|
|
543
|
+
return "textalign-right";
|
|
544
|
+
default:
|
|
545
|
+
return "textalign-left";
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Apply or toggle text alignment for the current line(s).
|
|
550
|
+
*
|
|
551
|
+
* @param alignment - 'left', 'center', or 'right'
|
|
552
|
+
*/
|
|
553
|
+
applyAlignment(alignment) {
|
|
554
|
+
if (this.isReadonly || this.isDisabled) return;
|
|
555
|
+
this.saveUndoStateBeforeChange();
|
|
556
|
+
this.withSelection((textarea, start, end, value) => {
|
|
557
|
+
const lineStart = value.lastIndexOf("\n", start - 1) + 1;
|
|
558
|
+
let lineEnd = value.indexOf("\n", end);
|
|
559
|
+
if (lineEnd === -1) lineEnd = value.length;
|
|
560
|
+
const before = value.slice(0, lineStart);
|
|
561
|
+
const selected = value.slice(lineStart, lineEnd);
|
|
562
|
+
const after = value.slice(lineEnd);
|
|
563
|
+
let transformed;
|
|
564
|
+
const currentAlignmentRegex = /<div style="text-align: (left|center|right);">(.*?)<\/div>/s;
|
|
565
|
+
const existingMatch = selected.match(currentAlignmentRegex);
|
|
566
|
+
if (existingMatch || this.hasNestedAlignment(selected)) {
|
|
567
|
+
if (existingMatch) {
|
|
568
|
+
const existingAlignment = existingMatch[1];
|
|
569
|
+
const innerContent = existingMatch[2];
|
|
570
|
+
if (existingAlignment === alignment) {
|
|
571
|
+
transformed = innerContent;
|
|
572
|
+
} else {
|
|
573
|
+
transformed = `<div style="text-align: ${alignment};">${innerContent}</div>`;
|
|
574
|
+
}
|
|
575
|
+
} else {
|
|
576
|
+
if (this.getNestedAlignment(selected) === alignment) {
|
|
577
|
+
transformed = this.removeNestedAlignment(selected);
|
|
578
|
+
} else {
|
|
579
|
+
transformed = this.replaceNestedAlignment(selected, alignment);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
} else {
|
|
583
|
+
if (alignment === "left") {
|
|
584
|
+
transformed = selected;
|
|
585
|
+
} else {
|
|
586
|
+
transformed = this.wrapContentWithAlignment(selected, alignment);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
this.value = before + transformed + after;
|
|
590
|
+
textarea.value = before + transformed + after;
|
|
591
|
+
const cursorPosition = before.length + transformed.length;
|
|
592
|
+
requestAnimationFrame(() => {
|
|
593
|
+
textarea.setSelectionRange(cursorPosition, cursorPosition);
|
|
594
|
+
this.updateActiveFormats();
|
|
595
|
+
});
|
|
596
|
+
this.dispatchChange();
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Check if content has nested alignment divs inside formatting markers.
|
|
601
|
+
*/
|
|
602
|
+
hasNestedAlignment(content) {
|
|
603
|
+
const alignmentRegex = /<div style="text-align: (left|center|right);">/;
|
|
604
|
+
return alignmentRegex.test(content);
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Get the alignment from nested alignment divs.
|
|
608
|
+
*/
|
|
609
|
+
getNestedAlignment(content) {
|
|
610
|
+
const alignmentMatch = content.match(
|
|
611
|
+
/<div style="text-align: (left|center|right);">/
|
|
612
|
+
);
|
|
613
|
+
return alignmentMatch ? alignmentMatch[1] : null;
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Remove nested alignment divs from content.
|
|
617
|
+
*/
|
|
618
|
+
removeNestedAlignment(content) {
|
|
619
|
+
return content.replace(
|
|
620
|
+
/<div style="text-align: (left|center|right);">([^<]*?)<\/div>/g,
|
|
621
|
+
"$2"
|
|
622
|
+
);
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* Replace nested alignment with a new alignment.
|
|
626
|
+
*/
|
|
627
|
+
replaceNestedAlignment(content, newAlignment) {
|
|
628
|
+
return content.replace(
|
|
629
|
+
/<div style="text-align: (left|center|right);">([^<]*?)<\/div>/g,
|
|
630
|
+
`<div style="text-align: ${newAlignment};">$2</div>`
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Wrap content with alignment div, ensuring proper nesting inside formatting markers.
|
|
635
|
+
* Examples:
|
|
636
|
+
* - **text** becomes **<div style="text-align: center;">text</div>**
|
|
637
|
+
* - *text* becomes *<div style="text-align: center;">text</div>*
|
|
638
|
+
* - # text becomes # <div style="text-align: center;">text</div>
|
|
639
|
+
*
|
|
640
|
+
* @param content - the content to wrap
|
|
641
|
+
* @param alignment - 'left', 'center' or 'right'
|
|
642
|
+
*/
|
|
643
|
+
wrapContentWithAlignment(content, alignment) {
|
|
644
|
+
const alignmentDiv = (innerContent) => `<div style="text-align: ${alignment};">${innerContent}</div>`;
|
|
645
|
+
const headingMatch = content.match(/^(#{1,6}\s+)(.*)$/);
|
|
646
|
+
if (headingMatch) {
|
|
647
|
+
const headingPrefix = headingMatch[1];
|
|
648
|
+
const headingText = headingMatch[2];
|
|
649
|
+
return headingPrefix + alignmentDiv(headingText);
|
|
650
|
+
}
|
|
651
|
+
const boldMatch = content.match(/^(\*\*)(.+?)(\*\*)$/s);
|
|
652
|
+
if (boldMatch) {
|
|
653
|
+
const innerText = boldMatch[2];
|
|
654
|
+
return `**${alignmentDiv(innerText)}**`;
|
|
655
|
+
}
|
|
656
|
+
const italicMatch = content.match(/^(\*)(.+?)(\*)$/s);
|
|
657
|
+
if (italicMatch) {
|
|
658
|
+
const innerText = italicMatch[2];
|
|
659
|
+
return `*${alignmentDiv(innerText)}*`;
|
|
660
|
+
}
|
|
661
|
+
const linkMatch = content.match(/^(\[)(.+?)(\]\([^)]+\))$/s);
|
|
662
|
+
if (linkMatch) {
|
|
663
|
+
const linkStart = linkMatch[1];
|
|
664
|
+
const linkText = linkMatch[2];
|
|
665
|
+
const linkEnd = linkMatch[3];
|
|
666
|
+
return linkStart + alignmentDiv(linkText) + linkEnd;
|
|
667
|
+
}
|
|
668
|
+
const colorMatch = content.match(
|
|
669
|
+
/^(<span style="color: [^"]+;">)(.+?)(<\/span>)$/s
|
|
670
|
+
);
|
|
671
|
+
if (colorMatch) {
|
|
672
|
+
const colorStart = colorMatch[1];
|
|
673
|
+
const colorText = colorMatch[2];
|
|
674
|
+
const colorEnd = colorMatch[3];
|
|
675
|
+
return colorStart + alignmentDiv(colorText) + colorEnd;
|
|
676
|
+
}
|
|
677
|
+
const listMatch = content.match(/^(\s*(?:[-*+]|\d+\.)\s+)(.*)$/);
|
|
678
|
+
if (listMatch) {
|
|
679
|
+
const listPrefix = listMatch[1];
|
|
680
|
+
const listText = listMatch[2];
|
|
681
|
+
return listPrefix + alignmentDiv(listText);
|
|
682
|
+
}
|
|
683
|
+
return alignmentDiv(content);
|
|
684
|
+
}
|
|
522
685
|
/**
|
|
523
686
|
* Toggle inline formatting by wrapping/unwrapping selection or current word.
|
|
524
687
|
*
|
|
@@ -752,6 +915,23 @@ let LuksoMarkdownEditor = class extends TailwindStyledElement(style) {
|
|
|
752
915
|
);
|
|
753
916
|
activeColor = beforeColorMatch?.[1] || selectedColorMatch?.[1] || "";
|
|
754
917
|
}
|
|
918
|
+
const alignmentRegex = /<div style="text-align: (left|center|right);">/;
|
|
919
|
+
let alignment = "left";
|
|
920
|
+
const alignmentMatch = currentLine.match(alignmentRegex);
|
|
921
|
+
if (alignmentMatch) {
|
|
922
|
+
alignment = alignmentMatch[1];
|
|
923
|
+
} else {
|
|
924
|
+
const beforeCurrentLine = this.value.slice(0, lineStart);
|
|
925
|
+
const alignmentStartMatch = beforeCurrentLine.match(
|
|
926
|
+
/.*<div style="text-align: (left|center|right);">[^<]*$/s
|
|
927
|
+
);
|
|
928
|
+
if (alignmentStartMatch) {
|
|
929
|
+
const afterCurrentLine = this.value.slice(lineEnd);
|
|
930
|
+
if (afterCurrentLine.includes("</div>")) {
|
|
931
|
+
alignment = alignmentStartMatch[1];
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
}
|
|
755
935
|
this.activeFormats = {
|
|
756
936
|
bold: hasBoldWrap,
|
|
757
937
|
italic: hasItalicWrap,
|
|
@@ -759,10 +939,12 @@ let LuksoMarkdownEditor = class extends TailwindStyledElement(style) {
|
|
|
759
939
|
h1: headingLevel === 1,
|
|
760
940
|
h2: headingLevel === 2,
|
|
761
941
|
h3: headingLevel === 3,
|
|
942
|
+
h4: headingLevel === 4,
|
|
762
943
|
color: hasColorWrap,
|
|
763
944
|
activeColor,
|
|
764
945
|
unorderedList: hasUnorderedList,
|
|
765
|
-
orderedList: hasOrderedList
|
|
946
|
+
orderedList: hasOrderedList,
|
|
947
|
+
alignment
|
|
766
948
|
};
|
|
767
949
|
}
|
|
768
950
|
/**
|
|
@@ -964,54 +1146,59 @@ let LuksoMarkdownEditor = class extends TailwindStyledElement(style) {
|
|
|
964
1146
|
return;
|
|
965
1147
|
}
|
|
966
1148
|
const { start: s, end: e } = this.expandSelectionToWord(start, end, value);
|
|
967
|
-
const
|
|
968
|
-
let selected = value.slice(s, e);
|
|
969
|
-
const after = value.slice(e);
|
|
970
|
-
const colorRegex = /<span style="color: ([^"]+)">(.*?)<\/span>/gs;
|
|
971
|
-
selected = selected.replace(colorRegex, "$2");
|
|
972
|
-
const fullColorRegex = /<span style="color: ([^"]+)">(.*?)<\/span>/g;
|
|
1149
|
+
const colorRegex = /<span style="color: ([^"]+)">(.*?)<\/span>/g;
|
|
973
1150
|
let match;
|
|
974
|
-
let
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
);
|
|
979
|
-
const searchOffset = Math.max(0, s - 100);
|
|
980
|
-
match = fullColorRegex.exec(searchText);
|
|
1151
|
+
let newValue = value;
|
|
1152
|
+
let foundSpan = false;
|
|
1153
|
+
colorRegex.lastIndex = 0;
|
|
1154
|
+
match = colorRegex.exec(value);
|
|
981
1155
|
while (match !== null) {
|
|
982
|
-
const
|
|
983
|
-
const
|
|
1156
|
+
const fullMatchStart = match.index;
|
|
1157
|
+
const fullMatchEnd = match.index + match[0].length;
|
|
984
1158
|
const spanOpenTag = `<span style="color: ${match[1]}">`;
|
|
985
|
-
const contentStart =
|
|
986
|
-
const contentEnd =
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
1159
|
+
const contentStart = fullMatchStart + spanOpenTag.length;
|
|
1160
|
+
const contentEnd = fullMatchEnd - 7;
|
|
1161
|
+
const innerContent = match[2];
|
|
1162
|
+
match = colorRegex.exec(value);
|
|
1163
|
+
const selectionOverlaps = s >= contentStart && s < contentEnd || e > contentStart && e <= contentEnd || s <= contentStart && e >= contentEnd;
|
|
1164
|
+
if (selectionOverlaps) {
|
|
1165
|
+
newValue = value.slice(0, fullMatchStart) + innerContent + value.slice(fullMatchEnd);
|
|
1166
|
+
foundSpan = true;
|
|
1167
|
+
this.value = newValue;
|
|
1168
|
+
textarea.value = newValue;
|
|
1169
|
+
let newStart = s;
|
|
1170
|
+
let newEnd = e;
|
|
1171
|
+
if (s >= fullMatchStart) {
|
|
1172
|
+
const spanOpenTagLength = spanOpenTag.length;
|
|
1173
|
+
if (s >= contentStart) {
|
|
1174
|
+
newStart = s - spanOpenTagLength;
|
|
1175
|
+
} else if (s >= fullMatchStart) {
|
|
1176
|
+
newStart = fullMatchStart;
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
if (e >= fullMatchStart) {
|
|
1180
|
+
const spanOpenTagLength = spanOpenTag.length;
|
|
1181
|
+
if (e <= contentEnd) {
|
|
1182
|
+
newEnd = e - spanOpenTagLength;
|
|
1183
|
+
} else {
|
|
1184
|
+
newEnd = e - spanOpenTagLength - 7;
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
995
1187
|
requestAnimationFrame(() => {
|
|
996
|
-
textarea.setSelectionRange(
|
|
1188
|
+
textarea.setSelectionRange(
|
|
1189
|
+
Math.max(0, newStart),
|
|
1190
|
+
Math.max(0, newEnd)
|
|
1191
|
+
);
|
|
997
1192
|
this.updateActiveFormats();
|
|
998
1193
|
});
|
|
999
1194
|
this.dispatchChange();
|
|
1000
|
-
|
|
1001
|
-
break;
|
|
1195
|
+
return;
|
|
1002
1196
|
}
|
|
1003
|
-
match = fullColorRegex.exec(searchText);
|
|
1004
1197
|
}
|
|
1005
|
-
if (!
|
|
1006
|
-
this.value = before + selected + after;
|
|
1007
|
-
textarea.value = before + selected + after;
|
|
1008
|
-
const selStart = before.length;
|
|
1009
|
-
const selEnd = selStart + selected.length;
|
|
1198
|
+
if (!foundSpan) {
|
|
1010
1199
|
requestAnimationFrame(() => {
|
|
1011
|
-
textarea.setSelectionRange(selStart, selEnd);
|
|
1012
1200
|
this.updateActiveFormats();
|
|
1013
1201
|
});
|
|
1014
|
-
this.dispatchChange();
|
|
1015
1202
|
}
|
|
1016
1203
|
});
|
|
1017
1204
|
}
|
|
@@ -1022,6 +1209,7 @@ let LuksoMarkdownEditor = class extends TailwindStyledElement(style) {
|
|
|
1022
1209
|
if (this.activeFormats.h1) return 1;
|
|
1023
1210
|
if (this.activeFormats.h2) return 2;
|
|
1024
1211
|
if (this.activeFormats.h3) return 3;
|
|
1212
|
+
if (this.activeFormats.h4) return 4;
|
|
1025
1213
|
return 0;
|
|
1026
1214
|
}
|
|
1027
1215
|
/**
|
|
@@ -1089,10 +1277,14 @@ let LuksoMarkdownEditor = class extends TailwindStyledElement(style) {
|
|
|
1089
1277
|
const textarea = this.textareaEl?.shadowRoot?.querySelector(
|
|
1090
1278
|
"textarea"
|
|
1091
1279
|
);
|
|
1092
|
-
if (!textarea)
|
|
1280
|
+
if (!textarea) {
|
|
1281
|
+
return false;
|
|
1282
|
+
}
|
|
1093
1283
|
const start = textarea.selectionStart ?? 0;
|
|
1094
1284
|
const end = textarea.selectionEnd ?? 0;
|
|
1095
|
-
if (start !== end)
|
|
1285
|
+
if (start !== end) {
|
|
1286
|
+
return false;
|
|
1287
|
+
}
|
|
1096
1288
|
const value = this.value;
|
|
1097
1289
|
const lineStart = value.lastIndexOf("\n", start - 1) + 1;
|
|
1098
1290
|
let lineEnd = value.indexOf("\n", start);
|
|
@@ -1130,10 +1322,8 @@ ${indent}${marker} `;
|
|
|
1130
1322
|
this.value = newValue;
|
|
1131
1323
|
textarea.value = newValue;
|
|
1132
1324
|
const newCursor = start + prefix.length;
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
this.updateActiveFormats();
|
|
1136
|
-
});
|
|
1325
|
+
textarea.setSelectionRange(newCursor, newCursor);
|
|
1326
|
+
this.updateActiveFormats();
|
|
1137
1327
|
this.dispatchChange();
|
|
1138
1328
|
return true;
|
|
1139
1329
|
}
|
|
@@ -1155,24 +1345,35 @@ ${indent}${marker} `;
|
|
|
1155
1345
|
this.dispatchChange();
|
|
1156
1346
|
return true;
|
|
1157
1347
|
}
|
|
1158
|
-
const
|
|
1348
|
+
const currentNumber = parseInt(numberStr, 10);
|
|
1349
|
+
const nextNumber = currentNumber + 1;
|
|
1159
1350
|
const before = value.slice(0, start);
|
|
1160
1351
|
const after = value.slice(start);
|
|
1161
1352
|
const prefix = `
|
|
1162
1353
|
${indent}${nextNumber}. `;
|
|
1163
|
-
|
|
1164
|
-
const
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
)
|
|
1169
|
-
|
|
1170
|
-
|
|
1354
|
+
let newValue = before + prefix + after;
|
|
1355
|
+
const lines = newValue.split("\n");
|
|
1356
|
+
const insertLineIndex = before.split("\n").length;
|
|
1357
|
+
const renumberStartIndex = insertLineIndex + 1;
|
|
1358
|
+
let currentNum = nextNumber + 1;
|
|
1359
|
+
const orderedRegex = /^(\s*)(\d+)\.\s+(.*)$/;
|
|
1360
|
+
for (let i = renumberStartIndex; i < lines.length; i++) {
|
|
1361
|
+
const line = lines[i];
|
|
1362
|
+
const match = line.match(orderedRegex);
|
|
1363
|
+
if (match && match[1] === indent) {
|
|
1364
|
+
const content2 = match[3];
|
|
1365
|
+
lines[i] = `${indent}${currentNum}. ${content2}`;
|
|
1366
|
+
currentNum++;
|
|
1367
|
+
} else if (match && match[1].length < indent.length) {
|
|
1368
|
+
break;
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
newValue = lines.join("\n");
|
|
1372
|
+
this.value = newValue;
|
|
1373
|
+
textarea.value = newValue;
|
|
1171
1374
|
const newCursor = start + prefix.length;
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
this.updateActiveFormats();
|
|
1175
|
-
});
|
|
1375
|
+
textarea.setSelectionRange(newCursor, newCursor);
|
|
1376
|
+
this.updateActiveFormats();
|
|
1176
1377
|
this.dispatchChange();
|
|
1177
1378
|
return true;
|
|
1178
1379
|
}
|
|
@@ -1186,10 +1387,14 @@ ${indent}${nextNumber}. `;
|
|
|
1186
1387
|
const textarea = this.textareaEl?.shadowRoot?.querySelector(
|
|
1187
1388
|
"textarea"
|
|
1188
1389
|
);
|
|
1189
|
-
if (!textarea)
|
|
1390
|
+
if (!textarea) {
|
|
1391
|
+
return false;
|
|
1392
|
+
}
|
|
1190
1393
|
const start = textarea.selectionStart ?? 0;
|
|
1191
1394
|
const end = textarea.selectionEnd ?? 0;
|
|
1192
|
-
if (start !== end)
|
|
1395
|
+
if (start !== end) {
|
|
1396
|
+
return false;
|
|
1397
|
+
}
|
|
1193
1398
|
const value = this.value;
|
|
1194
1399
|
const lineStart = value.lastIndexOf("\n", start - 1) + 1;
|
|
1195
1400
|
let lineEnd = value.indexOf("\n", start);
|
|
@@ -1215,20 +1420,86 @@ ${indent}${nextNumber}. `;
|
|
|
1215
1420
|
} else if (orderedMatch) {
|
|
1216
1421
|
const currentIndent = orderedMatch[1] ?? "";
|
|
1217
1422
|
const content = orderedMatch[3] ?? "";
|
|
1218
|
-
|
|
1219
|
-
|
|
1423
|
+
if (content.trim() === "") {
|
|
1424
|
+
newLine = currentLine;
|
|
1425
|
+
const nestedLine = `${currentIndent}${indent}1. `;
|
|
1426
|
+
let newValue2 = before + newLine + "\n" + nestedLine + after;
|
|
1427
|
+
const parentIndent = currentIndent;
|
|
1428
|
+
const lines = newValue2.split("\n");
|
|
1429
|
+
const currentLineIndex = Math.floor(lineStart / (newValue2.indexOf("\n") + 1)) || newValue2.slice(0, lineStart).split("\n").length - 1;
|
|
1430
|
+
const currentLineMatch = lines[currentLineIndex]?.match(
|
|
1431
|
+
/^(\s*)(\d+)\.\s*(.*)$/
|
|
1432
|
+
);
|
|
1433
|
+
const nextExpectedNumber = currentLineMatch ? parseInt(currentLineMatch[2], 10) : 1;
|
|
1434
|
+
const orderedRegex = /^(\s*)(\d+)\.\s*(.*)$/;
|
|
1435
|
+
let currentNumber = nextExpectedNumber;
|
|
1436
|
+
for (let i = currentLineIndex + 2; i < lines.length; i++) {
|
|
1437
|
+
const line = lines[i];
|
|
1438
|
+
const orderedMatch2 = line.match(orderedRegex);
|
|
1439
|
+
if (orderedMatch2 && orderedMatch2[1] === parentIndent) {
|
|
1440
|
+
const content2 = orderedMatch2[3];
|
|
1441
|
+
lines[i] = `${parentIndent}${currentNumber}. ${content2}`;
|
|
1442
|
+
currentNumber++;
|
|
1443
|
+
} else if (orderedMatch2 && orderedMatch2[1].length < parentIndent.length) {
|
|
1444
|
+
break;
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
newValue2 = lines.join("\n");
|
|
1448
|
+
const newCursor2 = lineStart + newLine.length + 1 + nestedLine.length;
|
|
1449
|
+
this.value = newValue2;
|
|
1450
|
+
textarea.value = newValue2;
|
|
1451
|
+
requestAnimationFrame(() => {
|
|
1452
|
+
textarea.setSelectionRange(newCursor2, newCursor2);
|
|
1453
|
+
this.updateActiveFormats();
|
|
1454
|
+
});
|
|
1455
|
+
this.dispatchChange();
|
|
1456
|
+
return true;
|
|
1457
|
+
} else {
|
|
1458
|
+
const newIndent = currentIndent + indent;
|
|
1459
|
+
let newNumber = 1;
|
|
1460
|
+
const beforeText = value.slice(0, lineStart);
|
|
1461
|
+
const lines = beforeText.split("\n");
|
|
1462
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
1463
|
+
const line = lines[i];
|
|
1464
|
+
const match = line.match(/^(\s*)(\d+)\.\s*(.*)$/);
|
|
1465
|
+
if (match && match[1] === newIndent) {
|
|
1466
|
+
newNumber = parseInt(match[2], 10) + 1;
|
|
1467
|
+
break;
|
|
1468
|
+
} else if (match && match[1].length < newIndent.length) {
|
|
1469
|
+
break;
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
newLine = `${newIndent}${newNumber}. ${content}`;
|
|
1473
|
+
newCursorOffset = indent.length;
|
|
1474
|
+
}
|
|
1220
1475
|
} else {
|
|
1221
1476
|
return false;
|
|
1222
1477
|
}
|
|
1223
|
-
|
|
1478
|
+
let newValue = before + newLine + after;
|
|
1479
|
+
if (orderedMatch) {
|
|
1480
|
+
const newIndent = (orderedMatch[1] ?? "") + indent;
|
|
1481
|
+
newValue = this.renumberOrderedListItems(
|
|
1482
|
+
newValue,
|
|
1483
|
+
lineStart + newLine.length,
|
|
1484
|
+
// Start renumbering after the current line
|
|
1485
|
+
newIndent
|
|
1486
|
+
// Use the new indentation level
|
|
1487
|
+
);
|
|
1488
|
+
const parentIndent = orderedMatch[1] ?? "";
|
|
1489
|
+
newValue = this.renumberOrderedListItems(
|
|
1490
|
+
newValue,
|
|
1491
|
+
lineStart,
|
|
1492
|
+
// Start from this line for parent level
|
|
1493
|
+
parentIndent
|
|
1494
|
+
// Use the parent level indentation
|
|
1495
|
+
);
|
|
1496
|
+
}
|
|
1224
1497
|
this.value = newValue;
|
|
1225
1498
|
textarea.value = newValue;
|
|
1226
1499
|
const cursorInLine = start - lineStart;
|
|
1227
1500
|
const newCursor = lineStart + cursorInLine + newCursorOffset;
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
this.updateActiveFormats();
|
|
1231
|
-
});
|
|
1501
|
+
textarea.setSelectionRange(newCursor, newCursor);
|
|
1502
|
+
this.updateActiveFormats();
|
|
1232
1503
|
this.dispatchChange();
|
|
1233
1504
|
return true;
|
|
1234
1505
|
}
|
|
@@ -1437,25 +1708,46 @@ ${indent}${nextNumber}. `;
|
|
|
1437
1708
|
markerEndPosition = indent.length + numberStr.length + 2;
|
|
1438
1709
|
hasContent = content.trim().length > 0;
|
|
1439
1710
|
}
|
|
1440
|
-
|
|
1711
|
+
const isAtOrAfterMarker = cursorPositionInLine >= markerEndPosition;
|
|
1712
|
+
if (isAtOrAfterMarker && !hasContent) {
|
|
1441
1713
|
this.saveUndoStateBeforeChange();
|
|
1442
1714
|
const before = value.slice(0, lineStart);
|
|
1443
|
-
const after = value.slice(
|
|
1444
|
-
|
|
1445
|
-
)
|
|
1446
|
-
|
|
1715
|
+
const after = value.slice(lineEnd);
|
|
1716
|
+
let newValue;
|
|
1717
|
+
if (lineEnd === value.length) {
|
|
1718
|
+
newValue = before.endsWith("\n") ? before.slice(0, -1) : before;
|
|
1719
|
+
} else {
|
|
1720
|
+
newValue = before + after.slice(1);
|
|
1721
|
+
}
|
|
1447
1722
|
if (orderedMatch) {
|
|
1448
1723
|
const indent = orderedMatch[1] ?? "";
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1724
|
+
const lines = newValue.split("\n");
|
|
1725
|
+
const startLineIndex = Math.max(0, before.split("\n").length - 1);
|
|
1726
|
+
let nextNumber = 1;
|
|
1727
|
+
for (let i = startLineIndex - 1; i >= 0; i--) {
|
|
1728
|
+
const line = lines[i];
|
|
1729
|
+
const match = line.match(/^(\s*)(\d+)\.\s*(.*)$/);
|
|
1730
|
+
if (match && match[1] === indent) {
|
|
1731
|
+
nextNumber = parseInt(match[2], 10) + 1;
|
|
1732
|
+
break;
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
for (let i = startLineIndex; i < lines.length; i++) {
|
|
1736
|
+
const line = lines[i];
|
|
1737
|
+
const match = line.match(/^(\s*)(\d+)\.\s*(.*)$/);
|
|
1738
|
+
if (match && match[1] === indent) {
|
|
1739
|
+
lines[i] = `${indent}${nextNumber}. ${match[3]}`;
|
|
1740
|
+
nextNumber++;
|
|
1741
|
+
} else if (match && match[1].length < indent.length) {
|
|
1742
|
+
break;
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
newValue = lines.join("\n");
|
|
1454
1746
|
}
|
|
1455
1747
|
this.value = newValue;
|
|
1456
1748
|
textarea.value = newValue;
|
|
1457
1749
|
let newCursor = before.length;
|
|
1458
|
-
if (before.endsWith("\n")
|
|
1750
|
+
if (newCursor > 0 && before.endsWith("\n")) {
|
|
1459
1751
|
newCursor = before.length - 1;
|
|
1460
1752
|
}
|
|
1461
1753
|
requestAnimationFrame(() => {
|
|
@@ -1615,6 +1907,7 @@ ${indent}${nextNumber}. `;
|
|
|
1615
1907
|
e.stopPropagation();
|
|
1616
1908
|
this.isColorDropdownOpen = false;
|
|
1617
1909
|
this.isListDropdownOpen = false;
|
|
1910
|
+
this.isAlignmentDropdownOpen = false;
|
|
1618
1911
|
this.isHeadingDropdownOpen = !this.isHeadingDropdownOpen;
|
|
1619
1912
|
}}
|
|
1620
1913
|
aria-expanded=${this.isHeadingDropdownOpen ? "true" : "false"}
|
|
@@ -1688,6 +1981,18 @@ ${indent}${nextNumber}. `;
|
|
|
1688
1981
|
>
|
|
1689
1982
|
Heading 3
|
|
1690
1983
|
</lukso-dropdown-option>
|
|
1984
|
+
<lukso-dropdown-option
|
|
1985
|
+
?is-selected=${this.getActiveHeadingLevel() === 4}
|
|
1986
|
+
@click=${(e) => {
|
|
1987
|
+
e.stopPropagation();
|
|
1988
|
+
this.restoreFocusAndSelection();
|
|
1989
|
+
this.applyHeading(4);
|
|
1990
|
+
this.isHeadingDropdownOpen = false;
|
|
1991
|
+
}}
|
|
1992
|
+
size="medium"
|
|
1993
|
+
>
|
|
1994
|
+
Heading 4
|
|
1995
|
+
</lukso-dropdown-option>
|
|
1691
1996
|
</lukso-dropdown>
|
|
1692
1997
|
</div>
|
|
1693
1998
|
|
|
@@ -1717,6 +2022,7 @@ ${indent}${nextNumber}. `;
|
|
|
1717
2022
|
this.restoreFocusAndSelection();
|
|
1718
2023
|
this.isHeadingDropdownOpen = false;
|
|
1719
2024
|
this.isColorDropdownOpen = false;
|
|
2025
|
+
this.isAlignmentDropdownOpen = false;
|
|
1720
2026
|
this.isListDropdownOpen = !this.isListDropdownOpen;
|
|
1721
2027
|
}}
|
|
1722
2028
|
aria-expanded=${this.isListDropdownOpen ? "true" : "false"}
|
|
@@ -1789,6 +2095,108 @@ ${indent}${nextNumber}. `;
|
|
|
1789
2095
|
this.activeFormats.link
|
|
1790
2096
|
)}
|
|
1791
2097
|
|
|
2098
|
+
<!-- Text Alignment -->
|
|
2099
|
+
<div class=${this.styles().alignmentMenu()}>
|
|
2100
|
+
<lukso-tooltip text="Text alignment" placement="top">
|
|
2101
|
+
<lukso-button
|
|
2102
|
+
id=${this.alignmentTriggerId}
|
|
2103
|
+
@click=${(e) => {
|
|
2104
|
+
e.stopPropagation();
|
|
2105
|
+
this.restoreFocusAndSelection();
|
|
2106
|
+
this.isHeadingDropdownOpen = false;
|
|
2107
|
+
this.isColorDropdownOpen = false;
|
|
2108
|
+
this.isListDropdownOpen = false;
|
|
2109
|
+
this.isAlignmentDropdownOpen = !this.isAlignmentDropdownOpen;
|
|
2110
|
+
}}
|
|
2111
|
+
aria-expanded=${this.isAlignmentDropdownOpen ? "true" : "false"}
|
|
2112
|
+
aria-label="Text alignment"
|
|
2113
|
+
variant="secondary"
|
|
2114
|
+
size="small"
|
|
2115
|
+
custom-class=${this.toolbarButton({
|
|
2116
|
+
active: this.activeFormats.alignment !== "left"
|
|
2117
|
+
})}
|
|
2118
|
+
is-icon
|
|
2119
|
+
>
|
|
2120
|
+
<lukso-icon
|
|
2121
|
+
name=${this.getAlignmentIcon()}
|
|
2122
|
+
size="small"
|
|
2123
|
+
pack="vuesax"
|
|
2124
|
+
variant="linear"
|
|
2125
|
+
></lukso-icon>
|
|
2126
|
+
</lukso-button>
|
|
2127
|
+
</lukso-tooltip>
|
|
2128
|
+
<lukso-dropdown
|
|
2129
|
+
id="alignmentDropdown"
|
|
2130
|
+
trigger-id=""
|
|
2131
|
+
size="medium"
|
|
2132
|
+
?is-open=${this.isAlignmentDropdownOpen}
|
|
2133
|
+
>
|
|
2134
|
+
<lukso-dropdown-option
|
|
2135
|
+
?is-selected=${this.activeFormats.alignment === "left"}
|
|
2136
|
+
@click=${(e) => {
|
|
2137
|
+
e.stopPropagation();
|
|
2138
|
+
this.restoreFocusAndSelection();
|
|
2139
|
+
this.applyAlignment("left");
|
|
2140
|
+
this.isAlignmentDropdownOpen = false;
|
|
2141
|
+
}}
|
|
2142
|
+
size="medium"
|
|
2143
|
+
aria-label="Align left"
|
|
2144
|
+
>
|
|
2145
|
+
<div style="display: flex; align-items: center; gap: 8px;">
|
|
2146
|
+
<lukso-icon
|
|
2147
|
+
name="textalign-left"
|
|
2148
|
+
size="small"
|
|
2149
|
+
pack="vuesax"
|
|
2150
|
+
variant="linear"
|
|
2151
|
+
></lukso-icon>
|
|
2152
|
+
Left
|
|
2153
|
+
</div>
|
|
2154
|
+
</lukso-dropdown-option>
|
|
2155
|
+
<lukso-dropdown-option
|
|
2156
|
+
?is-selected=${this.activeFormats.alignment === "center"}
|
|
2157
|
+
@click=${(e) => {
|
|
2158
|
+
e.stopPropagation();
|
|
2159
|
+
this.restoreFocusAndSelection();
|
|
2160
|
+
this.applyAlignment("center");
|
|
2161
|
+
this.isAlignmentDropdownOpen = false;
|
|
2162
|
+
}}
|
|
2163
|
+
size="medium"
|
|
2164
|
+
aria-label="Align center"
|
|
2165
|
+
>
|
|
2166
|
+
<div style="display: flex; align-items: center; gap: 8px;">
|
|
2167
|
+
<lukso-icon
|
|
2168
|
+
name="textalign-center"
|
|
2169
|
+
size="small"
|
|
2170
|
+
pack="vuesax"
|
|
2171
|
+
variant="linear"
|
|
2172
|
+
></lukso-icon>
|
|
2173
|
+
Center
|
|
2174
|
+
</div>
|
|
2175
|
+
</lukso-dropdown-option>
|
|
2176
|
+
<lukso-dropdown-option
|
|
2177
|
+
?is-selected=${this.activeFormats.alignment === "right"}
|
|
2178
|
+
@click=${(e) => {
|
|
2179
|
+
e.stopPropagation();
|
|
2180
|
+
this.restoreFocusAndSelection();
|
|
2181
|
+
this.applyAlignment("right");
|
|
2182
|
+
this.isAlignmentDropdownOpen = false;
|
|
2183
|
+
}}
|
|
2184
|
+
size="medium"
|
|
2185
|
+
aria-label="Align right"
|
|
2186
|
+
>
|
|
2187
|
+
<div style="display: flex; align-items: center; gap: 8px;">
|
|
2188
|
+
<lukso-icon
|
|
2189
|
+
name="textalign-right"
|
|
2190
|
+
size="small"
|
|
2191
|
+
pack="vuesax"
|
|
2192
|
+
variant="linear"
|
|
2193
|
+
></lukso-icon>
|
|
2194
|
+
Right
|
|
2195
|
+
</div>
|
|
2196
|
+
</lukso-dropdown-option>
|
|
2197
|
+
</lukso-dropdown>
|
|
2198
|
+
</div>
|
|
2199
|
+
|
|
1792
2200
|
<!-- Color -->
|
|
1793
2201
|
<div class=${this.styles().colorMenu()}>
|
|
1794
2202
|
<lukso-tooltip text="Text color" placement="top">
|
|
@@ -1799,6 +2207,7 @@ ${indent}${nextNumber}. `;
|
|
|
1799
2207
|
this.restoreFocusAndSelection();
|
|
1800
2208
|
this.isHeadingDropdownOpen = false;
|
|
1801
2209
|
this.isListDropdownOpen = false;
|
|
2210
|
+
this.isAlignmentDropdownOpen = false;
|
|
1802
2211
|
if (!this.isColorDropdownOpen) {
|
|
1803
2212
|
const ta = this.textareaEl?.shadowRoot?.querySelector("textarea");
|
|
1804
2213
|
if (ta) {
|
|
@@ -1844,6 +2253,7 @@ ${indent}${nextNumber}. `;
|
|
|
1844
2253
|
this.isColorDropdownOpen = false;
|
|
1845
2254
|
}}
|
|
1846
2255
|
type="button"
|
|
2256
|
+
aria-label="Clear color"
|
|
1847
2257
|
>
|
|
1848
2258
|
Clear
|
|
1849
2259
|
</button>` : E}
|
|
@@ -1855,6 +2265,7 @@ ${indent}${nextNumber}. `;
|
|
|
1855
2265
|
style="background-color: ${color}"
|
|
1856
2266
|
title=${color}
|
|
1857
2267
|
aria-pressed=${this.activeFormats.activeColor === color ? "true" : "false"}
|
|
2268
|
+
aria-label="Color ${color}"
|
|
1858
2269
|
@click=${(e) => {
|
|
1859
2270
|
e.stopPropagation();
|
|
1860
2271
|
this.selectColor(color);
|
|
@@ -1921,7 +2332,7 @@ __decorateClass([
|
|
|
1921
2332
|
n({ type: String })
|
|
1922
2333
|
], LuksoMarkdownEditor.prototype, "value", 2);
|
|
1923
2334
|
__decorateClass([
|
|
1924
|
-
n({ type: String })
|
|
2335
|
+
n({ type: String, reflect: true })
|
|
1925
2336
|
], LuksoMarkdownEditor.prototype, "name", 2);
|
|
1926
2337
|
__decorateClass([
|
|
1927
2338
|
n({ type: String })
|
|
@@ -1971,6 +2382,9 @@ __decorateClass([
|
|
|
1971
2382
|
__decorateClass([
|
|
1972
2383
|
r()
|
|
1973
2384
|
], LuksoMarkdownEditor.prototype, "isListDropdownOpen", 2);
|
|
2385
|
+
__decorateClass([
|
|
2386
|
+
r()
|
|
2387
|
+
], LuksoMarkdownEditor.prototype, "isAlignmentDropdownOpen", 2);
|
|
1974
2388
|
__decorateClass([
|
|
1975
2389
|
r()
|
|
1976
2390
|
], LuksoMarkdownEditor.prototype, "currentSelection", 2);
|