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