nuvox 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -21,18 +21,22 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var src_exports = {};
22
22
  __export(src_exports, {
23
23
  DEFAULT_COLOR_PRESETS: () => DEFAULT_PRESETS,
24
+ LANGUAGE_LABELS: () => LANGUAGE_LABELS,
24
25
  REQUIRED_TOKEN_PATHS: () => REQUIRED_TOKEN_PATHS,
25
26
  TOKEN_CATEGORIES: () => TOKEN_CATEGORIES,
26
27
  amber: () => amber,
27
28
  brutalism: () => brutalism,
28
29
  copper: () => copper,
30
+ createHistory: () => createHistory,
29
31
  cyberpunk: () => cyberpunk,
30
32
  dark: () => dark,
31
33
  darkGlass: () => darkGlass,
32
34
  defineTheme: () => defineTheme,
33
35
  emerald: () => emerald,
36
+ execRichTextCommand: () => execRichTextCommand,
34
37
  findMissingTokenPaths: () => findMissingTokenPaths,
35
38
  getAccent: () => getAccent,
39
+ getActiveCommands: () => getActiveCommands,
36
40
  getNextSortDirection: () => getNextSortDirection,
37
41
  getSelectAllState: () => getSelectAllState,
38
42
  getTheme: () => getTheme,
@@ -47,11 +51,16 @@ __export(src_exports, {
47
51
  material: () => material,
48
52
  minimalism: () => minimalism,
49
53
  neobrutalism: () => neobrutalism,
54
+ nuvoxHighlight: () => nuvoxHighlight,
55
+ pushHistory: () => pushHistory,
56
+ redoHistory: () => redoHistory,
50
57
  registerAccent: () => registerAccent,
51
58
  registerTheme: () => registerTheme,
52
59
  rose: () => rose,
60
+ sanitizeHtml: () => sanitizeHtml,
53
61
  skeuomorphism: () => skeuomorphism,
54
62
  teal: () => teal,
63
+ undoHistory: () => undoHistory,
55
64
  violet: () => violet
56
65
  });
57
66
  module.exports = __toCommonJS(src_exports);
@@ -890,21 +899,727 @@ function getSelectAllState(totalCount, selectedCount) {
890
899
  if (selectedCount >= totalCount) return "all";
891
900
  return "some";
892
901
  }
902
+
903
+ // src/core/code-highlight.ts
904
+ function tokenizeFlat(source, grammar) {
905
+ const tokens = [];
906
+ const n = source.length;
907
+ let i = 0;
908
+ outer: while (i < n) {
909
+ for (const rule of grammar.rules) {
910
+ if (rule.pattern) {
911
+ rule.pattern.lastIndex = i;
912
+ const m = rule.pattern.exec(source);
913
+ if (m && m.index === i && m[0].length > 0) {
914
+ tokens.push({ type: rule.type, content: m[0] });
915
+ i += m[0].length;
916
+ continue outer;
917
+ }
918
+ } else if (rule.match) {
919
+ const result = rule.match(source, i, tokens);
920
+ if (result && result.length > 0) {
921
+ tokens.push(...result.tokens);
922
+ i += result.length;
923
+ continue outer;
924
+ }
925
+ }
926
+ }
927
+ const ch = source[i];
928
+ const last = tokens[tokens.length - 1];
929
+ if (last && last.type === "plain") last.content += ch;
930
+ else tokens.push({ type: "plain", content: ch });
931
+ i++;
932
+ }
933
+ return tokens;
934
+ }
935
+ function splitIntoLines(tokens) {
936
+ const lines = [[]];
937
+ for (const tok of tokens) {
938
+ const parts = tok.content.split("\n");
939
+ parts.forEach((part, idx) => {
940
+ if (part.length > 0) lines[lines.length - 1].push({ type: tok.type, content: part });
941
+ if (idx < parts.length - 1) lines.push([]);
942
+ });
943
+ }
944
+ return lines;
945
+ }
946
+ var KEYWORD = (words) => new RegExp(`\\b(?:${words})\\b`, "y");
947
+ function matchTemplateLiteral(source, start, jsGrammar2) {
948
+ if (source[start] !== "`") return null;
949
+ const tokens = [{ type: "string", content: "`" }];
950
+ let i = start + 1;
951
+ let buf = "";
952
+ const flush = () => {
953
+ if (buf) {
954
+ tokens.push({ type: "string", content: buf });
955
+ buf = "";
956
+ }
957
+ };
958
+ while (i < source.length) {
959
+ const ch = source[i];
960
+ if (ch === "\\" && i + 1 < source.length) {
961
+ buf += ch + source[i + 1];
962
+ i += 2;
963
+ continue;
964
+ }
965
+ if (ch === "`") {
966
+ flush();
967
+ tokens.push({ type: "string", content: "`" });
968
+ i++;
969
+ return { length: i - start, tokens };
970
+ }
971
+ if (ch === "$" && source[i + 1] === "{") {
972
+ flush();
973
+ tokens.push({ type: "punctuation", content: "${" });
974
+ i += 2;
975
+ const exprStart = i;
976
+ let depth = 1;
977
+ while (i < source.length && depth > 0) {
978
+ if (source[i] === "{") depth++;
979
+ else if (source[i] === "}") depth--;
980
+ if (depth > 0) i++;
981
+ }
982
+ const exprSource = source.slice(exprStart, i);
983
+ tokens.push(...tokenizeFlat(exprSource, jsGrammar2));
984
+ if (i < source.length) {
985
+ tokens.push({ type: "punctuation", content: "}" });
986
+ i++;
987
+ }
988
+ continue;
989
+ }
990
+ buf += ch;
991
+ i++;
992
+ }
993
+ flush();
994
+ return { length: i - start, tokens };
995
+ }
996
+ var REGEX_EXPRESSION_KEYWORDS = /* @__PURE__ */ new Set([
997
+ "return",
998
+ "typeof",
999
+ "instanceof",
1000
+ "in",
1001
+ "of",
1002
+ "new",
1003
+ "delete",
1004
+ "void",
1005
+ "throw",
1006
+ "case",
1007
+ "do",
1008
+ "else",
1009
+ "yield",
1010
+ "await"
1011
+ ]);
1012
+ function matchRegexLiteral(source, start) {
1013
+ if (source[start] !== "/" || source[start + 1] === "/" || source[start + 1] === "*") return null;
1014
+ let j = start - 1;
1015
+ while (j >= 0 && /\s/.test(source[j])) j--;
1016
+ if (j >= 0) {
1017
+ const prevChar = source[j];
1018
+ const looksLikeValueEnd = /[)\]}\w$]/.test(prevChar) || prevChar === '"' || prevChar === "'" || prevChar === "`";
1019
+ if (looksLikeValueEnd) {
1020
+ const wordBefore = source.slice(0, j + 1).match(/([A-Za-z_$][\w$]*)$/)?.[1];
1021
+ if (!(wordBefore && REGEX_EXPRESSION_KEYWORDS.has(wordBefore))) return null;
1022
+ }
1023
+ }
1024
+ let i = start + 1;
1025
+ let inClass = false;
1026
+ while (i < source.length) {
1027
+ const ch = source[i];
1028
+ if (ch === "\\") {
1029
+ i += 2;
1030
+ continue;
1031
+ }
1032
+ if (ch === "\n") return null;
1033
+ if (ch === "[") inClass = true;
1034
+ else if (ch === "]") inClass = false;
1035
+ else if (ch === "/" && !inClass) {
1036
+ i++;
1037
+ break;
1038
+ }
1039
+ i++;
1040
+ }
1041
+ if (source[i - 1] !== "/") return null;
1042
+ while (i < source.length && /[a-z]/i.test(source[i])) i++;
1043
+ return { length: i - start, tokens: [{ type: "regex", content: source.slice(start, i) }] };
1044
+ }
1045
+ function buildJsGrammar(withTags) {
1046
+ const grammar = { rules: [] };
1047
+ const rules = grammar.rules;
1048
+ if (withTags) {
1049
+ rules.push({ type: "tag", pattern: /<\/?[A-Za-z][\w.-]*/y });
1050
+ }
1051
+ rules.push(
1052
+ { type: "comment", pattern: /\/\/[^\n]*|\/\*[\s\S]*?\*\//y },
1053
+ { type: "string", pattern: /"(?:[^"\\\n]|\\.)*"|'(?:[^'\\\n]|\\.)*'/y },
1054
+ { type: "custom-template", match: (src, idx) => matchTemplateLiteral(src, idx, grammar) },
1055
+ { type: "custom-regex", match: (src, idx) => matchRegexLiteral(src, idx) },
1056
+ { type: "decorator", pattern: /@[A-Za-z_$][\w$]*/y },
1057
+ { type: "boolean", pattern: KEYWORD("true|false|null|undefined") },
1058
+ {
1059
+ type: "keyword",
1060
+ pattern: KEYWORD(
1061
+ "const|let|var|function|return|if|else|for|while|do|switch|case|default|break|continue|class|extends|super|new|this|typeof|instanceof|in|of|try|catch|finally|throw|async|await|yield|import|export|from|as|void|delete|static|get|set|interface|type|enum|implements|public|private|protected|readonly|namespace|declare|abstract|is|keyof|infer|satisfies"
1062
+ )
1063
+ },
1064
+ {
1065
+ type: "builtin",
1066
+ pattern: KEYWORD(
1067
+ "console|Math|JSON|Promise|Array|Object|Map|Set|WeakMap|WeakSet|String|Number|Boolean|Symbol|Error|Date|RegExp|Reflect|Proxy|globalThis|Infinity|NaN|BigInt|fetch|localStorage|sessionStorage"
1068
+ )
1069
+ },
1070
+ { type: "number", pattern: /\b0[xX][0-9a-fA-F]+\b|\b\d+\.?\d*(?:[eE][+-]?\d+)?n?\b/y },
1071
+ { type: "function", pattern: /[A-Za-z_$][\w$]*(?=\s*\()/y },
1072
+ { type: "punctuation", pattern: /[{}()[\];,.:?]/y }
1073
+ );
1074
+ return grammar;
1075
+ }
1076
+ var jsGrammar = buildJsGrammar(false);
1077
+ var jsxGrammar = buildJsGrammar(true);
1078
+ var jsonGrammar = {
1079
+ rules: [
1080
+ { type: "string", pattern: /"(?:[^"\\\n]|\\.)*"/y },
1081
+ { type: "boolean", pattern: KEYWORD("true|false|null") },
1082
+ { type: "number", pattern: /-?\b\d+\.?\d*(?:[eE][+-]?\d+)?\b/y },
1083
+ { type: "punctuation", pattern: /[{}[\]:,]/y }
1084
+ ]
1085
+ };
1086
+ var cssGrammar = {
1087
+ rules: [
1088
+ { type: "comment", pattern: /\/\*[\s\S]*?\*\//y },
1089
+ { type: "string", pattern: /"(?:[^"\\\n]|\\.)*"|'(?:[^'\\\n]|\\.)*'/y },
1090
+ { type: "keyword", pattern: /!important\b/y },
1091
+ { type: "atrule", pattern: /@[a-zA-Z-]+/y },
1092
+ // Custom-property declarations/usage (--brand-color, var(--x)) —
1093
+ // checked before the generic hex-color/selector rules below.
1094
+ { type: "variable", pattern: /--[a-zA-Z_][\w-]*/y },
1095
+ { type: "function", pattern: /\b(?:var|calc|rgba?|hsla?|clamp|minmax|repeat|url|translate[XYZ]?|rotate[XYZ]?|scale[XYZ]?|linear-gradient|radial-gradient)(?=\()/y },
1096
+ // Pseudo-classes/elements (:hover, ::before) before the plain ":" punctuation.
1097
+ { type: "pseudo", pattern: /::?[a-zA-Z-]+/y },
1098
+ // Hex colors before the generic "#name" id-selector pattern below.
1099
+ { type: "builtin", pattern: /#[0-9a-fA-F]{3,8}\b/y },
1100
+ { type: "selector", pattern: /[.#][a-zA-Z_-][\w-]*/y },
1101
+ { type: "property", pattern: /[a-zA-Z-]+(?=\s*:)/y },
1102
+ { type: "number", pattern: /-?\d+\.?\d*(?:px|em|rem|%|vh|vw|s|ms|deg|fr)?\b/y },
1103
+ { type: "punctuation", pattern: /[{}:;,()]/y }
1104
+ ]
1105
+ };
1106
+ var htmlGrammar = {
1107
+ rules: [
1108
+ { type: "comment", pattern: /<!--[\s\S]*?-->/y },
1109
+ { type: "keyword", pattern: /<!DOCTYPE[^>]*>/iy },
1110
+ { type: "tag", pattern: /<\/?[a-zA-Z][\w:-]*/y },
1111
+ { type: "attrName", pattern: /[a-zA-Z-]+(?=\s*=)/y },
1112
+ { type: "string", pattern: /"(?:[^"\\\n]|\\.)*"|'(?:[^'\\\n]|\\.)*'/y },
1113
+ { type: "builtin", pattern: /&[a-zA-Z#0-9]+;/y },
1114
+ { type: "punctuation", pattern: /[<>=/]/y }
1115
+ ]
1116
+ };
1117
+ var bashGrammar = {
1118
+ rules: [
1119
+ { type: "comment", pattern: /#[^\n]*/y },
1120
+ { type: "string", pattern: /"(?:[^"\\\n]|\\.)*"|'[^'\n]*'/y },
1121
+ { type: "variable", pattern: /\$\{?\w+\}?/y },
1122
+ {
1123
+ type: "keyword",
1124
+ pattern: KEYWORD(
1125
+ "if|then|else|elif|fi|for|do|done|while|case|esac|function|return|exit|export|local|echo|cd|sudo|npm|yarn|pnpm|git|mkdir|rm|cp|mv|cat|grep|sed|awk"
1126
+ )
1127
+ },
1128
+ { type: "number", pattern: /\b\d+\b/y },
1129
+ { type: "function", pattern: /(?<=^|\s)--?[a-zA-Z][\w-]*/y },
1130
+ { type: "punctuation", pattern: /[|&;()]/y }
1131
+ ]
1132
+ };
1133
+ var pythonGrammar = {
1134
+ rules: [
1135
+ { type: "comment", pattern: /#[^\n]*/y },
1136
+ { type: "decorator", pattern: /@[A-Za-z_][\w.]*/y },
1137
+ {
1138
+ // f/r/b/u string prefixes (f"...", rb'...', etc.) before plain strings.
1139
+ type: "string",
1140
+ pattern: /[rRbBfFuU]{0,2}(?:"""[\s\S]*?"""|'''[\s\S]*?'''|"(?:[^"\\\n]|\\.)*"|'(?:[^'\\\n]|\\.)*')/y
1141
+ },
1142
+ { type: "boolean", pattern: KEYWORD("True|False|None") },
1143
+ {
1144
+ type: "keyword",
1145
+ pattern: KEYWORD(
1146
+ "def|return|if|elif|else|for|while|in|not|and|or|is|import|from|as|class|try|except|finally|raise|with|lambda|yield|pass|break|continue|global|nonlocal|assert|del|async|await|self"
1147
+ )
1148
+ },
1149
+ { type: "builtin", pattern: KEYWORD("print|len|range|str|int|float|list|dict|set|tuple|type|isinstance|super|open|input|enumerate|zip|map|filter|sorted|sum|min|max|any|all") },
1150
+ { type: "number", pattern: /\b\d+\.?\d*\b/y },
1151
+ { type: "function", pattern: /[A-Za-z_]\w*(?=\s*\()/y },
1152
+ { type: "punctuation", pattern: /[:,.()[\]{}]/y }
1153
+ ]
1154
+ };
1155
+ var markdownGrammar = {
1156
+ rules: [
1157
+ { type: "comment", pattern: /```[\s\S]*?```/y },
1158
+ { type: "keyword", pattern: /^#{1,6}[^\n]*/my },
1159
+ { type: "punctuation", pattern: /^(?:-{3,}|\*{3,}|_{3,})$/my },
1160
+ { type: "function", pattern: /`[^`\n]+`/y },
1161
+ { type: "builtin", pattern: /\*\*[^*\n]+\*\*/y },
1162
+ { type: "variable", pattern: /~~[^~\n]+~~/y },
1163
+ { type: "string", pattern: /\*[^*\n]+\*/y },
1164
+ { type: "tag", pattern: /!?\[[^\]\n]*\]\([^)\n]*\)/y },
1165
+ { type: "punctuation", pattern: /^[-*+]\s|^\d+\.\s/my }
1166
+ ]
1167
+ };
1168
+ var yamlGrammar = {
1169
+ rules: [
1170
+ { type: "comment", pattern: /#[^\n]*/y },
1171
+ { type: "property", pattern: /^[ \t]*[\w.-]+(?=\s*:)/my },
1172
+ { type: "string", pattern: /"(?:[^"\\\n]|\\.)*"|'[^'\n]*'/y },
1173
+ { type: "boolean", pattern: KEYWORD("true|false|null|yes|no|on|off|True|False|Null") },
1174
+ { type: "number", pattern: /-?\b\d+\.?\d*\b/y },
1175
+ { type: "variable", pattern: /[&*][\w-]+/y },
1176
+ { type: "punctuation", pattern: /[:\-[\]{},]/y }
1177
+ ]
1178
+ };
1179
+ var sqlGrammar = {
1180
+ rules: [
1181
+ { type: "comment", pattern: /--[^\n]*|\/\*[\s\S]*?\*\//y },
1182
+ { type: "string", pattern: /'(?:[^'\\\n]|\\.)*'/y },
1183
+ {
1184
+ type: "keyword",
1185
+ pattern: new RegExp(
1186
+ "\\b(?:SELECT|FROM|WHERE|INSERT|INTO|VALUES|UPDATE|SET|DELETE|CREATE|TABLE|ALTER|DROP|JOIN|INNER|LEFT|RIGHT|OUTER|ON|GROUP|BY|ORDER|HAVING|LIMIT|OFFSET|AS|AND|OR|NOT|NULL|IS|IN|LIKE|BETWEEN|DISTINCT|UNION|ALL|EXISTS|CASE|WHEN|THEN|ELSE|END|PRIMARY|KEY|FOREIGN|REFERENCES|DEFAULT|CONSTRAINT|INDEX|VIEW|WITH)\\b",
1187
+ "iy"
1188
+ )
1189
+ },
1190
+ { type: "number", pattern: /\b\d+\.?\d*\b/y },
1191
+ { type: "function", pattern: /\b[A-Za-z_]\w*(?=\s*\()/y },
1192
+ { type: "punctuation", pattern: /[(),;.*=<>!]/y }
1193
+ ]
1194
+ };
1195
+ var GRAMMARS = {
1196
+ javascript: jsGrammar,
1197
+ typescript: jsGrammar,
1198
+ jsx: jsxGrammar,
1199
+ tsx: jsxGrammar,
1200
+ json: jsonGrammar,
1201
+ css: cssGrammar,
1202
+ html: htmlGrammar,
1203
+ bash: bashGrammar,
1204
+ python: pythonGrammar,
1205
+ markdown: markdownGrammar,
1206
+ yaml: yamlGrammar,
1207
+ sql: sqlGrammar
1208
+ };
1209
+ var ALIASES = {
1210
+ js: "javascript",
1211
+ mjs: "javascript",
1212
+ cjs: "javascript",
1213
+ ts: "typescript",
1214
+ sh: "bash",
1215
+ shell: "bash",
1216
+ zsh: "bash",
1217
+ py: "python",
1218
+ md: "markdown",
1219
+ htm: "html",
1220
+ yml: "yaml"
1221
+ };
1222
+ var LANGUAGE_LABELS = {
1223
+ javascript: "JavaScript",
1224
+ typescript: "TypeScript",
1225
+ jsx: "JSX",
1226
+ tsx: "TSX",
1227
+ json: "JSON",
1228
+ css: "CSS",
1229
+ html: "HTML",
1230
+ bash: "Shell",
1231
+ python: "Python",
1232
+ markdown: "Markdown",
1233
+ yaml: "YAML",
1234
+ sql: "SQL",
1235
+ plaintext: "Plain text"
1236
+ };
1237
+ function normalizeLanguage(language) {
1238
+ const lower = language.trim().toLowerCase();
1239
+ return ALIASES[lower] ?? lower;
1240
+ }
1241
+ var nuvoxHighlight = (code, language) => {
1242
+ const key = normalizeLanguage(language ?? "plaintext");
1243
+ const grammar = GRAMMARS[key];
1244
+ if (!grammar) return splitIntoLines([{ type: "plain", content: code }]);
1245
+ try {
1246
+ return splitIntoLines(tokenizeFlat(code, grammar));
1247
+ } catch {
1248
+ return splitIntoLines([{ type: "plain", content: code }]);
1249
+ }
1250
+ };
1251
+
1252
+ // src/core/rich-text.ts
1253
+ var MARK_TAGS = {
1254
+ bold: "STRONG",
1255
+ italic: "EM",
1256
+ underline: "U",
1257
+ strike: "S",
1258
+ code: "CODE"
1259
+ };
1260
+ var BLOCK_TAGS = {
1261
+ paragraph: "P",
1262
+ h1: "H1",
1263
+ h2: "H2",
1264
+ h3: "H3",
1265
+ blockquote: "BLOCKQUOTE",
1266
+ bulletList: "UL",
1267
+ orderedList: "OL"
1268
+ };
1269
+ var BLOCK_TAG_NAMES = /* @__PURE__ */ new Set(["P", "H1", "H2", "H3", "BLOCKQUOTE", "DIV"]);
1270
+ function isMark(command) {
1271
+ return command in MARK_TAGS;
1272
+ }
1273
+ function isBlock(command) {
1274
+ return command in BLOCK_TAGS;
1275
+ }
1276
+ function isCommandActive(ancestorTags, command) {
1277
+ if (command === "link") return ancestorTags.includes("A");
1278
+ if (command === "unlink") return false;
1279
+ if (isMark(command)) return ancestorTags.includes(MARK_TAGS[command]);
1280
+ if (isBlock(command)) return ancestorTags.includes(BLOCK_TAGS[command]);
1281
+ return false;
1282
+ }
1283
+ function closestWithinRoot(node, tag, root) {
1284
+ let n = node;
1285
+ while (n && n !== root) {
1286
+ if (n.nodeType === Node.ELEMENT_NODE && n.tagName === tag) return n;
1287
+ n = n.parentNode;
1288
+ }
1289
+ return null;
1290
+ }
1291
+ function closestBlock(node, root) {
1292
+ let n = node && node.nodeType === Node.ELEMENT_NODE ? node : node?.parentNode ?? null;
1293
+ while (n && n !== root) {
1294
+ if (n.nodeType === Node.ELEMENT_NODE && BLOCK_TAG_NAMES.has(n.tagName)) return n;
1295
+ n = n.parentNode;
1296
+ }
1297
+ return null;
1298
+ }
1299
+ function unwrapElement(el) {
1300
+ const parent = el.parentNode;
1301
+ if (!parent) return;
1302
+ while (el.firstChild) parent.insertBefore(el.firstChild, el);
1303
+ parent.removeChild(el);
1304
+ }
1305
+ function replaceTag(el, tag) {
1306
+ const next = document.createElement(tag);
1307
+ while (el.firstChild) next.appendChild(el.firstChild);
1308
+ el.parentNode?.replaceChild(next, el);
1309
+ return next;
1310
+ }
1311
+ function selectNodeContents(node, collapseToEnd = false) {
1312
+ const sel = window.getSelection();
1313
+ if (!sel) return;
1314
+ const range = document.createRange();
1315
+ range.selectNodeContents(node);
1316
+ if (collapseToEnd) range.collapse(false);
1317
+ sel.removeAllRanges();
1318
+ sel.addRange(range);
1319
+ }
1320
+ var ZERO_WIDTH_SPACE = "\u200B";
1321
+ function selectAtMarker(sel, marker, offset) {
1322
+ const range = document.createRange();
1323
+ range.setStart(marker, offset);
1324
+ range.collapse(true);
1325
+ sel.removeAllRanges();
1326
+ sel.addRange(range);
1327
+ }
1328
+ function togglePendingMark(root, tag) {
1329
+ const sel = window.getSelection();
1330
+ if (!sel || sel.rangeCount === 0) return;
1331
+ const range = sel.getRangeAt(0);
1332
+ const existing = closestWithinRoot(range.startContainer, tag, root);
1333
+ if (existing) {
1334
+ const marker2 = document.createTextNode(ZERO_WIDTH_SPACE);
1335
+ existing.parentNode?.insertBefore(marker2, existing.nextSibling);
1336
+ selectAtMarker(sel, marker2, 1);
1337
+ return;
1338
+ }
1339
+ const wrapper = document.createElement(tag);
1340
+ const marker = document.createTextNode(ZERO_WIDTH_SPACE);
1341
+ wrapper.appendChild(marker);
1342
+ range.insertNode(wrapper);
1343
+ selectAtMarker(sel, marker, 1);
1344
+ }
1345
+ function toggleMark(root, tag) {
1346
+ const sel = window.getSelection();
1347
+ if (!sel || sel.rangeCount === 0) return;
1348
+ if (sel.isCollapsed) {
1349
+ togglePendingMark(root, tag);
1350
+ return;
1351
+ }
1352
+ const range = sel.getRangeAt(0);
1353
+ const existing = closestWithinRoot(range.commonAncestorContainer, tag, root);
1354
+ if (existing) {
1355
+ unwrapElement(existing);
1356
+ return;
1357
+ }
1358
+ const wrapper = document.createElement(tag);
1359
+ wrapper.appendChild(range.extractContents());
1360
+ range.insertNode(wrapper);
1361
+ selectNodeContents(wrapper);
1362
+ }
1363
+ function setBlockTag(root, targetTag) {
1364
+ const sel = window.getSelection();
1365
+ if (!sel || sel.rangeCount === 0) return;
1366
+ const block = closestBlock(sel.getRangeAt(0).startContainer, root);
1367
+ if (!block) return;
1368
+ const next = replaceTag(block, block.tagName === targetTag ? "P" : targetTag);
1369
+ selectNodeContents(next, true);
1370
+ }
1371
+ function toggleList(root, listTag) {
1372
+ const sel = window.getSelection();
1373
+ if (!sel || sel.rangeCount === 0) return;
1374
+ const range = sel.getRangeAt(0);
1375
+ const li = closestWithinRoot(range.startContainer, "LI", root);
1376
+ if (li && li.parentElement?.tagName === listTag) {
1377
+ const list2 = li.parentElement;
1378
+ const p = document.createElement("P");
1379
+ while (li.firstChild) p.appendChild(li.firstChild);
1380
+ list2.parentNode?.replaceChild(p, list2);
1381
+ selectNodeContents(p, true);
1382
+ return;
1383
+ }
1384
+ const block = closestBlock(range.startContainer, root);
1385
+ if (!block) return;
1386
+ const list = document.createElement(listTag);
1387
+ const item = document.createElement("LI");
1388
+ while (block.firstChild) item.appendChild(block.firstChild);
1389
+ list.appendChild(item);
1390
+ block.parentNode?.replaceChild(list, block);
1391
+ selectNodeContents(item, true);
1392
+ }
1393
+ function setLink(root, url) {
1394
+ const sel = window.getSelection();
1395
+ if (!sel || sel.rangeCount === 0 || sel.isCollapsed) return;
1396
+ const range = sel.getRangeAt(0);
1397
+ const existing = closestWithinRoot(range.commonAncestorContainer, "A", root);
1398
+ if (existing) {
1399
+ existing.setAttribute("href", url);
1400
+ return;
1401
+ }
1402
+ const a = document.createElement("A");
1403
+ a.setAttribute("href", url);
1404
+ a.setAttribute("rel", "noopener noreferrer");
1405
+ a.appendChild(range.extractContents());
1406
+ range.insertNode(a);
1407
+ selectNodeContents(a);
1408
+ }
1409
+ function unlink(root) {
1410
+ const sel = window.getSelection();
1411
+ if (!sel || sel.rangeCount === 0) return;
1412
+ const a = closestWithinRoot(sel.getRangeAt(0).commonAncestorContainer, "A", root);
1413
+ if (a) unwrapElement(a);
1414
+ }
1415
+ function findStyledSpanWithinRoot(node, property, root) {
1416
+ let n = node;
1417
+ while (n && n !== root) {
1418
+ if (n.nodeType === Node.ELEMENT_NODE) {
1419
+ const el = n;
1420
+ if (el.tagName === "SPAN" && el.style.getPropertyValue(property)) return el;
1421
+ }
1422
+ n = n.parentNode;
1423
+ }
1424
+ return null;
1425
+ }
1426
+ function setInlineStyle(root, property, value) {
1427
+ const sel = window.getSelection();
1428
+ if (!sel || sel.rangeCount === 0) return;
1429
+ const range = sel.getRangeAt(0);
1430
+ if (sel.isCollapsed) {
1431
+ const existing2 = findStyledSpanWithinRoot(range.startContainer, property, root);
1432
+ if (existing2) {
1433
+ existing2.style.setProperty(property, value);
1434
+ return;
1435
+ }
1436
+ const wrapper2 = document.createElement("span");
1437
+ wrapper2.style.setProperty(property, value);
1438
+ const marker = document.createTextNode(ZERO_WIDTH_SPACE);
1439
+ wrapper2.appendChild(marker);
1440
+ range.insertNode(wrapper2);
1441
+ selectAtMarker(sel, marker, 1);
1442
+ return;
1443
+ }
1444
+ const existing = findStyledSpanWithinRoot(range.commonAncestorContainer, property, root);
1445
+ if (existing) {
1446
+ existing.style.setProperty(property, value);
1447
+ return;
1448
+ }
1449
+ const wrapper = document.createElement("span");
1450
+ wrapper.style.setProperty(property, value);
1451
+ wrapper.appendChild(range.extractContents());
1452
+ range.insertNode(wrapper);
1453
+ selectNodeContents(wrapper);
1454
+ }
1455
+ function clearInlineStyle(root, property) {
1456
+ const sel = window.getSelection();
1457
+ if (!sel || sel.rangeCount === 0) return;
1458
+ const el = findStyledSpanWithinRoot(sel.getRangeAt(0).commonAncestorContainer, property, root);
1459
+ if (!el) return;
1460
+ el.style.removeProperty(property);
1461
+ if (!el.getAttribute("style")) unwrapElement(el);
1462
+ }
1463
+ function execRichTextCommand(root, command, value) {
1464
+ if (isMark(command)) toggleMark(root, MARK_TAGS[command]);
1465
+ else if (command === "bulletList") toggleList(root, "UL");
1466
+ else if (command === "orderedList") toggleList(root, "OL");
1467
+ else if (isBlock(command)) setBlockTag(root, BLOCK_TAGS[command]);
1468
+ else if (command === "link" && value) setLink(root, value);
1469
+ else if (command === "unlink") unlink(root);
1470
+ else if (command === "textColor" && value) setInlineStyle(root, "color", value);
1471
+ else if (command === "clearTextColor") clearInlineStyle(root, "color");
1472
+ else if (command === "highlight" && value) setInlineStyle(root, "background-color", value);
1473
+ else if (command === "clearHighlight") clearInlineStyle(root, "background-color");
1474
+ }
1475
+ function getActiveCommands(root) {
1476
+ const result = /* @__PURE__ */ new Set();
1477
+ if (typeof window === "undefined") return result;
1478
+ const sel = window.getSelection();
1479
+ if (!sel || sel.rangeCount === 0) return result;
1480
+ const tags = [];
1481
+ let node = sel.anchorNode;
1482
+ while (node && node !== root) {
1483
+ if (node.nodeType === Node.ELEMENT_NODE) tags.push(node.tagName);
1484
+ node = node.parentNode;
1485
+ }
1486
+ const commands = [...Object.keys(MARK_TAGS), ...Object.keys(BLOCK_TAGS), "link"];
1487
+ for (const command of commands) {
1488
+ if (isCommandActive(tags, command)) result.add(command);
1489
+ }
1490
+ return result;
1491
+ }
1492
+ var ALLOWED_TAGS = /* @__PURE__ */ new Set([
1493
+ "P",
1494
+ "BR",
1495
+ "STRONG",
1496
+ "B",
1497
+ "EM",
1498
+ "I",
1499
+ "U",
1500
+ "S",
1501
+ "STRIKE",
1502
+ "CODE",
1503
+ "A",
1504
+ "H1",
1505
+ "H2",
1506
+ "H3",
1507
+ "BLOCKQUOTE",
1508
+ "UL",
1509
+ "OL",
1510
+ "LI",
1511
+ "SPAN"
1512
+ ]);
1513
+ var ALLOWED_ATTRS = {
1514
+ A: ["href"],
1515
+ SPAN: ["style"]
1516
+ };
1517
+ var ALLOWED_STYLE_PROPS = /* @__PURE__ */ new Set(["color", "background-color"]);
1518
+ var SAFE_COLOR_VALUE = /^(#[0-9a-fA-F]{3,8}|rgb\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*\)|rgba\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*,\s*[\d.]+\s*\)|hsl\([^();]*\)|hsla\([^();]*\)|[a-zA-Z]+)$/;
1519
+ function sanitizeStyleAttr(value) {
1520
+ const out = [];
1521
+ for (const decl of value.split(";")) {
1522
+ const colon = decl.indexOf(":");
1523
+ if (colon === -1) continue;
1524
+ const prop = decl.slice(0, colon).trim().toLowerCase();
1525
+ const val = decl.slice(colon + 1).trim();
1526
+ if (ALLOWED_STYLE_PROPS.has(prop) && SAFE_COLOR_VALUE.test(val)) {
1527
+ out.push(`${prop}: ${val}`);
1528
+ }
1529
+ }
1530
+ return out.join("; ");
1531
+ }
1532
+ var STRIP_ENTIRELY = /* @__PURE__ */ new Set(["SCRIPT", "STYLE", "IFRAME", "OBJECT", "EMBED"]);
1533
+ function sanitizeElement(el) {
1534
+ for (const child of Array.from(el.childNodes)) {
1535
+ if (child.nodeType === Node.TEXT_NODE) continue;
1536
+ if (child.nodeType !== Node.ELEMENT_NODE) {
1537
+ child.parentNode?.removeChild(child);
1538
+ continue;
1539
+ }
1540
+ const childEl = child;
1541
+ if (STRIP_ENTIRELY.has(childEl.tagName)) {
1542
+ childEl.parentNode?.removeChild(childEl);
1543
+ continue;
1544
+ }
1545
+ if (!ALLOWED_TAGS.has(childEl.tagName)) {
1546
+ sanitizeElement(childEl);
1547
+ const parent = childEl.parentNode;
1548
+ while (childEl.firstChild) parent?.insertBefore(childEl.firstChild, childEl);
1549
+ parent?.removeChild(childEl);
1550
+ continue;
1551
+ }
1552
+ const allowedAttrs = ALLOWED_ATTRS[childEl.tagName] ?? [];
1553
+ for (const attr of Array.from(childEl.attributes)) {
1554
+ const name = attr.name.toLowerCase();
1555
+ if (!allowedAttrs.includes(name)) {
1556
+ childEl.removeAttribute(attr.name);
1557
+ continue;
1558
+ }
1559
+ if (name === "style") {
1560
+ const cleaned = sanitizeStyleAttr(attr.value);
1561
+ if (cleaned) childEl.setAttribute("style", cleaned);
1562
+ else childEl.removeAttribute("style");
1563
+ }
1564
+ }
1565
+ if (childEl.tagName === "A") {
1566
+ const href = childEl.getAttribute("href") ?? "";
1567
+ const safe = /^https?:\/\//i.test(href) || href.startsWith("/") || href.startsWith("#") || href.startsWith("mailto:");
1568
+ if (!safe) childEl.removeAttribute("href");
1569
+ childEl.setAttribute("rel", "noopener noreferrer");
1570
+ childEl.setAttribute("target", "_blank");
1571
+ }
1572
+ sanitizeElement(childEl);
1573
+ }
1574
+ }
1575
+ function sanitizeHtml(html) {
1576
+ if (typeof DOMParser === "undefined") return "";
1577
+ const doc = new DOMParser().parseFromString(html, "text/html");
1578
+ sanitizeElement(doc.body);
1579
+ return doc.body.innerHTML;
1580
+ }
1581
+ var HISTORY_LIMIT = 100;
1582
+ function createHistory() {
1583
+ return { past: [], future: [] };
1584
+ }
1585
+ function pushHistory(history, previousValue) {
1586
+ return { past: [...history.past, previousValue].slice(-HISTORY_LIMIT), future: [] };
1587
+ }
1588
+ function undoHistory(history, currentValue) {
1589
+ if (history.past.length === 0) return null;
1590
+ const value = history.past[history.past.length - 1];
1591
+ return {
1592
+ history: { past: history.past.slice(0, -1), future: [currentValue, ...history.future] },
1593
+ value
1594
+ };
1595
+ }
1596
+ function redoHistory(history, currentValue) {
1597
+ if (history.future.length === 0) return null;
1598
+ const value = history.future[0];
1599
+ return {
1600
+ history: { past: [...history.past, currentValue], future: history.future.slice(1) },
1601
+ value
1602
+ };
1603
+ }
893
1604
  // Annotate the CommonJS export names for ESM import in node:
894
1605
  0 && (module.exports = {
895
1606
  DEFAULT_COLOR_PRESETS,
1607
+ LANGUAGE_LABELS,
896
1608
  REQUIRED_TOKEN_PATHS,
897
1609
  TOKEN_CATEGORIES,
898
1610
  amber,
899
1611
  brutalism,
900
1612
  copper,
1613
+ createHistory,
901
1614
  cyberpunk,
902
1615
  dark,
903
1616
  darkGlass,
904
1617
  defineTheme,
905
1618
  emerald,
1619
+ execRichTextCommand,
906
1620
  findMissingTokenPaths,
907
1621
  getAccent,
1622
+ getActiveCommands,
908
1623
  getNextSortDirection,
909
1624
  getSelectAllState,
910
1625
  getTheme,
@@ -919,11 +1634,16 @@ function getSelectAllState(totalCount, selectedCount) {
919
1634
  material,
920
1635
  minimalism,
921
1636
  neobrutalism,
1637
+ nuvoxHighlight,
1638
+ pushHistory,
1639
+ redoHistory,
922
1640
  registerAccent,
923
1641
  registerTheme,
924
1642
  rose,
1643
+ sanitizeHtml,
925
1644
  skeuomorphism,
926
1645
  teal,
1646
+ undoHistory,
927
1647
  violet
928
1648
  });
929
1649
  //# sourceMappingURL=index.cjs.map