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