draftly 1.0.7 → 2.1.0

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.
Files changed (79) hide show
  1. package/README.md +12 -0
  2. package/dist/chunk-3T55CBNZ.cjs +33 -0
  3. package/dist/chunk-3T55CBNZ.cjs.map +1 -0
  4. package/dist/chunk-5MC4T7JH.cjs +58 -0
  5. package/dist/chunk-5MC4T7JH.cjs.map +1 -0
  6. package/dist/{chunk-KBQDZ5IW.cjs → chunk-CLW73JRX.cjs} +100 -75
  7. package/dist/chunk-CLW73JRX.cjs.map +1 -0
  8. package/dist/{chunk-72ZYRGRT.cjs → chunk-EQUQHE2E.cjs} +30 -26
  9. package/dist/chunk-EQUQHE2E.cjs.map +1 -0
  10. package/dist/{chunk-HPSMS2WB.js → chunk-I563H35S.js} +101 -75
  11. package/dist/chunk-I563H35S.js.map +1 -0
  12. package/dist/chunk-IAXF4SJL.js +55 -0
  13. package/dist/chunk-IAXF4SJL.js.map +1 -0
  14. package/dist/chunk-JF3WXXMJ.js +31 -0
  15. package/dist/chunk-JF3WXXMJ.js.map +1 -0
  16. package/dist/{chunk-N3WL3XPB.js → chunk-NRPI5O6Y.js} +2603 -604
  17. package/dist/chunk-NRPI5O6Y.js.map +1 -0
  18. package/dist/{chunk-2B3A3VSQ.cjs → chunk-OMFUE4AQ.cjs} +2642 -622
  19. package/dist/chunk-OMFUE4AQ.cjs.map +1 -0
  20. package/dist/{chunk-DFQYXFOP.js → chunk-TD3L5C45.js} +28 -3
  21. package/dist/chunk-TD3L5C45.js.map +1 -0
  22. package/dist/{chunk-CG4M4TC7.js → chunk-UCHBDJ4R.js} +26 -22
  23. package/dist/chunk-UCHBDJ4R.js.map +1 -0
  24. package/dist/{chunk-KDEDLC3D.cjs → chunk-W75QUUQC.cjs} +29 -2
  25. package/dist/chunk-W75QUUQC.cjs.map +1 -0
  26. package/dist/{draftly-BLnx3uGX.d.cts → draftly-BBL-AdOl.d.cts} +5 -1
  27. package/dist/{draftly-BLnx3uGX.d.ts → draftly-BBL-AdOl.d.ts} +5 -1
  28. package/dist/editor/index.cjs +22 -14
  29. package/dist/editor/index.d.cts +2 -1
  30. package/dist/editor/index.d.ts +2 -1
  31. package/dist/editor/index.js +2 -2
  32. package/dist/index.cjs +65 -39
  33. package/dist/index.d.cts +6 -3
  34. package/dist/index.d.ts +6 -3
  35. package/dist/index.js +6 -4
  36. package/dist/lib/index.cjs +12 -0
  37. package/dist/lib/index.cjs.map +1 -0
  38. package/dist/lib/index.d.cts +16 -0
  39. package/dist/lib/index.d.ts +16 -0
  40. package/dist/lib/index.js +3 -0
  41. package/dist/lib/index.js.map +1 -0
  42. package/dist/plugins/index.cjs +27 -17
  43. package/dist/plugins/index.d.cts +180 -10
  44. package/dist/plugins/index.d.ts +180 -10
  45. package/dist/plugins/index.js +5 -3
  46. package/dist/preview/index.cjs +16 -11
  47. package/dist/preview/index.d.cts +19 -4
  48. package/dist/preview/index.d.ts +19 -4
  49. package/dist/preview/index.js +3 -2
  50. package/package.json +8 -1
  51. package/src/editor/draftly.ts +30 -27
  52. package/src/editor/plugin.ts +5 -1
  53. package/src/editor/theme.ts +1 -0
  54. package/src/editor/utils.ts +33 -0
  55. package/src/index.ts +5 -4
  56. package/src/lib/index.ts +2 -0
  57. package/src/lib/input-handler.ts +45 -0
  58. package/src/plugins/code-plugin.theme.ts +426 -0
  59. package/src/plugins/code-plugin.ts +810 -561
  60. package/src/plugins/emoji-plugin.ts +140 -0
  61. package/src/plugins/index.ts +63 -57
  62. package/src/plugins/inline-plugin.ts +305 -291
  63. package/src/plugins/math-plugin.ts +12 -0
  64. package/src/plugins/table-plugin.ts +1759 -0
  65. package/src/preview/context.ts +4 -1
  66. package/src/preview/css-generator.ts +14 -1
  67. package/src/preview/index.ts +9 -1
  68. package/src/preview/preview.ts +2 -1
  69. package/src/preview/renderer.ts +21 -20
  70. package/src/preview/syntax-theme.ts +110 -0
  71. package/src/preview/types.ts +14 -0
  72. package/dist/chunk-2B3A3VSQ.cjs.map +0 -1
  73. package/dist/chunk-72ZYRGRT.cjs.map +0 -1
  74. package/dist/chunk-CG4M4TC7.js.map +0 -1
  75. package/dist/chunk-DFQYXFOP.js.map +0 -1
  76. package/dist/chunk-HPSMS2WB.js.map +0 -1
  77. package/dist/chunk-KBQDZ5IW.cjs.map +0 -1
  78. package/dist/chunk-KDEDLC3D.cjs.map +0 -1
  79. package/dist/chunk-N3WL3XPB.js.map +0 -1
@@ -1,13 +1,18 @@
1
- import { DraftlyPlugin, DecorationPlugin } from './chunk-CG4M4TC7.js';
2
- import { createTheme, toggleMarkdownStyle } from './chunk-DFQYXFOP.js';
3
- import { Decoration, WidgetType } from '@codemirror/view';
4
- import { syntaxTree } from '@codemirror/language';
5
- import { tags, highlightCode, classHighlighter } from '@lezer/highlight';
1
+ import { DraftlyPlugin, DecorationPlugin } from './chunk-UCHBDJ4R.js';
2
+ import { createWrapSelectionInputHandler } from './chunk-JF3WXXMJ.js';
3
+ import { PreviewRenderer } from './chunk-I563H35S.js';
4
+ import { createTheme, toggleMarkdownStyle } from './chunk-TD3L5C45.js';
5
+ import { Decoration, BlockWrapper, keymap, EditorView, WidgetType } from '@codemirror/view';
6
+ import { syntaxTree, LanguageDescription } from '@codemirror/language';
7
+ import { tags, highlightCode } from '@lezer/highlight';
8
+ import { Annotation, Prec, RangeSet } from '@codemirror/state';
9
+ import { Table } from '@lezer/markdown';
6
10
  import DOMPurify from 'dompurify';
7
11
  import katex from 'katex';
8
12
  import katexCss from 'katex/dist/katex.min.css?raw';
9
13
  import mermaid from 'mermaid';
10
14
  import { languages } from '@codemirror/language-data';
15
+ import * as emoji from 'node-emoji';
11
16
 
12
17
  // src/plugins/paragraph-plugin.ts
13
18
  var ParagraphPlugin = class extends DraftlyPlugin {
@@ -292,6 +297,17 @@ var InlinePlugin = class extends DecorationPlugin {
292
297
  }
293
298
  ];
294
299
  }
300
+ /**
301
+ * Intercepts inline marker typing to wrap selected text.
302
+ *
303
+ * If user types inline markers while text is selected, wraps each selected
304
+ * range with the appropriate marker:
305
+ * - * _ ~ ^ -> marker + selected + marker
306
+ * - = -> ==selected==
307
+ */
308
+ getExtensions() {
309
+ return [createWrapSelectionInputHandler({ "*": "*", _: "_", "~": "~", "^": "^", "=": "==" })];
310
+ }
295
311
  /**
296
312
  * Return markdown parser extensions for highlight syntax (==text==)
297
313
  */
@@ -1110,132 +1126,1493 @@ var theme5 = createTheme({
1110
1126
  }
1111
1127
  }
1112
1128
  });
