overtype 2.1.0 → 2.1.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/overtype.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * OverType v2.0.6
2
+ * OverType v2.1.0
3
3
  * A lightweight markdown editor library with perfect WYSIWYG alignment
4
4
  * @license MIT
5
5
  * @author David Miranda
@@ -759,854 +759,10 @@ var OverType = (() => {
759
759
  checkbox: /^(\s*)-\s+\[([ x])\]\s+(.*)$/
760
760
  });
761
761
 
762
- // node_modules/markdown-actions/dist/markdown-actions.esm.js
763
- var __defProp2 = Object.defineProperty;
764
- var __getOwnPropSymbols = Object.getOwnPropertySymbols;
765
- var __hasOwnProp2 = Object.prototype.hasOwnProperty;
766
- var __propIsEnum = Object.prototype.propertyIsEnumerable;
767
- var __defNormalProp2 = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
768
- var __spreadValues = (a, b) => {
769
- for (var prop in b || (b = {}))
770
- if (__hasOwnProp2.call(b, prop))
771
- __defNormalProp2(a, prop, b[prop]);
772
- if (__getOwnPropSymbols)
773
- for (var prop of __getOwnPropSymbols(b)) {
774
- if (__propIsEnum.call(b, prop))
775
- __defNormalProp2(a, prop, b[prop]);
776
- }
777
- return a;
778
- };
779
- var FORMATS = {
780
- bold: {
781
- prefix: "**",
782
- suffix: "**",
783
- trimFirst: true
784
- },
785
- italic: {
786
- prefix: "_",
787
- suffix: "_",
788
- trimFirst: true
789
- },
790
- code: {
791
- prefix: "`",
792
- suffix: "`",
793
- blockPrefix: "```",
794
- blockSuffix: "```"
795
- },
796
- link: {
797
- prefix: "[",
798
- suffix: "](url)",
799
- replaceNext: "url",
800
- scanFor: "https?://"
801
- },
802
- bulletList: {
803
- prefix: "- ",
804
- multiline: true,
805
- unorderedList: true
806
- },
807
- numberedList: {
808
- prefix: "1. ",
809
- multiline: true,
810
- orderedList: true
811
- },
812
- quote: {
813
- prefix: "> ",
814
- multiline: true,
815
- surroundWithNewlines: true
816
- },
817
- taskList: {
818
- prefix: "- [ ] ",
819
- multiline: true,
820
- surroundWithNewlines: true
821
- },
822
- header1: { prefix: "# " },
823
- header2: { prefix: "## " },
824
- header3: { prefix: "### " },
825
- header4: { prefix: "#### " },
826
- header5: { prefix: "##### " },
827
- header6: { prefix: "###### " }
828
- };
829
- function getDefaultStyle() {
830
- return {
831
- prefix: "",
832
- suffix: "",
833
- blockPrefix: "",
834
- blockSuffix: "",
835
- multiline: false,
836
- replaceNext: "",
837
- prefixSpace: false,
838
- scanFor: "",
839
- surroundWithNewlines: false,
840
- orderedList: false,
841
- unorderedList: false,
842
- trimFirst: false
843
- };
844
- }
845
- function mergeWithDefaults(format) {
846
- return __spreadValues(__spreadValues({}, getDefaultStyle()), format);
847
- }
848
- var debugMode = false;
849
- function getDebugMode() {
850
- return debugMode;
851
- }
852
- function debugLog(funcName, message, data) {
853
- if (!debugMode)
854
- return;
855
- console.group(`\u{1F50D} ${funcName}`);
856
- console.log(message);
857
- if (data) {
858
- console.log("Data:", data);
859
- }
860
- console.groupEnd();
861
- }
862
- function debugSelection(textarea, label) {
863
- if (!debugMode)
864
- return;
865
- const selected = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd);
866
- console.group(`\u{1F4CD} Selection: ${label}`);
867
- console.log("Position:", `${textarea.selectionStart}-${textarea.selectionEnd}`);
868
- console.log("Selected text:", JSON.stringify(selected));
869
- console.log("Length:", selected.length);
870
- const before = textarea.value.slice(Math.max(0, textarea.selectionStart - 10), textarea.selectionStart);
871
- const after = textarea.value.slice(textarea.selectionEnd, Math.min(textarea.value.length, textarea.selectionEnd + 10));
872
- console.log("Context:", JSON.stringify(before) + "[SELECTION]" + JSON.stringify(after));
873
- console.groupEnd();
874
- }
875
- function debugResult(result) {
876
- if (!debugMode)
877
- return;
878
- console.group("\u{1F4DD} Result");
879
- console.log("Text to insert:", JSON.stringify(result.text));
880
- console.log("New selection:", `${result.selectionStart}-${result.selectionEnd}`);
881
- console.groupEnd();
882
- }
883
- var canInsertText = null;
884
- function insertText(textarea, { text, selectionStart, selectionEnd }) {
885
- const debugMode2 = getDebugMode();
886
- if (debugMode2) {
887
- console.group("\u{1F527} insertText");
888
- console.log("Current selection:", `${textarea.selectionStart}-${textarea.selectionEnd}`);
889
- console.log("Text to insert:", JSON.stringify(text));
890
- console.log("New selection to set:", selectionStart, "-", selectionEnd);
891
- }
892
- textarea.focus();
893
- const originalSelectionStart = textarea.selectionStart;
894
- const originalSelectionEnd = textarea.selectionEnd;
895
- const before = textarea.value.slice(0, originalSelectionStart);
896
- const after = textarea.value.slice(originalSelectionEnd);
897
- if (debugMode2) {
898
- console.log("Before text (last 20):", JSON.stringify(before.slice(-20)));
899
- console.log("After text (first 20):", JSON.stringify(after.slice(0, 20)));
900
- console.log("Selected text being replaced:", JSON.stringify(textarea.value.slice(originalSelectionStart, originalSelectionEnd)));
901
- }
902
- const originalValue = textarea.value;
903
- const hasSelection = originalSelectionStart !== originalSelectionEnd;
904
- if (canInsertText === null || canInsertText === true) {
905
- textarea.contentEditable = "true";
906
- try {
907
- canInsertText = document.execCommand("insertText", false, text);
908
- if (debugMode2)
909
- console.log("execCommand returned:", canInsertText, "for text with", text.split("\n").length, "lines");
910
- } catch (error) {
911
- canInsertText = false;
912
- if (debugMode2)
913
- console.log("execCommand threw error:", error);
914
- }
915
- textarea.contentEditable = "false";
916
- }
917
- if (debugMode2) {
918
- console.log("canInsertText before:", canInsertText);
919
- console.log("execCommand result:", canInsertText);
920
- }
921
- if (canInsertText) {
922
- const expectedValue = before + text + after;
923
- const actualValue = textarea.value;
924
- if (debugMode2) {
925
- console.log("Expected length:", expectedValue.length);
926
- console.log("Actual length:", actualValue.length);
927
- }
928
- if (actualValue !== expectedValue) {
929
- if (debugMode2) {
930
- console.log("execCommand changed the value but not as expected");
931
- console.log("Expected:", JSON.stringify(expectedValue.slice(0, 100)));
932
- console.log("Actual:", JSON.stringify(actualValue.slice(0, 100)));
933
- }
934
- }
935
- }
936
- if (!canInsertText) {
937
- if (debugMode2)
938
- console.log("Using manual insertion");
939
- if (textarea.value === originalValue) {
940
- if (debugMode2)
941
- console.log("Value unchanged, doing manual replacement");
942
- try {
943
- document.execCommand("ms-beginUndoUnit");
944
- } catch (e) {
945
- }
946
- textarea.value = before + text + after;
947
- try {
948
- document.execCommand("ms-endUndoUnit");
949
- } catch (e) {
950
- }
951
- textarea.dispatchEvent(new CustomEvent("input", { bubbles: true, cancelable: true }));
952
- } else {
953
- if (debugMode2)
954
- console.log("Value was changed by execCommand, skipping manual insertion");
955
- }
956
- }
957
- if (debugMode2)
958
- console.log("Setting selection range:", selectionStart, selectionEnd);
959
- if (selectionStart != null && selectionEnd != null) {
960
- textarea.setSelectionRange(selectionStart, selectionEnd);
961
- } else {
962
- textarea.setSelectionRange(originalSelectionStart, textarea.selectionEnd);
963
- }
964
- if (debugMode2) {
965
- console.log("Final value length:", textarea.value.length);
966
- console.groupEnd();
967
- }
968
- }
969
- function isMultipleLines(string) {
970
- return string.trim().split("\n").length > 1;
971
- }
972
- function wordSelectionStart(text, i) {
973
- let index = i;
974
- while (text[index] && text[index - 1] != null && !text[index - 1].match(/\s/)) {
975
- index--;
976
- }
977
- return index;
978
- }
979
- function wordSelectionEnd(text, i, multiline) {
980
- let index = i;
981
- const breakpoint = multiline ? /\n/ : /\s/;
982
- while (text[index] && !text[index].match(breakpoint)) {
983
- index++;
984
- }
985
- return index;
986
- }
987
- function expandSelectionToLine(textarea) {
988
- const lines = textarea.value.split("\n");
989
- let counter = 0;
990
- for (let index = 0; index < lines.length; index++) {
991
- const lineLength = lines[index].length + 1;
992
- if (textarea.selectionStart >= counter && textarea.selectionStart < counter + lineLength) {
993
- textarea.selectionStart = counter;
994
- }
995
- if (textarea.selectionEnd >= counter && textarea.selectionEnd < counter + lineLength) {
996
- if (index === lines.length - 1) {
997
- textarea.selectionEnd = Math.min(counter + lines[index].length, textarea.value.length);
998
- } else {
999
- textarea.selectionEnd = counter + lineLength - 1;
1000
- }
1001
- }
1002
- counter += lineLength;
1003
- }
1004
- }
1005
- function expandSelectedText(textarea, prefixToUse, suffixToUse, multiline = false) {
1006
- if (textarea.selectionStart === textarea.selectionEnd) {
1007
- textarea.selectionStart = wordSelectionStart(textarea.value, textarea.selectionStart);
1008
- textarea.selectionEnd = wordSelectionEnd(textarea.value, textarea.selectionEnd, multiline);
1009
- } else {
1010
- const expandedSelectionStart = textarea.selectionStart - prefixToUse.length;
1011
- const expandedSelectionEnd = textarea.selectionEnd + suffixToUse.length;
1012
- const beginsWithPrefix = textarea.value.slice(expandedSelectionStart, textarea.selectionStart) === prefixToUse;
1013
- const endsWithSuffix = textarea.value.slice(textarea.selectionEnd, expandedSelectionEnd) === suffixToUse;
1014
- if (beginsWithPrefix && endsWithSuffix) {
1015
- textarea.selectionStart = expandedSelectionStart;
1016
- textarea.selectionEnd = expandedSelectionEnd;
1017
- }
1018
- }
1019
- return textarea.value.slice(textarea.selectionStart, textarea.selectionEnd);
1020
- }
1021
- function newlinesToSurroundSelectedText(textarea) {
1022
- const beforeSelection = textarea.value.slice(0, textarea.selectionStart);
1023
- const afterSelection = textarea.value.slice(textarea.selectionEnd);
1024
- const breaksBefore = beforeSelection.match(/\n*$/);
1025
- const breaksAfter = afterSelection.match(/^\n*/);
1026
- const newlinesBeforeSelection = breaksBefore ? breaksBefore[0].length : 0;
1027
- const newlinesAfterSelection = breaksAfter ? breaksAfter[0].length : 0;
1028
- let newlinesToAppend = "";
1029
- let newlinesToPrepend = "";
1030
- if (beforeSelection.match(/\S/) && newlinesBeforeSelection < 2) {
1031
- newlinesToAppend = "\n".repeat(2 - newlinesBeforeSelection);
1032
- }
1033
- if (afterSelection.match(/\S/) && newlinesAfterSelection < 2) {
1034
- newlinesToPrepend = "\n".repeat(2 - newlinesAfterSelection);
1035
- }
1036
- return { newlinesToAppend, newlinesToPrepend };
1037
- }
1038
- function applyLineOperation(textarea, operation, options = {}) {
1039
- const originalStart = textarea.selectionStart;
1040
- const originalEnd = textarea.selectionEnd;
1041
- const noInitialSelection = originalStart === originalEnd;
1042
- const value = textarea.value;
1043
- let lineStart = originalStart;
1044
- while (lineStart > 0 && value[lineStart - 1] !== "\n") {
1045
- lineStart--;
1046
- }
1047
- if (noInitialSelection) {
1048
- let lineEnd = originalStart;
1049
- while (lineEnd < value.length && value[lineEnd] !== "\n") {
1050
- lineEnd++;
1051
- }
1052
- textarea.selectionStart = lineStart;
1053
- textarea.selectionEnd = lineEnd;
1054
- } else {
1055
- expandSelectionToLine(textarea);
1056
- }
1057
- const result = operation(textarea);
1058
- if (options.adjustSelection) {
1059
- const selectedText = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd);
1060
- const isRemoving = selectedText.startsWith(options.prefix);
1061
- const adjusted = options.adjustSelection(isRemoving, originalStart, originalEnd, lineStart);
1062
- result.selectionStart = adjusted.start;
1063
- result.selectionEnd = adjusted.end;
1064
- } else if (options.prefix) {
1065
- const selectedText = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd);
1066
- const isRemoving = selectedText.startsWith(options.prefix);
1067
- if (noInitialSelection) {
1068
- if (isRemoving) {
1069
- result.selectionStart = Math.max(originalStart - options.prefix.length, lineStart);
1070
- result.selectionEnd = result.selectionStart;
1071
- } else {
1072
- result.selectionStart = originalStart + options.prefix.length;
1073
- result.selectionEnd = result.selectionStart;
1074
- }
1075
- } else {
1076
- if (isRemoving) {
1077
- result.selectionStart = Math.max(originalStart - options.prefix.length, lineStart);
1078
- result.selectionEnd = Math.max(originalEnd - options.prefix.length, lineStart);
1079
- } else {
1080
- result.selectionStart = originalStart + options.prefix.length;
1081
- result.selectionEnd = originalEnd + options.prefix.length;
1082
- }
1083
- }
1084
- }
1085
- return result;
1086
- }
1087
- function blockStyle(textarea, style) {
1088
- let newlinesToAppend;
1089
- let newlinesToPrepend;
1090
- const { prefix, suffix, blockPrefix, blockSuffix, replaceNext, prefixSpace, scanFor, surroundWithNewlines, trimFirst } = style;
1091
- const originalSelectionStart = textarea.selectionStart;
1092
- const originalSelectionEnd = textarea.selectionEnd;
1093
- let selectedText = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd);
1094
- let prefixToUse = isMultipleLines(selectedText) && blockPrefix && blockPrefix.length > 0 ? `${blockPrefix}
1095
- ` : prefix;
1096
- let suffixToUse = isMultipleLines(selectedText) && blockSuffix && blockSuffix.length > 0 ? `
1097
- ${blockSuffix}` : suffix;
1098
- if (prefixSpace) {
1099
- const beforeSelection = textarea.value[textarea.selectionStart - 1];
1100
- if (textarea.selectionStart !== 0 && beforeSelection != null && !beforeSelection.match(/\s/)) {
1101
- prefixToUse = ` ${prefixToUse}`;
1102
- }
1103
- }
1104
- selectedText = expandSelectedText(textarea, prefixToUse, suffixToUse, style.multiline);
1105
- let selectionStart = textarea.selectionStart;
1106
- let selectionEnd = textarea.selectionEnd;
1107
- const hasReplaceNext = replaceNext && replaceNext.length > 0 && suffixToUse.indexOf(replaceNext) > -1 && selectedText.length > 0;
1108
- if (surroundWithNewlines) {
1109
- const ref = newlinesToSurroundSelectedText(textarea);
1110
- newlinesToAppend = ref.newlinesToAppend;
1111
- newlinesToPrepend = ref.newlinesToPrepend;
1112
- prefixToUse = newlinesToAppend + prefix;
1113
- suffixToUse += newlinesToPrepend;
1114
- }
1115
- if (selectedText.startsWith(prefixToUse) && selectedText.endsWith(suffixToUse)) {
1116
- const replacementText = selectedText.slice(prefixToUse.length, selectedText.length - suffixToUse.length);
1117
- if (originalSelectionStart === originalSelectionEnd) {
1118
- let position = originalSelectionStart - prefixToUse.length;
1119
- position = Math.max(position, selectionStart);
1120
- position = Math.min(position, selectionStart + replacementText.length);
1121
- selectionStart = selectionEnd = position;
1122
- } else {
1123
- selectionEnd = selectionStart + replacementText.length;
1124
- }
1125
- return { text: replacementText, selectionStart, selectionEnd };
1126
- } else if (!hasReplaceNext) {
1127
- let replacementText = prefixToUse + selectedText + suffixToUse;
1128
- selectionStart = originalSelectionStart + prefixToUse.length;
1129
- selectionEnd = originalSelectionEnd + prefixToUse.length;
1130
- const whitespaceEdges = selectedText.match(/^\s*|\s*$/g);
1131
- if (trimFirst && whitespaceEdges) {
1132
- const leadingWhitespace = whitespaceEdges[0] || "";
1133
- const trailingWhitespace = whitespaceEdges[1] || "";
1134
- replacementText = leadingWhitespace + prefixToUse + selectedText.trim() + suffixToUse + trailingWhitespace;
1135
- selectionStart += leadingWhitespace.length;
1136
- selectionEnd -= trailingWhitespace.length;
1137
- }
1138
- return { text: replacementText, selectionStart, selectionEnd };
1139
- } else if (scanFor && scanFor.length > 0 && selectedText.match(scanFor)) {
1140
- suffixToUse = suffixToUse.replace(replaceNext, selectedText);
1141
- const replacementText = prefixToUse + suffixToUse;
1142
- selectionStart = selectionEnd = selectionStart + prefixToUse.length;
1143
- return { text: replacementText, selectionStart, selectionEnd };
1144
- } else {
1145
- const replacementText = prefixToUse + selectedText + suffixToUse;
1146
- selectionStart = selectionStart + prefixToUse.length + selectedText.length + suffixToUse.indexOf(replaceNext);
1147
- selectionEnd = selectionStart + replaceNext.length;
1148
- return { text: replacementText, selectionStart, selectionEnd };
1149
- }
1150
- }
1151
- function multilineStyle(textarea, style) {
1152
- const { prefix, suffix, surroundWithNewlines } = style;
1153
- let text = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd);
1154
- let selectionStart = textarea.selectionStart;
1155
- let selectionEnd = textarea.selectionEnd;
1156
- const lines = text.split("\n");
1157
- const undoStyle = lines.every((line) => line.startsWith(prefix) && (!suffix || line.endsWith(suffix)));
1158
- if (undoStyle) {
1159
- text = lines.map((line) => {
1160
- let result = line.slice(prefix.length);
1161
- if (suffix) {
1162
- result = result.slice(0, result.length - suffix.length);
1163
- }
1164
- return result;
1165
- }).join("\n");
1166
- selectionEnd = selectionStart + text.length;
1167
- } else {
1168
- text = lines.map((line) => prefix + line + (suffix || "")).join("\n");
1169
- if (surroundWithNewlines) {
1170
- const { newlinesToAppend, newlinesToPrepend } = newlinesToSurroundSelectedText(textarea);
1171
- selectionStart += newlinesToAppend.length;
1172
- selectionEnd = selectionStart + text.length;
1173
- text = newlinesToAppend + text + newlinesToPrepend;
1174
- }
1175
- }
1176
- return { text, selectionStart, selectionEnd };
1177
- }
1178
- function undoOrderedListStyle(text) {
1179
- const lines = text.split("\n");
1180
- const orderedListRegex = /^\d+\.\s+/;
1181
- const shouldUndoOrderedList = lines.every((line) => orderedListRegex.test(line));
1182
- let result = lines;
1183
- if (shouldUndoOrderedList) {
1184
- result = lines.map((line) => line.replace(orderedListRegex, ""));
1185
- }
1186
- return {
1187
- text: result.join("\n"),
1188
- processed: shouldUndoOrderedList
1189
- };
1190
- }
1191
- function undoUnorderedListStyle(text) {
1192
- const lines = text.split("\n");
1193
- const unorderedListPrefix = "- ";
1194
- const shouldUndoUnorderedList = lines.every((line) => line.startsWith(unorderedListPrefix));
1195
- let result = lines;
1196
- if (shouldUndoUnorderedList) {
1197
- result = lines.map((line) => line.slice(unorderedListPrefix.length));
1198
- }
1199
- return {
1200
- text: result.join("\n"),
1201
- processed: shouldUndoUnorderedList
1202
- };
1203
- }
1204
- function makePrefix(index, unorderedList) {
1205
- if (unorderedList) {
1206
- return "- ";
1207
- } else {
1208
- return `${index + 1}. `;
1209
- }
1210
- }
1211
- function clearExistingListStyle(style, selectedText) {
1212
- let undoResult;
1213
- let undoResultOppositeList;
1214
- let pristineText;
1215
- if (style.orderedList) {
1216
- undoResult = undoOrderedListStyle(selectedText);
1217
- undoResultOppositeList = undoUnorderedListStyle(undoResult.text);
1218
- pristineText = undoResultOppositeList.text;
1219
- } else {
1220
- undoResult = undoUnorderedListStyle(selectedText);
1221
- undoResultOppositeList = undoOrderedListStyle(undoResult.text);
1222
- pristineText = undoResultOppositeList.text;
1223
- }
1224
- return [undoResult, undoResultOppositeList, pristineText];
1225
- }
1226
- function listStyle(textarea, style) {
1227
- const noInitialSelection = textarea.selectionStart === textarea.selectionEnd;
1228
- let selectionStart = textarea.selectionStart;
1229
- let selectionEnd = textarea.selectionEnd;
1230
- expandSelectionToLine(textarea);
1231
- const selectedText = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd);
1232
- const [undoResult, undoResultOppositeList, pristineText] = clearExistingListStyle(style, selectedText);
1233
- const prefixedLines = pristineText.split("\n").map((value, index) => {
1234
- return `${makePrefix(index, style.unorderedList)}${value}`;
1235
- });
1236
- const totalPrefixLength = prefixedLines.reduce((previousValue, _currentValue, currentIndex) => {
1237
- return previousValue + makePrefix(currentIndex, style.unorderedList).length;
1238
- }, 0);
1239
- const totalPrefixLengthOppositeList = prefixedLines.reduce((previousValue, _currentValue, currentIndex) => {
1240
- return previousValue + makePrefix(currentIndex, !style.unorderedList).length;
1241
- }, 0);
1242
- if (undoResult.processed) {
1243
- if (noInitialSelection) {
1244
- selectionStart = Math.max(selectionStart - makePrefix(0, style.unorderedList).length, 0);
1245
- selectionEnd = selectionStart;
1246
- } else {
1247
- selectionStart = textarea.selectionStart;
1248
- selectionEnd = textarea.selectionEnd - totalPrefixLength;
1249
- }
1250
- return { text: pristineText, selectionStart, selectionEnd };
1251
- }
1252
- const { newlinesToAppend, newlinesToPrepend } = newlinesToSurroundSelectedText(textarea);
1253
- const text = newlinesToAppend + prefixedLines.join("\n") + newlinesToPrepend;
1254
- if (noInitialSelection) {
1255
- selectionStart = Math.max(selectionStart + makePrefix(0, style.unorderedList).length + newlinesToAppend.length, 0);
1256
- selectionEnd = selectionStart;
1257
- } else {
1258
- if (undoResultOppositeList.processed) {
1259
- selectionStart = Math.max(textarea.selectionStart + newlinesToAppend.length, 0);
1260
- selectionEnd = textarea.selectionEnd + newlinesToAppend.length + totalPrefixLength - totalPrefixLengthOppositeList;
1261
- } else {
1262
- selectionStart = Math.max(textarea.selectionStart + newlinesToAppend.length, 0);
1263
- selectionEnd = textarea.selectionEnd + newlinesToAppend.length + totalPrefixLength;
1264
- }
1265
- }
1266
- return { text, selectionStart, selectionEnd };
1267
- }
1268
- function applyListStyle(textarea, style) {
1269
- const result = applyLineOperation(
1270
- textarea,
1271
- (ta) => listStyle(ta, style),
1272
- {
1273
- // Custom selection adjustment for lists
1274
- adjustSelection: (isRemoving, selStart, selEnd, lineStart) => {
1275
- const currentLine = textarea.value.slice(lineStart, textarea.selectionEnd);
1276
- const orderedListRegex = /^\d+\.\s+/;
1277
- const unorderedListRegex = /^- /;
1278
- const hasOrderedList = orderedListRegex.test(currentLine);
1279
- const hasUnorderedList = unorderedListRegex.test(currentLine);
1280
- const isRemovingCurrent = style.orderedList && hasOrderedList || style.unorderedList && hasUnorderedList;
1281
- if (selStart === selEnd) {
1282
- if (isRemovingCurrent) {
1283
- const prefixMatch = currentLine.match(style.orderedList ? orderedListRegex : unorderedListRegex);
1284
- const prefixLength = prefixMatch ? prefixMatch[0].length : 0;
1285
- return {
1286
- start: Math.max(selStart - prefixLength, lineStart),
1287
- end: Math.max(selStart - prefixLength, lineStart)
1288
- };
1289
- } else if (hasOrderedList || hasUnorderedList) {
1290
- const oldPrefixMatch = currentLine.match(hasOrderedList ? orderedListRegex : unorderedListRegex);
1291
- const oldPrefixLength = oldPrefixMatch ? oldPrefixMatch[0].length : 0;
1292
- const newPrefixLength = style.unorderedList ? 2 : 3;
1293
- const adjustment = newPrefixLength - oldPrefixLength;
1294
- return {
1295
- start: selStart + adjustment,
1296
- end: selStart + adjustment
1297
- };
1298
- } else {
1299
- const prefixLength = style.unorderedList ? 2 : 3;
1300
- return {
1301
- start: selStart + prefixLength,
1302
- end: selStart + prefixLength
1303
- };
1304
- }
1305
- } else {
1306
- if (isRemovingCurrent) {
1307
- const prefixMatch = currentLine.match(style.orderedList ? orderedListRegex : unorderedListRegex);
1308
- const prefixLength = prefixMatch ? prefixMatch[0].length : 0;
1309
- return {
1310
- start: Math.max(selStart - prefixLength, lineStart),
1311
- end: Math.max(selEnd - prefixLength, lineStart)
1312
- };
1313
- } else if (hasOrderedList || hasUnorderedList) {
1314
- const oldPrefixMatch = currentLine.match(hasOrderedList ? orderedListRegex : unorderedListRegex);
1315
- const oldPrefixLength = oldPrefixMatch ? oldPrefixMatch[0].length : 0;
1316
- const newPrefixLength = style.unorderedList ? 2 : 3;
1317
- const adjustment = newPrefixLength - oldPrefixLength;
1318
- return {
1319
- start: selStart + adjustment,
1320
- end: selEnd + adjustment
1321
- };
1322
- } else {
1323
- const prefixLength = style.unorderedList ? 2 : 3;
1324
- return {
1325
- start: selStart + prefixLength,
1326
- end: selEnd + prefixLength
1327
- };
1328
- }
1329
- }
1330
- }
1331
- }
1332
- );
1333
- insertText(textarea, result);
1334
- }
1335
- function getActiveFormats(textarea) {
1336
- if (!textarea)
1337
- return [];
1338
- const formats = [];
1339
- const { selectionStart, selectionEnd, value } = textarea;
1340
- const lines = value.split("\n");
1341
- let lineStart = 0;
1342
- let currentLine = "";
1343
- for (const line of lines) {
1344
- if (selectionStart >= lineStart && selectionStart <= lineStart + line.length) {
1345
- currentLine = line;
1346
- break;
1347
- }
1348
- lineStart += line.length + 1;
1349
- }
1350
- if (currentLine.startsWith("- ")) {
1351
- if (currentLine.startsWith("- [ ] ") || currentLine.startsWith("- [x] ")) {
1352
- formats.push("task-list");
1353
- } else {
1354
- formats.push("bullet-list");
1355
- }
1356
- }
1357
- if (/^\d+\.\s/.test(currentLine)) {
1358
- formats.push("numbered-list");
1359
- }
1360
- if (currentLine.startsWith("> ")) {
1361
- formats.push("quote");
1362
- }
1363
- if (currentLine.startsWith("# "))
1364
- formats.push("header");
1365
- if (currentLine.startsWith("## "))
1366
- formats.push("header-2");
1367
- if (currentLine.startsWith("### "))
1368
- formats.push("header-3");
1369
- const lookBehind = Math.max(0, selectionStart - 10);
1370
- const lookAhead = Math.min(value.length, selectionEnd + 10);
1371
- const surrounding = value.slice(lookBehind, lookAhead);
1372
- if (surrounding.includes("**")) {
1373
- const beforeCursor = value.slice(Math.max(0, selectionStart - 100), selectionStart);
1374
- const afterCursor = value.slice(selectionEnd, Math.min(value.length, selectionEnd + 100));
1375
- const lastOpenBold = beforeCursor.lastIndexOf("**");
1376
- const nextCloseBold = afterCursor.indexOf("**");
1377
- if (lastOpenBold !== -1 && nextCloseBold !== -1) {
1378
- formats.push("bold");
1379
- }
1380
- }
1381
- if (surrounding.includes("_")) {
1382
- const beforeCursor = value.slice(Math.max(0, selectionStart - 100), selectionStart);
1383
- const afterCursor = value.slice(selectionEnd, Math.min(value.length, selectionEnd + 100));
1384
- const lastOpenItalic = beforeCursor.lastIndexOf("_");
1385
- const nextCloseItalic = afterCursor.indexOf("_");
1386
- if (lastOpenItalic !== -1 && nextCloseItalic !== -1) {
1387
- formats.push("italic");
1388
- }
1389
- }
1390
- if (surrounding.includes("`")) {
1391
- const beforeCursor = value.slice(Math.max(0, selectionStart - 100), selectionStart);
1392
- const afterCursor = value.slice(selectionEnd, Math.min(value.length, selectionEnd + 100));
1393
- if (beforeCursor.includes("`") && afterCursor.includes("`")) {
1394
- formats.push("code");
1395
- }
1396
- }
1397
- if (surrounding.includes("[") && surrounding.includes("]")) {
1398
- const beforeCursor = value.slice(Math.max(0, selectionStart - 100), selectionStart);
1399
- const afterCursor = value.slice(selectionEnd, Math.min(value.length, selectionEnd + 100));
1400
- const lastOpenBracket = beforeCursor.lastIndexOf("[");
1401
- const nextCloseBracket = afterCursor.indexOf("]");
1402
- if (lastOpenBracket !== -1 && nextCloseBracket !== -1) {
1403
- const afterBracket = value.slice(selectionEnd + nextCloseBracket + 1, selectionEnd + nextCloseBracket + 10);
1404
- if (afterBracket.startsWith("(")) {
1405
- formats.push("link");
1406
- }
1407
- }
1408
- }
1409
- return formats;
1410
- }
1411
- function toggleBold(textarea) {
1412
- if (!textarea || textarea.disabled || textarea.readOnly)
1413
- return;
1414
- debugLog("toggleBold", "Starting");
1415
- debugSelection(textarea, "Before");
1416
- const style = mergeWithDefaults(FORMATS.bold);
1417
- const result = blockStyle(textarea, style);
1418
- debugResult(result);
1419
- insertText(textarea, result);
1420
- debugSelection(textarea, "After");
1421
- }
1422
- function toggleItalic(textarea) {
1423
- if (!textarea || textarea.disabled || textarea.readOnly)
1424
- return;
1425
- const style = mergeWithDefaults(FORMATS.italic);
1426
- const result = blockStyle(textarea, style);
1427
- insertText(textarea, result);
1428
- }
1429
- function toggleCode(textarea) {
1430
- if (!textarea || textarea.disabled || textarea.readOnly)
1431
- return;
1432
- const style = mergeWithDefaults(FORMATS.code);
1433
- const result = blockStyle(textarea, style);
1434
- insertText(textarea, result);
1435
- }
1436
- function insertLink(textarea, options = {}) {
1437
- if (!textarea || textarea.disabled || textarea.readOnly)
1438
- return;
1439
- const selectedText = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd);
1440
- let style = mergeWithDefaults(FORMATS.link);
1441
- const isURL = selectedText && selectedText.match(/^https?:\/\//);
1442
- if (isURL && !options.url) {
1443
- style.suffix = `](${selectedText})`;
1444
- style.replaceNext = "";
1445
- } else if (options.url) {
1446
- style.suffix = `](${options.url})`;
1447
- style.replaceNext = "";
1448
- }
1449
- if (options.text && !selectedText) {
1450
- const pos = textarea.selectionStart;
1451
- textarea.value = textarea.value.slice(0, pos) + options.text + textarea.value.slice(pos);
1452
- textarea.selectionStart = pos;
1453
- textarea.selectionEnd = pos + options.text.length;
1454
- }
1455
- const result = blockStyle(textarea, style);
1456
- insertText(textarea, result);
1457
- }
1458
- function toggleBulletList(textarea) {
1459
- if (!textarea || textarea.disabled || textarea.readOnly)
1460
- return;
1461
- const style = mergeWithDefaults(FORMATS.bulletList);
1462
- applyListStyle(textarea, style);
1463
- }
1464
- function toggleNumberedList(textarea) {
1465
- if (!textarea || textarea.disabled || textarea.readOnly)
1466
- return;
1467
- const style = mergeWithDefaults(FORMATS.numberedList);
1468
- applyListStyle(textarea, style);
1469
- }
1470
- function toggleQuote(textarea) {
1471
- if (!textarea || textarea.disabled || textarea.readOnly)
1472
- return;
1473
- debugLog("toggleQuote", "Starting");
1474
- debugSelection(textarea, "Initial");
1475
- const style = mergeWithDefaults(FORMATS.quote);
1476
- const result = applyLineOperation(
1477
- textarea,
1478
- (ta) => multilineStyle(ta, style),
1479
- { prefix: style.prefix }
1480
- );
1481
- debugResult(result);
1482
- insertText(textarea, result);
1483
- debugSelection(textarea, "Final");
1484
- }
1485
- function toggleTaskList(textarea) {
1486
- if (!textarea || textarea.disabled || textarea.readOnly)
1487
- return;
1488
- const style = mergeWithDefaults(FORMATS.taskList);
1489
- const result = applyLineOperation(
1490
- textarea,
1491
- (ta) => multilineStyle(ta, style),
1492
- { prefix: style.prefix }
1493
- );
1494
- insertText(textarea, result);
1495
- }
1496
- function insertHeader(textarea, level = 1, toggle = false) {
1497
- if (!textarea || textarea.disabled || textarea.readOnly)
1498
- return;
1499
- if (level < 1 || level > 6)
1500
- level = 1;
1501
- debugLog("insertHeader", `============ START ============`);
1502
- debugLog("insertHeader", `Level: ${level}, Toggle: ${toggle}`);
1503
- debugLog("insertHeader", `Initial cursor: ${textarea.selectionStart}-${textarea.selectionEnd}`);
1504
- const headerKey = `header${level === 1 ? "1" : level}`;
1505
- const style = mergeWithDefaults(FORMATS[headerKey] || FORMATS.header1);
1506
- debugLog("insertHeader", `Style prefix: "${style.prefix}"`);
1507
- const value = textarea.value;
1508
- const originalStart = textarea.selectionStart;
1509
- const originalEnd = textarea.selectionEnd;
1510
- let lineStart = originalStart;
1511
- while (lineStart > 0 && value[lineStart - 1] !== "\n") {
1512
- lineStart--;
1513
- }
1514
- let lineEnd = originalEnd;
1515
- while (lineEnd < value.length && value[lineEnd] !== "\n") {
1516
- lineEnd++;
1517
- }
1518
- const currentLineContent = value.slice(lineStart, lineEnd);
1519
- debugLog("insertHeader", `Current line (before): "${currentLineContent}"`);
1520
- const existingHeaderMatch = currentLineContent.match(/^(#{1,6})\s*/);
1521
- const existingLevel = existingHeaderMatch ? existingHeaderMatch[1].length : 0;
1522
- const existingPrefixLength = existingHeaderMatch ? existingHeaderMatch[0].length : 0;
1523
- debugLog("insertHeader", `Existing header check:`);
1524
- debugLog("insertHeader", ` - Match: ${existingHeaderMatch ? `"${existingHeaderMatch[0]}"` : "none"}`);
1525
- debugLog("insertHeader", ` - Existing level: ${existingLevel}`);
1526
- debugLog("insertHeader", ` - Existing prefix length: ${existingPrefixLength}`);
1527
- debugLog("insertHeader", ` - Target level: ${level}`);
1528
- const shouldToggleOff = toggle && existingLevel === level;
1529
- debugLog("insertHeader", `Should toggle OFF: ${shouldToggleOff} (toggle=${toggle}, existingLevel=${existingLevel}, level=${level})`);
1530
- const result = applyLineOperation(
1531
- textarea,
1532
- (ta) => {
1533
- const currentLine = ta.value.slice(ta.selectionStart, ta.selectionEnd);
1534
- debugLog("insertHeader", `Line in operation: "${currentLine}"`);
1535
- const cleanedLine = currentLine.replace(/^#{1,6}\s*/, "");
1536
- debugLog("insertHeader", `Cleaned line: "${cleanedLine}"`);
1537
- let newLine;
1538
- if (shouldToggleOff) {
1539
- debugLog("insertHeader", "ACTION: Toggling OFF - removing header");
1540
- newLine = cleanedLine;
1541
- } else if (existingLevel > 0) {
1542
- debugLog("insertHeader", `ACTION: Replacing H${existingLevel} with H${level}`);
1543
- newLine = style.prefix + cleanedLine;
1544
- } else {
1545
- debugLog("insertHeader", "ACTION: Adding new header");
1546
- newLine = style.prefix + cleanedLine;
1547
- }
1548
- debugLog("insertHeader", `New line: "${newLine}"`);
1549
- return {
1550
- text: newLine,
1551
- selectionStart: ta.selectionStart,
1552
- selectionEnd: ta.selectionEnd
1553
- };
1554
- },
1555
- {
1556
- prefix: style.prefix,
1557
- // Custom selection adjustment for headers
1558
- adjustSelection: (isRemoving, selStart, selEnd, lineStartPos) => {
1559
- debugLog("insertHeader", `Adjusting selection:`);
1560
- debugLog("insertHeader", ` - isRemoving param: ${isRemoving}`);
1561
- debugLog("insertHeader", ` - shouldToggleOff: ${shouldToggleOff}`);
1562
- debugLog("insertHeader", ` - selStart: ${selStart}, selEnd: ${selEnd}`);
1563
- debugLog("insertHeader", ` - lineStartPos: ${lineStartPos}`);
1564
- if (shouldToggleOff) {
1565
- const adjustment = Math.max(selStart - existingPrefixLength, lineStartPos);
1566
- debugLog("insertHeader", ` - Removing header, adjusting by -${existingPrefixLength}`);
1567
- return {
1568
- start: adjustment,
1569
- end: selStart === selEnd ? adjustment : Math.max(selEnd - existingPrefixLength, lineStartPos)
1570
- };
1571
- } else if (existingPrefixLength > 0) {
1572
- const prefixDiff = style.prefix.length - existingPrefixLength;
1573
- debugLog("insertHeader", ` - Replacing header, adjusting by ${prefixDiff}`);
1574
- return {
1575
- start: selStart + prefixDiff,
1576
- end: selEnd + prefixDiff
1577
- };
1578
- } else {
1579
- debugLog("insertHeader", ` - Adding header, adjusting by +${style.prefix.length}`);
1580
- return {
1581
- start: selStart + style.prefix.length,
1582
- end: selEnd + style.prefix.length
1583
- };
1584
- }
1585
- }
1586
- }
1587
- );
1588
- debugLog("insertHeader", `Final result: text="${result.text}", cursor=${result.selectionStart}-${result.selectionEnd}`);
1589
- debugLog("insertHeader", `============ END ============`);
1590
- insertText(textarea, result);
1591
- }
1592
- function toggleH1(textarea) {
1593
- insertHeader(textarea, 1, true);
1594
- }
1595
- function toggleH2(textarea) {
1596
- insertHeader(textarea, 2, true);
1597
- }
1598
- function toggleH3(textarea) {
1599
- insertHeader(textarea, 3, true);
1600
- }
1601
- function getActiveFormats2(textarea) {
1602
- return getActiveFormats(textarea);
1603
- }
1604
-
1605
762
  // src/shortcuts.js
1606
763
  var ShortcutsManager = class {
1607
764
  constructor(editor) {
1608
765
  this.editor = editor;
1609
- this.textarea = editor.textarea;
1610
766
  }
1611
767
  /**
1612
768
  * Handle keydown events - called by OverType
@@ -1618,77 +774,36 @@ ${blockSuffix}` : suffix;
1618
774
  const modKey = isMac ? event.metaKey : event.ctrlKey;
1619
775
  if (!modKey)
1620
776
  return false;
1621
- let action = null;
777
+ let actionId = null;
1622
778
  switch (event.key.toLowerCase()) {
1623
779
  case "b":
1624
- if (!event.shiftKey) {
1625
- action = "toggleBold";
1626
- }
780
+ if (!event.shiftKey)
781
+ actionId = "toggleBold";
1627
782
  break;
1628
783
  case "i":
1629
- if (!event.shiftKey) {
1630
- action = "toggleItalic";
1631
- }
784
+ if (!event.shiftKey)
785
+ actionId = "toggleItalic";
1632
786
  break;
1633
787
  case "k":
1634
- if (!event.shiftKey) {
1635
- action = "insertLink";
1636
- }
788
+ if (!event.shiftKey)
789
+ actionId = "insertLink";
1637
790
  break;
1638
791
  case "7":
1639
- if (event.shiftKey) {
1640
- action = "toggleNumberedList";
1641
- }
792
+ if (event.shiftKey)
793
+ actionId = "toggleNumberedList";
1642
794
  break;
1643
795
  case "8":
1644
- if (event.shiftKey) {
1645
- action = "toggleBulletList";
1646
- }
796
+ if (event.shiftKey)
797
+ actionId = "toggleBulletList";
1647
798
  break;
1648
799
  }
1649
- if (action) {
800
+ if (actionId) {
1650
801
  event.preventDefault();
1651
- if (this.editor.toolbar) {
1652
- this.editor.toolbar.handleAction(action);
1653
- } else {
1654
- this.handleAction(action);
1655
- }
802
+ this.editor.performAction(actionId, event);
1656
803
  return true;
1657
804
  }
1658
805
  return false;
1659
806
  }
1660
- /**
1661
- * Handle action - fallback when no toolbar exists
1662
- * This duplicates toolbar.handleAction for consistency
1663
- */
1664
- async handleAction(action) {
1665
- const textarea = this.textarea;
1666
- if (!textarea)
1667
- return;
1668
- textarea.focus();
1669
- try {
1670
- switch (action) {
1671
- case "toggleBold":
1672
- toggleBold(textarea);
1673
- break;
1674
- case "toggleItalic":
1675
- toggleItalic(textarea);
1676
- break;
1677
- case "insertLink":
1678
- insertLink(textarea);
1679
- break;
1680
- case "toggleBulletList":
1681
- toggleBulletList(textarea);
1682
- break;
1683
- case "toggleNumberedList":
1684
- toggleNumberedList(textarea);
1685
- break;
1686
- }
1687
- textarea.dispatchEvent(new Event("input", { bubbles: true }));
1688
- } catch (error) {
1689
- console.error("Error in markdown action:", error);
1690
- }
1691
- }
1692
807
  /**
1693
808
  * Cleanup
1694
809
  */
@@ -2346,381 +1461,1224 @@ ${blockSuffix}` : suffix;
2346
1461
  z-index: 100 !important; /* Ensure toolbar is above wrapper */
2347
1462
  scrollbar-width: thin; /* Thin scrollbar on Firefox */
2348
1463
  }
2349
-
2350
- /* Thin scrollbar styling */
2351
- .overtype-toolbar::-webkit-scrollbar {
2352
- height: 4px;
1464
+
1465
+ /* Thin scrollbar styling */
1466
+ .overtype-toolbar::-webkit-scrollbar {
1467
+ height: 4px;
1468
+ }
1469
+
1470
+ .overtype-toolbar::-webkit-scrollbar-track {
1471
+ background: transparent;
1472
+ }
1473
+
1474
+ .overtype-toolbar::-webkit-scrollbar-thumb {
1475
+ background: rgba(0, 0, 0, 0.2);
1476
+ border-radius: 2px;
1477
+ }
1478
+
1479
+ .overtype-toolbar-button {
1480
+ display: flex;
1481
+ align-items: center;
1482
+ justify-content: center;
1483
+ width: 32px;
1484
+ height: 32px;
1485
+ padding: 0;
1486
+ border: none;
1487
+ border-radius: 6px;
1488
+ background: transparent;
1489
+ color: var(--toolbar-icon, var(--text-secondary, #666));
1490
+ cursor: pointer;
1491
+ transition: all 0.2s ease;
1492
+ flex-shrink: 0;
1493
+ }
1494
+
1495
+ .overtype-toolbar-button svg {
1496
+ width: 20px;
1497
+ height: 20px;
1498
+ fill: currentColor;
1499
+ }
1500
+
1501
+ .overtype-toolbar-button:hover {
1502
+ background: var(--toolbar-hover, var(--bg-secondary, #e9ecef));
1503
+ color: var(--toolbar-icon, var(--text-primary, #333));
1504
+ }
1505
+
1506
+ .overtype-toolbar-button:active {
1507
+ transform: scale(0.95);
1508
+ }
1509
+
1510
+ .overtype-toolbar-button.active {
1511
+ background: var(--toolbar-active, var(--primary, #007bff));
1512
+ color: var(--toolbar-icon, var(--text-primary, #333));
1513
+ }
1514
+
1515
+ .overtype-toolbar-button:disabled {
1516
+ opacity: 0.5;
1517
+ cursor: not-allowed;
1518
+ }
1519
+
1520
+ .overtype-toolbar-separator {
1521
+ width: 1px;
1522
+ height: 24px;
1523
+ background: var(--border, #e0e0e0);
1524
+ margin: 0 4px;
1525
+ flex-shrink: 0;
1526
+ }
1527
+
1528
+ /* Adjust wrapper when toolbar is present */
1529
+ /* Mobile toolbar adjustments */
1530
+ @media (max-width: 640px) {
1531
+ .overtype-toolbar {
1532
+ padding: 6px;
1533
+ gap: 2px;
1534
+ }
1535
+
1536
+ .overtype-toolbar-button {
1537
+ width: 36px;
1538
+ height: 36px;
1539
+ }
1540
+
1541
+ .overtype-toolbar-separator {
1542
+ margin: 0 2px;
1543
+ }
1544
+ }
1545
+
1546
+ /* Plain mode - hide preview and show textarea text */
1547
+ .overtype-container[data-mode="plain"] .overtype-preview {
1548
+ display: none !important;
1549
+ }
1550
+
1551
+ .overtype-container[data-mode="plain"] .overtype-input {
1552
+ color: var(--text, #0d3b66) !important;
1553
+ /* Use system font stack for better plain text readability */
1554
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
1555
+ "Helvetica Neue", Arial, sans-serif !important;
1556
+ }
1557
+
1558
+ /* Ensure textarea remains transparent in overlay mode */
1559
+ .overtype-container:not([data-mode="plain"]) .overtype-input {
1560
+ color: transparent !important;
1561
+ }
1562
+
1563
+ /* Dropdown menu styles */
1564
+ .overtype-toolbar-button {
1565
+ position: relative !important; /* Override reset - needed for dropdown */
1566
+ }
1567
+
1568
+ .overtype-toolbar-button.dropdown-active {
1569
+ background: var(--toolbar-active, var(--hover-bg, #f0f0f0));
1570
+ }
1571
+
1572
+ .overtype-dropdown-menu {
1573
+ position: fixed !important; /* Fixed positioning relative to viewport */
1574
+ background: var(--bg-secondary, white) !important; /* Override reset */
1575
+ border: 1px solid var(--border, #e0e0e0) !important; /* Override reset */
1576
+ border-radius: 6px;
1577
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1) !important; /* Override reset */
1578
+ z-index: 10000; /* Very high z-index to ensure visibility */
1579
+ min-width: 150px;
1580
+ padding: 4px 0 !important; /* Override reset */
1581
+ /* Position will be set via JavaScript based on button position */
1582
+ }
1583
+
1584
+ .overtype-dropdown-item {
1585
+ display: flex;
1586
+ align-items: center;
1587
+ width: 100%;
1588
+ padding: 8px 12px;
1589
+ border: none;
1590
+ background: none;
1591
+ text-align: left;
1592
+ cursor: pointer;
1593
+ font-size: 14px;
1594
+ color: var(--text, #333);
1595
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
1596
+ }
1597
+
1598
+ .overtype-dropdown-item:hover {
1599
+ background: var(--hover-bg, #f0f0f0);
1600
+ }
1601
+
1602
+ .overtype-dropdown-item.active {
1603
+ font-weight: 600;
1604
+ }
1605
+
1606
+ .overtype-dropdown-check {
1607
+ width: 16px;
1608
+ margin-right: 8px;
1609
+ color: var(--h1, #007bff);
1610
+ }
1611
+
1612
+ .overtype-dropdown-icon {
1613
+ width: 20px;
1614
+ margin-right: 8px;
1615
+ text-align: center;
1616
+ }
1617
+
1618
+ /* Preview mode styles */
1619
+ .overtype-container[data-mode="preview"] .overtype-input {
1620
+ display: none !important;
1621
+ }
1622
+
1623
+ .overtype-container[data-mode="preview"] .overtype-preview {
1624
+ pointer-events: auto !important;
1625
+ user-select: text !important;
1626
+ cursor: text !important;
1627
+ }
1628
+
1629
+ /* Hide syntax markers in preview mode */
1630
+ .overtype-container[data-mode="preview"] .syntax-marker {
1631
+ display: none !important;
1632
+ }
1633
+
1634
+ /* Hide URL part of links in preview mode - extra specificity */
1635
+ .overtype-container[data-mode="preview"] .syntax-marker.url-part,
1636
+ .overtype-container[data-mode="preview"] .url-part {
1637
+ display: none !important;
1638
+ }
1639
+
1640
+ /* Hide all syntax markers inside links too */
1641
+ .overtype-container[data-mode="preview"] a .syntax-marker {
1642
+ display: none !important;
1643
+ }
1644
+
1645
+ /* Headers - restore proper sizing in preview mode */
1646
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview h1,
1647
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview h2,
1648
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview h3 {
1649
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;
1650
+ font-weight: 600 !important;
1651
+ margin: 0 !important;
1652
+ display: block !important;
1653
+ color: inherit !important; /* Use parent text color */
1654
+ line-height: 1 !important; /* Tight line height for headings */
1655
+ }
1656
+
1657
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview h1 {
1658
+ font-size: 2em !important;
1659
+ }
1660
+
1661
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview h2 {
1662
+ font-size: 1.5em !important;
1663
+ }
1664
+
1665
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview h3 {
1666
+ font-size: 1.17em !important;
1667
+ }
1668
+
1669
+ /* Lists - restore list styling in preview mode */
1670
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview ul {
1671
+ display: block !important;
1672
+ list-style: disc !important;
1673
+ padding-left: 2em !important;
1674
+ margin: 1em 0 !important;
1675
+ }
1676
+
1677
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview ol {
1678
+ display: block !important;
1679
+ list-style: decimal !important;
1680
+ padding-left: 2em !important;
1681
+ margin: 1em 0 !important;
1682
+ }
1683
+
1684
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview li {
1685
+ display: list-item !important;
1686
+ margin: 0 !important;
1687
+ padding: 0 !important;
1688
+ }
1689
+
1690
+ /* Task list checkboxes - only in preview mode */
1691
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview li.task-list {
1692
+ list-style: none !important;
1693
+ position: relative !important;
1694
+ }
1695
+
1696
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview li.task-list input[type="checkbox"] {
1697
+ margin-right: 0.5em !important;
1698
+ cursor: default !important;
1699
+ vertical-align: middle !important;
1700
+ }
1701
+
1702
+ /* Task list in normal mode - keep syntax visible */
1703
+ .overtype-container:not([data-mode="preview"]) .overtype-wrapper .overtype-preview li.task-list {
1704
+ list-style: none !important;
1705
+ }
1706
+
1707
+ .overtype-container:not([data-mode="preview"]) .overtype-wrapper .overtype-preview li.task-list .syntax-marker {
1708
+ color: var(--syntax, #999999) !important;
1709
+ font-weight: normal !important;
1710
+ }
1711
+
1712
+ /* Links - make clickable in preview mode */
1713
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview a {
1714
+ pointer-events: auto !important;
1715
+ cursor: pointer !important;
1716
+ color: var(--link, #0066cc) !important;
1717
+ text-decoration: underline !important;
2353
1718
  }
2354
-
2355
- .overtype-toolbar::-webkit-scrollbar-track {
2356
- background: transparent;
1719
+
1720
+ /* Code blocks - proper pre/code styling in preview mode */
1721
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview pre.code-block {
1722
+ background: #2d2d2d !important;
1723
+ color: #f8f8f2 !important;
1724
+ padding: 1.2em !important;
1725
+ border-radius: 3px !important;
1726
+ overflow-x: auto !important;
1727
+ margin: 0 !important;
1728
+ display: block !important;
2357
1729
  }
2358
1730
 
2359
- .overtype-toolbar::-webkit-scrollbar-thumb {
2360
- background: rgba(0, 0, 0, 0.2);
2361
- border-radius: 2px;
1731
+ /* Cave theme code block background in preview mode */
1732
+ .overtype-container[data-theme="cave"][data-mode="preview"] .overtype-wrapper .overtype-preview pre.code-block {
1733
+ background: #11171F !important;
2362
1734
  }
2363
1735
 
2364
- .overtype-toolbar-button {
2365
- display: flex;
2366
- align-items: center;
2367
- justify-content: center;
2368
- width: 32px;
2369
- height: 32px;
2370
- padding: 0;
2371
- border: none;
2372
- border-radius: 6px;
2373
- background: transparent;
2374
- color: var(--toolbar-icon, var(--text-secondary, #666));
2375
- cursor: pointer;
2376
- transition: all 0.2s ease;
2377
- flex-shrink: 0;
1736
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview pre.code-block code {
1737
+ background: transparent !important;
1738
+ color: inherit !important;
1739
+ padding: 0 !important;
1740
+ font-family: ${fontFamily} !important;
1741
+ font-size: 0.9em !important;
1742
+ line-height: 1.4 !important;
2378
1743
  }
2379
1744
 
2380
- .overtype-toolbar-button svg {
2381
- width: 20px;
2382
- height: 20px;
2383
- fill: currentColor;
1745
+ /* Hide old code block lines and fences in preview mode */
1746
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview .code-block-line {
1747
+ display: none !important;
2384
1748
  }
2385
1749
 
2386
- .overtype-toolbar-button:hover {
2387
- background: var(--toolbar-hover, var(--bg-secondary, #e9ecef));
2388
- color: var(--toolbar-icon, var(--text-primary, #333));
1750
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview .code-fence {
1751
+ display: none !important;
2389
1752
  }
2390
1753
 
2391
- .overtype-toolbar-button:active {
2392
- transform: scale(0.95);
1754
+ /* Blockquotes - enhanced styling in preview mode */
1755
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview .blockquote {
1756
+ display: block !important;
1757
+ border-left: 4px solid var(--blockquote, #ddd) !important;
1758
+ padding-left: 1em !important;
1759
+ margin: 1em 0 !important;
1760
+ font-style: italic !important;
2393
1761
  }
2394
1762
 
2395
- .overtype-toolbar-button.active {
2396
- background: var(--toolbar-active, var(--primary, #007bff));
2397
- color: var(--toolbar-icon, var(--text-primary, #333));
1763
+ /* Typography improvements in preview mode */
1764
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview {
1765
+ font-family: Georgia, 'Times New Roman', serif !important;
1766
+ font-size: 16px !important;
1767
+ line-height: 1.8 !important;
1768
+ color: var(--text, #333) !important; /* Consistent text color */
2398
1769
  }
2399
1770
 
2400
- .overtype-toolbar-button:disabled {
2401
- opacity: 0.5;
2402
- cursor: not-allowed;
1771
+ /* Inline code in preview mode - keep monospace */
1772
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview code {
1773
+ font-family: ${fontFamily} !important;
1774
+ font-size: 0.9em !important;
1775
+ background: rgba(135, 131, 120, 0.15) !important;
1776
+ padding: 0.2em 0.4em !important;
1777
+ border-radius: 3px !important;
2403
1778
  }
2404
1779
 
2405
- .overtype-toolbar-separator {
2406
- width: 1px;
2407
- height: 24px;
2408
- background: var(--border, #e0e0e0);
2409
- margin: 0 4px;
2410
- flex-shrink: 0;
1780
+ /* Strong and em elements in preview mode */
1781
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview strong {
1782
+ font-weight: 700 !important;
1783
+ color: inherit !important; /* Use parent text color */
2411
1784
  }
2412
1785
 
2413
- /* Adjust wrapper when toolbar is present */
2414
- /* Mobile toolbar adjustments */
2415
- @media (max-width: 640px) {
2416
- .overtype-toolbar {
2417
- padding: 6px;
2418
- gap: 2px;
2419
- }
2420
-
2421
- .overtype-toolbar-button {
2422
- width: 36px;
2423
- height: 36px;
2424
- }
1786
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview em {
1787
+ font-style: italic !important;
1788
+ color: inherit !important; /* Use parent text color */
1789
+ }
2425
1790
 
2426
- .overtype-toolbar-separator {
2427
- margin: 0 2px;
2428
- }
1791
+ /* HR in preview mode */
1792
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview .hr-marker {
1793
+ display: block !important;
1794
+ border-top: 2px solid var(--hr, #ddd) !important;
1795
+ text-indent: -9999px !important;
1796
+ height: 2px !important;
2429
1797
  }
2430
-
2431
- /* Plain mode - hide preview and show textarea text */
2432
- .overtype-container[data-mode="plain"] .overtype-preview {
1798
+
1799
+ /* Link Tooltip - Base styles (all browsers) */
1800
+ .overtype-link-tooltip {
1801
+ /* Visual styles that work for both positioning methods */
1802
+ background: #333 !important;
1803
+ color: white !important;
1804
+ padding: 6px 10px !important;
1805
+ border-radius: 16px !important;
1806
+ font-size: 12px !important;
1807
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif !important;
2433
1808
  display: none !important;
1809
+ z-index: 10000 !important;
1810
+ cursor: pointer !important;
1811
+ box-shadow: 0 2px 8px rgba(0,0,0,0.3) !important;
1812
+ max-width: 300px !important;
1813
+ white-space: nowrap !important;
1814
+ overflow: hidden !important;
1815
+ text-overflow: ellipsis !important;
1816
+
1817
+ /* Base positioning for Floating UI fallback */
1818
+ position: absolute;
2434
1819
  }
2435
-
2436
- .overtype-container[data-mode="plain"] .overtype-input {
2437
- color: var(--text, #0d3b66) !important;
2438
- /* Use system font stack for better plain text readability */
2439
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
2440
- "Helvetica Neue", Arial, sans-serif !important;
1820
+
1821
+ .overtype-link-tooltip.visible {
1822
+ display: flex !important;
2441
1823
  }
2442
-
2443
- /* Ensure textarea remains transparent in overlay mode */
2444
- .overtype-container:not([data-mode="plain"]) .overtype-input {
2445
- color: transparent !important;
1824
+
1825
+ /* CSS Anchor Positioning (modern browsers only) */
1826
+ @supports (position-anchor: --x) and (position-area: center) {
1827
+ .overtype-link-tooltip {
1828
+ /* Only anchor positioning specific properties */
1829
+ position-anchor: var(--target-anchor, --link-0);
1830
+ position-area: block-end center;
1831
+ margin-top: 8px !important;
1832
+ position-try: most-width block-end inline-end, flip-inline, block-start center;
1833
+ position-visibility: anchors-visible;
1834
+ }
2446
1835
  }
2447
1836
 
2448
- /* Dropdown menu styles */
2449
- .overtype-toolbar-button {
2450
- position: relative !important; /* Override reset - needed for dropdown */
1837
+ ${mobileStyles}
1838
+ `;
1839
+ }
1840
+
1841
+ // node_modules/markdown-actions/dist/markdown-actions.esm.js
1842
+ var __defProp2 = Object.defineProperty;
1843
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
1844
+ var __hasOwnProp2 = Object.prototype.hasOwnProperty;
1845
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
1846
+ var __defNormalProp2 = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1847
+ var __spreadValues = (a, b) => {
1848
+ for (var prop in b || (b = {}))
1849
+ if (__hasOwnProp2.call(b, prop))
1850
+ __defNormalProp2(a, prop, b[prop]);
1851
+ if (__getOwnPropSymbols)
1852
+ for (var prop of __getOwnPropSymbols(b)) {
1853
+ if (__propIsEnum.call(b, prop))
1854
+ __defNormalProp2(a, prop, b[prop]);
1855
+ }
1856
+ return a;
1857
+ };
1858
+ var FORMATS = {
1859
+ bold: {
1860
+ prefix: "**",
1861
+ suffix: "**",
1862
+ trimFirst: true
1863
+ },
1864
+ italic: {
1865
+ prefix: "_",
1866
+ suffix: "_",
1867
+ trimFirst: true
1868
+ },
1869
+ code: {
1870
+ prefix: "`",
1871
+ suffix: "`",
1872
+ blockPrefix: "```",
1873
+ blockSuffix: "```"
1874
+ },
1875
+ link: {
1876
+ prefix: "[",
1877
+ suffix: "](url)",
1878
+ replaceNext: "url",
1879
+ scanFor: "https?://"
1880
+ },
1881
+ bulletList: {
1882
+ prefix: "- ",
1883
+ multiline: true,
1884
+ unorderedList: true
1885
+ },
1886
+ numberedList: {
1887
+ prefix: "1. ",
1888
+ multiline: true,
1889
+ orderedList: true
1890
+ },
1891
+ quote: {
1892
+ prefix: "> ",
1893
+ multiline: true,
1894
+ surroundWithNewlines: true
1895
+ },
1896
+ taskList: {
1897
+ prefix: "- [ ] ",
1898
+ multiline: true,
1899
+ surroundWithNewlines: true
1900
+ },
1901
+ header1: { prefix: "# " },
1902
+ header2: { prefix: "## " },
1903
+ header3: { prefix: "### " },
1904
+ header4: { prefix: "#### " },
1905
+ header5: { prefix: "##### " },
1906
+ header6: { prefix: "###### " }
1907
+ };
1908
+ function getDefaultStyle() {
1909
+ return {
1910
+ prefix: "",
1911
+ suffix: "",
1912
+ blockPrefix: "",
1913
+ blockSuffix: "",
1914
+ multiline: false,
1915
+ replaceNext: "",
1916
+ prefixSpace: false,
1917
+ scanFor: "",
1918
+ surroundWithNewlines: false,
1919
+ orderedList: false,
1920
+ unorderedList: false,
1921
+ trimFirst: false
1922
+ };
1923
+ }
1924
+ function mergeWithDefaults(format) {
1925
+ return __spreadValues(__spreadValues({}, getDefaultStyle()), format);
1926
+ }
1927
+ var debugMode = false;
1928
+ function getDebugMode() {
1929
+ return debugMode;
1930
+ }
1931
+ function debugLog(funcName, message, data) {
1932
+ if (!debugMode)
1933
+ return;
1934
+ console.group(`\u{1F50D} ${funcName}`);
1935
+ console.log(message);
1936
+ if (data) {
1937
+ console.log("Data:", data);
2451
1938
  }
2452
-
2453
- .overtype-toolbar-button.dropdown-active {
2454
- background: var(--toolbar-active, var(--hover-bg, #f0f0f0));
1939
+ console.groupEnd();
1940
+ }
1941
+ function debugSelection(textarea, label) {
1942
+ if (!debugMode)
1943
+ return;
1944
+ const selected = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd);
1945
+ console.group(`\u{1F4CD} Selection: ${label}`);
1946
+ console.log("Position:", `${textarea.selectionStart}-${textarea.selectionEnd}`);
1947
+ console.log("Selected text:", JSON.stringify(selected));
1948
+ console.log("Length:", selected.length);
1949
+ const before = textarea.value.slice(Math.max(0, textarea.selectionStart - 10), textarea.selectionStart);
1950
+ const after = textarea.value.slice(textarea.selectionEnd, Math.min(textarea.value.length, textarea.selectionEnd + 10));
1951
+ console.log("Context:", JSON.stringify(before) + "[SELECTION]" + JSON.stringify(after));
1952
+ console.groupEnd();
1953
+ }
1954
+ function debugResult(result) {
1955
+ if (!debugMode)
1956
+ return;
1957
+ console.group("\u{1F4DD} Result");
1958
+ console.log("Text to insert:", JSON.stringify(result.text));
1959
+ console.log("New selection:", `${result.selectionStart}-${result.selectionEnd}`);
1960
+ console.groupEnd();
1961
+ }
1962
+ var canInsertText = null;
1963
+ function insertText(textarea, { text, selectionStart, selectionEnd }) {
1964
+ const debugMode2 = getDebugMode();
1965
+ if (debugMode2) {
1966
+ console.group("\u{1F527} insertText");
1967
+ console.log("Current selection:", `${textarea.selectionStart}-${textarea.selectionEnd}`);
1968
+ console.log("Text to insert:", JSON.stringify(text));
1969
+ console.log("New selection to set:", selectionStart, "-", selectionEnd);
2455
1970
  }
2456
-
2457
- .overtype-dropdown-menu {
2458
- position: fixed !important; /* Fixed positioning relative to viewport */
2459
- background: var(--bg-secondary, white) !important; /* Override reset */
2460
- border: 1px solid var(--border, #e0e0e0) !important; /* Override reset */
2461
- border-radius: 6px;
2462
- box-shadow: 0 2px 8px rgba(0,0,0,0.1) !important; /* Override reset */
2463
- z-index: 10000; /* Very high z-index to ensure visibility */
2464
- min-width: 150px;
2465
- padding: 4px 0 !important; /* Override reset */
2466
- /* Position will be set via JavaScript based on button position */
1971
+ textarea.focus();
1972
+ const originalSelectionStart = textarea.selectionStart;
1973
+ const originalSelectionEnd = textarea.selectionEnd;
1974
+ const before = textarea.value.slice(0, originalSelectionStart);
1975
+ const after = textarea.value.slice(originalSelectionEnd);
1976
+ if (debugMode2) {
1977
+ console.log("Before text (last 20):", JSON.stringify(before.slice(-20)));
1978
+ console.log("After text (first 20):", JSON.stringify(after.slice(0, 20)));
1979
+ console.log("Selected text being replaced:", JSON.stringify(textarea.value.slice(originalSelectionStart, originalSelectionEnd)));
2467
1980
  }
2468
-
2469
- .overtype-dropdown-item {
2470
- display: flex;
2471
- align-items: center;
2472
- width: 100%;
2473
- padding: 8px 12px;
2474
- border: none;
2475
- background: none;
2476
- text-align: left;
2477
- cursor: pointer;
2478
- font-size: 14px;
2479
- color: var(--text, #333);
2480
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
1981
+ const originalValue = textarea.value;
1982
+ const hasSelection = originalSelectionStart !== originalSelectionEnd;
1983
+ if (canInsertText === null || canInsertText === true) {
1984
+ textarea.contentEditable = "true";
1985
+ try {
1986
+ canInsertText = document.execCommand("insertText", false, text);
1987
+ if (debugMode2)
1988
+ console.log("execCommand returned:", canInsertText, "for text with", text.split("\n").length, "lines");
1989
+ } catch (error) {
1990
+ canInsertText = false;
1991
+ if (debugMode2)
1992
+ console.log("execCommand threw error:", error);
1993
+ }
1994
+ textarea.contentEditable = "false";
2481
1995
  }
2482
-
2483
- .overtype-dropdown-item:hover {
2484
- background: var(--hover-bg, #f0f0f0);
1996
+ if (debugMode2) {
1997
+ console.log("canInsertText before:", canInsertText);
1998
+ console.log("execCommand result:", canInsertText);
2485
1999
  }
2486
-
2487
- .overtype-dropdown-item.active {
2488
- font-weight: 600;
2000
+ if (canInsertText) {
2001
+ const expectedValue = before + text + after;
2002
+ const actualValue = textarea.value;
2003
+ if (debugMode2) {
2004
+ console.log("Expected length:", expectedValue.length);
2005
+ console.log("Actual length:", actualValue.length);
2006
+ }
2007
+ if (actualValue !== expectedValue) {
2008
+ if (debugMode2) {
2009
+ console.log("execCommand changed the value but not as expected");
2010
+ console.log("Expected:", JSON.stringify(expectedValue.slice(0, 100)));
2011
+ console.log("Actual:", JSON.stringify(actualValue.slice(0, 100)));
2012
+ }
2013
+ }
2489
2014
  }
2490
-
2491
- .overtype-dropdown-check {
2492
- width: 16px;
2493
- margin-right: 8px;
2494
- color: var(--h1, #007bff);
2015
+ if (!canInsertText) {
2016
+ if (debugMode2)
2017
+ console.log("Using manual insertion");
2018
+ if (textarea.value === originalValue) {
2019
+ if (debugMode2)
2020
+ console.log("Value unchanged, doing manual replacement");
2021
+ try {
2022
+ document.execCommand("ms-beginUndoUnit");
2023
+ } catch (e) {
2024
+ }
2025
+ textarea.value = before + text + after;
2026
+ try {
2027
+ document.execCommand("ms-endUndoUnit");
2028
+ } catch (e) {
2029
+ }
2030
+ textarea.dispatchEvent(new CustomEvent("input", { bubbles: true, cancelable: true }));
2031
+ } else {
2032
+ if (debugMode2)
2033
+ console.log("Value was changed by execCommand, skipping manual insertion");
2034
+ }
2495
2035
  }
2496
-
2497
- .overtype-dropdown-icon {
2498
- width: 20px;
2499
- margin-right: 8px;
2500
- text-align: center;
2036
+ if (debugMode2)
2037
+ console.log("Setting selection range:", selectionStart, selectionEnd);
2038
+ if (selectionStart != null && selectionEnd != null) {
2039
+ textarea.setSelectionRange(selectionStart, selectionEnd);
2040
+ } else {
2041
+ textarea.setSelectionRange(originalSelectionStart, textarea.selectionEnd);
2501
2042
  }
2502
-
2503
- /* Preview mode styles */
2504
- .overtype-container[data-mode="preview"] .overtype-input {
2505
- display: none !important;
2043
+ if (debugMode2) {
2044
+ console.log("Final value length:", textarea.value.length);
2045
+ console.groupEnd();
2506
2046
  }
2507
-
2508
- .overtype-container[data-mode="preview"] .overtype-preview {
2509
- pointer-events: auto !important;
2510
- user-select: text !important;
2511
- cursor: text !important;
2047
+ }
2048
+ function isMultipleLines(string) {
2049
+ return string.trim().split("\n").length > 1;
2050
+ }
2051
+ function wordSelectionStart(text, i) {
2052
+ let index = i;
2053
+ while (text[index] && text[index - 1] != null && !text[index - 1].match(/\s/)) {
2054
+ index--;
2512
2055
  }
2513
-
2514
- /* Hide syntax markers in preview mode */
2515
- .overtype-container[data-mode="preview"] .syntax-marker {
2516
- display: none !important;
2056
+ return index;
2057
+ }
2058
+ function wordSelectionEnd(text, i, multiline) {
2059
+ let index = i;
2060
+ const breakpoint = multiline ? /\n/ : /\s/;
2061
+ while (text[index] && !text[index].match(breakpoint)) {
2062
+ index++;
2517
2063
  }
2518
-
2519
- /* Hide URL part of links in preview mode - extra specificity */
2520
- .overtype-container[data-mode="preview"] .syntax-marker.url-part,
2521
- .overtype-container[data-mode="preview"] .url-part {
2522
- display: none !important;
2064
+ return index;
2065
+ }
2066
+ function expandSelectionToLine(textarea) {
2067
+ const lines = textarea.value.split("\n");
2068
+ let counter = 0;
2069
+ for (let index = 0; index < lines.length; index++) {
2070
+ const lineLength = lines[index].length + 1;
2071
+ if (textarea.selectionStart >= counter && textarea.selectionStart < counter + lineLength) {
2072
+ textarea.selectionStart = counter;
2073
+ }
2074
+ if (textarea.selectionEnd >= counter && textarea.selectionEnd < counter + lineLength) {
2075
+ if (index === lines.length - 1) {
2076
+ textarea.selectionEnd = Math.min(counter + lines[index].length, textarea.value.length);
2077
+ } else {
2078
+ textarea.selectionEnd = counter + lineLength - 1;
2079
+ }
2080
+ }
2081
+ counter += lineLength;
2523
2082
  }
2524
-
2525
- /* Hide all syntax markers inside links too */
2526
- .overtype-container[data-mode="preview"] a .syntax-marker {
2527
- display: none !important;
2083
+ }
2084
+ function expandSelectedText(textarea, prefixToUse, suffixToUse, multiline = false) {
2085
+ if (textarea.selectionStart === textarea.selectionEnd) {
2086
+ textarea.selectionStart = wordSelectionStart(textarea.value, textarea.selectionStart);
2087
+ textarea.selectionEnd = wordSelectionEnd(textarea.value, textarea.selectionEnd, multiline);
2088
+ } else {
2089
+ const expandedSelectionStart = textarea.selectionStart - prefixToUse.length;
2090
+ const expandedSelectionEnd = textarea.selectionEnd + suffixToUse.length;
2091
+ const beginsWithPrefix = textarea.value.slice(expandedSelectionStart, textarea.selectionStart) === prefixToUse;
2092
+ const endsWithSuffix = textarea.value.slice(textarea.selectionEnd, expandedSelectionEnd) === suffixToUse;
2093
+ if (beginsWithPrefix && endsWithSuffix) {
2094
+ textarea.selectionStart = expandedSelectionStart;
2095
+ textarea.selectionEnd = expandedSelectionEnd;
2096
+ }
2528
2097
  }
2529
-
2530
- /* Headers - restore proper sizing in preview mode */
2531
- .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview h1,
2532
- .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview h2,
2533
- .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview h3 {
2534
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;
2535
- font-weight: 600 !important;
2536
- margin: 0 !important;
2537
- display: block !important;
2538
- color: inherit !important; /* Use parent text color */
2539
- line-height: 1 !important; /* Tight line height for headings */
2098
+ return textarea.value.slice(textarea.selectionStart, textarea.selectionEnd);
2099
+ }
2100
+ function newlinesToSurroundSelectedText(textarea) {
2101
+ const beforeSelection = textarea.value.slice(0, textarea.selectionStart);
2102
+ const afterSelection = textarea.value.slice(textarea.selectionEnd);
2103
+ const breaksBefore = beforeSelection.match(/\n*$/);
2104
+ const breaksAfter = afterSelection.match(/^\n*/);
2105
+ const newlinesBeforeSelection = breaksBefore ? breaksBefore[0].length : 0;
2106
+ const newlinesAfterSelection = breaksAfter ? breaksAfter[0].length : 0;
2107
+ let newlinesToAppend = "";
2108
+ let newlinesToPrepend = "";
2109
+ if (beforeSelection.match(/\S/) && newlinesBeforeSelection < 2) {
2110
+ newlinesToAppend = "\n".repeat(2 - newlinesBeforeSelection);
2540
2111
  }
2541
-
2542
- .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview h1 {
2543
- font-size: 2em !important;
2112
+ if (afterSelection.match(/\S/) && newlinesAfterSelection < 2) {
2113
+ newlinesToPrepend = "\n".repeat(2 - newlinesAfterSelection);
2544
2114
  }
2545
-
2546
- .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview h2 {
2547
- font-size: 1.5em !important;
2115
+ return { newlinesToAppend, newlinesToPrepend };
2116
+ }
2117
+ function applyLineOperation(textarea, operation, options = {}) {
2118
+ const originalStart = textarea.selectionStart;
2119
+ const originalEnd = textarea.selectionEnd;
2120
+ const noInitialSelection = originalStart === originalEnd;
2121
+ const value = textarea.value;
2122
+ let lineStart = originalStart;
2123
+ while (lineStart > 0 && value[lineStart - 1] !== "\n") {
2124
+ lineStart--;
2548
2125
  }
2549
-
2550
- .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview h3 {
2551
- font-size: 1.17em !important;
2126
+ if (noInitialSelection) {
2127
+ let lineEnd = originalStart;
2128
+ while (lineEnd < value.length && value[lineEnd] !== "\n") {
2129
+ lineEnd++;
2130
+ }
2131
+ textarea.selectionStart = lineStart;
2132
+ textarea.selectionEnd = lineEnd;
2133
+ } else {
2134
+ expandSelectionToLine(textarea);
2552
2135
  }
2553
-
2554
- /* Lists - restore list styling in preview mode */
2555
- .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview ul {
2556
- display: block !important;
2557
- list-style: disc !important;
2558
- padding-left: 2em !important;
2559
- margin: 1em 0 !important;
2136
+ const result = operation(textarea);
2137
+ if (options.adjustSelection) {
2138
+ const selectedText = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd);
2139
+ const isRemoving = selectedText.startsWith(options.prefix);
2140
+ const adjusted = options.adjustSelection(isRemoving, originalStart, originalEnd, lineStart);
2141
+ result.selectionStart = adjusted.start;
2142
+ result.selectionEnd = adjusted.end;
2143
+ } else if (options.prefix) {
2144
+ const selectedText = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd);
2145
+ const isRemoving = selectedText.startsWith(options.prefix);
2146
+ if (noInitialSelection) {
2147
+ if (isRemoving) {
2148
+ result.selectionStart = Math.max(originalStart - options.prefix.length, lineStart);
2149
+ result.selectionEnd = result.selectionStart;
2150
+ } else {
2151
+ result.selectionStart = originalStart + options.prefix.length;
2152
+ result.selectionEnd = result.selectionStart;
2153
+ }
2154
+ } else {
2155
+ if (isRemoving) {
2156
+ result.selectionStart = Math.max(originalStart - options.prefix.length, lineStart);
2157
+ result.selectionEnd = Math.max(originalEnd - options.prefix.length, lineStart);
2158
+ } else {
2159
+ result.selectionStart = originalStart + options.prefix.length;
2160
+ result.selectionEnd = originalEnd + options.prefix.length;
2161
+ }
2162
+ }
2560
2163
  }
2561
-
2562
- .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview ol {
2563
- display: block !important;
2564
- list-style: decimal !important;
2565
- padding-left: 2em !important;
2566
- margin: 1em 0 !important;
2164
+ return result;
2165
+ }
2166
+ function blockStyle(textarea, style) {
2167
+ let newlinesToAppend;
2168
+ let newlinesToPrepend;
2169
+ const { prefix, suffix, blockPrefix, blockSuffix, replaceNext, prefixSpace, scanFor, surroundWithNewlines, trimFirst } = style;
2170
+ const originalSelectionStart = textarea.selectionStart;
2171
+ const originalSelectionEnd = textarea.selectionEnd;
2172
+ let selectedText = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd);
2173
+ let prefixToUse = isMultipleLines(selectedText) && blockPrefix && blockPrefix.length > 0 ? `${blockPrefix}
2174
+ ` : prefix;
2175
+ let suffixToUse = isMultipleLines(selectedText) && blockSuffix && blockSuffix.length > 0 ? `
2176
+ ${blockSuffix}` : suffix;
2177
+ if (prefixSpace) {
2178
+ const beforeSelection = textarea.value[textarea.selectionStart - 1];
2179
+ if (textarea.selectionStart !== 0 && beforeSelection != null && !beforeSelection.match(/\s/)) {
2180
+ prefixToUse = ` ${prefixToUse}`;
2181
+ }
2567
2182
  }
2568
-
2569
- .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview li {
2570
- display: list-item !important;
2571
- margin: 0 !important;
2572
- padding: 0 !important;
2183
+ selectedText = expandSelectedText(textarea, prefixToUse, suffixToUse, style.multiline);
2184
+ let selectionStart = textarea.selectionStart;
2185
+ let selectionEnd = textarea.selectionEnd;
2186
+ const hasReplaceNext = replaceNext && replaceNext.length > 0 && suffixToUse.indexOf(replaceNext) > -1 && selectedText.length > 0;
2187
+ if (surroundWithNewlines) {
2188
+ const ref = newlinesToSurroundSelectedText(textarea);
2189
+ newlinesToAppend = ref.newlinesToAppend;
2190
+ newlinesToPrepend = ref.newlinesToPrepend;
2191
+ prefixToUse = newlinesToAppend + prefix;
2192
+ suffixToUse += newlinesToPrepend;
2573
2193
  }
2574
-
2575
- /* Task list checkboxes - only in preview mode */
2576
- .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview li.task-list {
2577
- list-style: none !important;
2578
- position: relative !important;
2194
+ if (selectedText.startsWith(prefixToUse) && selectedText.endsWith(suffixToUse)) {
2195
+ const replacementText = selectedText.slice(prefixToUse.length, selectedText.length - suffixToUse.length);
2196
+ if (originalSelectionStart === originalSelectionEnd) {
2197
+ let position = originalSelectionStart - prefixToUse.length;
2198
+ position = Math.max(position, selectionStart);
2199
+ position = Math.min(position, selectionStart + replacementText.length);
2200
+ selectionStart = selectionEnd = position;
2201
+ } else {
2202
+ selectionEnd = selectionStart + replacementText.length;
2203
+ }
2204
+ return { text: replacementText, selectionStart, selectionEnd };
2205
+ } else if (!hasReplaceNext) {
2206
+ let replacementText = prefixToUse + selectedText + suffixToUse;
2207
+ selectionStart = originalSelectionStart + prefixToUse.length;
2208
+ selectionEnd = originalSelectionEnd + prefixToUse.length;
2209
+ const whitespaceEdges = selectedText.match(/^\s*|\s*$/g);
2210
+ if (trimFirst && whitespaceEdges) {
2211
+ const leadingWhitespace = whitespaceEdges[0] || "";
2212
+ const trailingWhitespace = whitespaceEdges[1] || "";
2213
+ replacementText = leadingWhitespace + prefixToUse + selectedText.trim() + suffixToUse + trailingWhitespace;
2214
+ selectionStart += leadingWhitespace.length;
2215
+ selectionEnd -= trailingWhitespace.length;
2216
+ }
2217
+ return { text: replacementText, selectionStart, selectionEnd };
2218
+ } else if (scanFor && scanFor.length > 0 && selectedText.match(scanFor)) {
2219
+ suffixToUse = suffixToUse.replace(replaceNext, selectedText);
2220
+ const replacementText = prefixToUse + suffixToUse;
2221
+ selectionStart = selectionEnd = selectionStart + prefixToUse.length;
2222
+ return { text: replacementText, selectionStart, selectionEnd };
2223
+ } else {
2224
+ const replacementText = prefixToUse + selectedText + suffixToUse;
2225
+ selectionStart = selectionStart + prefixToUse.length + selectedText.length + suffixToUse.indexOf(replaceNext);
2226
+ selectionEnd = selectionStart + replaceNext.length;
2227
+ return { text: replacementText, selectionStart, selectionEnd };
2579
2228
  }
2580
-
2581
- .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview li.task-list input[type="checkbox"] {
2582
- margin-right: 0.5em !important;
2583
- cursor: default !important;
2584
- vertical-align: middle !important;
2229
+ }
2230
+ function multilineStyle(textarea, style) {
2231
+ const { prefix, suffix, surroundWithNewlines } = style;
2232
+ let text = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd);
2233
+ let selectionStart = textarea.selectionStart;
2234
+ let selectionEnd = textarea.selectionEnd;
2235
+ const lines = text.split("\n");
2236
+ const undoStyle = lines.every((line) => line.startsWith(prefix) && (!suffix || line.endsWith(suffix)));
2237
+ if (undoStyle) {
2238
+ text = lines.map((line) => {
2239
+ let result = line.slice(prefix.length);
2240
+ if (suffix) {
2241
+ result = result.slice(0, result.length - suffix.length);
2242
+ }
2243
+ return result;
2244
+ }).join("\n");
2245
+ selectionEnd = selectionStart + text.length;
2246
+ } else {
2247
+ text = lines.map((line) => prefix + line + (suffix || "")).join("\n");
2248
+ if (surroundWithNewlines) {
2249
+ const { newlinesToAppend, newlinesToPrepend } = newlinesToSurroundSelectedText(textarea);
2250
+ selectionStart += newlinesToAppend.length;
2251
+ selectionEnd = selectionStart + text.length;
2252
+ text = newlinesToAppend + text + newlinesToPrepend;
2253
+ }
2585
2254
  }
2586
-
2587
- /* Task list in normal mode - keep syntax visible */
2588
- .overtype-container:not([data-mode="preview"]) .overtype-wrapper .overtype-preview li.task-list {
2589
- list-style: none !important;
2255
+ return { text, selectionStart, selectionEnd };
2256
+ }
2257
+ function undoOrderedListStyle(text) {
2258
+ const lines = text.split("\n");
2259
+ const orderedListRegex = /^\d+\.\s+/;
2260
+ const shouldUndoOrderedList = lines.every((line) => orderedListRegex.test(line));
2261
+ let result = lines;
2262
+ if (shouldUndoOrderedList) {
2263
+ result = lines.map((line) => line.replace(orderedListRegex, ""));
2590
2264
  }
2591
-
2592
- .overtype-container:not([data-mode="preview"]) .overtype-wrapper .overtype-preview li.task-list .syntax-marker {
2593
- color: var(--syntax, #999999) !important;
2594
- font-weight: normal !important;
2265
+ return {
2266
+ text: result.join("\n"),
2267
+ processed: shouldUndoOrderedList
2268
+ };
2269
+ }
2270
+ function undoUnorderedListStyle(text) {
2271
+ const lines = text.split("\n");
2272
+ const unorderedListPrefix = "- ";
2273
+ const shouldUndoUnorderedList = lines.every((line) => line.startsWith(unorderedListPrefix));
2274
+ let result = lines;
2275
+ if (shouldUndoUnorderedList) {
2276
+ result = lines.map((line) => line.slice(unorderedListPrefix.length));
2595
2277
  }
2596
-
2597
- /* Links - make clickable in preview mode */
2598
- .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview a {
2599
- pointer-events: auto !important;
2600
- cursor: pointer !important;
2601
- color: var(--link, #0066cc) !important;
2602
- text-decoration: underline !important;
2278
+ return {
2279
+ text: result.join("\n"),
2280
+ processed: shouldUndoUnorderedList
2281
+ };
2282
+ }
2283
+ function makePrefix(index, unorderedList) {
2284
+ if (unorderedList) {
2285
+ return "- ";
2286
+ } else {
2287
+ return `${index + 1}. `;
2603
2288
  }
2604
-
2605
- /* Code blocks - proper pre/code styling in preview mode */
2606
- .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview pre.code-block {
2607
- background: #2d2d2d !important;
2608
- color: #f8f8f2 !important;
2609
- padding: 1.2em !important;
2610
- border-radius: 3px !important;
2611
- overflow-x: auto !important;
2612
- margin: 0 !important;
2613
- display: block !important;
2289
+ }
2290
+ function clearExistingListStyle(style, selectedText) {
2291
+ let undoResult;
2292
+ let undoResultOppositeList;
2293
+ let pristineText;
2294
+ if (style.orderedList) {
2295
+ undoResult = undoOrderedListStyle(selectedText);
2296
+ undoResultOppositeList = undoUnorderedListStyle(undoResult.text);
2297
+ pristineText = undoResultOppositeList.text;
2298
+ } else {
2299
+ undoResult = undoUnorderedListStyle(selectedText);
2300
+ undoResultOppositeList = undoOrderedListStyle(undoResult.text);
2301
+ pristineText = undoResultOppositeList.text;
2302
+ }
2303
+ return [undoResult, undoResultOppositeList, pristineText];
2304
+ }
2305
+ function listStyle(textarea, style) {
2306
+ const noInitialSelection = textarea.selectionStart === textarea.selectionEnd;
2307
+ let selectionStart = textarea.selectionStart;
2308
+ let selectionEnd = textarea.selectionEnd;
2309
+ expandSelectionToLine(textarea);
2310
+ const selectedText = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd);
2311
+ const [undoResult, undoResultOppositeList, pristineText] = clearExistingListStyle(style, selectedText);
2312
+ const prefixedLines = pristineText.split("\n").map((value, index) => {
2313
+ return `${makePrefix(index, style.unorderedList)}${value}`;
2314
+ });
2315
+ const totalPrefixLength = prefixedLines.reduce((previousValue, _currentValue, currentIndex) => {
2316
+ return previousValue + makePrefix(currentIndex, style.unorderedList).length;
2317
+ }, 0);
2318
+ const totalPrefixLengthOppositeList = prefixedLines.reduce((previousValue, _currentValue, currentIndex) => {
2319
+ return previousValue + makePrefix(currentIndex, !style.unorderedList).length;
2320
+ }, 0);
2321
+ if (undoResult.processed) {
2322
+ if (noInitialSelection) {
2323
+ selectionStart = Math.max(selectionStart - makePrefix(0, style.unorderedList).length, 0);
2324
+ selectionEnd = selectionStart;
2325
+ } else {
2326
+ selectionStart = textarea.selectionStart;
2327
+ selectionEnd = textarea.selectionEnd - totalPrefixLength;
2328
+ }
2329
+ return { text: pristineText, selectionStart, selectionEnd };
2614
2330
  }
2615
-
2616
- /* Cave theme code block background in preview mode */
2617
- .overtype-container[data-theme="cave"][data-mode="preview"] .overtype-wrapper .overtype-preview pre.code-block {
2618
- background: #11171F !important;
2331
+ const { newlinesToAppend, newlinesToPrepend } = newlinesToSurroundSelectedText(textarea);
2332
+ const text = newlinesToAppend + prefixedLines.join("\n") + newlinesToPrepend;
2333
+ if (noInitialSelection) {
2334
+ selectionStart = Math.max(selectionStart + makePrefix(0, style.unorderedList).length + newlinesToAppend.length, 0);
2335
+ selectionEnd = selectionStart;
2336
+ } else {
2337
+ if (undoResultOppositeList.processed) {
2338
+ selectionStart = Math.max(textarea.selectionStart + newlinesToAppend.length, 0);
2339
+ selectionEnd = textarea.selectionEnd + newlinesToAppend.length + totalPrefixLength - totalPrefixLengthOppositeList;
2340
+ } else {
2341
+ selectionStart = Math.max(textarea.selectionStart + newlinesToAppend.length, 0);
2342
+ selectionEnd = textarea.selectionEnd + newlinesToAppend.length + totalPrefixLength;
2343
+ }
2619
2344
  }
2620
-
2621
- .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview pre.code-block code {
2622
- background: transparent !important;
2623
- color: inherit !important;
2624
- padding: 0 !important;
2625
- font-family: ${fontFamily} !important;
2626
- font-size: 0.9em !important;
2627
- line-height: 1.4 !important;
2345
+ return { text, selectionStart, selectionEnd };
2346
+ }
2347
+ function applyListStyle(textarea, style) {
2348
+ const result = applyLineOperation(
2349
+ textarea,
2350
+ (ta) => listStyle(ta, style),
2351
+ {
2352
+ // Custom selection adjustment for lists
2353
+ adjustSelection: (isRemoving, selStart, selEnd, lineStart) => {
2354
+ const currentLine = textarea.value.slice(lineStart, textarea.selectionEnd);
2355
+ const orderedListRegex = /^\d+\.\s+/;
2356
+ const unorderedListRegex = /^- /;
2357
+ const hasOrderedList = orderedListRegex.test(currentLine);
2358
+ const hasUnorderedList = unorderedListRegex.test(currentLine);
2359
+ const isRemovingCurrent = style.orderedList && hasOrderedList || style.unorderedList && hasUnorderedList;
2360
+ if (selStart === selEnd) {
2361
+ if (isRemovingCurrent) {
2362
+ const prefixMatch = currentLine.match(style.orderedList ? orderedListRegex : unorderedListRegex);
2363
+ const prefixLength = prefixMatch ? prefixMatch[0].length : 0;
2364
+ return {
2365
+ start: Math.max(selStart - prefixLength, lineStart),
2366
+ end: Math.max(selStart - prefixLength, lineStart)
2367
+ };
2368
+ } else if (hasOrderedList || hasUnorderedList) {
2369
+ const oldPrefixMatch = currentLine.match(hasOrderedList ? orderedListRegex : unorderedListRegex);
2370
+ const oldPrefixLength = oldPrefixMatch ? oldPrefixMatch[0].length : 0;
2371
+ const newPrefixLength = style.unorderedList ? 2 : 3;
2372
+ const adjustment = newPrefixLength - oldPrefixLength;
2373
+ return {
2374
+ start: selStart + adjustment,
2375
+ end: selStart + adjustment
2376
+ };
2377
+ } else {
2378
+ const prefixLength = style.unorderedList ? 2 : 3;
2379
+ return {
2380
+ start: selStart + prefixLength,
2381
+ end: selStart + prefixLength
2382
+ };
2383
+ }
2384
+ } else {
2385
+ if (isRemovingCurrent) {
2386
+ const prefixMatch = currentLine.match(style.orderedList ? orderedListRegex : unorderedListRegex);
2387
+ const prefixLength = prefixMatch ? prefixMatch[0].length : 0;
2388
+ return {
2389
+ start: Math.max(selStart - prefixLength, lineStart),
2390
+ end: Math.max(selEnd - prefixLength, lineStart)
2391
+ };
2392
+ } else if (hasOrderedList || hasUnorderedList) {
2393
+ const oldPrefixMatch = currentLine.match(hasOrderedList ? orderedListRegex : unorderedListRegex);
2394
+ const oldPrefixLength = oldPrefixMatch ? oldPrefixMatch[0].length : 0;
2395
+ const newPrefixLength = style.unorderedList ? 2 : 3;
2396
+ const adjustment = newPrefixLength - oldPrefixLength;
2397
+ return {
2398
+ start: selStart + adjustment,
2399
+ end: selEnd + adjustment
2400
+ };
2401
+ } else {
2402
+ const prefixLength = style.unorderedList ? 2 : 3;
2403
+ return {
2404
+ start: selStart + prefixLength,
2405
+ end: selEnd + prefixLength
2406
+ };
2407
+ }
2408
+ }
2409
+ }
2410
+ }
2411
+ );
2412
+ insertText(textarea, result);
2413
+ }
2414
+ function getActiveFormats(textarea) {
2415
+ if (!textarea)
2416
+ return [];
2417
+ const formats = [];
2418
+ const { selectionStart, selectionEnd, value } = textarea;
2419
+ const lines = value.split("\n");
2420
+ let lineStart = 0;
2421
+ let currentLine = "";
2422
+ for (const line of lines) {
2423
+ if (selectionStart >= lineStart && selectionStart <= lineStart + line.length) {
2424
+ currentLine = line;
2425
+ break;
2426
+ }
2427
+ lineStart += line.length + 1;
2628
2428
  }
2629
-
2630
- /* Hide old code block lines and fences in preview mode */
2631
- .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview .code-block-line {
2632
- display: none !important;
2429
+ if (currentLine.startsWith("- ")) {
2430
+ if (currentLine.startsWith("- [ ] ") || currentLine.startsWith("- [x] ")) {
2431
+ formats.push("task-list");
2432
+ } else {
2433
+ formats.push("bullet-list");
2434
+ }
2633
2435
  }
2634
-
2635
- .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview .code-fence {
2636
- display: none !important;
2436
+ if (/^\d+\.\s/.test(currentLine)) {
2437
+ formats.push("numbered-list");
2637
2438
  }
2638
-
2639
- /* Blockquotes - enhanced styling in preview mode */
2640
- .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview .blockquote {
2641
- display: block !important;
2642
- border-left: 4px solid var(--blockquote, #ddd) !important;
2643
- padding-left: 1em !important;
2644
- margin: 1em 0 !important;
2645
- font-style: italic !important;
2439
+ if (currentLine.startsWith("> ")) {
2440
+ formats.push("quote");
2646
2441
  }
2647
-
2648
- /* Typography improvements in preview mode */
2649
- .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview {
2650
- font-family: Georgia, 'Times New Roman', serif !important;
2651
- font-size: 16px !important;
2652
- line-height: 1.8 !important;
2653
- color: var(--text, #333) !important; /* Consistent text color */
2442
+ if (currentLine.startsWith("# "))
2443
+ formats.push("header");
2444
+ if (currentLine.startsWith("## "))
2445
+ formats.push("header-2");
2446
+ if (currentLine.startsWith("### "))
2447
+ formats.push("header-3");
2448
+ const lookBehind = Math.max(0, selectionStart - 10);
2449
+ const lookAhead = Math.min(value.length, selectionEnd + 10);
2450
+ const surrounding = value.slice(lookBehind, lookAhead);
2451
+ if (surrounding.includes("**")) {
2452
+ const beforeCursor = value.slice(Math.max(0, selectionStart - 100), selectionStart);
2453
+ const afterCursor = value.slice(selectionEnd, Math.min(value.length, selectionEnd + 100));
2454
+ const lastOpenBold = beforeCursor.lastIndexOf("**");
2455
+ const nextCloseBold = afterCursor.indexOf("**");
2456
+ if (lastOpenBold !== -1 && nextCloseBold !== -1) {
2457
+ formats.push("bold");
2458
+ }
2654
2459
  }
2655
-
2656
- /* Inline code in preview mode - keep monospace */
2657
- .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview code {
2658
- font-family: ${fontFamily} !important;
2659
- font-size: 0.9em !important;
2660
- background: rgba(135, 131, 120, 0.15) !important;
2661
- padding: 0.2em 0.4em !important;
2662
- border-radius: 3px !important;
2460
+ if (surrounding.includes("_")) {
2461
+ const beforeCursor = value.slice(Math.max(0, selectionStart - 100), selectionStart);
2462
+ const afterCursor = value.slice(selectionEnd, Math.min(value.length, selectionEnd + 100));
2463
+ const lastOpenItalic = beforeCursor.lastIndexOf("_");
2464
+ const nextCloseItalic = afterCursor.indexOf("_");
2465
+ if (lastOpenItalic !== -1 && nextCloseItalic !== -1) {
2466
+ formats.push("italic");
2467
+ }
2663
2468
  }
2664
-
2665
- /* Strong and em elements in preview mode */
2666
- .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview strong {
2667
- font-weight: 700 !important;
2668
- color: inherit !important; /* Use parent text color */
2469
+ if (surrounding.includes("`")) {
2470
+ const beforeCursor = value.slice(Math.max(0, selectionStart - 100), selectionStart);
2471
+ const afterCursor = value.slice(selectionEnd, Math.min(value.length, selectionEnd + 100));
2472
+ if (beforeCursor.includes("`") && afterCursor.includes("`")) {
2473
+ formats.push("code");
2474
+ }
2669
2475
  }
2670
-
2671
- .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview em {
2672
- font-style: italic !important;
2673
- color: inherit !important; /* Use parent text color */
2476
+ if (surrounding.includes("[") && surrounding.includes("]")) {
2477
+ const beforeCursor = value.slice(Math.max(0, selectionStart - 100), selectionStart);
2478
+ const afterCursor = value.slice(selectionEnd, Math.min(value.length, selectionEnd + 100));
2479
+ const lastOpenBracket = beforeCursor.lastIndexOf("[");
2480
+ const nextCloseBracket = afterCursor.indexOf("]");
2481
+ if (lastOpenBracket !== -1 && nextCloseBracket !== -1) {
2482
+ const afterBracket = value.slice(selectionEnd + nextCloseBracket + 1, selectionEnd + nextCloseBracket + 10);
2483
+ if (afterBracket.startsWith("(")) {
2484
+ formats.push("link");
2485
+ }
2486
+ }
2674
2487
  }
2675
-
2676
- /* HR in preview mode */
2677
- .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview .hr-marker {
2678
- display: block !important;
2679
- border-top: 2px solid var(--hr, #ddd) !important;
2680
- text-indent: -9999px !important;
2681
- height: 2px !important;
2488
+ return formats;
2489
+ }
2490
+ function toggleBold(textarea) {
2491
+ if (!textarea || textarea.disabled || textarea.readOnly)
2492
+ return;
2493
+ debugLog("toggleBold", "Starting");
2494
+ debugSelection(textarea, "Before");
2495
+ const style = mergeWithDefaults(FORMATS.bold);
2496
+ const result = blockStyle(textarea, style);
2497
+ debugResult(result);
2498
+ insertText(textarea, result);
2499
+ debugSelection(textarea, "After");
2500
+ }
2501
+ function toggleItalic(textarea) {
2502
+ if (!textarea || textarea.disabled || textarea.readOnly)
2503
+ return;
2504
+ const style = mergeWithDefaults(FORMATS.italic);
2505
+ const result = blockStyle(textarea, style);
2506
+ insertText(textarea, result);
2507
+ }
2508
+ function toggleCode(textarea) {
2509
+ if (!textarea || textarea.disabled || textarea.readOnly)
2510
+ return;
2511
+ const style = mergeWithDefaults(FORMATS.code);
2512
+ const result = blockStyle(textarea, style);
2513
+ insertText(textarea, result);
2514
+ }
2515
+ function insertLink(textarea, options = {}) {
2516
+ if (!textarea || textarea.disabled || textarea.readOnly)
2517
+ return;
2518
+ const selectedText = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd);
2519
+ let style = mergeWithDefaults(FORMATS.link);
2520
+ const isURL = selectedText && selectedText.match(/^https?:\/\//);
2521
+ if (isURL && !options.url) {
2522
+ style.suffix = `](${selectedText})`;
2523
+ style.replaceNext = "";
2524
+ } else if (options.url) {
2525
+ style.suffix = `](${options.url})`;
2526
+ style.replaceNext = "";
2682
2527
  }
2683
-
2684
- /* Link Tooltip - Base styles (all browsers) */
2685
- .overtype-link-tooltip {
2686
- /* Visual styles that work for both positioning methods */
2687
- background: #333 !important;
2688
- color: white !important;
2689
- padding: 6px 10px !important;
2690
- border-radius: 16px !important;
2691
- font-size: 12px !important;
2692
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif !important;
2693
- display: none !important;
2694
- z-index: 10000 !important;
2695
- cursor: pointer !important;
2696
- box-shadow: 0 2px 8px rgba(0,0,0,0.3) !important;
2697
- max-width: 300px !important;
2698
- white-space: nowrap !important;
2699
- overflow: hidden !important;
2700
- text-overflow: ellipsis !important;
2701
-
2702
- /* Base positioning for Floating UI fallback */
2703
- position: absolute;
2528
+ if (options.text && !selectedText) {
2529
+ const pos = textarea.selectionStart;
2530
+ textarea.value = textarea.value.slice(0, pos) + options.text + textarea.value.slice(pos);
2531
+ textarea.selectionStart = pos;
2532
+ textarea.selectionEnd = pos + options.text.length;
2704
2533
  }
2705
-
2706
- .overtype-link-tooltip.visible {
2707
- display: flex !important;
2534
+ const result = blockStyle(textarea, style);
2535
+ insertText(textarea, result);
2536
+ }
2537
+ function toggleBulletList(textarea) {
2538
+ if (!textarea || textarea.disabled || textarea.readOnly)
2539
+ return;
2540
+ const style = mergeWithDefaults(FORMATS.bulletList);
2541
+ applyListStyle(textarea, style);
2542
+ }
2543
+ function toggleNumberedList(textarea) {
2544
+ if (!textarea || textarea.disabled || textarea.readOnly)
2545
+ return;
2546
+ const style = mergeWithDefaults(FORMATS.numberedList);
2547
+ applyListStyle(textarea, style);
2548
+ }
2549
+ function toggleQuote(textarea) {
2550
+ if (!textarea || textarea.disabled || textarea.readOnly)
2551
+ return;
2552
+ debugLog("toggleQuote", "Starting");
2553
+ debugSelection(textarea, "Initial");
2554
+ const style = mergeWithDefaults(FORMATS.quote);
2555
+ const result = applyLineOperation(
2556
+ textarea,
2557
+ (ta) => multilineStyle(ta, style),
2558
+ { prefix: style.prefix }
2559
+ );
2560
+ debugResult(result);
2561
+ insertText(textarea, result);
2562
+ debugSelection(textarea, "Final");
2563
+ }
2564
+ function toggleTaskList(textarea) {
2565
+ if (!textarea || textarea.disabled || textarea.readOnly)
2566
+ return;
2567
+ const style = mergeWithDefaults(FORMATS.taskList);
2568
+ const result = applyLineOperation(
2569
+ textarea,
2570
+ (ta) => multilineStyle(ta, style),
2571
+ { prefix: style.prefix }
2572
+ );
2573
+ insertText(textarea, result);
2574
+ }
2575
+ function insertHeader(textarea, level = 1, toggle = false) {
2576
+ if (!textarea || textarea.disabled || textarea.readOnly)
2577
+ return;
2578
+ if (level < 1 || level > 6)
2579
+ level = 1;
2580
+ debugLog("insertHeader", `============ START ============`);
2581
+ debugLog("insertHeader", `Level: ${level}, Toggle: ${toggle}`);
2582
+ debugLog("insertHeader", `Initial cursor: ${textarea.selectionStart}-${textarea.selectionEnd}`);
2583
+ const headerKey = `header${level === 1 ? "1" : level}`;
2584
+ const style = mergeWithDefaults(FORMATS[headerKey] || FORMATS.header1);
2585
+ debugLog("insertHeader", `Style prefix: "${style.prefix}"`);
2586
+ const value = textarea.value;
2587
+ const originalStart = textarea.selectionStart;
2588
+ const originalEnd = textarea.selectionEnd;
2589
+ let lineStart = originalStart;
2590
+ while (lineStart > 0 && value[lineStart - 1] !== "\n") {
2591
+ lineStart--;
2708
2592
  }
2709
-
2710
- /* CSS Anchor Positioning (modern browsers only) */
2711
- @supports (position-anchor: --x) and (position-area: center) {
2712
- .overtype-link-tooltip {
2713
- /* Only anchor positioning specific properties */
2714
- position-anchor: var(--target-anchor, --link-0);
2715
- position-area: block-end center;
2716
- margin-top: 8px !important;
2717
- position-try: most-width block-end inline-end, flip-inline, block-start center;
2718
- position-visibility: anchors-visible;
2719
- }
2593
+ let lineEnd = originalEnd;
2594
+ while (lineEnd < value.length && value[lineEnd] !== "\n") {
2595
+ lineEnd++;
2720
2596
  }
2721
-
2722
- ${mobileStyles}
2723
- `;
2597
+ const currentLineContent = value.slice(lineStart, lineEnd);
2598
+ debugLog("insertHeader", `Current line (before): "${currentLineContent}"`);
2599
+ const existingHeaderMatch = currentLineContent.match(/^(#{1,6})\s*/);
2600
+ const existingLevel = existingHeaderMatch ? existingHeaderMatch[1].length : 0;
2601
+ const existingPrefixLength = existingHeaderMatch ? existingHeaderMatch[0].length : 0;
2602
+ debugLog("insertHeader", `Existing header check:`);
2603
+ debugLog("insertHeader", ` - Match: ${existingHeaderMatch ? `"${existingHeaderMatch[0]}"` : "none"}`);
2604
+ debugLog("insertHeader", ` - Existing level: ${existingLevel}`);
2605
+ debugLog("insertHeader", ` - Existing prefix length: ${existingPrefixLength}`);
2606
+ debugLog("insertHeader", ` - Target level: ${level}`);
2607
+ const shouldToggleOff = toggle && existingLevel === level;
2608
+ debugLog("insertHeader", `Should toggle OFF: ${shouldToggleOff} (toggle=${toggle}, existingLevel=${existingLevel}, level=${level})`);
2609
+ const result = applyLineOperation(
2610
+ textarea,
2611
+ (ta) => {
2612
+ const currentLine = ta.value.slice(ta.selectionStart, ta.selectionEnd);
2613
+ debugLog("insertHeader", `Line in operation: "${currentLine}"`);
2614
+ const cleanedLine = currentLine.replace(/^#{1,6}\s*/, "");
2615
+ debugLog("insertHeader", `Cleaned line: "${cleanedLine}"`);
2616
+ let newLine;
2617
+ if (shouldToggleOff) {
2618
+ debugLog("insertHeader", "ACTION: Toggling OFF - removing header");
2619
+ newLine = cleanedLine;
2620
+ } else if (existingLevel > 0) {
2621
+ debugLog("insertHeader", `ACTION: Replacing H${existingLevel} with H${level}`);
2622
+ newLine = style.prefix + cleanedLine;
2623
+ } else {
2624
+ debugLog("insertHeader", "ACTION: Adding new header");
2625
+ newLine = style.prefix + cleanedLine;
2626
+ }
2627
+ debugLog("insertHeader", `New line: "${newLine}"`);
2628
+ return {
2629
+ text: newLine,
2630
+ selectionStart: ta.selectionStart,
2631
+ selectionEnd: ta.selectionEnd
2632
+ };
2633
+ },
2634
+ {
2635
+ prefix: style.prefix,
2636
+ // Custom selection adjustment for headers
2637
+ adjustSelection: (isRemoving, selStart, selEnd, lineStartPos) => {
2638
+ debugLog("insertHeader", `Adjusting selection:`);
2639
+ debugLog("insertHeader", ` - isRemoving param: ${isRemoving}`);
2640
+ debugLog("insertHeader", ` - shouldToggleOff: ${shouldToggleOff}`);
2641
+ debugLog("insertHeader", ` - selStart: ${selStart}, selEnd: ${selEnd}`);
2642
+ debugLog("insertHeader", ` - lineStartPos: ${lineStartPos}`);
2643
+ if (shouldToggleOff) {
2644
+ const adjustment = Math.max(selStart - existingPrefixLength, lineStartPos);
2645
+ debugLog("insertHeader", ` - Removing header, adjusting by -${existingPrefixLength}`);
2646
+ return {
2647
+ start: adjustment,
2648
+ end: selStart === selEnd ? adjustment : Math.max(selEnd - existingPrefixLength, lineStartPos)
2649
+ };
2650
+ } else if (existingPrefixLength > 0) {
2651
+ const prefixDiff = style.prefix.length - existingPrefixLength;
2652
+ debugLog("insertHeader", ` - Replacing header, adjusting by ${prefixDiff}`);
2653
+ return {
2654
+ start: selStart + prefixDiff,
2655
+ end: selEnd + prefixDiff
2656
+ };
2657
+ } else {
2658
+ debugLog("insertHeader", ` - Adding header, adjusting by +${style.prefix.length}`);
2659
+ return {
2660
+ start: selStart + style.prefix.length,
2661
+ end: selEnd + style.prefix.length
2662
+ };
2663
+ }
2664
+ }
2665
+ }
2666
+ );
2667
+ debugLog("insertHeader", `Final result: text="${result.text}", cursor=${result.selectionStart}-${result.selectionEnd}`);
2668
+ debugLog("insertHeader", `============ END ============`);
2669
+ insertText(textarea, result);
2670
+ }
2671
+ function toggleH1(textarea) {
2672
+ insertHeader(textarea, 1, true);
2673
+ }
2674
+ function toggleH2(textarea) {
2675
+ insertHeader(textarea, 2, true);
2676
+ }
2677
+ function toggleH3(textarea) {
2678
+ insertHeader(textarea, 3, true);
2679
+ }
2680
+ function getActiveFormats2(textarea) {
2681
+ return getActiveFormats(textarea);
2724
2682
  }
2725
2683
 
2726
2684
  // src/toolbar.js
@@ -2780,55 +2738,43 @@ ${blockSuffix}` : suffix;
2780
2738
  });
2781
2739
  return button;
2782
2740
  }
2783
- button._clickHandler = async (e) => {
2741
+ button._clickHandler = (e) => {
2784
2742
  e.preventDefault();
2785
- this.editor.textarea.focus();
2786
- try {
2787
- if (buttonConfig.action) {
2788
- await buttonConfig.action({
2789
- editor: this.editor,
2790
- getValue: () => this.editor.getValue(),
2791
- setValue: (value) => this.editor.setValue(value),
2792
- event: e
2793
- });
2794
- }
2795
- } catch (error) {
2796
- console.error(`Button "${buttonConfig.name}" error:`, error);
2797
- this.editor.wrapper.dispatchEvent(new CustomEvent("button-error", {
2798
- detail: { buttonName: buttonConfig.name, error }
2799
- }));
2800
- button.classList.add("button-error");
2801
- button.style.animation = "buttonError 0.3s";
2802
- setTimeout(() => {
2803
- button.classList.remove("button-error");
2804
- button.style.animation = "";
2805
- }, 300);
2806
- }
2743
+ const actionId = buttonConfig.actionId || buttonConfig.name;
2744
+ this.editor.performAction(actionId, e);
2807
2745
  };
2808
2746
  button.addEventListener("click", button._clickHandler);
2809
2747
  return button;
2810
2748
  }
2811
2749
  /**
2812
- * Handle button action programmatically (used by keyboard shortcuts)
2813
- * @param {Object} buttonConfig - Button configuration object with action function
2750
+ * Handle button action programmatically
2751
+ * Accepts either an actionId string or a buttonConfig object (backwards compatible)
2752
+ * @param {string|Object} actionIdOrConfig - Action identifier string or button config object
2753
+ * @returns {Promise<boolean>} Whether the action was executed
2814
2754
  */
2815
- async handleAction(buttonConfig) {
2816
- this.editor.textarea.focus();
2817
- try {
2818
- if (buttonConfig.action) {
2819
- await buttonConfig.action({
2755
+ async handleAction(actionIdOrConfig) {
2756
+ if (actionIdOrConfig && typeof actionIdOrConfig === "object" && typeof actionIdOrConfig.action === "function") {
2757
+ this.editor.textarea.focus();
2758
+ try {
2759
+ await actionIdOrConfig.action({
2820
2760
  editor: this.editor,
2821
2761
  getValue: () => this.editor.getValue(),
2822
2762
  setValue: (value) => this.editor.setValue(value),
2823
2763
  event: null
2824
2764
  });
2765
+ return true;
2766
+ } catch (error) {
2767
+ console.error(`Action "${actionIdOrConfig.name}" error:`, error);
2768
+ this.editor.wrapper.dispatchEvent(new CustomEvent("button-error", {
2769
+ detail: { buttonName: actionIdOrConfig.name, error }
2770
+ }));
2771
+ return false;
2825
2772
  }
2826
- } catch (error) {
2827
- console.error(`Action "${buttonConfig.name}" error:`, error);
2828
- this.editor.wrapper.dispatchEvent(new CustomEvent("button-error", {
2829
- detail: { buttonName: buttonConfig.name, error }
2830
- }));
2831
2773
  }
2774
+ if (typeof actionIdOrConfig === "string") {
2775
+ return this.editor.performAction(actionIdOrConfig, null);
2776
+ }
2777
+ return false;
2832
2778
  }
2833
2779
  /**
2834
2780
  * Sanitize SVG to prevent XSS
@@ -2998,6 +2944,7 @@ ${blockSuffix}` : suffix;
2998
2944
  this.visibilityChangeHandler = null;
2999
2945
  this.useFloatingUI = false;
3000
2946
  this.floatingUI = null;
2947
+ this.isTooltipHovered = false;
3001
2948
  this.init();
3002
2949
  }
3003
2950
  async init() {
@@ -3031,14 +2978,25 @@ ${blockSuffix}` : suffix;
3031
2978
  this.hide();
3032
2979
  }
3033
2980
  });
3034
- this.editor.textarea.addEventListener("blur", () => this.hide());
2981
+ this.editor.textarea.addEventListener("blur", () => {
2982
+ if (!this.isTooltipHovered) {
2983
+ this.hide();
2984
+ }
2985
+ });
3035
2986
  this.visibilityChangeHandler = () => {
3036
2987
  if (document.hidden) {
3037
2988
  this.hide();
3038
2989
  }
3039
2990
  };
3040
2991
  document.addEventListener("visibilitychange", this.visibilityChangeHandler);
3041
- this.tooltip.addEventListener("mouseenter", () => this.cancelHide());
2992
+ this.tooltip.addEventListener("mouseenter", () => {
2993
+ this.isTooltipHovered = true;
2994
+ this.cancelHide();
2995
+ });
2996
+ this.tooltip.addEventListener("mouseleave", () => {
2997
+ this.isTooltipHovered = false;
2998
+ this.scheduleHide();
2999
+ });
3042
3000
  }
3043
3001
  createTooltip() {
3044
3002
  this.tooltip = document.createElement("div");
@@ -3148,6 +3106,7 @@ ${blockSuffix}` : suffix;
3148
3106
  hide() {
3149
3107
  this.tooltip.classList.remove("visible");
3150
3108
  this.currentLink = null;
3109
+ this.isTooltipHovered = false;
3151
3110
  }
3152
3111
  scheduleHide() {
3153
3112
  this.cancelHide();
@@ -3172,6 +3131,7 @@ ${blockSuffix}` : suffix;
3172
3131
  this.currentLink = null;
3173
3132
  this.floatingUI = null;
3174
3133
  this.useFloatingUI = false;
3134
+ this.isTooltipHovered = false;
3175
3135
  }
3176
3136
  };
3177
3137
 
@@ -3242,27 +3202,30 @@ ${blockSuffix}` : suffix;
3242
3202
  var toolbarButtons = {
3243
3203
  bold: {
3244
3204
  name: "bold",
3205
+ actionId: "toggleBold",
3245
3206
  icon: boldIcon,
3246
3207
  title: "Bold (Ctrl+B)",
3247
- action: ({ editor, event }) => {
3208
+ action: ({ editor }) => {
3248
3209
  toggleBold(editor.textarea);
3249
3210
  editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3250
3211
  }
3251
3212
  },
3252
3213
  italic: {
3253
3214
  name: "italic",
3215
+ actionId: "toggleItalic",
3254
3216
  icon: italicIcon,
3255
3217
  title: "Italic (Ctrl+I)",
3256
- action: ({ editor, event }) => {
3218
+ action: ({ editor }) => {
3257
3219
  toggleItalic(editor.textarea);
3258
3220
  editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3259
3221
  }
3260
3222
  },
3261
3223
  code: {
3262
3224
  name: "code",
3225
+ actionId: "toggleCode",
3263
3226
  icon: codeIcon,
3264
3227
  title: "Inline Code",
3265
- action: ({ editor, event }) => {
3228
+ action: ({ editor }) => {
3266
3229
  toggleCode(editor.textarea);
3267
3230
  editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3268
3231
  }
@@ -3273,63 +3236,70 @@ ${blockSuffix}` : suffix;
3273
3236
  },
3274
3237
  link: {
3275
3238
  name: "link",
3239
+ actionId: "insertLink",
3276
3240
  icon: linkIcon,
3277
3241
  title: "Insert Link",
3278
- action: ({ editor, event }) => {
3242
+ action: ({ editor }) => {
3279
3243
  insertLink(editor.textarea);
3280
3244
  editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3281
3245
  }
3282
3246
  },
3283
3247
  h1: {
3284
3248
  name: "h1",
3249
+ actionId: "toggleH1",
3285
3250
  icon: h1Icon,
3286
3251
  title: "Heading 1",
3287
- action: ({ editor, event }) => {
3252
+ action: ({ editor }) => {
3288
3253
  toggleH1(editor.textarea);
3289
3254
  editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3290
3255
  }
3291
3256
  },
3292
3257
  h2: {
3293
3258
  name: "h2",
3259
+ actionId: "toggleH2",
3294
3260
  icon: h2Icon,
3295
3261
  title: "Heading 2",
3296
- action: ({ editor, event }) => {
3262
+ action: ({ editor }) => {
3297
3263
  toggleH2(editor.textarea);
3298
3264
  editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3299
3265
  }
3300
3266
  },
3301
3267
  h3: {
3302
3268
  name: "h3",
3269
+ actionId: "toggleH3",
3303
3270
  icon: h3Icon,
3304
3271
  title: "Heading 3",
3305
- action: ({ editor, event }) => {
3272
+ action: ({ editor }) => {
3306
3273
  toggleH3(editor.textarea);
3307
3274
  editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3308
3275
  }
3309
3276
  },
3310
3277
  bulletList: {
3311
3278
  name: "bulletList",
3279
+ actionId: "toggleBulletList",
3312
3280
  icon: bulletListIcon,
3313
3281
  title: "Bullet List",
3314
- action: ({ editor, event }) => {
3282
+ action: ({ editor }) => {
3315
3283
  toggleBulletList(editor.textarea);
3316
3284
  editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3317
3285
  }
3318
3286
  },
3319
3287
  orderedList: {
3320
3288
  name: "orderedList",
3289
+ actionId: "toggleNumberedList",
3321
3290
  icon: orderedListIcon,
3322
3291
  title: "Numbered List",
3323
- action: ({ editor, event }) => {
3292
+ action: ({ editor }) => {
3324
3293
  toggleNumberedList(editor.textarea);
3325
3294
  editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3326
3295
  }
3327
3296
  },
3328
3297
  taskList: {
3329
3298
  name: "taskList",
3299
+ actionId: "toggleTaskList",
3330
3300
  icon: taskListIcon,
3331
3301
  title: "Task List",
3332
- action: ({ editor, event }) => {
3302
+ action: ({ editor }) => {
3333
3303
  if (toggleTaskList) {
3334
3304
  toggleTaskList(editor.textarea);
3335
3305
  editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
@@ -3338,9 +3308,10 @@ ${blockSuffix}` : suffix;
3338
3308
  },
3339
3309
  quote: {
3340
3310
  name: "quote",
3311
+ actionId: "toggleQuote",
3341
3312
  icon: quoteIcon,
3342
3313
  title: "Quote",
3343
- action: ({ editor, event }) => {
3314
+ action: ({ editor }) => {
3344
3315
  toggleQuote(editor.textarea);
3345
3316
  editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3346
3317
  }
@@ -3374,6 +3345,45 @@ ${blockSuffix}` : suffix;
3374
3345
  ];
3375
3346
 
3376
3347
  // src/overtype.js
3348
+ function buildActionsMap(buttons) {
3349
+ const map = {};
3350
+ (buttons || []).forEach((btn) => {
3351
+ if (!btn || btn.name === "separator")
3352
+ return;
3353
+ const id = btn.actionId || btn.name;
3354
+ if (btn.action) {
3355
+ map[id] = btn.action;
3356
+ }
3357
+ });
3358
+ return map;
3359
+ }
3360
+ function normalizeButtons(buttons) {
3361
+ const list = buttons || defaultToolbarButtons;
3362
+ if (!Array.isArray(list))
3363
+ return null;
3364
+ return list.map((btn) => ({
3365
+ name: (btn == null ? void 0 : btn.name) || null,
3366
+ actionId: (btn == null ? void 0 : btn.actionId) || (btn == null ? void 0 : btn.name) || null,
3367
+ icon: (btn == null ? void 0 : btn.icon) || null,
3368
+ title: (btn == null ? void 0 : btn.title) || null
3369
+ }));
3370
+ }
3371
+ function toolbarButtonsChanged(prevButtons, nextButtons) {
3372
+ const prev = normalizeButtons(prevButtons);
3373
+ const next = normalizeButtons(nextButtons);
3374
+ if (prev === null || next === null)
3375
+ return prev !== next;
3376
+ if (prev.length !== next.length)
3377
+ return true;
3378
+ for (let i = 0; i < prev.length; i++) {
3379
+ const a = prev[i];
3380
+ const b = next[i];
3381
+ if (a.name !== b.name || a.actionId !== b.actionId || a.icon !== b.icon || a.title !== b.title) {
3382
+ return true;
3383
+ }
3384
+ }
3385
+ return false;
3386
+ }
3377
3387
  var _OverType = class _OverType {
3378
3388
  /**
3379
3389
  * Constructor - Always returns an array of instances
@@ -3431,6 +3441,7 @@ ${blockSuffix}` : suffix;
3431
3441
  this._buildFromScratch();
3432
3442
  }
3433
3443
  this.shortcuts = new ShortcutsManager(this);
3444
+ this._rebuildActionsMap();
3434
3445
  this.linkTooltip = new LinkTooltip(this);
3435
3446
  requestAnimationFrame(() => {
3436
3447
  requestAnimationFrame(() => {
@@ -3686,6 +3697,17 @@ ${blockSuffix}` : suffix;
3686
3697
  this._toolbarInputListener = null;
3687
3698
  }
3688
3699
  }
3700
+ /**
3701
+ * Rebuild the action map from current toolbar button configuration
3702
+ * Called during init and reinit to keep shortcuts in sync with toolbar buttons
3703
+ * @private
3704
+ */
3705
+ _rebuildActionsMap() {
3706
+ this.actionsById = buildActionsMap(defaultToolbarButtons);
3707
+ if (this.options.toolbarButtons) {
3708
+ Object.assign(this.actionsById, buildActionsMap(this.options.toolbarButtons));
3709
+ }
3710
+ }
3689
3711
  /**
3690
3712
  * Apply options to the editor
3691
3713
  * @private
@@ -3947,6 +3969,40 @@ ${blockSuffix}` : suffix;
3947
3969
  this._updateAutoHeight();
3948
3970
  }
3949
3971
  }
3972
+ /**
3973
+ * Execute an action by ID
3974
+ * Central dispatcher used by toolbar clicks, keyboard shortcuts, and programmatic calls
3975
+ * @param {string} actionId - The action identifier (e.g., 'toggleBold', 'insertLink')
3976
+ * @param {Event|null} event - Optional event that triggered the action
3977
+ * @returns {Promise<boolean>} Whether the action was executed successfully
3978
+ */
3979
+ async performAction(actionId, event = null) {
3980
+ var _a;
3981
+ const textarea = this.textarea;
3982
+ if (!textarea)
3983
+ return false;
3984
+ const action = (_a = this.actionsById) == null ? void 0 : _a[actionId];
3985
+ if (!action) {
3986
+ console.warn(`OverType: Unknown action "${actionId}"`);
3987
+ return false;
3988
+ }
3989
+ textarea.focus();
3990
+ try {
3991
+ await action({
3992
+ editor: this,
3993
+ getValue: () => this.getValue(),
3994
+ setValue: (value) => this.setValue(value),
3995
+ event
3996
+ });
3997
+ return true;
3998
+ } catch (error) {
3999
+ console.error(`OverType: Action "${actionId}" error:`, error);
4000
+ this.wrapper.dispatchEvent(new CustomEvent("button-error", {
4001
+ detail: { actionId, error }
4002
+ }));
4003
+ return false;
4004
+ }
4005
+ }
3950
4006
  /**
3951
4007
  * Get the rendered HTML of the current content
3952
4008
  * @param {Object} options - Rendering options
@@ -4003,7 +4059,17 @@ ${blockSuffix}` : suffix;
4003
4059
  * @param {Object} options - New options to apply
4004
4060
  */
4005
4061
  reinit(options = {}) {
4062
+ var _a;
4063
+ const prevToolbarButtons = (_a = this.options) == null ? void 0 : _a.toolbarButtons;
4006
4064
  this.options = this._mergeOptions({ ...this.options, ...options });
4065
+ const toolbarNeedsRebuild = this.toolbar && this.options.toolbar && toolbarButtonsChanged(prevToolbarButtons, this.options.toolbarButtons);
4066
+ this._rebuildActionsMap();
4067
+ if (toolbarNeedsRebuild) {
4068
+ this._cleanupToolbarListeners();
4069
+ this.toolbar.destroy();
4070
+ this.toolbar = null;
4071
+ this._createToolbar();
4072
+ }
4007
4073
  this._applyOptions();
4008
4074
  this.updatePreview();
4009
4075
  }