1113
- var htmlMarkDecorations = {
1114
- "html-tag": Decoration.mark({ class: "cm-draftly-html-tag" }),
1115
- "html-comment": Decoration.mark({ class: "cm-draftly-html-comment" })
1116
- };
1117
- var htmlLineDecorations = {
1118
- "html-block": Decoration.line({ class: "cm-draftly-line-html-block" }),
1119
- "hidden-line": Decoration.line({ class: "cm-draftly-hidden-line" })
1129
+ var BREAK_TAG = "<br />";
1130
+ var BREAK_TAG_REGEX = /<br\s*\/?>/gi;
1131
+ var DELIMITER_CELL_PATTERN = /^:?-{3,}:?$/;
1132
+ var TABLE_SUB_NODE_NAMES = /* @__PURE__ */ new Set(["TableHeader", "TableDelimiter", "TableRow", "TableCell"]);
1133
+ var TABLE_TEMPLATE = {
1134
+ headers: ["Header 1", "Header 2", "Header 3"],
1135
+ alignments: ["left", "left", "left"],
1136
+ rows: [["", "", ""]]
1120
1137
  };
1121
- var HTMLPreviewWidget = class extends WidgetType {
1122
- constructor(html) {
1123
- super();
1124
- this.html = html;
1125
- }
1126
- eq(other) {
1127
- return other.html === this.html;
1138
+ var normalizeAnnotation = Annotation.define();
1139
+ var repairSelectionAnnotation = Annotation.define();
1140
+ var pipeReplace = Decoration.replace({});
1141
+ var delimiterReplace = Decoration.replace({});
1142
+ var tableBlockWrapper = BlockWrapper.create({
1143
+ tagName: "div",
1144
+ attributes: { class: "cm-draftly-table-wrapper" }
1145
+ });
1146
+ var TableBreakWidget = class extends WidgetType {
1147
+ /** Reuses the same widget instance for identical break markers. */
1148
+ eq() {
1149
+ return true;
1128
1150
  }
1151
+ /** Renders an inline `<br />` placeholder inside a table cell. */
1129
1152
  toDOM() {
1130
- const div = document.createElement("div");
1131
- div.className = "cm-draftly-html-preview";
1132
- div.innerHTML = DOMPurify.sanitize(this.html);
1133
- return div;
1153
+ const span = document.createElement("span");
1154
+ span.className = "cm-draftly-table-break";
1155
+ span.setAttribute("aria-label", "line break");
1156
+ span.appendChild(document.createElement("br"));
1157
+ return span;
1134
1158
  }
1159
+ /** Allows the editor to observe events on the rendered break widget. */
1135
1160
  ignoreEvent() {
1136
1161
  return false;
1137
1162
  }
1138
1163
  };
1139
- var InlineHTMLPreviewWidget = class extends WidgetType {
1140
- constructor(html) {
1164
+ var TableControlsWidget = class extends WidgetType {
1165
+ constructor(onAddRow, onAddColumn) {
1141
1166
  super();
1142
- this.html = html;
1167
+ this.onAddRow = onAddRow;
1168
+ this.onAddColumn = onAddColumn;
1143
1169
  }
1144
- eq(other) {
1145
- return other.html === this.html;
1170
+ /** Forces the control widget to be recreated so handlers stay current. */
1171
+ eq() {
1172
+ return false;
1146
1173
  }
1147
- toDOM() {
1148
- const span = document.createElement("span");
1149
- span.className = "cm-draftly-inline-html-preview";
1150
- span.innerHTML = DOMPurify.sanitize(this.html);
1151
- return span;
1174
+ /** Renders the hover controls used to append rows and columns. */
1175
+ toDOM(view) {
1176
+ const anchor = document.createElement("span");
1177
+ anchor.className = "cm-draftly-table-controls-anchor";
1178
+ anchor.setAttribute("aria-hidden", "true");
1179
+ const rightButton = this.createButton("Add column", "cm-draftly-table-control cm-draftly-table-control-column");
1180
+ rightButton.addEventListener("mousedown", (event) => {
1181
+ event.preventDefault();
1182
+ event.stopPropagation();
1183
+ this.onAddColumn(view);
1184
+ });
1185
+ const bottomButton = this.createButton("Add row", "cm-draftly-table-control cm-draftly-table-control-row");
1186
+ bottomButton.addEventListener("mousedown", (event) => {
1187
+ event.preventDefault();
1188
+ event.stopPropagation();
1189
+ this.onAddRow(view);
1190
+ });
1191
+ anchor.append(rightButton, bottomButton);
1192
+ return anchor;
1152
1193
  }
1194
+ /** Lets button events bubble through the widget. */
1153
1195
  ignoreEvent() {
1154
1196
  return false;
1155
1197
  }
1198
+ /** Builds a single control button with the provided label and class. */
1199
+ createButton(label, className) {
1200
+ const button = document.createElement("button");
1201
+ button.type = "button";
1202
+ button.className = className;
1203
+ button.setAttribute("tabindex", "-1");
1204
+ button.setAttribute("aria-label", label);
1205
+ button.textContent = "+";
1206
+ return button;
1207
+ }
1156
1208
  };
1157
- function parseHTMLTag(content) {
1158
- const match = content.match(/^<\s*(\/?)([a-zA-Z][a-zA-Z0-9-]*)[^>]*(\/?)>$/);
1159
- if (!match) return null;
1209
+ function isEscaped(text, index) {
1210
+ let slashCount = 0;
1211
+ for (let i = index - 1; i >= 0 && text[i] === "\\"; i--) {
1212
+ slashCount++;
1213
+ }
1214
+ return slashCount % 2 === 1;
1215
+ }
1216
+ function getPipePositions(lineText) {
1217
+ const positions = [];
1218
+ for (let index = 0; index < lineText.length; index++) {
1219
+ if (lineText[index] === "|" && !isEscaped(lineText, index)) {
1220
+ positions.push(index);
1221
+ }
1222
+ }
1223
+ return positions;
1224
+ }
1225
+ function splitTableLine(lineText) {
1226
+ const cells = [];
1227
+ const trimmed = lineText.trim();
1228
+ if (!trimmed.includes("|")) {
1229
+ return [trimmed];
1230
+ }
1231
+ let current = "";
1232
+ for (let index = 0; index < trimmed.length; index++) {
1233
+ const char = trimmed[index];
1234
+ if (char === "|" && !isEscaped(trimmed, index)) {
1235
+ cells.push(current);
1236
+ current = "";
1237
+ continue;
1238
+ }
1239
+ current += char;
1240
+ }
1241
+ cells.push(current);
1242
+ if (trimmed.startsWith("|")) {
1243
+ cells.shift();
1244
+ }
1245
+ if (trimmed.endsWith("|")) {
1246
+ cells.pop();
1247
+ }
1248
+ return cells;
1249
+ }
1250
+ function isTableRowLine(lineText) {
1251
+ return getPipePositions(lineText.trim()).length > 0;
1252
+ }
1253
+ function parseAlignment(cell) {
1254
+ const trimmed = cell.trim();
1255
+ const left = trimmed.startsWith(":");
1256
+ const right = trimmed.endsWith(":");
1257
+ if (left && right) return "center";
1258
+ if (right) return "right";
1259
+ return "left";
1260
+ }
1261
+ function parseDelimiterAlignments(lineText) {
1262
+ const cells = splitTableLine(lineText).map((cell) => cell.trim());
1263
+ if (cells.length === 0 || !cells.every((cell) => DELIMITER_CELL_PATTERN.test(cell))) {
1264
+ return null;
1265
+ }
1266
+ return cells.map(parseAlignment);
1267
+ }
1268
+ function splitTableAndTrailingMarkdown(markdown) {
1269
+ const lines = markdown.split("\n");
1270
+ if (lines.length < 2) {
1271
+ return { tableMarkdown: markdown, trailingMarkdown: "" };
1272
+ }
1273
+ const headerLine = lines[0] || "";
1274
+ const delimiterLine = lines[1] || "";
1275
+ if (!isTableRowLine(headerLine) || !parseDelimiterAlignments(delimiterLine)) {
1276
+ return { tableMarkdown: markdown, trailingMarkdown: "" };
1277
+ }
1278
+ let endIndex = 1;
1279
+ for (let index = 2; index < lines.length; index++) {
1280
+ if (!isTableRowLine(lines[index] || "")) {
1281
+ break;
1282
+ }
1283
+ endIndex = index;
1284
+ }
1160
1285
  return {
1161
- tagName: match[2].toLowerCase(),
1162
- isClosing: match[1] === "/",
1163
- isSelfClosing: match[3] === "/" || ["br", "hr", "img", "input", "meta", "link", "area", "base", "col", "embed", "source", "track", "wbr"].includes(
1164
- match[2].toLowerCase()
1165
- )
1286
+ tableMarkdown: lines.slice(0, endIndex + 1).join("\n"),
1287
+ trailingMarkdown: lines.slice(endIndex + 1).join("\n")
1166
1288
  };
1167
1289
  }
1168
- var HTMLPlugin = class extends DecorationPlugin {
1169
- name = "html";
1170
- version = "1.0.0";
1171
- decorationPriority = 30;
1172
- constructor() {
1173
- super();
1290
+ function canonicalizeBreakTags(text) {
1291
+ return text.replace(BREAK_TAG_REGEX, BREAK_TAG);
1292
+ }
1293
+ function escapeUnescapedPipes(text) {
1294
+ let result = "";
1295
+ for (let index = 0; index < text.length; index++) {
1296
+ const char = text[index];
1297
+ if (char === "|" && !isEscaped(text, index)) {
1298
+ result += "\\|";
1299
+ continue;
1300
+ }
1301
+ result += char;
1174
1302
  }
1175
- /**
1176
- * Plugin theme
1177
- */
1303
+ return result;
1304
+ }
1305
+ function normalizeCellContent(text) {
1306
+ const normalizedBreaks = canonicalizeBreakTags(text.trim());
1307
+ if (!normalizedBreaks) {
1308
+ return "";
1309
+ }
1310
+ const parts = normalizedBreaks.split(BREAK_TAG_REGEX).map((part) => escapeUnescapedPipes(part.trim()));
1311
+ if (parts.length === 1) {
1312
+ return parts[0] || "";
1313
+ }
1314
+ return parts.join(` ${BREAK_TAG} `).trim();
1315
+ }
1316
+ function renderWidth(text) {
1317
+ return canonicalizeBreakTags(text).replace(BREAK_TAG, " ").replace(/\\\|/g, "|").length;
1318
+ }
1319
+ function padCell(text, width, alignment) {
1320
+ const safeWidth = Math.max(width, renderWidth(text));
1321
+ const difference = safeWidth - renderWidth(text);
1322
+ if (difference <= 0) {
1323
+ return text;
1324
+ }
1325
+ if (alignment === "right") {
1326
+ return " ".repeat(difference) + text;
1327
+ }
1328
+ if (alignment === "center") {
1329
+ const left = Math.floor(difference / 2);
1330
+ const right = difference - left;
1331
+ return " ".repeat(left) + text + " ".repeat(right);
1332
+ }
1333
+ return text + " ".repeat(difference);
1334
+ }
1335
+ function delimiterCell(width, alignment) {
1336
+ const hyphenCount = Math.max(width, 3);
1337
+ if (alignment === "center") {
1338
+ return ":" + "-".repeat(Math.max(1, hyphenCount - 2)) + ":";
1339
+ }
1340
+ if (alignment === "right") {
1341
+ return "-".repeat(Math.max(2, hyphenCount - 1)) + ":";
1342
+ }
1343
+ return "-".repeat(hyphenCount);
1344
+ }
1345
+ function parseTableMarkdown(markdown) {
1346
+ const { tableMarkdown } = splitTableAndTrailingMarkdown(markdown);
1347
+ const lines = tableMarkdown.split("\n");
1348
+ if (lines.length < 2) {
1349
+ return null;
1350
+ }
1351
+ const headers = splitTableLine(lines[0] || "").map((cell) => cell.trim());
1352
+ const alignments = parseDelimiterAlignments(lines[1] || "");
1353
+ if (!alignments) {
1354
+ return null;
1355
+ }
1356
+ const rows = lines.slice(2).filter((line) => isTableRowLine(line)).map((line) => splitTableLine(line).map((cell) => cell.trim()));
1357
+ return { headers, alignments, rows };
1358
+ }
1359
+ function normalizeParsedTable(parsed) {
1360
+ const columnCount = Math.max(
1361
+ parsed.headers.length,
1362
+ parsed.alignments.length,
1363
+ ...parsed.rows.map((row) => row.length),
1364
+ 1
1365
+ );
1366
+ const headers = Array.from({ length: columnCount }, (_, index) => normalizeCellContent(parsed.headers[index] || ""));
1367
+ const alignments = Array.from({ length: columnCount }, (_, index) => parsed.alignments[index] || "left");
1368
+ const rows = parsed.rows.map(
1369
+ (row) => Array.from({ length: columnCount }, (_, index) => normalizeCellContent(row[index] || ""))
1370
+ );
1371
+ return { headers, alignments, rows };
1372
+ }
1373
+ function formatTableMarkdown(parsed) {
1374
+ const normalized = normalizeParsedTable(parsed);
1375
+ const widths = normalized.headers.map(
1376
+ (header, index) => Math.max(renderWidth(header), ...normalized.rows.map((row) => renderWidth(row[index] || "")), 3)
1377
+ );
1378
+ const formatRow = (cells) => `| ${cells.map((cell, index) => padCell(cell, widths[index] || 3, normalized.alignments[index] || "left")).join(" | ")} |`;
1379
+ const headerLine = formatRow(normalized.headers);
1380
+ const delimiterLine = `| ${normalized.alignments.map((alignment, index) => delimiterCell(widths[index] || 3, alignment)).join(" | ")} |`;
1381
+ const bodyLines = normalized.rows.map((row) => formatRow(row));
1382
+ return [headerLine, delimiterLine, ...bodyLines].join("\n");
1383
+ }
1384
+ function buildEmptyRow(columnCount) {
1385
+ return Array.from({ length: columnCount }, () => "");
1386
+ }
1387
+ function createPreviewRenderer(markdown, config) {
1388
+ const plugins = (config?.plugins || []).filter((plugin) => plugin.name !== "paragraph");
1389
+ return new PreviewRenderer(markdown, plugins, config?.markdown || [], config?.theme || "auto" /* AUTO */, true);
1390
+ }
1391
+ function stripSingleParagraph(html) {
1392
+ const trimmed = html.trim();
1393
+ const match = trimmed.match(/^<p\b[^>]*>([\s\S]*)<\/p>$/i);
1394
+ return match?.[1] || trimmed;
1395
+ }
1396
+ async function renderCellToHtml(text, config) {
1397
+ if (!text.trim()) {
1398
+ return "&nbsp;";
1399
+ }
1400
+ return stripSingleParagraph(await createPreviewRenderer(text, config).render());
1401
+ }
1402
+ async function renderTableToHtml(parsed, config) {
1403
+ const normalized = normalizeParsedTable(parsed);
1404
+ let html = '<div class="cm-draftly-table-widget"><table class="cm-draftly-table cm-draftly-table-preview">';
1405
+ html += '<thead><tr class="cm-draftly-table-row cm-draftly-table-header-row">';
1406
+ for (let index = 0; index < normalized.headers.length; index++) {
1407
+ const alignment = normalized.alignments[index] || "left";
1408
+ const content = await renderCellToHtml(normalized.headers[index] || "", config);
1409
+ html += `<th class="cm-draftly-table-cell cm-draftly-table-th${alignment === "center" ? " cm-draftly-table-cell-center" : alignment === "right" ? " cm-draftly-table-cell-right" : ""}${index === normalized.headers.length - 1 ? " cm-draftly-table-cell-last" : ""}">${content}</th>`;
1410
+ }
1411
+ html += "</tr></thead><tbody>";
1412
+ for (let rowIndex = 0; rowIndex < normalized.rows.length; rowIndex++) {
1413
+ const row = normalized.rows[rowIndex] || [];
1414
+ html += `<tr class="cm-draftly-table-row cm-draftly-table-body-row${rowIndex % 2 === 1 ? " cm-draftly-table-row-even" : ""}${rowIndex === normalized.rows.length - 1 ? " cm-draftly-table-row-last" : ""}">`;
1415
+ for (let index = 0; index < normalized.headers.length; index++) {
1416
+ const alignment = normalized.alignments[index] || "left";
1417
+ const content = await renderCellToHtml(row[index] || "", config);
1418
+ html += `<td class="cm-draftly-table-cell${alignment === "center" ? " cm-draftly-table-cell-center" : alignment === "right" ? " cm-draftly-table-cell-right" : ""}${index === normalized.headers.length - 1 ? " cm-draftly-table-cell-last" : ""}">${content}</td>`;
1419
+ }
1420
+ html += "</tr>";
1421
+ }
1422
+ html += "</tbody></table></div>";
1423
+ return html;
1424
+ }
1425
+ function getVisibleBounds(rawCellText) {
1426
+ const leading = rawCellText.length - rawCellText.trimStart().length;
1427
+ const trailing = rawCellText.length - rawCellText.trimEnd().length;
1428
+ const trimmedLength = rawCellText.trim().length;
1429
+ if (trimmedLength === 0) {
1430
+ const placeholderOffset = Math.min(Math.floor(rawCellText.length / 2), Math.max(rawCellText.length - 1, 0));
1431
+ return {
1432
+ startOffset: placeholderOffset,
1433
+ endOffset: Math.min(placeholderOffset + 1, rawCellText.length)
1434
+ };
1435
+ }
1436
+ return {
1437
+ startOffset: leading,
1438
+ endOffset: rawCellText.length - trailing
1439
+ };
1440
+ }
1441
+ function isBodyRowEmpty(row) {
1442
+ return row.every((cell) => normalizeCellContent(cell.rawText) === "");
1443
+ }
1444
+ function buildTableFromInfo(tableInfo) {
1445
+ return {
1446
+ headers: tableInfo.headerCells.map((cell) => normalizeCellContent(cell.rawText)),
1447
+ alignments: [...tableInfo.alignments],
1448
+ rows: tableInfo.bodyCells.map((row) => row.map((cell) => normalizeCellContent(cell.rawText)))
1449
+ };
1450
+ }
1451
+ function getRowLineIndex(rowIndex) {
1452
+ return rowIndex === 0 ? 0 : rowIndex + 1;
1453
+ }
1454
+ function getCellAnchorInFormattedTable(formattedTable, rowIndex, columnIndex, offset = 0) {
1455
+ const lines = formattedTable.split("\n");
1456
+ const lineIndex = getRowLineIndex(rowIndex);
1457
+ const lineText = lines[lineIndex] || "";
1458
+ const pipes = getPipePositions(lineText);
1459
+ if (pipes.length < columnIndex + 2) {
1460
+ return formattedTable.length;
1461
+ }
1462
+ const rawFrom = pipes[columnIndex] + 1;
1463
+ const rawTo = pipes[columnIndex + 1];
1464
+ const visible = getVisibleBounds(lineText.slice(rawFrom, rawTo));
1465
+ const lineOffset = lines.slice(0, lineIndex).reduce((sum, line) => sum + line.length + 1, 0);
1466
+ return lineOffset + Math.min(rawFrom + visible.startOffset + offset, rawFrom + Math.max(visible.endOffset - 1, visible.startOffset));
1467
+ }
1468
+ function createTableInsert(state, from, to, tableMarkdown) {
1469
+ let insert = tableMarkdown;
1470
+ let prefixLength = 0;
1471
+ const startLine = state.doc.lineAt(from);
1472
+ if (startLine.number === 1 || state.doc.line(startLine.number - 1).text.trim() !== "") {
1473
+ insert = "\n" + insert;
1474
+ prefixLength = 1;
1475
+ }
1476
+ const endLine = state.doc.lineAt(Math.max(from, to));
1477
+ if (endLine.number === state.doc.lines || state.doc.line(endLine.number + 1).text.trim() !== "") {
1478
+ insert += "\n";
1479
+ }
1480
+ return { from, to, insert, prefixLength };
1481
+ }
1482
+ function readTableInfo(state, nodeFrom, nodeTo) {
1483
+ const startLine = state.doc.lineAt(nodeFrom);
1484
+ const endLine = state.doc.lineAt(nodeTo);
1485
+ const delimiterLineNumber = startLine.number + 1;
1486
+ if (delimiterLineNumber > endLine.number) {
1487
+ return null;
1488
+ }
1489
+ const delimiterLine = state.doc.line(delimiterLineNumber);
1490
+ const alignments = parseDelimiterAlignments(delimiterLine.text);
1491
+ if (!alignments) {
1492
+ return null;
1493
+ }
1494
+ let effectiveEndLineNumber = delimiterLineNumber;
1495
+ for (let lineNumber = delimiterLineNumber + 1; lineNumber <= endLine.number; lineNumber++) {
1496
+ const line = state.doc.line(lineNumber);
1497
+ if (!isTableRowLine(line.text)) {
1498
+ break;
1499
+ }
1500
+ effectiveEndLineNumber = lineNumber;
1501
+ }
1502
+ const cellsByRow = [];
1503
+ for (let lineNumber = startLine.number; lineNumber <= effectiveEndLineNumber; lineNumber++) {
1504
+ if (lineNumber === delimiterLineNumber) {
1505
+ continue;
1506
+ }
1507
+ const line = state.doc.line(lineNumber);
1508
+ const pipes = getPipePositions(line.text);
1509
+ if (pipes.length < 2) {
1510
+ return null;
1511
+ }
1512
+ const isHeader = lineNumber === startLine.number;
1513
+ const rowIndex = isHeader ? 0 : cellsByRow.length;
1514
+ const cells = [];
1515
+ for (let columnIndex = 0; columnIndex < pipes.length - 1; columnIndex++) {
1516
+ const from = line.from + pipes[columnIndex] + 1;
1517
+ const to = line.from + pipes[columnIndex + 1];
1518
+ const rawText = line.text.slice(pipes[columnIndex] + 1, pipes[columnIndex + 1]);
1519
+ const visible = getVisibleBounds(rawText);
1520
+ cells.push({
1521
+ rowKind: isHeader ? "header" : "body",
1522
+ rowIndex,
1523
+ columnIndex,
1524
+ from,
1525
+ to,
1526
+ contentFrom: from + visible.startOffset,
1527
+ contentTo: from + visible.endOffset,
1528
+ lineFrom: line.from,
1529
+ lineNumber,
1530
+ rawText
1531
+ });
1532
+ }
1533
+ cellsByRow.push(cells);
1534
+ }
1535
+ if (cellsByRow.length === 0) {
1536
+ return null;
1537
+ }
1538
+ return {
1539
+ from: startLine.from,
1540
+ to: state.doc.line(effectiveEndLineNumber).to,
1541
+ startLineNumber: startLine.number,
1542
+ delimiterLineNumber,
1543
+ endLineNumber: effectiveEndLineNumber,
1544
+ columnCount: cellsByRow[0].length,
1545
+ alignments: Array.from({ length: cellsByRow[0].length }, (_, index) => alignments[index] || "left"),
1546
+ cellsByRow,
1547
+ headerCells: cellsByRow[0],
1548
+ bodyCells: cellsByRow.slice(1)
1549
+ };
1550
+ }
1551
+ function getTableInfoAtPosition(state, position) {
1552
+ let resolved = null;
1553
+ syntaxTree(state).iterate({
1554
+ enter: (node) => {
1555
+ if (resolved || node.name !== "Table") {
1556
+ return;
1557
+ }
1558
+ const info = readTableInfo(state, node.from, node.to);
1559
+ if (info && position >= info.from && position <= info.to) {
1560
+ resolved = info;
1561
+ }
1562
+ }
1563
+ });
1564
+ return resolved;
1565
+ }
1566
+ function findCellAtPosition(tableInfo, position) {
1567
+ for (const row of tableInfo.cellsByRow) {
1568
+ for (const cell of row) {
1569
+ if (position >= cell.from && position <= cell.to) {
1570
+ return cell;
1571
+ }
1572
+ }
1573
+ }
1574
+ for (const row of tableInfo.cellsByRow) {
1575
+ for (const cell of row) {
1576
+ if (position >= cell.from - 1 && position <= cell.to + 1) {
1577
+ return cell;
1578
+ }
1579
+ }
1580
+ }
1581
+ let nearestCell = null;
1582
+ let nearestDistance = Number.POSITIVE_INFINITY;
1583
+ for (const row of tableInfo.cellsByRow) {
1584
+ for (const cell of row) {
1585
+ const distance = Math.min(Math.abs(position - cell.from), Math.abs(position - cell.to));
1586
+ if (distance < nearestDistance) {
1587
+ nearestCell = cell;
1588
+ nearestDistance = distance;
1589
+ }
1590
+ }
1591
+ }
1592
+ return nearestCell;
1593
+ }
1594
+ function clampCellPosition(cell, position) {
1595
+ const cellEnd = Math.max(cell.contentFrom, cell.contentTo);
1596
+ return Math.max(cell.contentFrom, Math.min(position, cellEnd));
1597
+ }
1598
+ function collectBreakRanges(tableInfo) {
1599
+ const ranges = [];
1600
+ for (const row of tableInfo.cellsByRow) {
1601
+ for (const cell of row) {
1602
+ let match;
1603
+ const regex = new RegExp(BREAK_TAG_REGEX);
1604
+ while ((match = regex.exec(cell.rawText)) !== null) {
1605
+ ranges.push({
1606
+ from: cell.from + match.index,
1607
+ to: cell.from + match.index + match[0].length
1608
+ });
1609
+ }
1610
+ }
1611
+ }
1612
+ return ranges;
1613
+ }
1614
+ var lineDecorations = {
1615
+ header: Decoration.line({ class: "cm-draftly-table-row cm-draftly-table-header-row" }),
1616
+ delimiter: Decoration.line({ class: "cm-draftly-table-row cm-draftly-table-delimiter-row" }),
1617
+ body: Decoration.line({ class: "cm-draftly-table-row cm-draftly-table-body-row" }),
1618
+ even: Decoration.line({ class: "cm-draftly-table-row cm-draftly-table-body-row cm-draftly-table-row-even" }),
1619
+ last: Decoration.line({ class: "cm-draftly-table-row-last" })
1620
+ };
1621
+ var cellDecorations = {
1622
+ "th-left": Decoration.mark({ class: "cm-draftly-table-cell cm-draftly-table-th" }),
1623
+ "th-center": Decoration.mark({ class: "cm-draftly-table-cell cm-draftly-table-th cm-draftly-table-cell-center" }),
1624
+ "th-right": Decoration.mark({ class: "cm-draftly-table-cell cm-draftly-table-th cm-draftly-table-cell-right" }),
1625
+ "th-left-last": Decoration.mark({ class: "cm-draftly-table-cell cm-draftly-table-th cm-draftly-table-cell-last" }),
1626
+ "th-center-last": Decoration.mark({
1627
+ class: "cm-draftly-table-cell cm-draftly-table-th cm-draftly-table-cell-center cm-draftly-table-cell-last"
1628
+ }),
1629
+ "th-right-last": Decoration.mark({
1630
+ class: "cm-draftly-table-cell cm-draftly-table-th cm-draftly-table-cell-right cm-draftly-table-cell-last"
1631
+ }),
1632
+ "td-left": Decoration.mark({ class: "cm-draftly-table-cell" }),
1633
+ "td-center": Decoration.mark({ class: "cm-draftly-table-cell cm-draftly-table-cell-center" }),
1634
+ "td-right": Decoration.mark({ class: "cm-draftly-table-cell cm-draftly-table-cell-right" }),
1635
+ "td-left-last": Decoration.mark({ class: "cm-draftly-table-cell cm-draftly-table-cell-last" }),
1636
+ "td-center-last": Decoration.mark({
1637
+ class: "cm-draftly-table-cell cm-draftly-table-cell-center cm-draftly-table-cell-last"
1638
+ }),
1639
+ "td-right-last": Decoration.mark({
1640
+ class: "cm-draftly-table-cell cm-draftly-table-cell-right cm-draftly-table-cell-last"
1641
+ })
1642
+ };
1643
+ function getCellDecoration(isHeader, alignment, isLastCell) {
1644
+ const key = `${isHeader ? "th" : "td"}-${alignment}${isLastCell ? "-last" : ""}`;
1645
+ return cellDecorations[key];
1646
+ }
1647
+ var TablePlugin = class extends DecorationPlugin {
1648
+ name = "table";
1649
+ version = "2.0.0";
1650
+ decorationPriority = 20;
1651
+ requiredNodes = ["Table", "TableHeader", "TableDelimiter", "TableRow", "TableCell"];
1652
+ draftlyConfig;
1653
+ pendingNormalizationView = null;
1654
+ pendingPaddingView = null;
1655
+ pendingSelectionRepairView = null;
1656
+ /** Stores the editor config for preview rendering and shared behavior. */
1657
+ onRegister(context) {
1658
+ super.onRegister(context);
1659
+ this.draftlyConfig = context.config;
1660
+ }
1661
+ /** Exposes the plugin theme used for editor and preview styling. */
1178
1662
  get theme() {
1179
1663
  return theme6;
1180
1664
  }
1665
+ /** Enables GFM table parsing for the editor and preview renderer. */
1666
+ getMarkdownConfig() {
1667
+ return Table;
1668
+ }
1669
+ /** Registers block wrappers and atomic ranges for the table UI. */
1670
+ getExtensions() {
1671
+ return [
1672
+ Prec.highest(keymap.of(this.buildTableKeymap())),
1673
+ EditorView.blockWrappers.of((view) => this.computeBlockWrappers(view)),
1674
+ EditorView.atomicRanges.of((view) => this.computeAtomicRanges(view)),
1675
+ EditorView.domEventHandlers({
1676
+ keydown: (event, view) => this.handleDomKeydown(view, event)
1677
+ })
1678
+ ];
1679
+ }
1680
+ /** Provides the table-specific keyboard shortcuts and navigation. */
1681
+ getKeymap() {
1682
+ return [];
1683
+ }
1684
+ /** Builds the high-priority key bindings used inside tables. */
1685
+ buildTableKeymap() {
1686
+ return [
1687
+ { key: "Mod-Shift-t", run: (view) => this.insertTable(view), preventDefault: true },
1688
+ { key: "Mod-Alt-ArrowDown", run: (view) => this.addRow(view), preventDefault: true },
1689
+ { key: "Mod-Alt-ArrowRight", run: (view) => this.addColumn(view), preventDefault: true },
1690
+ { key: "Mod-Alt-Backspace", run: (view) => this.removeRow(view), preventDefault: true },
1691
+ { key: "Mod-Alt-Delete", run: (view) => this.removeColumn(view), preventDefault: true },
1692
+ { key: "Tab", run: (view) => this.handleTab(view, false) },
1693
+ { key: "Shift-Tab", run: (view) => this.handleTab(view, true) },
1694
+ { key: "ArrowLeft", run: (view) => this.handleArrowHorizontal(view, false) },
1695
+ { key: "ArrowRight", run: (view) => this.handleArrowHorizontal(view, true) },
1696
+ { key: "ArrowUp", run: (view) => this.handleArrowVertical(view, false) },
1697
+ { key: "ArrowDown", run: (view) => this.handleArrowVertical(view, true) },
1698
+ { key: "Enter", run: (view) => this.handleEnter(view) },
1699
+ { key: "Shift-Enter", run: (view) => this.insertBreakTag(view), preventDefault: true },
1700
+ { key: "Backspace", run: (view) => this.handleBreakDeletion(view, false) },
1701
+ { key: "Delete", run: (view) => this.handleBreakDeletion(view, true) }
1702
+ ];
1703
+ }
1704
+ /** Schedules an initial normalization pass once the view is ready. */
1705
+ onViewReady(view) {
1706
+ this.scheduleNormalization(view);
1707
+ }
1708
+ /** Re-schedules normalization after user-driven document changes. */
1709
+ onViewUpdate(update) {
1710
+ if (update.docChanged && !update.transactions.some((transaction) => transaction.annotation(normalizeAnnotation))) {
1711
+ this.schedulePadding(update.view);
1712
+ }
1713
+ if (update.selectionSet && !update.transactions.some((transaction) => transaction.annotation(repairSelectionAnnotation))) {
1714
+ this.scheduleSelectionRepair(update.view);
1715
+ }
1716
+ }
1717
+ /** Intercepts table-specific DOM key handling before browser defaults run. */
1718
+ handleDomKeydown(view, event) {
1719
+ if (event.defaultPrevented || event.isComposing || event.altKey || event.metaKey || event.ctrlKey) {
1720
+ return false;
1721
+ }
1722
+ let handled = false;
1723
+ if (event.key === "Tab") {
1724
+ handled = this.handleTab(view, event.shiftKey);
1725
+ } else if (event.key === "Enter" && event.shiftKey) {
1726
+ handled = this.insertBreakTag(view);
1727
+ } else if (event.key === "Enter") {
1728
+ handled = this.handleEnter(view);
1729
+ } else if (event.key === "ArrowLeft") {
1730
+ handled = this.handleArrowHorizontal(view, false);
1731
+ } else if (event.key === "ArrowRight") {
1732
+ handled = this.handleArrowHorizontal(view, true);
1733
+ } else if (event.key === "ArrowUp") {
1734
+ handled = this.handleArrowVertical(view, false);
1735
+ } else if (event.key === "ArrowDown") {
1736
+ handled = this.handleArrowVertical(view, true);
1737
+ } else if (event.key === "Backspace") {
1738
+ handled = this.handleBreakDeletion(view, false);
1739
+ } else if (event.key === "Delete") {
1740
+ handled = this.handleBreakDeletion(view, true);
1741
+ }
1742
+ if (handled) {
1743
+ event.preventDefault();
1744
+ event.stopPropagation();
1745
+ }
1746
+ return handled;
1747
+ }
1748
+ /** Builds the visual table decorations for every parsed table block. */
1181
1749
  buildDecorations(ctx) {
1182
1750
  const { view, decorations } = ctx;
1183
- const tree = syntaxTree(view.state);
1184
- const htmlGroups = [];
1185
- const htmlTags = [];
1186
- tree.iterate({
1751
+ syntaxTree(view.state).iterate({
1187
1752
  enter: (node) => {
1188
- const { from, to, name } = node;
1189
- if (name === "Comment") {
1190
- decorations.push(htmlMarkDecorations["html-comment"].range(from, to));
1753
+ if (node.name !== "Table") {
1191
1754
  return;
1192
1755
  }
1193
- if (name === "HTMLTag") {
1194
- const content = view.state.sliceDoc(from, to);
1195
- const parsed = parseHTMLTag(content);
1196
- if (parsed) {
1197
- htmlTags.push({
1198
- from,
1199
- to,
1200
- tagName: parsed.tagName,
1201
- isClosing: parsed.isClosing,
1202
- isSelfClosing: parsed.isSelfClosing
1203
- });
1204
- }
1756
+ const tableInfo = readTableInfo(view.state, node.from, node.to);
1757
+ if (tableInfo) {
1758
+ this.decorateTable(view, decorations, tableInfo);
1205
1759
  }
1206
- if (name === "HTMLBlock") {
1207
- const last = htmlGroups[htmlGroups.length - 1];
1208
- if (last) {
1209
- const gap = view.state.sliceDoc(last.to, from);
1210
- if (!gap.trim()) {
1211
- last.to = to;
1212
- return;
1760
+ }
1761
+ });
1762
+ }
1763
+ /** Renders the full table node to semantic preview HTML. */
1764
+ async renderToHTML(node, _children, ctx) {
1765
+ if (node.name === "Table") {
1766
+ const content = ctx.sliceDoc(node.from, node.to);
1767
+ const { tableMarkdown, trailingMarkdown } = splitTableAndTrailingMarkdown(content);
1768
+ const parsed = parseTableMarkdown(tableMarkdown);
1769
+ if (!parsed) {
1770
+ return null;
1771
+ }
1772
+ const tableHtml = await renderTableToHtml(parsed, this.draftlyConfig);
1773
+ if (!trailingMarkdown.trim()) {
1774
+ return tableHtml;
1775
+ }
1776
+ return tableHtml + await createPreviewRenderer(trailingMarkdown, this.draftlyConfig).render();
1777
+ }
1778
+ if (TABLE_SUB_NODE_NAMES.has(node.name)) {
1779
+ return "";
1780
+ }
1781
+ return null;
1782
+ }
1783
+ /** Computes the block wrapper ranges used to group table lines. */
1784
+ computeBlockWrappers(view) {
1785
+ const wrappers = [];
1786
+ syntaxTree(view.state).iterate({
1787
+ enter: (node) => {
1788
+ if (node.name !== "Table") {
1789
+ return;
1790
+ }
1791
+ const tableInfo = readTableInfo(view.state, node.from, node.to);
1792
+ if (tableInfo) {
1793
+ wrappers.push(tableBlockWrapper.range(tableInfo.from, tableInfo.to));
1794
+ }
1795
+ }
1796
+ });
1797
+ return BlockWrapper.set(wrappers, true);
1798
+ }
1799
+ /** Computes atomic ranges for delimiters and inline break tags. */
1800
+ computeAtomicRanges(view) {
1801
+ const ranges = [];
1802
+ syntaxTree(view.state).iterate({
1803
+ enter: (node) => {
1804
+ if (node.name !== "Table") {
1805
+ return;
1806
+ }
1807
+ const tableInfo = readTableInfo(view.state, node.from, node.to);
1808
+ if (!tableInfo) {
1809
+ return;
1810
+ }
1811
+ for (let lineNumber = tableInfo.startLineNumber; lineNumber <= tableInfo.endLineNumber; lineNumber++) {
1812
+ const line = view.state.doc.line(lineNumber);
1813
+ if (lineNumber === tableInfo.delimiterLineNumber) {
1814
+ ranges.push(delimiterReplace.range(line.from, line.to));
1815
+ continue;
1816
+ }
1817
+ const pipes = getPipePositions(line.text);
1818
+ for (const pipe of pipes) {
1819
+ ranges.push(pipeReplace.range(line.from + pipe, line.from + pipe + 1));
1820
+ }
1821
+ for (let columnIndex = 0; columnIndex < pipes.length - 1; columnIndex++) {
1822
+ const rawFrom = pipes[columnIndex] + 1;
1823
+ const rawTo = pipes[columnIndex + 1];
1824
+ const rawText = line.text.slice(rawFrom, rawTo);
1825
+ const visible = getVisibleBounds(rawText);
1826
+ if (visible.startOffset > 0) {
1827
+ ranges.push(pipeReplace.range(line.from + rawFrom, line.from + rawFrom + visible.startOffset));
1828
+ }
1829
+ if (visible.endOffset < rawText.length) {
1830
+ ranges.push(pipeReplace.range(line.from + rawFrom + visible.endOffset, line.from + rawTo));
1831
+ }
1832
+ let match;
1833
+ const regex = new RegExp(BREAK_TAG_REGEX);
1834
+ while ((match = regex.exec(rawText)) !== null) {
1835
+ ranges.push(
1836
+ Decoration.replace({ widget: new TableBreakWidget() }).range(
1837
+ line.from + rawFrom + match.index,
1838
+ line.from + rawFrom + match.index + match[0].length
1839
+ )
1840
+ );
1213
1841
  }
1214
1842
  }
1215
- htmlGroups.push({ from, to });
1216
1843
  }
1217
1844
  }
1218
1845
  });
1219
- const inlineElements = [];
1220
- const usedTags = /* @__PURE__ */ new Set();
1221
- for (let i = 0; i < htmlTags.length; i++) {
1222
- if (usedTags.has(i)) continue;
1223
- const openTag = htmlTags[i];
1224
- if (openTag.isClosing) continue;
1225
- if (openTag.isSelfClosing) {
1226
- inlineElements.push({
1227
- from: openTag.from,
1228
- to: openTag.to,
1229
- content: view.state.sliceDoc(openTag.from, openTag.to)
1230
- });
1231
- usedTags.add(i);
1846
+ return RangeSet.of(ranges, true);
1847
+ }
1848
+ /** Applies row, cell, and control decorations for a single table. */
1849
+ decorateTable(view, decorations, tableInfo) {
1850
+ for (let lineNumber = tableInfo.startLineNumber; lineNumber <= tableInfo.endLineNumber; lineNumber++) {
1851
+ const line = view.state.doc.line(lineNumber);
1852
+ const isHeader = lineNumber === tableInfo.startLineNumber;
1853
+ const isDelimiter = lineNumber === tableInfo.delimiterLineNumber;
1854
+ const isLastBody = !isHeader && !isDelimiter && lineNumber === tableInfo.endLineNumber;
1855
+ const bodyIndex = isHeader || isDelimiter ? -1 : lineNumber - tableInfo.delimiterLineNumber - 1;
1856
+ if (isHeader) {
1857
+ decorations.push(lineDecorations.header.range(line.from));
1858
+ } else if (isDelimiter) {
1859
+ decorations.push(lineDecorations.delimiter.range(line.from));
1860
+ } else if (bodyIndex % 2 === 1) {
1861
+ decorations.push(lineDecorations.even.range(line.from));
1862
+ } else {
1863
+ decorations.push(lineDecorations.body.range(line.from));
1864
+ }
1865
+ if (isLastBody) {
1866
+ decorations.push(lineDecorations.last.range(line.from));
1867
+ }
1868
+ if (isDelimiter) {
1869
+ decorations.push(delimiterReplace.range(line.from, line.to));
1232
1870
  continue;
1233
1871
  }
1234
- const openLine = view.state.doc.lineAt(openTag.from);
1235
- let depth = 1;
1236
- let closeTagIndex = null;
1237
- for (let j = i + 1; j < htmlTags.length && depth > 0; j++) {
1238
- const tag = htmlTags[j];
1872
+ this.decorateLine(decorations, line.from, line.text, tableInfo.alignments, isHeader);
1873
+ }
1874
+ decorations.push(
1875
+ Decoration.widget({
1876
+ widget: new TableControlsWidget(
1877
+ (view2) => {
1878
+ const liveTable = getTableInfoAtPosition(view2.state, tableInfo.from);
1879
+ if (liveTable) {
1880
+ this.appendRow(view2, liveTable, liveTable.columnCount - 1);
1881
+ }
1882
+ },
1883
+ (view2) => {
1884
+ const liveTable = getTableInfoAtPosition(view2.state, tableInfo.from);
1885
+ if (liveTable) {
1886
+ this.appendColumn(view2, liveTable);
1887
+ }
1888
+ }
1889
+ ),
1890
+ side: 1
1891
+ }).range(tableInfo.to)
1892
+ );
1893
+ }
1894
+ /** Applies the visual cell decorations for a single table row line. */
1895
+ decorateLine(decorations, lineFrom, lineText, alignments, isHeader) {
1896
+ const pipes = getPipePositions(lineText);
1897
+ if (pipes.length < 2) {
1898
+ return;
1899
+ }
1900
+ for (const pipe of pipes) {
1901
+ decorations.push(pipeReplace.range(lineFrom + pipe, lineFrom + pipe + 1));
1902
+ }
1903
+ for (let columnIndex = 0; columnIndex < pipes.length - 1; columnIndex++) {
1904
+ const rawFrom = pipes[columnIndex] + 1;
1905
+ const rawTo = pipes[columnIndex + 1];
1906
+ const rawText = lineText.slice(rawFrom, rawTo);
1907
+ const visible = getVisibleBounds(rawText);
1908
+ const absoluteFrom = lineFrom + rawFrom;
1909
+ const absoluteTo = lineFrom + rawTo;
1910
+ if (visible.startOffset > 0) {
1911
+ decorations.push(pipeReplace.range(absoluteFrom, absoluteFrom + visible.startOffset));
1912
+ }
1913
+ if (visible.endOffset < rawText.length) {
1914
+ decorations.push(pipeReplace.range(absoluteFrom + visible.endOffset, absoluteTo));
1915
+ }
1916
+ decorations.push(
1917
+ getCellDecoration(isHeader, alignments[columnIndex] || "left", columnIndex === pipes.length - 2).range(
1918
+ absoluteFrom,
1919
+ absoluteTo
1920
+ )
1921
+ );
1922
+ let match;
1923
+ const regex = new RegExp(BREAK_TAG_REGEX);
1924
+ while ((match = regex.exec(rawText)) !== null) {
1925
+ decorations.push(
1926
+ Decoration.replace({ widget: new TableBreakWidget() }).range(
1927
+ absoluteFrom + match.index,
1928
+ absoluteFrom + match.index + match[0].length
1929
+ )
1930
+ );
1931
+ }
1932
+ }
1933
+ }
1934
+ /** Normalizes every parsed table block back into canonical markdown. */
1935
+ normalizeTables(view) {
1936
+ const changes = [];
1937
+ syntaxTree(view.state).iterate({
1938
+ enter: (node) => {
1939
+ if (node.name !== "Table") {
1940
+ return;
1941
+ }
1942
+ const content = view.state.sliceDoc(node.from, node.to);
1943
+ const { tableMarkdown } = splitTableAndTrailingMarkdown(content);
1944
+ const parsed = parseTableMarkdown(tableMarkdown);
1945
+ if (!parsed) {
1946
+ return;
1947
+ }
1948
+ const formatted = formatTableMarkdown(parsed);
1949
+ const change = createTableInsert(view.state, node.from, node.from + tableMarkdown.length, formatted);
1950
+ if (change.insert !== tableMarkdown || change.from !== node.from || change.to !== node.from + tableMarkdown.length) {
1951
+ changes.push({
1952
+ from: change.from,
1953
+ to: change.to,
1954
+ insert: change.insert
1955
+ });
1956
+ }
1957
+ }
1958
+ });
1959
+ if (changes.length > 0) {
1960
+ view.dispatch({
1961
+ changes: changes.sort((left, right) => right.from - left.from),
1962
+ annotations: normalizeAnnotation.of(true)
1963
+ });
1964
+ }
1965
+ }
1966
+ /** Defers table normalization until the current update cycle is finished. */
1967
+ scheduleNormalization(view) {
1968
+ if (this.pendingNormalizationView === view) {
1969
+ return;
1970
+ }
1971
+ this.pendingNormalizationView = view;
1972
+ queueMicrotask(() => {
1973
+ if (this.pendingNormalizationView !== view) {
1974
+ return;
1975
+ }
1976
+ this.pendingNormalizationView = null;
1977
+ this.normalizeTables(view);
1978
+ });
1979
+ }
1980
+ /** Adds missing spacer lines above and below tables after edits. */
1981
+ ensureTablePadding(view) {
1982
+ const changes = [];
1983
+ syntaxTree(view.state).iterate({
1984
+ enter: (node) => {
1985
+ if (node.name !== "Table") {
1986
+ return;
1987
+ }
1988
+ const tableInfo = readTableInfo(view.state, node.from, node.to);
1989
+ if (!tableInfo) {
1990
+ return;
1991
+ }
1992
+ const startLine = view.state.doc.lineAt(tableInfo.from);
1993
+ if (startLine.number === 1) {
1994
+ changes.push({ from: startLine.from, to: startLine.from, insert: "\n" });
1995
+ } else {
1996
+ const previousLine = view.state.doc.line(startLine.number - 1);
1997
+ if (previousLine.text.trim() !== "") {
1998
+ changes.push({ from: startLine.from, to: startLine.from, insert: "\n" });
1999
+ }
2000
+ }
2001
+ const endLine = view.state.doc.lineAt(tableInfo.to);
2002
+ if (endLine.number === view.state.doc.lines) {
2003
+ changes.push({ from: endLine.to, to: endLine.to, insert: "\n" });
2004
+ } else {
2005
+ const nextLine = view.state.doc.line(endLine.number + 1);
2006
+ if (nextLine.text.trim() !== "") {
2007
+ changes.push({ from: endLine.to, to: endLine.to, insert: "\n" });
2008
+ }
2009
+ }
2010
+ }
2011
+ });
2012
+ if (changes.length > 0) {
2013
+ view.dispatch({
2014
+ changes: changes.sort((left, right) => right.from - left.from),
2015
+ annotations: normalizeAnnotation.of(true)
2016
+ });
2017
+ }
2018
+ }
2019
+ /** Schedules a padding-only pass after the current update cycle finishes. */
2020
+ schedulePadding(view) {
2021
+ if (this.pendingPaddingView === view) {
2022
+ return;
2023
+ }
2024
+ this.pendingPaddingView = view;
2025
+ queueMicrotask(() => {
2026
+ if (this.pendingPaddingView !== view) {
2027
+ return;
2028
+ }
2029
+ this.pendingPaddingView = null;
2030
+ this.ensureTablePadding(view);
2031
+ });
2032
+ }
2033
+ /** Repairs carets that land in hidden table markup instead of editable cell content. */
2034
+ ensureTableSelection(view) {
2035
+ const selection = view.state.selection.main;
2036
+ if (!selection.empty) {
2037
+ return;
2038
+ }
2039
+ const tableInfo = getTableInfoAtPosition(view.state, selection.head);
2040
+ if (!tableInfo) {
2041
+ return;
2042
+ }
2043
+ const cell = findCellAtPosition(tableInfo, selection.head);
2044
+ if (!cell) {
2045
+ return;
2046
+ }
2047
+ const anchor = clampCellPosition(cell, selection.head);
2048
+ if (anchor === selection.head) {
2049
+ return;
2050
+ }
2051
+ view.dispatch({
2052
+ selection: { anchor },
2053
+ annotations: repairSelectionAnnotation.of(true),
2054
+ scrollIntoView: true
2055
+ });
2056
+ }
2057
+ /** Schedules table selection repair after the current update finishes. */
2058
+ scheduleSelectionRepair(view) {
2059
+ if (this.pendingSelectionRepairView === view) {
2060
+ return;
2061
+ }
2062
+ this.pendingSelectionRepairView = view;
2063
+ queueMicrotask(() => {
2064
+ if (this.pendingSelectionRepairView !== view) {
2065
+ return;
2066
+ }
2067
+ this.pendingSelectionRepairView = null;
2068
+ this.ensureTableSelection(view);
2069
+ });
2070
+ }
2071
+ /** Rewrites a table block and restores the caret to a target cell position. */
2072
+ replaceTable(view, tableInfo, parsed, targetRowIndex, targetColumnIndex, offset = 0) {
2073
+ const formatted = formatTableMarkdown(parsed);
2074
+ const change = createTableInsert(view.state, tableInfo.from, tableInfo.to, formatted);
2075
+ const selection = change.from + change.prefixLength + getCellAnchorInFormattedTable(
2076
+ formatted,
2077
+ Math.max(0, targetRowIndex),
2078
+ Math.max(0, Math.min(targetColumnIndex, Math.max(parsed.headers.length - 1, 0))),
2079
+ Math.max(0, offset)
2080
+ );
2081
+ view.dispatch({
2082
+ changes: { from: change.from, to: change.to, insert: change.insert },
2083
+ selection: { anchor: selection }
2084
+ });
2085
+ }
2086
+ /** Inserts an empty body row below the given logical row index. */
2087
+ insertRowBelow(view, tableInfo, afterRowIndex, targetColumn) {
2088
+ const parsed = normalizeParsedTable(buildTableFromInfo(tableInfo));
2089
+ const insertBodyIndex = Math.max(0, Math.min(afterRowIndex, parsed.rows.length));
2090
+ parsed.rows.splice(insertBodyIndex, 0, buildEmptyRow(tableInfo.columnCount));
2091
+ this.replaceTable(view, tableInfo, parsed, insertBodyIndex + 1, targetColumn);
2092
+ }
2093
+ /** Inserts a starter table near the current cursor line. */
2094
+ insertTable(view) {
2095
+ const { state } = view;
2096
+ const cursor = state.selection.main.head;
2097
+ const line = state.doc.lineAt(cursor);
2098
+ const insertAt = line.text.trim() ? line.to : line.from;
2099
+ const formatted = formatTableMarkdown(TABLE_TEMPLATE);
2100
+ const change = createTableInsert(state, insertAt, insertAt, formatted);
2101
+ const selection = change.from + change.prefixLength + getCellAnchorInFormattedTable(formatted, 0, 0);
2102
+ view.dispatch({
2103
+ changes: { from: change.from, to: change.to, insert: change.insert },
2104
+ selection: { anchor: selection }
2105
+ });
2106
+ return true;
2107
+ }
2108
+ /** Adds a new empty body row to the active table. */
2109
+ addRow(view) {
2110
+ const tableInfo = this.getTableAtCursor(view);
2111
+ if (!tableInfo) {
2112
+ return false;
2113
+ }
2114
+ const cell = this.getCurrentCell(view, tableInfo);
2115
+ this.appendRow(view, tableInfo, cell?.columnIndex || 0);
2116
+ return true;
2117
+ }
2118
+ /** Appends a row and keeps the caret in the requested column. */
2119
+ appendRow(view, tableInfo, targetColumn) {
2120
+ this.insertRowBelow(view, tableInfo, tableInfo.bodyCells.length, targetColumn);
2121
+ }
2122
+ /** Inserts a new column after the current column. */
2123
+ addColumn(view) {
2124
+ const tableInfo = this.getTableAtCursor(view);
2125
+ if (!tableInfo) {
2126
+ return false;
2127
+ }
2128
+ const cell = this.getCurrentCell(view, tableInfo);
2129
+ const insertAfter = cell?.columnIndex ?? tableInfo.columnCount - 1;
2130
+ const parsed = normalizeParsedTable(buildTableFromInfo(tableInfo));
2131
+ parsed.headers.splice(insertAfter + 1, 0, "");
2132
+ parsed.alignments.splice(insertAfter + 1, 0, "left");
2133
+ for (const row of parsed.rows) {
2134
+ row.splice(insertAfter + 1, 0, "");
2135
+ }
2136
+ this.replaceTable(view, tableInfo, parsed, cell?.rowIndex || 0, insertAfter + 1);
2137
+ return true;
2138
+ }
2139
+ /** Appends a new column at the far right of the table. */
2140
+ appendColumn(view, tableInfo) {
2141
+ const parsed = normalizeParsedTable(buildTableFromInfo(tableInfo));
2142
+ parsed.headers.push("");
2143
+ parsed.alignments.push("left");
2144
+ for (const row of parsed.rows) {
2145
+ row.push("");
2146
+ }
2147
+ this.replaceTable(view, tableInfo, parsed, 0, parsed.headers.length - 1);
2148
+ }
2149
+ /** Removes the current body row or clears the last remaining row. */
2150
+ removeRow(view) {
2151
+ const tableInfo = this.getTableAtCursor(view);
2152
+ if (!tableInfo) {
2153
+ return false;
2154
+ }
2155
+ const cell = this.getCurrentCell(view, tableInfo);
2156
+ if (!cell || cell.rowKind !== "body") {
2157
+ return false;
2158
+ }
2159
+ const parsed = normalizeParsedTable(buildTableFromInfo(tableInfo));
2160
+ const bodyIndex = cell.rowIndex - 1;
2161
+ if (bodyIndex < 0 || bodyIndex >= parsed.rows.length) {
2162
+ return false;
2163
+ }
2164
+ if (parsed.rows.length === 1) {
2165
+ parsed.rows[0] = buildEmptyRow(tableInfo.columnCount);
2166
+ } else {
2167
+ parsed.rows.splice(bodyIndex, 1);
2168
+ }
2169
+ const nextRowIndex = Math.max(1, Math.min(cell.rowIndex, parsed.rows.length));
2170
+ this.replaceTable(view, tableInfo, parsed, nextRowIndex, Math.min(cell.columnIndex, tableInfo.columnCount - 1));
2171
+ return true;
2172
+ }
2173
+ /** Removes the current column when the table has more than one column. */
2174
+ removeColumn(view) {
2175
+ const tableInfo = this.getTableAtCursor(view);
2176
+ if (!tableInfo || tableInfo.columnCount <= 1) {
2177
+ return false;
2178
+ }
2179
+ const cell = this.getCurrentCell(view, tableInfo);
2180
+ const removeAt = cell?.columnIndex ?? tableInfo.columnCount - 1;
2181
+ const parsed = normalizeParsedTable(buildTableFromInfo(tableInfo));
2182
+ parsed.headers.splice(removeAt, 1);
2183
+ parsed.alignments.splice(removeAt, 1);
2184
+ for (const row of parsed.rows) {
2185
+ row.splice(removeAt, 1);
2186
+ }
2187
+ this.replaceTable(view, tableInfo, parsed, cell?.rowIndex || 0, Math.min(removeAt, parsed.headers.length - 1));
2188
+ return true;
2189
+ }
2190
+ /** Moves to the next or previous logical cell with Tab navigation. */
2191
+ handleTab(view, backwards) {
2192
+ const tableInfo = this.getTableAtCursor(view);
2193
+ if (!tableInfo) {
2194
+ return false;
2195
+ }
2196
+ const cell = this.getCurrentCell(view, tableInfo);
2197
+ if (!cell) {
2198
+ return false;
2199
+ }
2200
+ const cells = tableInfo.cellsByRow.flat();
2201
+ const currentIndex = cells.findIndex((candidate) => candidate.from === cell.from && candidate.to === cell.to);
2202
+ if (currentIndex < 0) {
2203
+ return false;
2204
+ }
2205
+ const nextIndex = backwards ? currentIndex - 1 : currentIndex + 1;
2206
+ if (nextIndex < 0) {
2207
+ return true;
2208
+ }
2209
+ if (nextIndex >= cells.length) {
2210
+ this.appendRow(view, tableInfo, 0);
2211
+ return true;
2212
+ }
2213
+ this.moveSelectionToCell(view, cells[nextIndex]);
2214
+ return true;
2215
+ }
2216
+ /** Moves horizontally between adjacent cells when the caret hits an edge. */
2217
+ handleArrowHorizontal(view, forward) {
2218
+ const tableInfo = this.getTableAtCursor(view);
2219
+ if (!tableInfo) {
2220
+ return false;
2221
+ }
2222
+ const cell = this.getCurrentCell(view, tableInfo);
2223
+ if (!cell) {
2224
+ return false;
2225
+ }
2226
+ const cursor = view.state.selection.main.head;
2227
+ const rightEdge = Math.max(cell.contentFrom, cell.contentTo);
2228
+ if (forward && cursor < rightEdge) {
2229
+ return false;
2230
+ }
2231
+ if (!forward && cursor > cell.contentFrom) {
2232
+ return false;
2233
+ }
2234
+ const row = tableInfo.cellsByRow[cell.rowIndex] || [];
2235
+ const nextCell = row[cell.columnIndex + (forward ? 1 : -1)];
2236
+ if (!nextCell) {
2237
+ return false;
2238
+ }
2239
+ this.moveSelectionToCell(view, nextCell);
2240
+ return true;
2241
+ }
2242
+ /** Moves vertically between rows while keeping the current column. */
2243
+ handleArrowVertical(view, forward) {
2244
+ const tableInfo = this.getTableAtCursor(view);
2245
+ if (!tableInfo) {
2246
+ return false;
2247
+ }
2248
+ const cell = this.getCurrentCell(view, tableInfo);
2249
+ if (!cell) {
2250
+ return false;
2251
+ }
2252
+ const nextRow = tableInfo.cellsByRow[cell.rowIndex + (forward ? 1 : -1)];
2253
+ if (!nextRow) {
2254
+ return false;
2255
+ }
2256
+ const nextCell = nextRow[cell.columnIndex];
2257
+ if (!nextCell) {
2258
+ return false;
2259
+ }
2260
+ this.moveSelectionToCell(view, nextCell);
2261
+ return true;
2262
+ }
2263
+ /** Advances downward on Enter and manages the trailing empty row behavior. */
2264
+ handleEnter(view) {
2265
+ const tableInfo = this.getTableAtCursor(view);
2266
+ if (!tableInfo) {
2267
+ return false;
2268
+ }
2269
+ const cell = this.getCurrentCell(view, tableInfo);
2270
+ if (!cell) {
2271
+ return false;
2272
+ }
2273
+ if (cell.rowKind === "body") {
2274
+ const currentRow = tableInfo.bodyCells[cell.rowIndex - 1];
2275
+ if (currentRow && isBodyRowEmpty(currentRow)) {
2276
+ const parsed = normalizeParsedTable(buildTableFromInfo(tableInfo));
2277
+ parsed.rows.splice(cell.rowIndex - 1, 1);
2278
+ const formatted = formatTableMarkdown(parsed);
2279
+ const change = createTableInsert(view.state, tableInfo.from, tableInfo.to, formatted);
2280
+ const anchor = Math.min(change.from + change.insert.length, view.state.doc.length + change.insert.length);
2281
+ view.dispatch({
2282
+ changes: { from: change.from, to: change.to, insert: change.insert },
2283
+ selection: { anchor }
2284
+ });
2285
+ return true;
2286
+ }
2287
+ }
2288
+ if (cell.rowKind === "body" && cell.rowIndex === tableInfo.cellsByRow.length - 1) {
2289
+ const parsed = normalizeParsedTable(buildTableFromInfo(tableInfo));
2290
+ parsed.rows.push(buildEmptyRow(tableInfo.columnCount));
2291
+ this.replaceTable(view, tableInfo, parsed, parsed.rows.length, cell.columnIndex);
2292
+ return true;
2293
+ }
2294
+ this.insertRowBelow(view, tableInfo, cell.rowIndex, cell.columnIndex);
2295
+ return true;
2296
+ }
2297
+ /** Inserts a canonical `<br />` token inside the current table cell. */
2298
+ insertBreakTag(view) {
2299
+ const tableInfo = this.getTableAtCursor(view);
2300
+ if (!tableInfo) {
2301
+ return false;
2302
+ }
2303
+ const selection = view.state.selection.main;
2304
+ view.dispatch({
2305
+ changes: { from: selection.from, to: selection.to, insert: BREAK_TAG },
2306
+ selection: { anchor: selection.from + BREAK_TAG.length }
2307
+ });
2308
+ return true;
2309
+ }
2310
+ /** Deletes a whole `<br />` token when backspace or delete hits it. */
2311
+ handleBreakDeletion(view, forward) {
2312
+ const tableInfo = this.getTableAtCursor(view);
2313
+ if (!tableInfo) {
2314
+ return false;
2315
+ }
2316
+ const selection = view.state.selection.main;
2317
+ const cursor = selection.head;
2318
+ for (const range of collectBreakRanges(tableInfo)) {
2319
+ const within = cursor > range.from && cursor < range.to;
2320
+ const matchesBackspace = !forward && cursor === range.to;
2321
+ const matchesDelete = forward && cursor === range.from;
2322
+ const overlapsSelection = !selection.empty && selection.from <= range.from && selection.to >= range.to;
2323
+ if (within || matchesBackspace || matchesDelete || overlapsSelection) {
2324
+ view.dispatch({
2325
+ changes: { from: range.from, to: range.to, insert: "" },
2326
+ selection: { anchor: range.from }
2327
+ });
2328
+ return true;
2329
+ }
2330
+ }
2331
+ return false;
2332
+ }
2333
+ /** Moves the current selection anchor into a target cell. */
2334
+ moveSelectionToCell(view, cell, offset = 0) {
2335
+ const end = Math.max(cell.contentFrom, cell.contentTo);
2336
+ view.dispatch({
2337
+ selection: { anchor: Math.min(cell.contentFrom + offset, end) },
2338
+ scrollIntoView: true
2339
+ });
2340
+ }
2341
+ /** Returns the table currently containing the editor cursor. */
2342
+ getTableAtCursor(view) {
2343
+ return getTableInfoAtPosition(view.state, view.state.selection.main.head);
2344
+ }
2345
+ /** Returns the active cell under the current selection head. */
2346
+ getCurrentCell(view, tableInfo) {
2347
+ return findCellAtPosition(tableInfo, view.state.selection.main.head);
2348
+ }
2349
+ };
2350
+ var theme6 = createTheme({
2351
+ default: {
2352
+ ".cm-draftly-table-wrapper, .cm-draftly-table-widget": {
2353
+ display: "table",
2354
+ width: "100%",
2355
+ borderCollapse: "separate",
2356
+ borderSpacing: "0",
2357
+ position: "relative",
2358
+ overflow: "visible",
2359
+ border: "1px solid var(--color-border, #d7dee7)",
2360
+ borderRadius: "0.75rem",
2361
+ backgroundColor: "var(--color-background, #ffffff)",
2362
+ "& .cm-draftly-table": {
2363
+ width: "100%",
2364
+ borderCollapse: "separate",
2365
+ borderSpacing: "0",
2366
+ tableLayout: "auto"
2367
+ },
2368
+ "& .cm-draftly-table-row": {
2369
+ display: "table-row !important"
2370
+ },
2371
+ "& .cm-draftly-table-header-row": {
2372
+ backgroundColor: "rgba(15, 23, 42, 0.04)"
2373
+ },
2374
+ "& .cm-draftly-table-row-even": {
2375
+ backgroundColor: "rgba(15, 23, 42, 0.02)"
2376
+ },
2377
+ "& .cm-draftly-table-delimiter-row": {
2378
+ display: "none !important"
2379
+ },
2380
+ "& .cm-draftly-table-cell": {
2381
+ display: "table-cell",
2382
+ minWidth: "4rem",
2383
+ minHeight: "2.5rem",
2384
+ height: "2.75rem",
2385
+ padding: "0.5rem 0.875rem",
2386
+ verticalAlign: "top",
2387
+ borderRight: "1px solid var(--color-border, #d7dee7)",
2388
+ borderBottom: "1px solid var(--color-border, #d7dee7)",
2389
+ whiteSpace: "normal",
2390
+ overflowWrap: "break-word",
2391
+ wordBreak: "normal",
2392
+ lineHeight: "1.6"
2393
+ },
2394
+ "& .cm-draftly-table-body-row": {
2395
+ minHeight: "2.75rem"
2396
+ },
2397
+ "& .cm-draftly-table-cell .cm-draftly-code-inline": {
2398
+ whiteSpace: "normal",
2399
+ overflowWrap: "anywhere"
2400
+ },
2401
+ "& .cm-draftly-table-th": {
2402
+ fontWeight: "600",
2403
+ borderBottomWidth: "2px"
2404
+ },
2405
+ "& .cm-draftly-table-cell-last": {
2406
+ borderRight: "none"
2407
+ },
2408
+ "& .cm-draftly-table-row-last .cm-draftly-table-cell": {
2409
+ borderBottom: "none"
2410
+ },
2411
+ "& .cm-draftly-table-cell-center": {
2412
+ textAlign: "center"
2413
+ },
2414
+ "& .cm-draftly-table-cell-right": {
2415
+ textAlign: "right"
2416
+ },
2417
+ "& .cm-draftly-table-break": {
2418
+ display: "inline"
2419
+ },
2420
+ "& .cm-draftly-table-controls-anchor": {
2421
+ position: "absolute",
2422
+ inset: "0",
2423
+ pointerEvents: "none"
2424
+ },
2425
+ "& .cm-draftly-table-control": {
2426
+ position: "absolute",
2427
+ width: "1.75rem",
2428
+ height: "1.75rem",
2429
+ border: "1px solid var(--color-border, #d7dee7)",
2430
+ borderRadius: "999px",
2431
+ backgroundColor: "var(--color-background, #ffffff)",
2432
+ color: "var(--color-text, #0f172a)",
2433
+ boxShadow: "0 10px 24px rgba(15, 23, 42, 0.12)",
2434
+ display: "inline-flex",
2435
+ alignItems: "center",
2436
+ justifyContent: "center",
2437
+ opacity: "0",
2438
+ pointerEvents: "auto",
2439
+ transition: "opacity 120ms ease, transform 120ms ease, background-color 120ms ease"
2440
+ },
2441
+ "& .cm-draftly-table-control:hover": {
2442
+ backgroundColor: "rgba(15, 23, 42, 0.05)"
2443
+ },
2444
+ "& .cm-draftly-table-control-column": {
2445
+ top: "50%",
2446
+ right: "-0.95rem",
2447
+ transform: "translate(0.35rem, -50%)"
2448
+ },
2449
+ "& .cm-draftly-table-control-row": {
2450
+ left: "50%",
2451
+ bottom: "-0.95rem",
2452
+ transform: "translate(-50%, 0.35rem)"
2453
+ },
2454
+ "&:hover .cm-draftly-table-control, &:focus-within .cm-draftly-table-control": {
2455
+ opacity: "1"
2456
+ },
2457
+ "&:hover .cm-draftly-table-control-column, &:focus-within .cm-draftly-table-control-column": {
2458
+ transform: "translate(0, -50%)"
2459
+ },
2460
+ "&:hover .cm-draftly-table-control-row, &:focus-within .cm-draftly-table-control-row": {
2461
+ transform: "translate(-50%, 0)"
2462
+ }
2463
+ }
2464
+ },
2465
+ dark: {
2466
+ ".cm-draftly-table-wrapper, .cm-draftly-table-widget": {
2467
+ borderColor: "var(--color-border, #30363d)",
2468
+ backgroundColor: "var(--color-background, #0d1117)",
2469
+ "& .cm-draftly-table-header-row": {
2470
+ backgroundColor: "rgba(255, 255, 255, 0.05)"
2471
+ },
2472
+ "& .cm-draftly-table-row-even": {
2473
+ backgroundColor: "rgba(255, 255, 255, 0.025)"
2474
+ },
2475
+ "& .cm-draftly-table-cell": {
2476
+ borderColor: "var(--color-border, #30363d)"
2477
+ },
2478
+ "& .cm-draftly-table-control": {
2479
+ borderColor: "var(--color-border, #30363d)",
2480
+ backgroundColor: "var(--color-background, #161b22)",
2481
+ color: "var(--color-text, #e6edf3)",
2482
+ boxShadow: "0 12px 28px rgba(0, 0, 0, 0.35)"
2483
+ },
2484
+ "& .cm-draftly-table-control:hover": {
2485
+ backgroundColor: "rgba(255, 255, 255, 0.08)"
2486
+ }
2487
+ }
2488
+ }
2489
+ });
2490
+ var htmlMarkDecorations = {
2491
+ "html-tag": Decoration.mark({ class: "cm-draftly-html-tag" }),
2492
+ "html-comment": Decoration.mark({ class: "cm-draftly-html-comment" })
2493
+ };
2494
+ var htmlLineDecorations = {
2495
+ "html-block": Decoration.line({ class: "cm-draftly-line-html-block" }),
2496
+ "hidden-line": Decoration.line({ class: "cm-draftly-hidden-line" })
2497
+ };
2498
+ var HTMLPreviewWidget = class extends WidgetType {
2499
+ constructor(html) {
2500
+ super();
2501
+ this.html = html;
2502
+ }
2503
+ eq(other) {
2504
+ return other.html === this.html;
2505
+ }
2506
+ toDOM() {
2507
+ const div = document.createElement("div");
2508
+ div.className = "cm-draftly-html-preview";
2509
+ div.innerHTML = DOMPurify.sanitize(this.html);
2510
+ return div;
2511
+ }
2512
+ ignoreEvent() {
2513
+ return false;
2514
+ }
2515
+ };
2516
+ var InlineHTMLPreviewWidget = class extends WidgetType {
2517
+ constructor(html) {
2518
+ super();
2519
+ this.html = html;
2520
+ }
2521
+ eq(other) {
2522
+ return other.html === this.html;
2523
+ }
2524
+ toDOM() {
2525
+ const span = document.createElement("span");
2526
+ span.className = "cm-draftly-inline-html-preview";
2527
+ span.innerHTML = DOMPurify.sanitize(this.html);
2528
+ return span;
2529
+ }
2530
+ ignoreEvent() {
2531
+ return false;
2532
+ }
2533
+ };
2534
+ function parseHTMLTag(content) {
2535
+ const match = content.match(/^<\s*(\/?)([a-zA-Z][a-zA-Z0-9-]*)[^>]*(\/?)>$/);
2536
+ if (!match) return null;
2537
+ return {
2538
+ tagName: match[2].toLowerCase(),
2539
+ isClosing: match[1] === "/",
2540
+ isSelfClosing: match[3] === "/" || ["br", "hr", "img", "input", "meta", "link", "area", "base", "col", "embed", "source", "track", "wbr"].includes(
2541
+ match[2].toLowerCase()
2542
+ )
2543
+ };
2544
+ }
2545
+ var HTMLPlugin = class extends DecorationPlugin {
2546
+ name = "html";
2547
+ version = "1.0.0";
2548
+ decorationPriority = 30;
2549
+ constructor() {
2550
+ super();
2551
+ }
2552
+ /**
2553
+ * Plugin theme
2554
+ */
2555
+ get theme() {
2556
+ return theme7;
2557
+ }
2558
+ buildDecorations(ctx) {
2559
+ const { view, decorations } = ctx;
2560
+ const tree = syntaxTree(view.state);
2561
+ const htmlGroups = [];
2562
+ const htmlTags = [];
2563
+ tree.iterate({
2564
+ enter: (node) => {
2565
+ const { from, to, name } = node;
2566
+ if (name === "Comment") {
2567
+ decorations.push(htmlMarkDecorations["html-comment"].range(from, to));
2568
+ return;
2569
+ }
2570
+ if (name === "HTMLTag") {
2571
+ const content = view.state.sliceDoc(from, to);
2572
+ const parsed = parseHTMLTag(content);
2573
+ if (parsed) {
2574
+ htmlTags.push({
2575
+ from,
2576
+ to,
2577
+ tagName: parsed.tagName,
2578
+ isClosing: parsed.isClosing,
2579
+ isSelfClosing: parsed.isSelfClosing
2580
+ });
2581
+ }
2582
+ }
2583
+ if (name === "HTMLBlock") {
2584
+ const last = htmlGroups[htmlGroups.length - 1];
2585
+ if (last) {
2586
+ const gap = view.state.sliceDoc(last.to, from);
2587
+ if (!gap.trim()) {
2588
+ last.to = to;
2589
+ return;
2590
+ }
2591
+ }
2592
+ htmlGroups.push({ from, to });
2593
+ }
2594
+ }
2595
+ });
2596
+ const inlineElements = [];
2597
+ const usedTags = /* @__PURE__ */ new Set();
2598
+ for (let i = 0; i < htmlTags.length; i++) {
2599
+ if (usedTags.has(i)) continue;
2600
+ const openTag = htmlTags[i];
2601
+ if (openTag.isClosing) continue;
2602
+ if (openTag.isSelfClosing) {
2603
+ inlineElements.push({
2604
+ from: openTag.from,
2605
+ to: openTag.to,
2606
+ content: view.state.sliceDoc(openTag.from, openTag.to)
2607
+ });
2608
+ usedTags.add(i);
2609
+ continue;
2610
+ }
2611
+ const openLine = view.state.doc.lineAt(openTag.from);
2612
+ let depth = 1;
2613
+ let closeTagIndex = null;
2614
+ for (let j = i + 1; j < htmlTags.length && depth > 0; j++) {
2615
+ const tag = htmlTags[j];
1239
2616
  if (tag.from > openLine.to) break;
1240
2617
  if (tag.tagName === openTag.tagName) {
1241
2618
  if (tag.isClosing) {
@@ -1316,7 +2693,7 @@ var HTMLPlugin = class extends DecorationPlugin {
1316
2693
  }
1317
2694
  }
1318
2695
  };
1319
- var theme6 = createTheme({
2696
+ var theme7 = createTheme({
1320
2697
  default: {
1321
2698
  ".cm-draftly-html-tag": {
1322
2699
  color: "#6a737d",
@@ -1446,7 +2823,7 @@ var ImagePlugin = class extends DecorationPlugin {
1446
2823
  * Plugin theme
1447
2824
  */
1448
2825
  get theme() {
1449
- return theme7;
2826
+ return theme8;
1450
2827
  }
1451
2828
  /**
1452
2829
  * Keyboard shortcuts for image formatting
@@ -1601,7 +2978,7 @@ var ImagePlugin = class extends DecorationPlugin {
1601
2978
  return html;
1602
2979
  }
1603
2980
  };
1604
- var theme7 = createTheme({
2981
+ var theme8 = createTheme({
1605
2982
  default: {
1606
2983
  ".cm-draftly-image-block br": {
1607
2984
  display: "none"
@@ -1850,7 +3227,16 @@ var MathPlugin = class extends DecorationPlugin {
1850
3227
  * Plugin theme
1851
3228
  */
1852
3229
  get theme() {
1853
- return theme8;
3230
+ return theme9;
3231
+ }
3232
+ /**
3233
+ * Intercepts dollar typing to wrap selected text as inline math.
3234
+ *
3235
+ * If user types '$' while text is selected, wraps each selected range
3236
+ * with single dollars (selected -> $selected$).
3237
+ */
3238
+ getExtensions() {
3239
+ return [createWrapSelectionInputHandler({ "$": "$" })];
1854
3240
  }
1855
3241
  /**
1856
3242
  * Return markdown parser extensions for math syntax
@@ -1958,7 +3344,7 @@ var MathPlugin = class extends DecorationPlugin {
1958
3344
  return null;
1959
3345
  }
1960
3346
  };
1961
- var theme8 = createTheme({
3347
+ var theme9 = createTheme({
1962
3348
  default: {
1963
3349
  ".cm-draftly-math-block": {
1964
3350
  fontFamily: "var(--font-jetbrains-mono, monospace)"
@@ -2157,7 +3543,7 @@ var MermaidPlugin = class extends DecorationPlugin {
2157
3543
  * Plugin theme
2158
3544
  */
2159
3545
  get theme() {
2160
- return theme9;
3546
+ return theme10;
2161
3547
  }
2162
3548
  /**
2163
3549
  * Return markdown parser extensions for mermaid syntax
@@ -2265,7 +3651,7 @@ var MermaidPlugin = class extends DecorationPlugin {
2265
3651
  return null;
2266
3652
  }
2267
3653
  };
2268
- var theme9 = createTheme({
3654
+ var theme10 = createTheme({
2269
3655
  default: {
2270
3656
  // Raw mermaid block lines (monospace)
2271
3657
  ".cm-draftly-mermaid-block:not(.cm-draftly-mermaid-block-rendered)": {
@@ -2313,65 +3699,430 @@ var theme9 = createTheme({
2313
3699
  color: "#6a737d",
2314
3700
  fontFamily: "var(--font-jetbrains-mono, monospace)"
2315
3701
  },
2316
- // Hidden mermaid syntax (when cursor is not in range)
2317
- ".cm-draftly-mermaid-hidden": {
2318
- display: "none"
3702
+ // Hidden mermaid syntax (when cursor is not in range)
3703
+ ".cm-draftly-mermaid-hidden": {
3704
+ display: "none"
3705
+ },
3706
+ // Rendered mermaid container
3707
+ ".cm-draftly-mermaid-rendered": {
3708
+ display: "flex",
3709
+ justifyContent: "center",
3710
+ alignItems: "center",
3711
+ padding: "1em 0",
3712
+ borderRadius: "4px",
3713
+ overflow: "auto"
3714
+ },
3715
+ // SVG inside rendered container
3716
+ ".cm-draftly-mermaid-rendered svg": {
3717
+ maxWidth: "100%",
3718
+ height: "auto",
3719
+ aspectRatio: "auto"
3720
+ },
3721
+ // Loading state
3722
+ ".cm-draftly-mermaid-loading": {
3723
+ display: "inline-block",
3724
+ padding: "0.5em 1em",
3725
+ color: "#6a737d",
3726
+ fontSize: "0.875em",
3727
+ fontStyle: "italic",
3728
+ fontFamily: "var(--font-jetbrains-mono, monospace)"
3729
+ },
3730
+ // Error styling
3731
+ ".cm-draftly-mermaid-error": {
3732
+ display: "inline-block",
3733
+ padding: "0.25em 0.5em",
3734
+ backgroundColor: "rgba(255, 0, 0, 0.1)",
3735
+ color: "#d73a49",
3736
+ borderRadius: "4px",
3737
+ fontSize: "0.875em",
3738
+ fontStyle: "italic",
3739
+ fontFamily: "var(--font-jetbrains-mono, monospace)"
3740
+ }
3741
+ },
3742
+ dark: {
3743
+ ".cm-draftly-mermaid-block:not(.cm-draftly-mermaid-block-rendered)": {
3744
+ backgroundColor: "rgba(255, 255, 255, 0.03)"
3745
+ },
3746
+ ".cm-draftly-mermaid-marker": {
3747
+ color: "#8b949e"
3748
+ },
3749
+ ".cm-draftly-mermaid-loading": {
3750
+ color: "#8b949e"
3751
+ },
3752
+ ".cm-draftly-mermaid-error": {
3753
+ backgroundColor: "rgba(255, 0, 0, 0.15)",
3754
+ color: "#f85149"
3755
+ }
3756
+ }
3757
+ });
3758
+
3759
+ // src/plugins/code-plugin.theme.ts
3760
+ var codePluginTheme = createTheme({
3761
+ default: {
3762
+ // Inline code
3763
+ ".cm-draftly-code-inline": {
3764
+ fontFamily: "var(--font-jetbrains-mono, monospace)",
3765
+ fontSize: "0.9rem",
3766
+ backgroundColor: "rgba(0, 0, 0, 0.05)",
3767
+ padding: "0.1rem 0.25rem",
3768
+ border: "1px solid var(--color-border)",
3769
+ borderRadius: "3px"
3770
+ },
3771
+ // Fenced code block lines
3772
+ ".cm-draftly-code-block-line": {
3773
+ "--radius": "0.375rem",
3774
+ fontFamily: "var(--font-jetbrains-mono, monospace)",
3775
+ fontSize: "0.9rem",
3776
+ backgroundColor: "rgba(0, 0, 0, 0.03)",
3777
+ padding: "0 1rem !important",
3778
+ lineHeight: "1.5",
3779
+ borderLeft: "1px solid var(--color-border)",
3780
+ borderRight: "1px solid var(--color-border)"
3781
+ },
3782
+ // First line of code block
3783
+ ".cm-draftly-code-block-line-start": {
3784
+ borderTopLeftRadius: "var(--radius)",
3785
+ borderTopRightRadius: "var(--radius)",
3786
+ position: "relative",
3787
+ overflow: "hidden",
3788
+ borderTop: "1px solid var(--color-border)",
3789
+ paddingBottom: "0.5rem !important"
3790
+ },
3791
+ // Remove top radius when header is present
3792
+ ".cm-draftly-code-block-has-header": {
3793
+ padding: "0 !important",
3794
+ paddingBottom: "0.5rem !important"
3795
+ },
3796
+ // Code block header widget
3797
+ ".cm-draftly-code-header": {
3798
+ display: "flex",
3799
+ justifyContent: "space-between",
3800
+ alignItems: "center",
3801
+ padding: "0.25rem 1rem",
3802
+ backgroundColor: "rgba(0, 0, 0, 0.06)",
3803
+ fontFamily: "var(--font-jetbrains-mono, monospace)",
3804
+ fontSize: "0.85rem",
3805
+ ".cm-draftly-code-header-left": {
3806
+ display: "flex",
3807
+ alignItems: "center",
3808
+ gap: "0.5rem",
3809
+ ".cm-draftly-code-header-title": {
3810
+ color: "var(--color-text, inherit)",
3811
+ fontWeight: "500"
3812
+ },
3813
+ ".cm-draftly-code-header-lang": {
3814
+ color: "#6a737d",
3815
+ opacity: "0.8"
3816
+ }
3817
+ },
3818
+ ".cm-draftly-code-header-right": {
3819
+ display: "flex",
3820
+ alignItems: "center",
3821
+ gap: "0.5rem",
3822
+ ".cm-draftly-code-copy-btn": {
3823
+ display: "flex",
3824
+ alignItems: "center",
3825
+ justifyContent: "center",
3826
+ padding: "0.25rem",
3827
+ backgroundColor: "transparent",
3828
+ border: "none",
3829
+ borderRadius: "4px",
3830
+ cursor: "pointer",
3831
+ color: "#6a737d",
3832
+ transition: "color 0.2s, background-color 0.2s",
3833
+ "&:hover": {
3834
+ backgroundColor: "rgba(0, 0, 0, 0.1)",
3835
+ color: "var(--color-text, inherit)"
3836
+ },
3837
+ "&.copied": {
3838
+ color: "#22c55e"
3839
+ }
3840
+ }
3841
+ }
3842
+ },
3843
+ // Caption (below code block)
3844
+ ".cm-draftly-code-block-has-caption": {
3845
+ padding: "0 !important",
3846
+ paddingTop: "0.5rem !important"
3847
+ },
3848
+ ".cm-draftly-code-caption": {
3849
+ textAlign: "center",
3850
+ fontSize: "0.85rem",
3851
+ color: "#6a737d",
3852
+ fontStyle: "italic",
3853
+ padding: "0.25rem 1rem",
3854
+ backgroundColor: "rgba(0, 0, 0, 0.06)"
3855
+ },
3856
+ // Last line of code block
3857
+ ".cm-draftly-code-block-line-end": {
3858
+ borderBottomLeftRadius: "var(--radius)",
3859
+ borderBottomRightRadius: "var(--radius)",
3860
+ borderBottom: "1px solid var(--color-border)",
3861
+ paddingTop: "0.5rem !important",
3862
+ "& br": {
3863
+ display: "none"
3864
+ }
3865
+ },
3866
+ // Fence markers (```)
3867
+ ".cm-draftly-code-fence": {
3868
+ color: "#6a737d",
3869
+ fontFamily: "var(--font-jetbrains-mono, monospace)"
3870
+ },
3871
+ // Line numbers
3872
+ ".cm-draftly-code-line-numbered": {
3873
+ paddingLeft: "calc(var(--line-num-width, 2ch) + 1rem) !important",
3874
+ position: "relative",
3875
+ "&::before": {
3876
+ content: "attr(data-line-num)",
3877
+ position: "absolute",
3878
+ left: "0.5rem",
3879
+ top: "0.2rem",
3880
+ width: "var(--line-num-width, 2ch)",
3881
+ textAlign: "right",
3882
+ color: "#6a737d",
3883
+ opacity: "0.6",
3884
+ fontFamily: "var(--font-jetbrains-mono, monospace)",
3885
+ fontSize: "0.85rem",
3886
+ userSelect: "none"
3887
+ }
3888
+ },
3889
+ ".cm-draftly-code-line-numbered-diff": {
3890
+ paddingLeft: "calc(var(--line-num-old-width, 2ch) + var(--line-num-new-width, 2ch) + 2.75rem) !important",
3891
+ position: "relative",
3892
+ "&::before": {
3893
+ content: "attr(data-line-num-old)",
3894
+ position: "absolute",
3895
+ left: "0.5rem",
3896
+ top: "0.2rem",
3897
+ width: "var(--line-num-old-width, 2ch)",
3898
+ textAlign: "right",
3899
+ color: "#6a737d",
3900
+ opacity: "0.6",
3901
+ fontFamily: "var(--font-jetbrains-mono, monospace)",
3902
+ fontSize: "0.85rem",
3903
+ userSelect: "none"
3904
+ },
3905
+ "&::after": {
3906
+ content: 'attr(data-line-num-new) " " attr(data-diff-marker)',
3907
+ position: "absolute",
3908
+ left: "calc(0.5rem + var(--line-num-old-width, 2ch) + 0.75rem)",
3909
+ top: "0.2rem",
3910
+ width: "calc(var(--line-num-new-width, 2ch) + 2ch)",
3911
+ textAlign: "right",
3912
+ color: "#6a737d",
3913
+ opacity: "0.6",
3914
+ fontFamily: "var(--font-jetbrains-mono, monospace)",
3915
+ fontSize: "0.85rem",
3916
+ userSelect: "none"
3917
+ },
3918
+ "&.cm-draftly-code-line-diff-gutter": {
3919
+ paddingLeft: "calc(var(--line-num-width, 2ch) + 2rem) !important",
3920
+ "&::after": {
3921
+ content: "attr(data-diff-marker)",
3922
+ position: "absolute",
3923
+ left: "calc(0.5rem + var(--line-num-width, 2ch) + 0.35rem)",
3924
+ top: "0.1rem",
3925
+ width: "1ch",
3926
+ textAlign: "right",
3927
+ fontFamily: "var(--font-jetbrains-mono, monospace)",
3928
+ fontSize: "0.85rem",
3929
+ fontWeight: "700",
3930
+ userSelect: "none"
3931
+ }
3932
+ }
3933
+ },
3934
+ // Preview: code lines (need block display for full-width highlights)
3935
+ ".cm-draftly-code-line": {
3936
+ display: "block",
3937
+ position: "relative",
3938
+ paddingLeft: "1rem",
3939
+ paddingRight: "1rem",
3940
+ lineHeight: "1.5",
3941
+ borderLeft: "3px solid transparent"
3942
+ },
3943
+ // Line highlight
3944
+ ".cm-draftly-code-line-highlight": {
3945
+ backgroundColor: "rgba(255, 220, 100, 0.2) !important",
3946
+ borderLeft: "3px solid #f0b429 !important"
3947
+ },
3948
+ ".cm-draftly-code-line-diff-add": {
3949
+ color: "inherit",
3950
+ backgroundColor: "rgba(34, 197, 94, 0.12) !important",
3951
+ borderLeft: "3px solid #22c55e !important",
3952
+ "&.cm-draftly-code-line-diff-gutter::after": {
3953
+ color: "#16a34a"
3954
+ }
3955
+ },
3956
+ ".cm-draftly-code-line-diff-del": {
3957
+ color: "inherit",
3958
+ backgroundColor: "rgba(239, 68, 68, 0.12) !important",
3959
+ borderLeft: "3px solid #ef4444 !important",
3960
+ "&.cm-draftly-code-line-diff-gutter::after": {
3961
+ color: "#dc2626"
3962
+ }
3963
+ },
3964
+ ".cm-draftly-code-diff-sign-add": {
3965
+ color: "#16a34a",
3966
+ fontWeight: "700"
3967
+ },
3968
+ ".cm-draftly-code-diff-sign-del": {
3969
+ color: "#dc2626",
3970
+ fontWeight: "700"
3971
+ },
3972
+ ".cm-draftly-code-diff-mod-add": {
3973
+ color: "inherit",
3974
+ backgroundColor: "rgba(34, 197, 94, 0.25)",
3975
+ borderRadius: "2px",
3976
+ padding: "0.1rem 0"
3977
+ },
3978
+ ".cm-draftly-code-diff-mod-del": {
3979
+ color: "inherit",
3980
+ backgroundColor: "rgba(239, 68, 68, 0.25)",
3981
+ borderRadius: "2px",
3982
+ padding: "0.1rem 0"
3983
+ },
3984
+ // Text highlight
3985
+ ".cm-draftly-code-text-highlight": {
3986
+ color: "inherit",
3987
+ backgroundColor: "rgba(255, 220, 100, 0.4)",
3988
+ borderRadius: "2px",
3989
+ padding: "0.1rem 0"
3990
+ },
3991
+ // Preview: container wrapper
3992
+ ".cm-draftly-code-container": {
3993
+ margin: "1rem 0",
3994
+ borderRadius: "var(--radius)",
3995
+ overflow: "hidden",
3996
+ border: "1px solid var(--color-border)",
3997
+ ".cm-draftly-code-header": {
3998
+ borderRadius: "0",
3999
+ border: "none",
4000
+ borderBottom: "1px solid var(--color-border)"
4001
+ },
4002
+ ".cm-draftly-code-block": {
4003
+ margin: "0",
4004
+ borderRadius: "0",
4005
+ border: "none",
4006
+ whiteSpace: "pre-wrap"
4007
+ },
4008
+ ".cm-draftly-code-caption": {
4009
+ borderTop: "1px solid var(--color-border)"
4010
+ }
4011
+ },
4012
+ // Preview: standalone code block (not in container)
4013
+ ".cm-draftly-code-block": {
4014
+ fontFamily: "var(--font-jetbrains-mono, monospace)",
4015
+ fontSize: "0.9rem",
4016
+ backgroundColor: "rgba(0, 0, 0, 0.03)",
4017
+ padding: "1rem",
4018
+ overflow: "auto",
4019
+ position: "relative",
4020
+ borderRadius: "var(--radius)",
4021
+ border: "1px solid var(--color-border)",
4022
+ "&.cm-draftly-code-block-has-header": {
4023
+ borderTopLeftRadius: "0",
4024
+ borderTopRightRadius: "0",
4025
+ borderTop: "none",
4026
+ margin: "0",
4027
+ paddingTop: "0.5rem !important"
4028
+ },
4029
+ "&.cm-draftly-code-block-has-caption": {
4030
+ borderBottomLeftRadius: "0",
4031
+ borderBottomRightRadius: "0",
4032
+ borderBottom: "none",
4033
+ paddingBottom: "0.5rem !important"
4034
+ }
4035
+ }
4036
+ },
4037
+ dark: {
4038
+ ".cm-draftly-code-inline": {
4039
+ backgroundColor: "rgba(255, 255, 255, 0.1)"
4040
+ },
4041
+ ".cm-draftly-code-block-line": {
4042
+ backgroundColor: "rgba(255, 255, 255, 0.05)"
4043
+ },
4044
+ ".cm-draftly-code-fence": {
4045
+ color: "#8b949e"
4046
+ },
4047
+ ".cm-draftly-code-block": {
4048
+ backgroundColor: "rgba(255, 255, 255, 0.05)"
4049
+ },
4050
+ ".cm-draftly-code-header": {
4051
+ backgroundColor: "rgba(255, 255, 255, 0.08)",
4052
+ ".cm-draftly-code-header-lang": {
4053
+ color: "#8b949e"
4054
+ },
4055
+ ".cm-draftly-code-copy-btn": {
4056
+ color: "#8b949e",
4057
+ "&:hover": {
4058
+ backgroundColor: "rgba(255, 255, 255, 0.1)"
4059
+ }
4060
+ }
4061
+ },
4062
+ ".cm-draftly-code-caption": {
4063
+ backgroundColor: "rgba(255, 255, 255, 0.05)"
4064
+ },
4065
+ ".cm-draftly-code-line-numbered": {
4066
+ "&::before": {
4067
+ color: "#8b949e"
4068
+ }
4069
+ },
4070
+ ".cm-draftly-code-line-numbered-diff": {
4071
+ "&::before": {
4072
+ color: "#8b949e"
4073
+ },
4074
+ "&::after": {
4075
+ color: "#8b949e"
4076
+ }
4077
+ },
4078
+ ".cm-draftly-code-line-diff-gutter": {
4079
+ "&::after": {
4080
+ color: "#8b949e"
4081
+ }
2319
4082
  },
2320
- // Rendered mermaid container
2321
- ".cm-draftly-mermaid-rendered": {
2322
- display: "flex",
2323
- justifyContent: "center",
2324
- alignItems: "center",
2325
- padding: "1em 0",
2326
- borderRadius: "4px",
2327
- overflow: "auto"
4083
+ ".cm-draftly-code-line-highlight": {
4084
+ backgroundColor: "rgba(255, 220, 100, 0.15) !important",
4085
+ borderLeft: "3px solid #d9a520 !important"
2328
4086
  },
2329
- // SVG inside rendered container
2330
- ".cm-draftly-mermaid-rendered svg": {
2331
- maxWidth: "100%",
2332
- height: "auto",
2333
- aspectRatio: "auto"
4087
+ ".cm-draftly-code-line-diff-add": {
4088
+ backgroundColor: "rgba(34, 197, 94, 0.15) !important",
4089
+ borderLeft: "3px solid #22c55e !important",
4090
+ "&.cm-draftly-code-line-diff-gutter::after": {
4091
+ color: "#4ade80"
4092
+ }
2334
4093
  },
2335
- // Loading state
2336
- ".cm-draftly-mermaid-loading": {
2337
- display: "inline-block",
2338
- padding: "0.5em 1em",
2339
- color: "#6a737d",
2340
- fontSize: "0.875em",
2341
- fontStyle: "italic",
2342
- fontFamily: "var(--font-jetbrains-mono, monospace)"
4094
+ ".cm-draftly-code-line-diff-del": {
4095
+ backgroundColor: "rgba(239, 68, 68, 0.15) !important",
4096
+ borderLeft: "3px solid #ef4444 !important",
4097
+ "&.cm-draftly-code-line-diff-gutter::after": {
4098
+ color: "#f87171"
4099
+ }
2343
4100
  },
2344
- // Error styling
2345
- ".cm-draftly-mermaid-error": {
2346
- display: "inline-block",
2347
- padding: "0.25em 0.5em",
2348
- backgroundColor: "rgba(255, 0, 0, 0.1)",
2349
- color: "#d73a49",
2350
- borderRadius: "4px",
2351
- fontSize: "0.875em",
2352
- fontStyle: "italic",
2353
- fontFamily: "var(--font-jetbrains-mono, monospace)"
2354
- }
2355
- },
2356
- dark: {
2357
- ".cm-draftly-mermaid-block:not(.cm-draftly-mermaid-block-rendered)": {
2358
- backgroundColor: "rgba(255, 255, 255, 0.03)"
4101
+ ".cm-draftly-code-diff-sign-add": {
4102
+ color: "#4ade80"
2359
4103
  },
2360
- ".cm-draftly-mermaid-marker": {
2361
- color: "#8b949e"
4104
+ ".cm-draftly-code-diff-sign-del": {
4105
+ color: "#f87171"
2362
4106
  },
2363
- ".cm-draftly-mermaid-loading": {
2364
- color: "#8b949e"
4107
+ ".cm-draftly-code-diff-mod-add": {
4108
+ backgroundColor: "rgba(34, 197, 94, 0.3)"
2365
4109
  },
2366
- ".cm-draftly-mermaid-error": {
2367
- backgroundColor: "rgba(255, 0, 0, 0.15)",
2368
- color: "#f85149"
4110
+ ".cm-draftly-code-diff-mod-del": {
4111
+ backgroundColor: "rgba(239, 68, 68, 0.3)"
4112
+ },
4113
+ ".cm-draftly-code-text-highlight": {
4114
+ backgroundColor: "rgba(255, 220, 100, 0.3)"
2369
4115
  }
2370
4116
  }
2371
4117
  });
4118
+
4119
+ // src/plugins/code-plugin.ts
2372
4120
  var COPY_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>`;
2373
4121
  var CHECK_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>`;
2374
4122
  var COPY_RESET_DELAY = 2e3;
4123
+ var CODE_FENCE = "```";
4124
+ var QUOTED_INFO_PATTERN = /(\w+)="([^"]*)"/g;
4125
+ var TEXT_HIGHLIGHT_PATTERN = /\/([^/]+)\/(?:(\d+(?:-\d+)?(?:,\d+(?:-\d+)?)*))?/g;
2375
4126
  var codeMarkDecorations = {
2376
4127
  // Inline code
2377
4128
  "inline-code": Decoration.mark({ class: "cm-draftly-code-inline" }),
@@ -2384,7 +4135,15 @@ var codeMarkDecorations = {
2384
4135
  "code-hidden": Decoration.replace({}),
2385
4136
  // Highlights
2386
4137
  "code-line-highlight": Decoration.line({ class: "cm-draftly-code-line-highlight" }),
2387
- "code-text-highlight": Decoration.mark({ class: "cm-draftly-code-text-highlight" })
4138
+ "code-text-highlight": Decoration.mark({ class: "cm-draftly-code-text-highlight" }),
4139
+ // Diff preview
4140
+ "diff-line-add": Decoration.line({ class: "cm-draftly-code-line-diff-add" }),
4141
+ "diff-line-del": Decoration.line({ class: "cm-draftly-code-line-diff-del" }),
4142
+ "diff-sign-add": Decoration.mark({ class: "cm-draftly-code-diff-sign-add" }),
4143
+ "diff-sign-del": Decoration.mark({ class: "cm-draftly-code-diff-sign-del" }),
4144
+ "diff-mod-add": Decoration.mark({ class: "cm-draftly-code-diff-mod-add" }),
4145
+ "diff-mod-del": Decoration.mark({ class: "cm-draftly-code-diff-mod-del" }),
4146
+ "diff-escape-hidden": Decoration.replace({})
2388
4147
  };
2389
4148
  var CodeBlockHeaderWidget = class extends WidgetType {
2390
4149
  constructor(props, codeContent) {
@@ -2470,11 +4229,12 @@ var CodePlugin = class extends DecorationPlugin {
2470
4229
  version = "1.0.0";
2471
4230
  decorationPriority = 25;
2472
4231
  requiredNodes = ["InlineCode", "FencedCode", "CodeMark", "CodeInfo", "CodeText"];
4232
+ parserCache = /* @__PURE__ */ new Map();
2473
4233
  /**
2474
4234
  * Plugin theme
2475
4235
  */
2476
4236
  get theme() {
2477
- return theme10;
4237
+ return codePluginTheme;
2478
4238
  }
2479
4239
  /**
2480
4240
  * Keyboard shortcuts for code formatting
@@ -2493,6 +4253,15 @@ var CodePlugin = class extends DecorationPlugin {
2493
4253
  }
2494
4254
  ];
2495
4255
  }
4256
+ /**
4257
+ * Intercepts backtick typing to wrap selected text as inline code.
4258
+ *
4259
+ * If user types '`' while text is selected, wraps each selected range
4260
+ * with backticks (selected -> `selected`).
4261
+ */
4262
+ getExtensions() {
4263
+ return [createWrapSelectionInputHandler({ "`": "`" })];
4264
+ }
2496
4265
  /**
2497
4266
  * Toggle code block on current line or selected lines
2498
4267
  */
@@ -2505,7 +4274,7 @@ var CodePlugin = class extends DecorationPlugin {
2505
4274
  const nextLineNum = endLine.number < state.doc.lines ? endLine.number + 1 : endLine.number;
2506
4275
  const prevLine = state.doc.line(prevLineNum);
2507
4276
  const nextLine = state.doc.line(nextLineNum);
2508
- const isWrapped = prevLine.text.trim().startsWith("```") && nextLine.text.trim() === "```" && prevLineNum !== startLine.number && nextLineNum !== endLine.number;
4277
+ const isWrapped = prevLine.text.trim().startsWith(CODE_FENCE) && nextLine.text.trim() === CODE_FENCE && prevLineNum !== startLine.number && nextLineNum !== endLine.number;
2509
4278
  if (isWrapped) {
2510
4279
  view.dispatch({
2511
4280
  changes: [
@@ -2516,8 +4285,10 @@ var CodePlugin = class extends DecorationPlugin {
2516
4285
  ]
2517
4286
  });
2518
4287
  } else {
2519
- const openFence = "```\n";
2520
- const closeFence = "\n```";
4288
+ const openFence = `${CODE_FENCE}
4289
+ `;
4290
+ const closeFence = `
4291
+ ${CODE_FENCE}`;
2521
4292
  view.dispatch({
2522
4293
  changes: [
2523
4294
  { from: startLine.from, insert: openFence },
@@ -2546,6 +4317,7 @@ var CodePlugin = class extends DecorationPlugin {
2546
4317
  * lineNumbers: 5,
2547
4318
  * title: "hello.tsx",
2548
4319
  * copy: true,
4320
+ * diff: false,
2549
4321
  * highlightLines: [2,3,4,5],
2550
4322
  * highlightText: [{ pattern: "Hello", instances: [3,4,5] }]
2551
4323
  * }
@@ -2557,14 +4329,21 @@ var CodePlugin = class extends DecorationPlugin {
2557
4329
  return props;
2558
4330
  }
2559
4331
  let remaining = codeInfo.trim();
2560
- const langMatch = remaining.match(/^(\w+)/);
2561
- if (langMatch && langMatch[1]) {
2562
- props.language = langMatch[1];
2563
- remaining = remaining.slice(langMatch[0].length).trim();
4332
+ const firstTokenMatch = remaining.match(/^([^\s]+)/);
4333
+ if (firstTokenMatch && firstTokenMatch[1]) {
4334
+ const firstToken = firstTokenMatch[1];
4335
+ const normalizedToken = firstToken.toLowerCase();
4336
+ const isLineNumberDirective = /^(?:line-numbers|linenumbers|showlinenumbers)(?:\{\d+\})?$/.test(
4337
+ normalizedToken
4338
+ );
4339
+ const isKnownDirective = isLineNumberDirective || normalizedToken === "copy" || normalizedToken === "diff" || normalizedToken.startsWith("{") || normalizedToken.startsWith("/");
4340
+ if (!isKnownDirective) {
4341
+ props.language = firstToken;
4342
+ remaining = remaining.slice(firstToken.length).trim();
4343
+ }
2564
4344
  }
2565
- const quotedPattern = /(\w+)="([^"]*)"/g;
2566
4345
  let quotedMatch;
2567
- while ((quotedMatch = quotedPattern.exec(remaining)) !== null) {
4346
+ while ((quotedMatch = QUOTED_INFO_PATTERN.exec(remaining)) !== null) {
2568
4347
  const key = quotedMatch[1]?.toLowerCase();
2569
4348
  const value = quotedMatch[2];
2570
4349
  if (key === "title" && value !== void 0) {
@@ -2573,13 +4352,13 @@ var CodePlugin = class extends DecorationPlugin {
2573
4352
  props.caption = value;
2574
4353
  }
2575
4354
  }
2576
- remaining = remaining.replace(quotedPattern, "").trim();
2577
- const lineNumbersMatch = remaining.match(/line-numbers(?:\{(\d+)\})?/);
4355
+ remaining = remaining.replace(QUOTED_INFO_PATTERN, "").trim();
4356
+ const lineNumbersMatch = remaining.match(/\b(?:line-numbers|lineNumbers|showLineNumbers)(?:\{(\d+)\})?/i);
2578
4357
  if (lineNumbersMatch) {
2579
4358
  if (lineNumbersMatch[1]) {
2580
- props.lineNumbers = parseInt(lineNumbersMatch[1], 10);
4359
+ props.showLineNumbers = parseInt(lineNumbersMatch[1], 10);
2581
4360
  } else {
2582
- props.lineNumbers = true;
4361
+ props.showLineNumbers = true;
2583
4362
  }
2584
4363
  remaining = remaining.replace(lineNumbersMatch[0], "").trim();
2585
4364
  }
@@ -2587,52 +4366,27 @@ var CodePlugin = class extends DecorationPlugin {
2587
4366
  props.copy = true;
2588
4367
  remaining = remaining.replace(/\bcopy\b/, "").trim();
2589
4368
  }
4369
+ if (/\bdiff\b/.test(remaining)) {
4370
+ props.diff = true;
4371
+ remaining = remaining.replace(/\bdiff\b/, "").trim();
4372
+ }
2590
4373
  const lineHighlightMatch = remaining.match(/\{([^}]+)\}/);
2591
4374
  if (lineHighlightMatch && lineHighlightMatch[1]) {
2592
- const highlightLines = [];
2593
- const parts = lineHighlightMatch[1].split(",");
2594
- for (const part of parts) {
2595
- const trimmed = part.trim();
2596
- const rangeMatch = trimmed.match(/^(\d+)-(\d+)$/);
2597
- if (rangeMatch && rangeMatch[1] && rangeMatch[2]) {
2598
- const start = parseInt(rangeMatch[1], 10);
2599
- const end = parseInt(rangeMatch[2], 10);
2600
- for (let i = start; i <= end; i++) {
2601
- highlightLines.push(i);
2602
- }
2603
- } else if (/^\d+$/.test(trimmed)) {
2604
- highlightLines.push(parseInt(trimmed, 10));
2605
- }
2606
- }
4375
+ const highlightLines = this.parseNumberList(lineHighlightMatch[1]);
2607
4376
  if (highlightLines.length > 0) {
2608
4377
  props.highlightLines = highlightLines;
2609
4378
  }
2610
4379
  remaining = remaining.replace(lineHighlightMatch[0], "").trim();
2611
4380
  }
2612
- const textHighlightPattern = /\/([^/]+)\/(?:(\d+(?:-\d+)?(?:,\d+(?:-\d+)?)*))?/g;
2613
4381
  let textMatch;
2614
4382
  const highlightText = [];
2615
- while ((textMatch = textHighlightPattern.exec(remaining)) !== null) {
4383
+ while ((textMatch = TEXT_HIGHLIGHT_PATTERN.exec(remaining)) !== null) {
2616
4384
  if (!textMatch[1]) continue;
2617
4385
  const highlight = {
2618
4386
  pattern: textMatch[1]
2619
4387
  };
2620
4388
  if (textMatch[2]) {
2621
- const instanceStr = textMatch[2];
2622
- const instances = [];
2623
- const instanceParts = instanceStr.split(",");
2624
- for (const part of instanceParts) {
2625
- const rangeMatch = part.match(/^(\d+)-(\d+)$/);
2626
- if (rangeMatch && rangeMatch[1] && rangeMatch[2]) {
2627
- const start = parseInt(rangeMatch[1], 10);
2628
- const end = parseInt(rangeMatch[2], 10);
2629
- for (let i = start; i <= end; i++) {
2630
- instances.push(i);
2631
- }
2632
- } else if (/^\d+$/.test(part)) {
2633
- instances.push(parseInt(part, 10));
2634
- }
2635
- }
4389
+ const instances = this.parseNumberList(textMatch[2]);
2636
4390
  if (instances.length > 0) {
2637
4391
  highlight.instances = instances;
2638
4392
  }
@@ -2649,148 +4403,244 @@ var CodePlugin = class extends DecorationPlugin {
2649
4403
  * Handles line numbers, highlights, header/caption widgets, and fence visibility.
2650
4404
  */
2651
4405
  buildDecorations(ctx) {
2652
- const { view, decorations } = ctx;
2653
- const tree = syntaxTree(view.state);
4406
+ const tree = syntaxTree(ctx.view.state);
2654
4407
  tree.iterate({
2655
4408
  enter: (node) => {
2656
- const { from, to, name } = node;
2657
- if (name === "InlineCode") {
2658
- decorations.push(codeMarkDecorations["inline-code"].range(from, to));
2659
- const cursorInRange = ctx.selectionOverlapsRange(from, to);
2660
- if (!cursorInRange) {
2661
- for (let child = node.node.firstChild; child; child = child.nextSibling) {
2662
- if (child.name === "CodeMark") {
2663
- decorations.push(codeMarkDecorations["inline-mark"].range(child.from, child.to));
2664
- }
2665
- }
2666
- }
4409
+ if (node.name === "InlineCode") {
4410
+ this.decorateInlineCode(node, ctx);
4411
+ return;
2667
4412
  }
2668
- if (name === "FencedCode") {
2669
- const nodeLineStart = view.state.doc.lineAt(from);
2670
- const nodeLineEnd = view.state.doc.lineAt(to);
2671
- const cursorInRange = ctx.selectionOverlapsRange(nodeLineStart.from, nodeLineEnd.to);
2672
- let infoProps = { language: "" };
2673
- for (let child = node.node.firstChild; child; child = child.nextSibling) {
2674
- if (child.name === "CodeInfo") {
2675
- infoProps = this.parseCodeInfo(view.state.sliceDoc(child.from, child.to).trim());
2676
- break;
2677
- }
2678
- }
2679
- const totalCodeLines = nodeLineEnd.number - nodeLineStart.number - 1;
2680
- const startLineNum = typeof infoProps.lineNumbers === "number" ? infoProps.lineNumbers : 1;
2681
- const maxLineNum = startLineNum + totalCodeLines - 1;
2682
- const lineNumWidth = Math.max(String(maxLineNum).length, String(startLineNum).length);
2683
- let codeLineIndex = 0;
2684
- let codeContent = "";
2685
- for (let child = node.node.firstChild; child; child = child.nextSibling) {
2686
- if (child.name === "CodeText") {
2687
- codeContent = view.state.sliceDoc(child.from, child.to);
2688
- break;
2689
- }
2690
- }
2691
- const shouldShowHeader = !cursorInRange && (infoProps.title || infoProps.copy || infoProps.language);
2692
- const shouldShowCaption = !cursorInRange && infoProps.caption;
2693
- if (shouldShowHeader) {
2694
- decorations.push(
2695
- Decoration.widget({
2696
- widget: new CodeBlockHeaderWidget(infoProps, codeContent),
2697
- block: false
2698
- }).range(nodeLineStart.from)
2699
- );
2700
- }
2701
- for (let i = nodeLineStart.number; i <= nodeLineEnd.number; i++) {
2702
- const line = view.state.doc.line(i);
2703
- const isFenceLine = i === nodeLineStart.number || i === nodeLineEnd.number;
2704
- const relativeLineNum = startLineNum + codeLineIndex;
2705
- decorations.push(codeMarkDecorations["code-block-line"].range(line.from));
2706
- if (i === nodeLineStart.number) {
2707
- decorations.push(codeMarkDecorations["code-block-line-start"].range(line.from));
2708
- if (shouldShowHeader) {
2709
- decorations.push(
2710
- Decoration.line({
2711
- class: "cm-draftly-code-block-has-header"
2712
- }).range(line.from)
2713
- );
2714
- }
2715
- }
2716
- if (i === nodeLineEnd.number) {
2717
- decorations.push(codeMarkDecorations["code-block-line-end"].range(line.from));
2718
- if (shouldShowCaption) {
2719
- decorations.push(
2720
- Decoration.line({
2721
- class: "cm-draftly-code-block-has-caption"
2722
- }).range(line.from)
2723
- );
2724
- }
2725
- }
2726
- if (!isFenceLine && infoProps.lineNumbers) {
2727
- decorations.push(
2728
- Decoration.line({
2729
- class: "cm-draftly-code-line-numbered",
2730
- attributes: {
2731
- "data-line-num": String(relativeLineNum),
2732
- style: `--line-num-width: ${lineNumWidth}ch`
2733
- }
2734
- }).range(line.from)
2735
- );
2736
- }
2737
- if (!isFenceLine && infoProps.highlightLines) {
2738
- if (infoProps.highlightLines.includes(codeLineIndex + 1)) {
2739
- decorations.push(codeMarkDecorations["code-line-highlight"].range(line.from));
2740
- }
2741
- }
2742
- if (!isFenceLine && infoProps.highlightText && infoProps.highlightText.length > 0) {
2743
- const lineText = view.state.sliceDoc(line.from, line.to);
2744
- for (const textHighlight of infoProps.highlightText) {
2745
- try {
2746
- const regex = new RegExp(textHighlight.pattern, "g");
2747
- let match;
2748
- let matchIndex = 0;
2749
- while ((match = regex.exec(lineText)) !== null) {
2750
- matchIndex++;
2751
- const shouldHighlight = !textHighlight.instances || textHighlight.instances.includes(matchIndex);
2752
- if (shouldHighlight) {
2753
- const matchFrom = line.from + match.index;
2754
- const matchTo = matchFrom + match[0].length;
2755
- decorations.push(codeMarkDecorations["code-text-highlight"].range(matchFrom, matchTo));
2756
- }
2757
- }
2758
- } catch {
2759
- }
2760
- }
2761
- }
2762
- if (!isFenceLine) {
2763
- codeLineIndex++;
4413
+ if (node.name === "FencedCode") {
4414
+ this.decorateFencedCode(node, ctx);
4415
+ }
4416
+ }
4417
+ });
4418
+ }
4419
+ decorateInlineCode(node, ctx) {
4420
+ const { from, to } = node;
4421
+ ctx.decorations.push(codeMarkDecorations["inline-code"].range(from, to));
4422
+ if (ctx.selectionOverlapsRange(from, to)) {
4423
+ return;
4424
+ }
4425
+ for (let child = node.node.firstChild; child; child = child.nextSibling) {
4426
+ if (child.name === "CodeMark") {
4427
+ ctx.decorations.push(codeMarkDecorations["inline-mark"].range(child.from, child.to));
4428
+ }
4429
+ }
4430
+ }
4431
+ decorateFencedCode(node, ctx) {
4432
+ const { view, decorations } = ctx;
4433
+ const nodeLineStart = view.state.doc.lineAt(node.from);
4434
+ const nodeLineEnd = view.state.doc.lineAt(node.to);
4435
+ const cursorInRange = ctx.selectionOverlapsRange(nodeLineStart.from, nodeLineEnd.to);
4436
+ let infoProps = { language: "" };
4437
+ let codeContent = "";
4438
+ for (let child = node.node.firstChild; child; child = child.nextSibling) {
4439
+ if (child.name === "CodeInfo") {
4440
+ infoProps = this.parseCodeInfo(view.state.sliceDoc(child.from, child.to).trim());
4441
+ }
4442
+ if (child.name === "CodeText") {
4443
+ codeContent = view.state.sliceDoc(child.from, child.to);
4444
+ }
4445
+ }
4446
+ const codeLines = [];
4447
+ for (let i = nodeLineStart.number + 1; i <= nodeLineEnd.number - 1; i++) {
4448
+ const codeLine = view.state.doc.line(i);
4449
+ codeLines.push(view.state.sliceDoc(codeLine.from, codeLine.to));
4450
+ }
4451
+ const totalCodeLines = nodeLineEnd.number - nodeLineStart.number - 1;
4452
+ const startLineNum = typeof infoProps.showLineNumbers === "number" ? infoProps.showLineNumbers : 1;
4453
+ const maxLineNum = startLineNum + totalCodeLines - 1;
4454
+ const lineNumWidth = Math.max(String(maxLineNum).length, String(startLineNum).length);
4455
+ const highlightInstanceCounters = new Array(infoProps.highlightText?.length ?? 0).fill(0);
4456
+ const diffStates = infoProps.diff ? this.analyzeDiffLines(codeLines) : [];
4457
+ const diffDisplayLineNumbers = infoProps.diff ? this.computeDiffDisplayLineNumbers(diffStates, startLineNum) : [];
4458
+ const displayLineNumbers = infoProps.diff ? diffDisplayLineNumbers.map((numbers, index) => numbers.newLine ?? numbers.oldLine ?? startLineNum + index) : codeLines.map((_, index) => startLineNum + index);
4459
+ const diffHighlightLineNumbers = infoProps.diff ? this.computeDiffDisplayLineNumbers(diffStates, startLineNum).map(
4460
+ (numbers, index) => numbers.newLine ?? numbers.oldLine ?? startLineNum + index
4461
+ ) : [];
4462
+ const maxOldDiffLineNum = diffDisplayLineNumbers.reduce((max, numbers) => {
4463
+ const oldLine = numbers.oldLine ?? 0;
4464
+ return oldLine > max ? oldLine : max;
4465
+ }, startLineNum);
4466
+ const maxNewDiffLineNum = diffDisplayLineNumbers.reduce((max, numbers) => {
4467
+ const newLine = numbers.newLine ?? 0;
4468
+ return newLine > max ? newLine : max;
4469
+ }, startLineNum);
4470
+ const diffOldLineNumWidth = Math.max(String(startLineNum).length, String(maxOldDiffLineNum).length);
4471
+ const diffNewLineNumWidth = Math.max(String(startLineNum).length, String(maxNewDiffLineNum).length);
4472
+ const shouldShowHeader = !cursorInRange && (infoProps.title || infoProps.copy || infoProps.language);
4473
+ const shouldShowCaption = !cursorInRange && !!infoProps.caption;
4474
+ if (shouldShowHeader) {
4475
+ decorations.push(
4476
+ Decoration.widget({
4477
+ widget: new CodeBlockHeaderWidget(infoProps, codeContent),
4478
+ block: false,
4479
+ side: -1
4480
+ }).range(nodeLineStart.from)
4481
+ );
4482
+ }
4483
+ let codeLineIndex = 0;
4484
+ for (let lineNumber = nodeLineStart.number; lineNumber <= nodeLineEnd.number; lineNumber++) {
4485
+ const line = view.state.doc.line(lineNumber);
4486
+ const isFenceLine = lineNumber === nodeLineStart.number || lineNumber === nodeLineEnd.number;
4487
+ const relativeLineNum = displayLineNumbers[codeLineIndex] ?? startLineNum + codeLineIndex;
4488
+ decorations.push(codeMarkDecorations["code-block-line"].range(line.from));
4489
+ if (lineNumber === nodeLineStart.number) {
4490
+ decorations.push(codeMarkDecorations["code-block-line-start"].range(line.from));
4491
+ if (shouldShowHeader) {
4492
+ decorations.push(Decoration.line({ class: "cm-draftly-code-block-has-header" }).range(line.from));
4493
+ }
4494
+ }
4495
+ if (lineNumber === nodeLineEnd.number) {
4496
+ decorations.push(codeMarkDecorations["code-block-line-end"].range(line.from));
4497
+ if (shouldShowCaption) {
4498
+ decorations.push(Decoration.line({ class: "cm-draftly-code-block-has-caption" }).range(line.from));
4499
+ }
4500
+ }
4501
+ if (!isFenceLine && infoProps.showLineNumbers && !infoProps.diff) {
4502
+ decorations.push(
4503
+ Decoration.line({
4504
+ class: "cm-draftly-code-line-numbered",
4505
+ attributes: {
4506
+ "data-line-num": String(relativeLineNum),
4507
+ style: `--line-num-width: ${lineNumWidth}ch`
2764
4508
  }
2765
- }
2766
- for (let child = node.node.firstChild; child; child = child.nextSibling) {
2767
- if (child.name === "CodeMark" || child.name === "CodeInfo") {
2768
- if (cursorInRange) {
2769
- decorations.push(codeMarkDecorations["code-fence"].range(child.from, child.to));
2770
- } else {
2771
- decorations.push(codeMarkDecorations["code-hidden"].range(child.from, child.to));
2772
- }
4509
+ }).range(line.from)
4510
+ );
4511
+ }
4512
+ if (!isFenceLine && infoProps.showLineNumbers && infoProps.diff) {
4513
+ const diffLineNumbers = diffDisplayLineNumbers[codeLineIndex];
4514
+ const diffState = diffStates[codeLineIndex];
4515
+ const diffMarker = diffState?.kind === "addition" ? "+" : diffState?.kind === "deletion" ? "-" : " ";
4516
+ decorations.push(
4517
+ Decoration.line({
4518
+ class: "cm-draftly-code-line-numbered-diff",
4519
+ attributes: {
4520
+ "data-line-num-old": diffLineNumbers?.oldLine != null ? String(diffLineNumbers.oldLine) : "",
4521
+ "data-line-num-new": diffLineNumbers?.newLine != null ? String(diffLineNumbers.newLine) : "",
4522
+ "data-diff-marker": diffMarker,
4523
+ style: `--line-num-old-width: ${diffOldLineNumWidth}ch; --line-num-new-width: ${diffNewLineNumWidth}ch`
2773
4524
  }
4525
+ }).range(line.from)
4526
+ );
4527
+ }
4528
+ if (!isFenceLine && infoProps.diff) {
4529
+ this.decorateDiffLine(
4530
+ line,
4531
+ codeLineIndex,
4532
+ diffStates,
4533
+ cursorInRange,
4534
+ !infoProps.showLineNumbers,
4535
+ decorations
4536
+ );
4537
+ }
4538
+ if (!isFenceLine && infoProps.highlightLines) {
4539
+ const highlightLineNumber = infoProps.diff ? diffHighlightLineNumbers[codeLineIndex] ?? codeLineIndex + 1 : startLineNum + codeLineIndex;
4540
+ if (infoProps.highlightLines.includes(highlightLineNumber)) {
4541
+ decorations.push(codeMarkDecorations["code-line-highlight"].range(line.from));
4542
+ }
4543
+ }
4544
+ if (!isFenceLine && infoProps.highlightText?.length) {
4545
+ this.decorateTextHighlights(
4546
+ line.from,
4547
+ view.state.sliceDoc(line.from, line.to),
4548
+ infoProps.highlightText,
4549
+ highlightInstanceCounters,
4550
+ decorations
4551
+ );
4552
+ }
4553
+ if (!isFenceLine) {
4554
+ codeLineIndex++;
4555
+ }
4556
+ }
4557
+ this.decorateFenceMarkers(node.node, cursorInRange, decorations);
4558
+ if (!cursorInRange && infoProps.caption) {
4559
+ decorations.push(
4560
+ Decoration.widget({
4561
+ widget: new CodeBlockCaptionWidget(infoProps.caption),
4562
+ block: false,
4563
+ side: 1
4564
+ }).range(nodeLineEnd.to)
4565
+ );
4566
+ }
4567
+ }
4568
+ decorateFenceMarkers(node, cursorInRange, decorations) {
4569
+ for (let child = node.firstChild; child; child = child.nextSibling) {
4570
+ if (child.name === "CodeMark" || child.name === "CodeInfo") {
4571
+ decorations.push(
4572
+ (cursorInRange ? codeMarkDecorations["code-fence"] : codeMarkDecorations["code-hidden"]).range(
4573
+ child.from,
4574
+ child.to
4575
+ )
4576
+ );
4577
+ }
4578
+ }
4579
+ }
4580
+ decorateDiffLine(line, codeLineIndex, diffStates, cursorInRange, showDiffMarkerGutter, decorations) {
4581
+ const diffState = diffStates[codeLineIndex];
4582
+ const diffMarker = diffState?.kind === "addition" ? "+" : diffState?.kind === "deletion" ? "-" : " ";
4583
+ if (showDiffMarkerGutter) {
4584
+ decorations.push(
4585
+ Decoration.line({
4586
+ class: "cm-draftly-code-line-diff-gutter",
4587
+ attributes: {
4588
+ "data-diff-marker": diffMarker
2774
4589
  }
2775
- if (!cursorInRange && infoProps.caption) {
2776
- decorations.push(
2777
- Decoration.widget({
2778
- widget: new CodeBlockCaptionWidget(infoProps.caption),
2779
- block: false,
2780
- side: 1
2781
- // After the content
2782
- }).range(nodeLineEnd.to)
2783
- );
4590
+ }).range(line.from)
4591
+ );
4592
+ }
4593
+ if (diffState?.kind === "addition") {
4594
+ decorations.push(codeMarkDecorations["diff-line-add"].range(line.from));
4595
+ if (cursorInRange && line.to > line.from) {
4596
+ decorations.push(codeMarkDecorations["diff-sign-add"].range(line.from, line.from + 1));
4597
+ }
4598
+ }
4599
+ if (diffState?.kind === "deletion") {
4600
+ decorations.push(codeMarkDecorations["diff-line-del"].range(line.from));
4601
+ if (cursorInRange && line.to > line.from) {
4602
+ decorations.push(codeMarkDecorations["diff-sign-del"].range(line.from, line.from + 1));
4603
+ }
4604
+ }
4605
+ if (!cursorInRange && line.to > line.from && (diffState?.escapedMarker || diffState?.kind === "addition" || diffState?.kind === "deletion")) {
4606
+ decorations.push(codeMarkDecorations["diff-escape-hidden"].range(line.from, line.from + 1));
4607
+ }
4608
+ if (diffState?.modificationRanges?.length) {
4609
+ for (const [start, end] of diffState.modificationRanges) {
4610
+ const rangeFrom = line.from + diffState.contentOffset + start;
4611
+ const rangeTo = line.from + diffState.contentOffset + end;
4612
+ if (rangeTo > rangeFrom) {
4613
+ decorations.push(
4614
+ (diffState.kind === "addition" ? codeMarkDecorations["diff-mod-add"] : codeMarkDecorations["diff-mod-del"]).range(rangeFrom, rangeTo)
4615
+ );
4616
+ }
4617
+ }
4618
+ }
4619
+ }
4620
+ decorateTextHighlights(lineFrom, lineText, highlights, instanceCounters, decorations) {
4621
+ for (const [highlightIndex, textHighlight] of highlights.entries()) {
4622
+ try {
4623
+ const regex = new RegExp(textHighlight.pattern, "g");
4624
+ let match;
4625
+ while ((match = regex.exec(lineText)) !== null) {
4626
+ instanceCounters[highlightIndex] = (instanceCounters[highlightIndex] ?? 0) + 1;
4627
+ const globalMatchIndex = instanceCounters[highlightIndex];
4628
+ const shouldHighlight = !textHighlight.instances || textHighlight.instances.includes(globalMatchIndex);
4629
+ if (shouldHighlight) {
4630
+ const matchFrom = lineFrom + match.index;
4631
+ const matchTo = matchFrom + match[0].length;
4632
+ decorations.push(codeMarkDecorations["code-text-highlight"].range(matchFrom, matchTo));
2784
4633
  }
2785
4634
  }
4635
+ } catch {
2786
4636
  }
2787
- });
4637
+ }
2788
4638
  }
2789
4639
  /**
2790
4640
  * Render code elements to HTML for static preview.
2791
4641
  * Applies syntax highlighting using @lezer/highlight.
2792
4642
  */
2793
- renderToHTML(node, children, ctx) {
4643
+ async renderToHTML(node, _children, ctx) {
2794
4644
  if (node.name === "CodeMark") {
2795
4645
  return "";
2796
4646
  }
@@ -2800,7 +4650,7 @@ var CodePlugin = class extends DecorationPlugin {
2800
4650
  if (match && match[1]) {
2801
4651
  content = match[1];
2802
4652
  }
2803
- return `<code class="cm-draftly-code-inline" style="padding: 0.1rem 0.25rem">${ctx.sanitize(content)}</code>`;
4653
+ return `<code class="cm-draftly-code-inline" style="padding: 0.1rem 0.25rem">${this.escapeHtml(content)}</code>`;
2804
4654
  }
2805
4655
  if (node.name === "FencedCode") {
2806
4656
  const content = ctx.sliceDoc(node.from, node.to);
@@ -2818,9 +4668,9 @@ var CodePlugin = class extends DecorationPlugin {
2818
4668
  html += `<div class="cm-draftly-code-header">`;
2819
4669
  html += `<div class="cm-draftly-code-header-left">`;
2820
4670
  if (props.title) {
2821
- html += `<span class="cm-draftly-code-header-title">${ctx.sanitize(props.title)}</span>`;
4671
+ html += `<span class="cm-draftly-code-header-title">${this.escapeHtml(props.title)}</span>`;
2822
4672
  } else if (props.language) {
2823
- html += `<span class="cm-draftly-code-header-lang">${ctx.sanitize(props.language)}</span>`;
4673
+ html += `<span class="cm-draftly-code-header-lang">${this.escapeHtml(props.language)}</span>`;
2824
4674
  }
2825
4675
  html += `</div>`;
2826
4676
  if (props.copy !== false) {
@@ -2833,32 +4683,83 @@ var CodePlugin = class extends DecorationPlugin {
2833
4683
  }
2834
4684
  html += `</div>`;
2835
4685
  }
2836
- const startLineNum = typeof props.lineNumbers === "number" ? props.lineNumbers : 1;
2837
- const lineNumWidth = String(startLineNum + codeLines.length - 1).length;
4686
+ const startLineNum = typeof props.showLineNumbers === "number" ? props.showLineNumbers : 1;
4687
+ const previewHighlightCounters = new Array(props.highlightText?.length ?? 0).fill(0);
4688
+ const diffStates = props.diff ? this.analyzeDiffLines(codeLines) : [];
4689
+ const previewDiffLineNumbers = props.diff ? this.computeDiffDisplayLineNumbers(diffStates, startLineNum) : [];
4690
+ const previewLineNumbers = props.diff ? previewDiffLineNumbers.map((numbers, index) => numbers.newLine ?? numbers.oldLine ?? startLineNum + index) : codeLines.map((_, index) => startLineNum + index);
4691
+ const previewHighlightLineNumbers = props.diff ? this.computeDiffDisplayLineNumbers(diffStates, startLineNum).map(
4692
+ (numbers, index) => numbers.newLine ?? numbers.oldLine ?? startLineNum + index
4693
+ ) : [];
4694
+ const lineNumWidth = String(Math.max(...previewLineNumbers, startLineNum)).length;
4695
+ const previewOldLineNumWidth = String(
4696
+ Math.max(
4697
+ ...previewDiffLineNumbers.map((numbers) => numbers.oldLine ?? 0),
4698
+ startLineNum
4699
+ )
4700
+ ).length;
4701
+ const previewNewLineNumWidth = String(
4702
+ Math.max(
4703
+ ...previewDiffLineNumbers.map((numbers) => numbers.newLine ?? 0),
4704
+ startLineNum
4705
+ )
4706
+ ).length;
4707
+ const previewContentLines = props.diff ? diffStates.map((state) => state.content) : codeLines;
4708
+ const highlightedLines = await this.highlightCodeLines(
4709
+ previewContentLines.join("\n"),
4710
+ props.language || "",
4711
+ ctx.syntaxHighlighters
4712
+ );
2838
4713
  const hasHeader = showHeader ? " cm-draftly-code-block-has-header" : "";
2839
4714
  const hasCaption = props.caption ? " cm-draftly-code-block-has-caption" : "";
2840
- html += `<pre class="cm-draftly-code-block${hasHeader}${hasCaption}"${props.language ? ` data-lang="${ctx.sanitize(props.language)}"` : ""}>`;
4715
+ html += `<pre class="cm-draftly-code-block${hasHeader}${hasCaption}"${props.language ? ` data-lang="${this.escapeAttribute(props.language)}"` : ""}>`;
2841
4716
  html += `<code>`;
2842
4717
  codeLines.forEach((line, index) => {
2843
- const lineNum = startLineNum + index;
2844
- const isHighlighted = props.highlightLines?.includes(index + 1);
4718
+ const lineNum = previewLineNumbers[index] ?? startLineNum + index;
4719
+ const highlightLineNumber = props.diff ? previewHighlightLineNumbers[index] ?? startLineNum + index : startLineNum + index;
4720
+ const isHighlighted = props.highlightLines?.includes(highlightLineNumber);
4721
+ const diffState = props.diff ? diffStates[index] : void 0;
4722
+ const diffLineNumbers = props.diff ? previewDiffLineNumbers[index] : void 0;
2845
4723
  const lineClasses = ["cm-draftly-code-line"];
2846
4724
  if (isHighlighted) lineClasses.push("cm-draftly-code-line-highlight");
2847
- if (props.lineNumbers) lineClasses.push("cm-draftly-code-line-numbered");
4725
+ if (props.showLineNumbers) {
4726
+ lineClasses.push(props.diff ? "cm-draftly-code-line-numbered-diff" : "cm-draftly-code-line-numbered");
4727
+ }
4728
+ if (diffState?.kind === "addition") lineClasses.push("cm-draftly-code-line-diff-add");
4729
+ if (diffState?.kind === "deletion") lineClasses.push("cm-draftly-code-line-diff-del");
2848
4730
  const lineAttrs = [`class="${lineClasses.join(" ")}"`];
2849
- if (props.lineNumbers) {
4731
+ if (props.showLineNumbers && !props.diff) {
2850
4732
  lineAttrs.push(`data-line-num="${lineNum}"`);
2851
4733
  lineAttrs.push(`style="--line-num-width: ${lineNumWidth}ch"`);
2852
4734
  }
2853
- let lineContent = this.highlightCodeLine(line, props.language || "", ctx);
4735
+ if (props.diff) {
4736
+ const diffMarker = diffState?.kind === "addition" ? "+" : diffState?.kind === "deletion" ? "-" : " ";
4737
+ if (props.showLineNumbers) {
4738
+ lineAttrs.push(`data-line-num-old="${diffLineNumbers?.oldLine != null ? diffLineNumbers.oldLine : ""}"`);
4739
+ lineAttrs.push(`data-line-num-new="${diffLineNumbers?.newLine != null ? diffLineNumbers.newLine : ""}"`);
4740
+ lineAttrs.push(`data-diff-marker="${diffMarker}"`);
4741
+ lineAttrs.push(
4742
+ `style="--line-num-old-width: ${previewOldLineNumWidth}ch; --line-num-new-width: ${previewNewLineNumWidth}ch"`
4743
+ );
4744
+ } else {
4745
+ lineAttrs.push(`data-diff-marker="${diffMarker}"`);
4746
+ lineClasses.push("cm-draftly-code-line-diff-gutter");
4747
+ lineAttrs[0] = `class="${lineClasses.join(" ")}"`;
4748
+ }
4749
+ }
4750
+ const highlightedLine = highlightedLines[index] ?? this.escapeHtml(previewContentLines[index] ?? line);
4751
+ let lineContent = highlightedLine;
4752
+ if (diffState) {
4753
+ lineContent = this.renderDiffPreviewLine(diffState, highlightedLine);
4754
+ }
2854
4755
  if (props.highlightText && props.highlightText.length > 0) {
2855
- lineContent = this.applyTextHighlights(lineContent, props.highlightText);
4756
+ lineContent = this.applyTextHighlights(lineContent, props.highlightText, previewHighlightCounters);
2856
4757
  }
2857
4758
  html += `<span ${lineAttrs.join(" ")}>${lineContent || " "}</span>`;
2858
4759
  });
2859
4760
  html += `</code></pre>`;
2860
4761
  if (props.caption) {
2861
- html += `<div class="cm-draftly-code-caption">${ctx.sanitize(props.caption)}</div>`;
4762
+ html += `<div class="cm-draftly-code-caption">${this.escapeHtml(props.caption)}</div>`;
2862
4763
  }
2863
4764
  html += `</div>`;
2864
4765
  return html;
@@ -2868,54 +4769,290 @@ var CodePlugin = class extends DecorationPlugin {
2868
4769
  }
2869
4770
  return null;
2870
4771
  }
4772
+ /** Parse comma-separated numbers and ranges (e.g. "1,3-5") into [1,3,4,5]. */
4773
+ parseNumberList(value) {
4774
+ const result = [];
4775
+ for (const part of value.split(",")) {
4776
+ const trimmed = part.trim();
4777
+ const rangeMatch = trimmed.match(/^(\d+)-(\d+)$/);
4778
+ if (rangeMatch && rangeMatch[1] && rangeMatch[2]) {
4779
+ const start = parseInt(rangeMatch[1], 10);
4780
+ const end = parseInt(rangeMatch[2], 10);
4781
+ for (let i = start; i <= end; i++) {
4782
+ result.push(i);
4783
+ }
4784
+ continue;
4785
+ }
4786
+ if (/^\d+$/.test(trimmed)) {
4787
+ result.push(parseInt(trimmed, 10));
4788
+ }
4789
+ }
4790
+ return result;
4791
+ }
2871
4792
  /**
2872
4793
  * Highlight a single line of code using the language's Lezer parser.
2873
4794
  * Falls back to sanitized plain text if the language is not supported.
2874
4795
  */
2875
- highlightCodeLine(line, lang, ctx) {
2876
- if (!lang || !line) {
2877
- return ctx.sanitize(line);
4796
+ async highlightCodeLines(code, lang, syntaxHighlighters) {
4797
+ const rawLines = code.split("\n");
4798
+ if (!lang || !code) {
4799
+ return rawLines.map((line) => this.escapeHtml(line));
2878
4800
  }
2879
- const langDesc = languages.find(
2880
- (l) => l.name.toLowerCase() === lang.toLowerCase() || l.alias && l.alias.includes(lang.toLowerCase())
2881
- );
2882
- if (!langDesc || !langDesc.support) {
2883
- return ctx.sanitize(line);
4801
+ const parser = await this.resolveLanguageParser(lang);
4802
+ if (!parser) {
4803
+ return rawLines.map((line) => this.escapeHtml(line));
2884
4804
  }
2885
4805
  try {
2886
- const parser = langDesc.support.language.parser;
2887
- const tree = parser.parse(line);
2888
- let result = "";
4806
+ const tree = parser.parse(code);
4807
+ const highlightedLines = [""];
2889
4808
  highlightCode(
2890
- line,
4809
+ code,
2891
4810
  tree,
2892
- classHighlighter,
4811
+ syntaxHighlighters && syntaxHighlighters.length > 0 ? syntaxHighlighters : [],
2893
4812
  (text, classes2) => {
2894
- if (classes2) {
2895
- result += `<span class="${classes2}">${ctx.sanitize(text)}</span>`;
2896
- } else {
2897
- result += ctx.sanitize(text);
2898
- }
4813
+ const chunk = classes2 ? `<span class="${this.escapeAttribute(classes2)}">${this.escapeHtml(text)}</span>` : this.escapeHtml(text);
4814
+ highlightedLines[highlightedLines.length - 1] += chunk;
2899
4815
  },
2900
4816
  () => {
4817
+ highlightedLines.push("");
2901
4818
  }
2902
- // No newlines for single line
2903
4819
  );
2904
- return result;
4820
+ return rawLines.map((line, index) => highlightedLines[index] || this.escapeHtml(line));
2905
4821
  } catch {
2906
- return ctx.sanitize(line);
4822
+ return rawLines.map((line) => this.escapeHtml(line));
4823
+ }
4824
+ }
4825
+ async resolveLanguageParser(lang) {
4826
+ const normalizedLang = this.normalizeLanguage(lang);
4827
+ if (!normalizedLang) return null;
4828
+ const cached = this.parserCache.get(normalizedLang);
4829
+ if (cached) return cached;
4830
+ const parserPromise = (async () => {
4831
+ const langDesc = LanguageDescription.matchLanguageName(languages, normalizedLang, true);
4832
+ if (!langDesc) return null;
4833
+ if (langDesc.support) {
4834
+ return langDesc.support.language.parser;
4835
+ }
4836
+ if (typeof langDesc.load === "function") {
4837
+ try {
4838
+ const support = await langDesc.load();
4839
+ return support.language.parser;
4840
+ } catch {
4841
+ return null;
4842
+ }
4843
+ }
4844
+ return null;
4845
+ })();
4846
+ this.parserCache.set(normalizedLang, parserPromise);
4847
+ return parserPromise;
4848
+ }
4849
+ normalizeLanguage(lang) {
4850
+ const normalized = lang.trim().toLowerCase();
4851
+ if (!normalized) return "";
4852
+ const normalizedMap = {
4853
+ "c++": "cpp",
4854
+ "c#": "csharp",
4855
+ "f#": "fsharp",
4856
+ py: "python",
4857
+ js: "javascript",
4858
+ ts: "typescript",
4859
+ sh: "shell"
4860
+ };
4861
+ return normalizedMap[normalized] ?? normalized;
4862
+ }
4863
+ escapeHtml(value) {
4864
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
4865
+ }
4866
+ escapeAttribute(value) {
4867
+ return this.escapeHtml(value).replace(/`/g, "&#96;");
4868
+ }
4869
+ analyzeDiffLines(lines) {
4870
+ const states = lines.map((line) => this.parseDiffLineState(line));
4871
+ let index = 0;
4872
+ while (index < states.length) {
4873
+ if (states[index]?.kind !== "deletion") {
4874
+ index++;
4875
+ continue;
4876
+ }
4877
+ const deletionStart = index;
4878
+ while (index < states.length && states[index]?.kind === "deletion") {
4879
+ index++;
4880
+ }
4881
+ const deletionEnd = index;
4882
+ const additionStart = index;
4883
+ while (index < states.length && states[index]?.kind === "addition") {
4884
+ index++;
4885
+ }
4886
+ const additionEnd = index;
4887
+ if (additionStart === additionEnd) {
4888
+ continue;
4889
+ }
4890
+ const pairCount = Math.min(deletionEnd - deletionStart, additionEnd - additionStart);
4891
+ for (let pairIndex = 0; pairIndex < pairCount; pairIndex++) {
4892
+ const deletionState = states[deletionStart + pairIndex];
4893
+ const additionState = states[additionStart + pairIndex];
4894
+ if (!deletionState || !additionState) {
4895
+ continue;
4896
+ }
4897
+ const ranges = this.computeChangedRanges(deletionState.content, additionState.content);
4898
+ if (ranges.oldRanges.length > 0) {
4899
+ deletionState.modificationRanges = ranges.oldRanges;
4900
+ }
4901
+ if (ranges.newRanges.length > 0) {
4902
+ additionState.modificationRanges = ranges.newRanges;
4903
+ }
4904
+ }
4905
+ }
4906
+ return states;
4907
+ }
4908
+ computeDiffDisplayLineNumbers(states, startLineNum) {
4909
+ const numbers = [];
4910
+ let oldLineNumber = startLineNum;
4911
+ let newLineNumber = startLineNum;
4912
+ for (const state of states) {
4913
+ if (state.kind === "deletion") {
4914
+ numbers.push({ oldLine: oldLineNumber, newLine: null });
4915
+ oldLineNumber++;
4916
+ continue;
4917
+ }
4918
+ if (state.kind === "addition") {
4919
+ numbers.push({ oldLine: null, newLine: newLineNumber });
4920
+ newLineNumber++;
4921
+ continue;
4922
+ }
4923
+ numbers.push({ oldLine: oldLineNumber, newLine: newLineNumber });
4924
+ oldLineNumber++;
4925
+ newLineNumber++;
4926
+ }
4927
+ return numbers;
4928
+ }
4929
+ parseDiffLineState(line) {
4930
+ const escapedMarker = line.startsWith("\\+") || line.startsWith("\\-");
4931
+ if (escapedMarker) {
4932
+ return {
4933
+ kind: "normal",
4934
+ content: line.slice(1),
4935
+ contentOffset: 1,
4936
+ escapedMarker: true
4937
+ };
4938
+ }
4939
+ if (line.startsWith("+")) {
4940
+ return {
4941
+ kind: "addition",
4942
+ content: line.slice(1),
4943
+ contentOffset: 1,
4944
+ escapedMarker: false
4945
+ };
4946
+ }
4947
+ if (line.startsWith("-")) {
4948
+ return {
4949
+ kind: "deletion",
4950
+ content: line.slice(1),
4951
+ contentOffset: 1,
4952
+ escapedMarker: false
4953
+ };
4954
+ }
4955
+ return {
4956
+ kind: "normal",
4957
+ content: line,
4958
+ contentOffset: 0,
4959
+ escapedMarker: false
4960
+ };
4961
+ }
4962
+ computeChangedRanges(oldText, newText) {
4963
+ let prefix = 0;
4964
+ while (prefix < oldText.length && prefix < newText.length && oldText[prefix] === newText[prefix]) {
4965
+ prefix++;
4966
+ }
4967
+ let oldSuffix = oldText.length;
4968
+ let newSuffix = newText.length;
4969
+ while (oldSuffix > prefix && newSuffix > prefix && oldText[oldSuffix - 1] === newText[newSuffix - 1]) {
4970
+ oldSuffix--;
4971
+ newSuffix--;
4972
+ }
4973
+ const oldRanges = [];
4974
+ const newRanges = [];
4975
+ if (oldSuffix > prefix) {
4976
+ oldRanges.push([prefix, oldSuffix]);
4977
+ }
4978
+ if (newSuffix > prefix) {
4979
+ newRanges.push([prefix, newSuffix]);
4980
+ }
4981
+ return { oldRanges, newRanges };
4982
+ }
4983
+ renderDiffPreviewLine(diffState, highlightedContent) {
4984
+ const modClass = diffState.kind === "addition" ? "cm-draftly-code-diff-mod-add" : diffState.kind === "deletion" ? "cm-draftly-code-diff-mod-del" : "";
4985
+ const baseHighlightedContent = highlightedContent || this.escapeHtml(diffState.content);
4986
+ const contentHtml = diffState.modificationRanges && modClass ? this.applyRangesToHighlightedHTML(baseHighlightedContent, diffState.modificationRanges, modClass) : baseHighlightedContent;
4987
+ return contentHtml || " ";
4988
+ }
4989
+ applyRangesToHighlightedHTML(htmlContent, ranges, className) {
4990
+ const normalizedRanges = ranges.map(([start, end]) => [Math.max(0, start), Math.max(0, end)]).filter(([start, end]) => end > start).sort((a, b) => a[0] - b[0]);
4991
+ if (normalizedRanges.length === 0 || !htmlContent) {
4992
+ return htmlContent;
4993
+ }
4994
+ const isInsideRange = (position) => {
4995
+ for (const [start, end] of normalizedRanges) {
4996
+ if (position >= start && position < end) return true;
4997
+ if (position < start) return false;
4998
+ }
4999
+ return false;
5000
+ };
5001
+ let result = "";
5002
+ let htmlIndex = 0;
5003
+ let textPosition = 0;
5004
+ let markOpen = false;
5005
+ while (htmlIndex < htmlContent.length) {
5006
+ const char = htmlContent[htmlIndex];
5007
+ if (char === "<") {
5008
+ const tagEnd = htmlContent.indexOf(">", htmlIndex);
5009
+ if (tagEnd === -1) {
5010
+ result += htmlContent.slice(htmlIndex);
5011
+ break;
5012
+ }
5013
+ result += htmlContent.slice(htmlIndex, tagEnd + 1);
5014
+ htmlIndex = tagEnd + 1;
5015
+ continue;
5016
+ }
5017
+ let token = char;
5018
+ if (char === "&") {
5019
+ const entityEnd = htmlContent.indexOf(";", htmlIndex);
5020
+ if (entityEnd !== -1) {
5021
+ token = htmlContent.slice(htmlIndex, entityEnd + 1);
5022
+ htmlIndex = entityEnd + 1;
5023
+ } else {
5024
+ htmlIndex += 1;
5025
+ }
5026
+ } else {
5027
+ htmlIndex += 1;
5028
+ }
5029
+ const shouldMark = isInsideRange(textPosition);
5030
+ if (shouldMark && !markOpen) {
5031
+ result += `<mark class="${className}">`;
5032
+ markOpen = true;
5033
+ }
5034
+ if (!shouldMark && markOpen) {
5035
+ result += "</mark>";
5036
+ markOpen = false;
5037
+ }
5038
+ result += token;
5039
+ textPosition += 1;
2907
5040
  }
5041
+ if (markOpen) {
5042
+ result += "</mark>";
5043
+ }
5044
+ return result;
2908
5045
  }
2909
5046
  /**
2910
5047
  * Apply text highlights (regex patterns) to already syntax-highlighted HTML.
2911
5048
  * Wraps matched patterns in `<mark>` elements.
2912
5049
  */
2913
- applyTextHighlights(htmlContent, highlights) {
5050
+ applyTextHighlights(htmlContent, highlights, instanceCounters) {
2914
5051
  let result = htmlContent;
2915
- for (const highlight of highlights) {
5052
+ for (const [highlightIndex, highlight] of highlights.entries()) {
2916
5053
  try {
2917
5054
  const regex = new RegExp(`(${highlight.pattern})`, "g");
2918
- let matchCount = 0;
5055
+ let matchCount = instanceCounters?.[highlightIndex] ?? 0;
2919
5056
  result = result.replace(regex, (match) => {
2920
5057
  matchCount++;
2921
5058
  const shouldHighlight = !highlight.instances || highlight.instances.includes(matchCount);
@@ -2924,252 +5061,15 @@ var CodePlugin = class extends DecorationPlugin {
2924
5061
  }
2925
5062
  return match;
2926
5063
  });
5064
+ if (instanceCounters) {
5065
+ instanceCounters[highlightIndex] = matchCount;
5066
+ }
2927
5067
  } catch {
2928
5068
  }
2929
5069
  }
2930
5070
  return result;
2931
5071
  }
2932
5072
  };
2933
- var theme10 = createTheme({
2934
- default: {
2935
- // Inline code
2936
- ".cm-draftly-code-inline": {
2937
- fontFamily: "var(--font-jetbrains-mono, monospace)",
2938
- fontSize: "0.9rem",
2939
- backgroundColor: "rgba(0, 0, 0, 0.05)",
2940
- padding: "0.1rem 0.25rem",
2941
- border: "1px solid var(--color-border)",
2942
- borderRadius: "3px"
2943
- },
2944
- // Fenced code block lines
2945
- ".cm-draftly-code-block-line": {
2946
- "--radius": "0.375rem",
2947
- fontFamily: "var(--font-jetbrains-mono, monospace)",
2948
- fontSize: "0.9rem",
2949
- backgroundColor: "rgba(0, 0, 0, 0.03)",
2950
- padding: "0 1rem !important",
2951
- lineHeight: "1.5",
2952
- borderLeft: "1px solid var(--color-border)",
2953
- borderRight: "1px solid var(--color-border)"
2954
- },
2955
- // First line of code block
2956
- ".cm-draftly-code-block-line-start": {
2957
- borderTopLeftRadius: "var(--radius)",
2958
- borderTopRightRadius: "var(--radius)",
2959
- position: "relative",
2960
- overflow: "hidden",
2961
- borderTop: "1px solid var(--color-border)",
2962
- paddingBottom: "0.5rem !important"
2963
- },
2964
- // Remove top radius when header is present
2965
- ".cm-draftly-code-block-has-header": {
2966
- padding: "0 !important",
2967
- paddingBottom: "0.5rem !important"
2968
- },
2969
- // Code block header widget
2970
- ".cm-draftly-code-header": {
2971
- display: "flex",
2972
- justifyContent: "space-between",
2973
- alignItems: "center",
2974
- padding: "0.25rem 1rem",
2975
- backgroundColor: "rgba(0, 0, 0, 0.06)",
2976
- fontFamily: "var(--font-jetbrains-mono, monospace)",
2977
- fontSize: "0.85rem"
2978
- },
2979
- ".cm-draftly-code-header-left": {
2980
- display: "flex",
2981
- alignItems: "center",
2982
- gap: "0.5rem"
2983
- },
2984
- ".cm-draftly-code-header-title": {
2985
- color: "var(--color-text, inherit)",
2986
- fontWeight: "500"
2987
- },
2988
- ".cm-draftly-code-header-lang": {
2989
- color: "#6a737d",
2990
- opacity: "0.8"
2991
- },
2992
- ".cm-draftly-code-header-right": {
2993
- display: "flex",
2994
- alignItems: "center",
2995
- gap: "0.5rem"
2996
- },
2997
- ".cm-draftly-code-copy-btn": {
2998
- display: "flex",
2999
- alignItems: "center",
3000
- justifyContent: "center",
3001
- padding: "0.25rem",
3002
- backgroundColor: "transparent",
3003
- border: "none",
3004
- borderRadius: "4px",
3005
- cursor: "pointer",
3006
- color: "#6a737d",
3007
- transition: "color 0.2s, background-color 0.2s"
3008
- },
3009
- ".cm-draftly-code-copy-btn:hover": {
3010
- backgroundColor: "rgba(0, 0, 0, 0.1)",
3011
- color: "var(--color-text, inherit)"
3012
- },
3013
- ".cm-draftly-code-copy-btn.copied": {
3014
- color: "#22c55e"
3015
- },
3016
- // Caption (below code block)
3017
- ".cm-draftly-code-block-has-caption": {
3018
- padding: "0 !important",
3019
- paddingTop: "0.5rem !important"
3020
- },
3021
- ".cm-draftly-code-caption": {
3022
- textAlign: "center",
3023
- fontSize: "0.85rem",
3024
- color: "#6a737d",
3025
- fontStyle: "italic",
3026
- padding: "0.25rem 1rem",
3027
- backgroundColor: "rgba(0, 0, 0, 0.06)"
3028
- },
3029
- // Last line of code block
3030
- ".cm-draftly-code-block-line-end": {
3031
- borderBottomLeftRadius: "var(--radius)",
3032
- borderBottomRightRadius: "var(--radius)",
3033
- borderBottom: "1px solid var(--color-border)",
3034
- paddingTop: "0.5rem !important"
3035
- },
3036
- ".cm-draftly-code-block-line-end br": {
3037
- display: "none"
3038
- },
3039
- // Fence markers (```)
3040
- ".cm-draftly-code-fence": {
3041
- color: "#6a737d",
3042
- fontFamily: "var(--font-jetbrains-mono, monospace)"
3043
- },
3044
- // Line numbers
3045
- ".cm-draftly-code-line-numbered": {
3046
- paddingLeft: "calc(var(--line-num-width, 2ch) + 1rem) !important",
3047
- position: "relative"
3048
- },
3049
- ".cm-draftly-code-line-numbered::before": {
3050
- content: "attr(data-line-num)",
3051
- position: "absolute",
3052
- left: "0.5rem",
3053
- top: "0.2rem",
3054
- width: "var(--line-num-width, 2ch)",
3055
- textAlign: "right",
3056
- color: "#6a737d",
3057
- opacity: "0.6",
3058
- fontFamily: "var(--font-jetbrains-mono, monospace)",
3059
- fontSize: "0.85rem",
3060
- userSelect: "none"
3061
- },
3062
- // Preview: code lines (need block display for full-width highlights)
3063
- ".cm-draftly-code-line": {
3064
- display: "block",
3065
- position: "relative",
3066
- paddingLeft: "1rem",
3067
- paddingRight: "1rem",
3068
- lineHeight: "1.5",
3069
- borderLeft: "3px solid transparent"
3070
- },
3071
- // Line highlight
3072
- ".cm-draftly-code-line-highlight": {
3073
- backgroundColor: "rgba(255, 220, 100, 0.2) !important",
3074
- borderLeft: "3px solid #f0b429 !important"
3075
- },
3076
- // Text highlight
3077
- ".cm-draftly-code-text-highlight": {
3078
- backgroundColor: "rgba(255, 220, 100, 0.4)",
3079
- borderRadius: "2px",
3080
- padding: "0.1rem 0"
3081
- },
3082
- // Preview: container wrapper
3083
- ".cm-draftly-code-container": {
3084
- margin: "1rem 0",
3085
- borderRadius: "var(--radius)",
3086
- overflow: "hidden",
3087
- border: "1px solid var(--color-border)"
3088
- },
3089
- // Preview: header inside container
3090
- ".cm-draftly-code-container .cm-draftly-code-header": {
3091
- borderRadius: "0",
3092
- border: "none",
3093
- borderBottom: "1px solid var(--color-border)"
3094
- },
3095
- // Preview: code block inside container
3096
- ".cm-draftly-code-container .cm-draftly-code-block": {
3097
- margin: "0",
3098
- borderRadius: "0",
3099
- border: "none",
3100
- whiteSpace: "pre-wrap"
3101
- },
3102
- // Preview: caption inside container
3103
- ".cm-draftly-code-container .cm-draftly-code-caption": {
3104
- borderTop: "1px solid var(--color-border)"
3105
- },
3106
- // Preview: standalone code block (not in container)
3107
- ".cm-draftly-code-block": {
3108
- fontFamily: "var(--font-jetbrains-mono, monospace)",
3109
- fontSize: "0.9rem",
3110
- backgroundColor: "rgba(0, 0, 0, 0.03)",
3111
- padding: "1rem",
3112
- overflow: "auto",
3113
- position: "relative",
3114
- borderRadius: "var(--radius)",
3115
- border: "1px solid var(--color-border)"
3116
- },
3117
- // Preview: code block with header (remove top radius)
3118
- ".cm-draftly-code-block.cm-draftly-code-block-has-header": {
3119
- borderTopLeftRadius: "0",
3120
- borderTopRightRadius: "0",
3121
- borderTop: "none",
3122
- margin: "0",
3123
- paddingTop: "0.5rem !important"
3124
- },
3125
- // Preview: code block with caption (remove bottom radius)
3126
- ".cm-draftly-code-block.cm-draftly-code-block-has-caption": {
3127
- borderBottomLeftRadius: "0",
3128
- borderBottomRightRadius: "0",
3129
- borderBottom: "none",
3130
- paddingBottom: "0.5rem !important"
3131
- }
3132
- },
3133
- dark: {
3134
- ".cm-draftly-code-inline": {
3135
- backgroundColor: "rgba(255, 255, 255, 0.1)"
3136
- },
3137
- ".cm-draftly-code-block-line": {
3138
- backgroundColor: "rgba(255, 255, 255, 0.05)"
3139
- },
3140
- ".cm-draftly-code-fence": {
3141
- color: "#8b949e"
3142
- },
3143
- ".cm-draftly-code-block": {
3144
- backgroundColor: "rgba(255, 255, 255, 0.05)"
3145
- },
3146
- ".cm-draftly-code-header": {
3147
- backgroundColor: "rgba(255, 255, 255, 0.08)"
3148
- },
3149
- ".cm-draftly-code-header-lang": {
3150
- color: "#8b949e"
3151
- },
3152
- ".cm-draftly-code-copy-btn": {
3153
- color: "#8b949e"
3154
- },
3155
- ".cm-draftly-code-copy-btn:hover": {
3156
- backgroundColor: "rgba(255, 255, 255, 0.1)"
3157
- },
3158
- ".cm-draftly-code-caption": {
3159
- backgroundColor: "rgba(255, 255, 255, 0.05)"
3160
- },
3161
- ".cm-draftly-code-line-numbered::before": {
3162
- color: "#8b949e"
3163
- },
3164
- ".cm-draftly-code-line-highlight": {
3165
- backgroundColor: "rgba(255, 220, 100, 0.15) !important",
3166
- borderLeft: "3px solid #d9a520 !important"
3167
- },
3168
- ".cm-draftly-code-text-highlight": {
3169
- backgroundColor: "rgba(255, 220, 100, 0.3)"
3170
- }
3171
- }
3172
- });
3173
5073
  var quoteMarkDecorations = {
3174
5074
  /** Decoration for the > marker */
3175
5075
  "quote-mark": Decoration.replace({}),
@@ -3337,6 +5237,103 @@ var theme12 = createTheme({
3337
5237
  }
3338
5238
  }
3339
5239
  });
5240
+ function shortcodeToEmoji(raw) {
5241
+ const rendered = emoji.emojify(raw);
5242
+ return rendered !== raw ? rendered : null;
5243
+ }
5244
+ var EmojiWidget = class extends WidgetType {
5245
+ constructor(rendered) {
5246
+ super();
5247
+ this.rendered = rendered;
5248
+ }
5249
+ eq(other) {
5250
+ return other.rendered === this.rendered;
5251
+ }
5252
+ toDOM() {
5253
+ const span = document.createElement("span");
5254
+ span.className = "cm-draftly-emoji";
5255
+ span.textContent = this.rendered;
5256
+ return span;
5257
+ }
5258
+ ignoreEvent() {
5259
+ return false;
5260
+ }
5261
+ };
5262
+ var emojiMarkDecorations = {
5263
+ "emoji-source": Decoration.mark({ class: "cm-draftly-emoji-source" })
5264
+ };
5265
+ var EmojiPlugin = class extends DecorationPlugin {
5266
+ name = "emoji";
5267
+ version = "1.0.0";
5268
+ decorationPriority = 20;
5269
+ requiredNodes = ["Emoji", "EmojiMark"];
5270
+ constructor() {
5271
+ super();
5272
+ }
5273
+ /**
5274
+ * Plugin theme
5275
+ */
5276
+ get theme() {
5277
+ return theme13;
5278
+ }
5279
+ /**
5280
+ * Build emoji decorations by iterating the syntax tree
5281
+ */
5282
+ buildDecorations(ctx) {
5283
+ const { view, decorations } = ctx;
5284
+ const tree = syntaxTree(view.state);
5285
+ tree.iterate({
5286
+ enter: (node) => {
5287
+ const { from, to, name } = node;
5288
+ if (name !== "Emoji") {
5289
+ return;
5290
+ }
5291
+ const raw = view.state.sliceDoc(from, to);
5292
+ const rendered = shortcodeToEmoji(raw);
5293
+ if (!rendered) {
5294
+ return;
5295
+ }
5296
+ const cursorInNode = ctx.selectionOverlapsRange(from, to);
5297
+ if (cursorInNode) {
5298
+ decorations.push(emojiMarkDecorations["emoji-source"].range(from, to));
5299
+ return;
5300
+ }
5301
+ decorations.push(
5302
+ Decoration.replace({
5303
+ widget: new EmojiWidget(rendered)
5304
+ }).range(from, to)
5305
+ );
5306
+ }
5307
+ });
5308
+ }
5309
+ renderToHTML(node, children, ctx) {
5310
+ if (node.name === "EmojiMark") {
5311
+ return "";
5312
+ }
5313
+ if (node.name !== "Emoji") {
5314
+ return null;
5315
+ }
5316
+ const raw = ctx.sliceDoc(node.from, node.to);
5317
+ const rendered = shortcodeToEmoji(raw);
5318
+ if (!rendered) {
5319
+ return `<span class="cm-draftly-emoji-source">${children}</span>`;
5320
+ }
5321
+ return `<span class="cm-draftly-emoji">${rendered}</span>`;
5322
+ }
5323
+ };
5324
+ var theme13 = createTheme({
5325
+ default: {
5326
+ ".cm-draftly-emoji": {
5327
+ fontFamily: '"Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Segoe UI Symbol", sans-serif',
5328
+ fontVariantEmoji: "emoji",
5329
+ lineHeight: "1.2"
5330
+ },
5331
+ ".cm-draftly-emoji-source": {
5332
+ fontFamily: "inherit",
5333
+ lineHeight: "inherit"
5334
+ }
5335
+ }
5336
+ });
3340
5337
 
3341
5338
  // src/plugins/index.ts
3342
5339
  var essentialPlugins = [
@@ -3345,16 +5342,18 @@ var essentialPlugins = [
3345
5342
  new InlinePlugin(),
3346
5343
  new LinkPlugin(),
3347
5344
  new ListPlugin(),
5345
+ new TablePlugin(),
3348
5346
  new HTMLPlugin(),
3349
5347
  new ImagePlugin(),
3350
5348
  new MathPlugin(),
3351
5349
  new MermaidPlugin(),
3352
5350
  new CodePlugin(),
3353
5351
  new QuotePlugin(),
3354
- new HRPlugin()
5352
+ new HRPlugin(),
5353
+ new EmojiPlugin()
3355
5354
  ];
3356
5355
  var allPlugins = [...essentialPlugins];
3357
5356
 
3358
- export { CodePlugin, HRPlugin, HTMLPlugin, HeadingPlugin, ImagePlugin, InlinePlugin, LinkPlugin, ListPlugin, MathPlugin, MermaidPlugin, ParagraphPlugin, QuotePlugin, allPlugins, essentialPlugins };
3359
- //# sourceMappingURL=chunk-N3WL3XPB.js.map
3360
- //# sourceMappingURL=chunk-N3WL3XPB.js.map
5357
+ export { CodePlugin, EmojiPlugin, HRPlugin, HTMLPlugin, HeadingPlugin, ImagePlugin, InlinePlugin, LinkPlugin, ListPlugin, MathPlugin, MermaidPlugin, ParagraphPlugin, QuotePlugin, TablePlugin, allPlugins, essentialPlugins };
5358
+ //# sourceMappingURL=chunk-NRPI5O6Y.js.map
5359
+ //# sourceMappingURL=chunk-NRPI5O6Y.js.map