draftly 1.0.0-alpha.2 → 2.0.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-72ZYRGRT.cjs → chunk-BWJLMREN.cjs} +11 -9
  7. package/dist/chunk-BWJLMREN.cjs.map +1 -0
  8. package/dist/{chunk-KBQDZ5IW.cjs → chunk-CLW73JRX.cjs} +100 -75
  9. package/dist/chunk-CLW73JRX.cjs.map +1 -0
  10. package/dist/{chunk-DFQYXFOP.js → chunk-EEHILRG5.js} +26 -3
  11. package/dist/chunk-EEHILRG5.js.map +1 -0
  12. package/dist/{chunk-HPSMS2WB.js → chunk-I563H35S.js} +101 -75
  13. package/dist/chunk-I563H35S.js.map +1 -0
  14. package/dist/chunk-IAXF4SJL.js +55 -0
  15. package/dist/chunk-IAXF4SJL.js.map +1 -0
  16. package/dist/chunk-JF3WXXMJ.js +31 -0
  17. package/dist/chunk-JF3WXXMJ.js.map +1 -0
  18. package/dist/{chunk-N3WL3XPB.js → chunk-L2XSK57Y.js} +1761 -478
  19. package/dist/chunk-L2XSK57Y.js.map +1 -0
  20. package/dist/{chunk-KDEDLC3D.cjs → chunk-TBVZEK2H.cjs} +27 -2
  21. package/dist/chunk-TBVZEK2H.cjs.map +1 -0
  22. package/dist/{chunk-2B3A3VSQ.cjs → chunk-W5ALMXG2.cjs} +1808 -504
  23. package/dist/chunk-W5ALMXG2.cjs.map +1 -0
  24. package/dist/{chunk-CG4M4TC7.js → chunk-ZUI3GI3W.js} +7 -5
  25. package/dist/chunk-ZUI3GI3W.js.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 +144 -9
  44. package/dist/plugins/index.d.ts +144 -9
  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 +1 -0
  52. package/src/editor/plugin.ts +5 -1
  53. package/src/editor/theme.ts +1 -0
  54. package/src/editor/utils.ts +31 -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 +900 -0
  65. package/src/preview/context.ts +4 -1
  66. package/src/preview/css-generator.ts +14 -1
  67. package/src/preview/index.ts +9 -1
  68. package/src/preview/preview.ts +2 -1
  69. package/src/preview/renderer.ts +21 -20
  70. package/src/preview/syntax-theme.ts +110 -0
  71. package/src/preview/types.ts +14 -0
  72. package/dist/chunk-2B3A3VSQ.cjs.map +0 -1
  73. package/dist/chunk-72ZYRGRT.cjs.map +0 -1
  74. package/dist/chunk-CG4M4TC7.js.map +0 -1
  75. package/dist/chunk-DFQYXFOP.js.map +0 -1
  76. package/dist/chunk-HPSMS2WB.js.map +0 -1
  77. package/dist/chunk-KBQDZ5IW.cjs.map +0 -1
  78. package/dist/chunk-KDEDLC3D.cjs.map +0 -1
  79. package/dist/chunk-N3WL3XPB.js.map +0 -1
@@ -1,13 +1,16 @@
1
- import { DraftlyPlugin, DecorationPlugin } from './chunk-CG4M4TC7.js';
2
- import { createTheme, toggleMarkdownStyle } from './chunk-DFQYXFOP.js';
1
+ import { DraftlyPlugin, DecorationPlugin } from './chunk-ZUI3GI3W.js';
2
+ import { createWrapSelectionInputHandler } from './chunk-JF3WXXMJ.js';
3
+ import { PreviewRenderer } from './chunk-I563H35S.js';
4
+ import { createTheme, toggleMarkdownStyle } from './chunk-EEHILRG5.js';
3
5
  import { Decoration, WidgetType } from '@codemirror/view';
4
- import { syntaxTree } from '@codemirror/language';
5
- import { tags, highlightCode, classHighlighter } from '@lezer/highlight';
6
+ import { syntaxTree, LanguageDescription } from '@codemirror/language';
7
+ import { tags, highlightCode } from '@lezer/highlight';
6
8
  import DOMPurify from 'dompurify';
7
9
  import katex from 'katex';
8
10
  import katexCss from 'katex/dist/katex.min.css?raw';
9
11
  import mermaid from 'mermaid';
10
12
  import { languages } from '@codemirror/language-data';
13
+ import * as emoji from 'node-emoji';
11
14
 
12
15
  // src/plugins/paragraph-plugin.ts
13
16
  var ParagraphPlugin = class extends DraftlyPlugin {
@@ -292,6 +295,17 @@ var InlinePlugin = class extends DecorationPlugin {
292
295
  }
293
296
  ];
294
297
  }
298
+ /**
299
+ * Intercepts inline marker typing to wrap selected text.
300
+ *
301
+ * If user types inline markers while text is selected, wraps each selected
302
+ * range with the appropriate marker:
303
+ * - * _ ~ ^ -> marker + selected + marker
304
+ * - = -> ==selected==
305
+ */
306
+ getExtensions() {
307
+ return [createWrapSelectionInputHandler({ "*": "*", _: "_", "~": "~", "^": "^", "=": "==" })];
308
+ }
295
309
  /**
296
310
  * Return markdown parser extensions for highlight syntax (==text==)
297
311
  */
@@ -1110,6 +1124,653 @@ var theme5 = createTheme({
1110
1124
  }
1111
1125
  }
1112
1126
  });
1127
+ function parseAlignment(cell) {
1128
+ const trimmed = cell.trim();
1129
+ const left = trimmed.startsWith(":");
1130
+ const right = trimmed.endsWith(":");
1131
+ if (left && right) return "center";
1132
+ if (right) return "right";
1133
+ return "left";
1134
+ }
1135
+ function parseTableMarkdown(markdown) {
1136
+ const lines = markdown.split("\n").filter((l) => l.trim().length > 0);
1137
+ if (lines.length < 2) return null;
1138
+ const parseCells = (line) => {
1139
+ let trimmed = line.trim();
1140
+ if (trimmed.startsWith("|")) trimmed = trimmed.slice(1);
1141
+ if (trimmed.endsWith("|")) trimmed = trimmed.slice(0, -1);
1142
+ return trimmed.split("|").map((c) => c.trim());
1143
+ };
1144
+ const headers = parseCells(lines[0]);
1145
+ const delimiterCells = parseCells(lines[1]);
1146
+ const isDelimiter = delimiterCells.every((c) => /^:?-+:?$/.test(c.trim()));
1147
+ if (!isDelimiter) return null;
1148
+ const alignments = delimiterCells.map(parseAlignment);
1149
+ const rows = [];
1150
+ for (let i = 2; i < lines.length; i++) {
1151
+ rows.push(parseCells(lines[i]));
1152
+ }
1153
+ return { headers, alignments, rows };
1154
+ }
1155
+ function isRowEmpty(rowText) {
1156
+ const trimmed = rowText.trim();
1157
+ if (!trimmed.startsWith("|")) return false;
1158
+ let inner = trimmed;
1159
+ if (inner.startsWith("|")) inner = inner.slice(1);
1160
+ if (inner.endsWith("|")) inner = inner.slice(0, -1);
1161
+ return inner.split("|").every((cell) => cell.trim() === "");
1162
+ }
1163
+ async function renderCellWithPreviewRenderer(text, config) {
1164
+ if (!text.trim()) {
1165
+ return "&nbsp;";
1166
+ }
1167
+ const renderer = new PreviewRenderer(
1168
+ text,
1169
+ config?.plugins || [],
1170
+ config?.markdown || [],
1171
+ config?.theme || "auto" /* AUTO */,
1172
+ true
1173
+ );
1174
+ const html = await renderer.render();
1175
+ const paragraphMatch = html.match(/^\s*<p>([\s\S]*)<\/p>\s*$/i);
1176
+ if (paragraphMatch && paragraphMatch[1] !== void 0) {
1177
+ return paragraphMatch[1];
1178
+ }
1179
+ return html;
1180
+ }
1181
+ function getColumnAlignment(alignments, index) {
1182
+ return alignments[index] || "left";
1183
+ }
1184
+ function getColumnCount(headers, row) {
1185
+ return Math.max(headers.length, row.length);
1186
+ }
1187
+ async function renderTableToHtml(parsed, config) {
1188
+ const { headers, alignments, rows } = parsed;
1189
+ let html = '<div class="cm-draftly-table-widget">';
1190
+ html += '<table class="cm-draftly-table">';
1191
+ html += "<thead><tr>";
1192
+ for (let i = 0; i < headers.length; i++) {
1193
+ const cell = headers[i] || "";
1194
+ const align = getColumnAlignment(alignments, i);
1195
+ const rendered = await renderCellWithPreviewRenderer(cell, config);
1196
+ html += `<th style="text-align: ${align}">${rendered}</th>`;
1197
+ }
1198
+ html += "</tr></thead>";
1199
+ html += "<tbody>";
1200
+ for (const row of rows) {
1201
+ html += "<tr>";
1202
+ const colCount = getColumnCount(headers, row);
1203
+ for (let i = 0; i < colCount; i++) {
1204
+ const align = getColumnAlignment(alignments, i);
1205
+ const cell = row[i] || "";
1206
+ const rendered = await renderCellWithPreviewRenderer(cell, config);
1207
+ html += `<td style="text-align: ${align}">${rendered}</td>`;
1208
+ }
1209
+ html += "</tr>";
1210
+ }
1211
+ html += "</tbody>";
1212
+ html += "</table></div>";
1213
+ return html;
1214
+ }
1215
+ var TableWidget = class extends WidgetType {
1216
+ constructor(tableMarkdown, from, to, config) {
1217
+ super();
1218
+ this.tableMarkdown = tableMarkdown;
1219
+ this.from = from;
1220
+ this.to = to;
1221
+ this.config = config;
1222
+ }
1223
+ eq(other) {
1224
+ return other.tableMarkdown === this.tableMarkdown && other.from === this.from && other.to === this.to && other.config === this.config;
1225
+ }
1226
+ toDOM(view) {
1227
+ const wrapper = document.createElement("div");
1228
+ wrapper.className = "cm-draftly-table-widget";
1229
+ const parsed = parseTableMarkdown(this.tableMarkdown);
1230
+ if (!parsed) {
1231
+ wrapper.textContent = "[Invalid table]";
1232
+ return wrapper;
1233
+ }
1234
+ const { headers, alignments, rows } = parsed;
1235
+ const table = document.createElement("table");
1236
+ table.className = "cm-draftly-table";
1237
+ const thead = document.createElement("thead");
1238
+ const headerRow = document.createElement("tr");
1239
+ headers.forEach((h, i) => {
1240
+ const th = document.createElement("th");
1241
+ th.innerHTML = "&nbsp;";
1242
+ this.renderCellAsync(h, th);
1243
+ const align = alignments[i];
1244
+ if (align) th.style.textAlign = align;
1245
+ headerRow.appendChild(th);
1246
+ });
1247
+ thead.appendChild(headerRow);
1248
+ table.appendChild(thead);
1249
+ const tbody = document.createElement("tbody");
1250
+ rows.forEach((row) => {
1251
+ const tr = document.createElement("tr");
1252
+ const colCount = getColumnCount(headers, row);
1253
+ for (let i = 0; i < colCount; i++) {
1254
+ const td = document.createElement("td");
1255
+ td.innerHTML = "&nbsp;";
1256
+ this.renderCellAsync(row[i] || "", td);
1257
+ const align = getColumnAlignment(alignments, i);
1258
+ td.style.textAlign = align;
1259
+ tr.appendChild(td);
1260
+ }
1261
+ tbody.appendChild(tr);
1262
+ });
1263
+ table.appendChild(tbody);
1264
+ wrapper.appendChild(table);
1265
+ wrapper.addEventListener("click", (e) => {
1266
+ e.preventDefault();
1267
+ e.stopPropagation();
1268
+ view.dispatch({
1269
+ selection: { anchor: this.from },
1270
+ scrollIntoView: true
1271
+ });
1272
+ view.focus();
1273
+ });
1274
+ return wrapper;
1275
+ }
1276
+ /**
1277
+ * Render cell content asynchronously using PreviewRenderer
1278
+ */
1279
+ async renderCellAsync(text, element) {
1280
+ if (!text.trim()) {
1281
+ element.innerHTML = "&nbsp;";
1282
+ return;
1283
+ }
1284
+ try {
1285
+ element.innerHTML = await renderCellWithPreviewRenderer(text, this.config);
1286
+ } catch (error) {
1287
+ console.error("Failed to render table cell:", error);
1288
+ element.textContent = text;
1289
+ }
1290
+ }
1291
+ ignoreEvent(event) {
1292
+ return event.type !== "click";
1293
+ }
1294
+ };
1295
+ var tableMarkDecorations = {
1296
+ "table-line": Decoration.line({ class: "cm-draftly-table-line" }),
1297
+ "table-line-start": Decoration.line({ class: "cm-draftly-table-line-start" }),
1298
+ "table-line-end": Decoration.line({ class: "cm-draftly-table-line-end" }),
1299
+ "table-delimiter": Decoration.line({ class: "cm-draftly-table-delimiter-line" }),
1300
+ "table-rendered": Decoration.line({ class: "cm-draftly-table-rendered" }),
1301
+ // "table-hidden": Decoration.mark({ class: "cm-draftly-table-hidden" }),
1302
+ "table-hidden": Decoration.replace({})
1303
+ };
1304
+ var TablePlugin = class extends DecorationPlugin {
1305
+ name = "table";
1306
+ version = "1.0.0";
1307
+ decorationPriority = 20;
1308
+ requiredNodes = ["Table", "TableHeader", "TableDelimiter", "TableRow", "TableCell"];
1309
+ /** Configuration stored from onRegister */
1310
+ draftlyConfig;
1311
+ onRegister(context) {
1312
+ super.onRegister(context);
1313
+ this.draftlyConfig = context.config;
1314
+ }
1315
+ get theme() {
1316
+ return theme6;
1317
+ }
1318
+ // ============================================
1319
+ // Keymaps
1320
+ // ============================================
1321
+ getKeymap() {
1322
+ return [
1323
+ {
1324
+ key: "Mod-Shift-t",
1325
+ run: (view) => this.insertTable(view),
1326
+ preventDefault: true
1327
+ },
1328
+ {
1329
+ key: "Mod-Enter",
1330
+ run: (view) => this.addRow(view),
1331
+ preventDefault: true
1332
+ },
1333
+ {
1334
+ key: "Mod-Shift-Enter",
1335
+ run: (view) => this.addColumn(view),
1336
+ preventDefault: true
1337
+ },
1338
+ {
1339
+ key: "Enter",
1340
+ run: (view) => this.handleEnter(view)
1341
+ },
1342
+ {
1343
+ key: "Tab",
1344
+ run: (view) => this.handleTab(view, false)
1345
+ },
1346
+ {
1347
+ key: "Shift-Tab",
1348
+ run: (view) => this.handleTab(view, true)
1349
+ }
1350
+ ];
1351
+ }
1352
+ // ============================================
1353
+ // Decorations
1354
+ // ============================================
1355
+ buildDecorations(ctx) {
1356
+ const { view, decorations } = ctx;
1357
+ const tree = syntaxTree(view.state);
1358
+ tree.iterate({
1359
+ enter: (node) => {
1360
+ if (node.name !== "Table") return;
1361
+ const { from, to } = node;
1362
+ const nodeLineStart = view.state.doc.lineAt(from);
1363
+ const nodeLineEnd = view.state.doc.lineAt(to);
1364
+ const cursorInRange = ctx.selectionOverlapsRange(nodeLineStart.from, nodeLineEnd.to);
1365
+ if (cursorInRange) {
1366
+ for (let i = nodeLineStart.number; i <= nodeLineEnd.number; i++) {
1367
+ const line = view.state.doc.line(i);
1368
+ decorations.push(tableMarkDecorations["table-line"].range(line.from));
1369
+ if (i === nodeLineStart.number) {
1370
+ decorations.push(tableMarkDecorations["table-line-start"].range(line.from));
1371
+ }
1372
+ if (i === nodeLineEnd.number) {
1373
+ decorations.push(tableMarkDecorations["table-line-end"].range(line.from));
1374
+ }
1375
+ if (i === nodeLineStart.number + 1) {
1376
+ decorations.push(tableMarkDecorations["table-delimiter"].range(line.from));
1377
+ }
1378
+ }
1379
+ } else {
1380
+ const tableContent = view.state.sliceDoc(from, to);
1381
+ for (let i = nodeLineStart.number; i <= nodeLineEnd.number; i++) {
1382
+ const line = view.state.doc.line(i);
1383
+ decorations.push(tableMarkDecorations["table-rendered"].range(line.from));
1384
+ decorations.push(tableMarkDecorations["table-hidden"].range(line.from, line.to));
1385
+ }
1386
+ decorations.push(
1387
+ Decoration.widget({
1388
+ widget: new TableWidget(tableContent, from, to, this.draftlyConfig),
1389
+ side: 1,
1390
+ block: false
1391
+ }).range(to)
1392
+ );
1393
+ }
1394
+ }
1395
+ });
1396
+ }
1397
+ // ============================================
1398
+ // Keymap Handlers
1399
+ // ============================================
1400
+ /**
1401
+ * Insert a new 3×3 table at cursor position
1402
+ */
1403
+ insertTable(view) {
1404
+ const { state } = view;
1405
+ const cursor = state.selection.main.head;
1406
+ const line = state.doc.lineAt(cursor);
1407
+ const insertPos = line.text.trim() ? line.to : line.from;
1408
+ const template = [
1409
+ "| Header 1 | Header 2 | Header 3 |",
1410
+ "| -------- | -------- | -------- |",
1411
+ "| | | |"
1412
+ ].join("\n");
1413
+ const prefix = line.text.trim() ? "\n" : "";
1414
+ const suffix = "\n";
1415
+ view.dispatch({
1416
+ changes: {
1417
+ from: insertPos,
1418
+ insert: prefix + template + suffix
1419
+ },
1420
+ selection: {
1421
+ anchor: insertPos + prefix.length + 2
1422
+ // Position cursor in first header cell
1423
+ }
1424
+ });
1425
+ return true;
1426
+ }
1427
+ /**
1428
+ * Add a new row below the current row (Mod-Enter)
1429
+ */
1430
+ addRow(view) {
1431
+ const tableInfo = this.getTableAtCursor(view);
1432
+ if (!tableInfo) return false;
1433
+ const { state } = view;
1434
+ const cursor = state.selection.main.head;
1435
+ const currentLine = state.doc.lineAt(cursor);
1436
+ const parsed = parseTableMarkdown(state.sliceDoc(tableInfo.from, tableInfo.to));
1437
+ if (!parsed) return false;
1438
+ const colCount = parsed.headers.length;
1439
+ const emptyRow = "| " + Array.from({ length: colCount }, () => " ").join(" | ") + " |";
1440
+ view.dispatch({
1441
+ changes: {
1442
+ from: currentLine.to,
1443
+ insert: "\n" + emptyRow
1444
+ },
1445
+ selection: {
1446
+ anchor: currentLine.to + 3
1447
+ // Position in first cell of new row
1448
+ }
1449
+ });
1450
+ return true;
1451
+ }
1452
+ /**
1453
+ * Add a new column after the current column (Mod-Shift-Enter)
1454
+ */
1455
+ addColumn(view) {
1456
+ const tableInfo = this.getTableAtCursor(view);
1457
+ if (!tableInfo) return false;
1458
+ const { state } = view;
1459
+ const cursor = state.selection.main.head;
1460
+ const currentLine = state.doc.lineAt(cursor);
1461
+ const lineText = currentLine.text;
1462
+ const cursorInLine = cursor - currentLine.from;
1463
+ let colIndex = -1;
1464
+ for (let i = 0; i < cursorInLine; i++) {
1465
+ if (lineText[i] === "|") colIndex++;
1466
+ }
1467
+ colIndex = Math.max(0, colIndex);
1468
+ const tableText = state.sliceDoc(tableInfo.from, tableInfo.to);
1469
+ const lines = tableText.split("\n");
1470
+ const newLines = lines.map((line, lineIdx) => {
1471
+ const cells = this.splitLineToCells(line);
1472
+ const insertAfter = Math.min(colIndex, cells.length - 1);
1473
+ if (lineIdx === 1) {
1474
+ cells.splice(insertAfter + 1, 0, " -------- ");
1475
+ } else {
1476
+ cells.splice(insertAfter + 1, 0, " ");
1477
+ }
1478
+ return "|" + cells.join("|") + "|";
1479
+ });
1480
+ view.dispatch({
1481
+ changes: {
1482
+ from: tableInfo.from,
1483
+ to: tableInfo.to,
1484
+ insert: newLines.join("\n")
1485
+ }
1486
+ });
1487
+ return true;
1488
+ }
1489
+ /**
1490
+ * Handle Enter key inside a table.
1491
+ * - Last cell of last row: create a new row
1492
+ * - Empty last row: remove it and move cursor after table
1493
+ */
1494
+ handleEnter(view) {
1495
+ const tableInfo = this.getTableAtCursor(view);
1496
+ if (!tableInfo) return false;
1497
+ const { state } = view;
1498
+ const cursor = state.selection.main.head;
1499
+ const cursorLine = state.doc.lineAt(cursor);
1500
+ const tableEndLine = state.doc.lineAt(tableInfo.to);
1501
+ if (cursorLine.number !== tableEndLine.number) return false;
1502
+ const lineText = cursorLine.text;
1503
+ const cursorOffset = cursor - cursorLine.from;
1504
+ const pipes = [];
1505
+ for (let i = 0; i < lineText.length; i++) {
1506
+ if (lineText[i] === "|") pipes.push(i);
1507
+ }
1508
+ if (pipes.length < 2) return false;
1509
+ const lastCellStart = pipes[pipes.length - 2];
1510
+ if (cursorOffset < lastCellStart) return false;
1511
+ if (isRowEmpty(lineText)) {
1512
+ const removeFrom = cursorLine.from - 1;
1513
+ const removeTo = cursorLine.to;
1514
+ view.dispatch({
1515
+ changes: { from: Math.max(0, removeFrom), to: removeTo },
1516
+ selection: {
1517
+ anchor: Math.min(Math.max(0, removeFrom) + 1, view.state.doc.length)
1518
+ }
1519
+ });
1520
+ return true;
1521
+ }
1522
+ const parsed = parseTableMarkdown(state.sliceDoc(tableInfo.from, tableInfo.to));
1523
+ if (!parsed) return false;
1524
+ const colCount = parsed.headers.length;
1525
+ const emptyRow = "| " + Array.from({ length: colCount }, () => " ").join(" | ") + " |";
1526
+ view.dispatch({
1527
+ changes: {
1528
+ from: cursorLine.to,
1529
+ insert: "\n" + emptyRow
1530
+ },
1531
+ selection: {
1532
+ anchor: cursorLine.to + 3
1533
+ // Position in first cell of new row
1534
+ }
1535
+ });
1536
+ return true;
1537
+ }
1538
+ /**
1539
+ * Handle Tab key inside a table — move to next/previous cell
1540
+ */
1541
+ handleTab(view, backwards) {
1542
+ const tableInfo = this.getTableAtCursor(view);
1543
+ if (!tableInfo) return false;
1544
+ const { state } = view;
1545
+ const cursor = state.selection.main.head;
1546
+ const tableText = state.sliceDoc(tableInfo.from, tableInfo.to);
1547
+ const lines = tableText.split("\n");
1548
+ const cellPositions = [];
1549
+ for (let li = 0; li < lines.length; li++) {
1550
+ if (li === 1) continue;
1551
+ const line = lines[li];
1552
+ const lineFrom = tableInfo.from + lines.slice(0, li).reduce((sum, l) => sum + l.length + 1, 0);
1553
+ const pipes = [];
1554
+ for (let i = 0; i < line.length; i++) {
1555
+ if (line[i] === "|") pipes.push(i);
1556
+ }
1557
+ for (let p = 0; p < pipes.length - 1; p++) {
1558
+ const cellStart = pipes[p] + 1;
1559
+ const cellEnd = pipes[p + 1];
1560
+ cellPositions.push({
1561
+ lineFrom,
1562
+ start: cellStart,
1563
+ end: cellEnd
1564
+ });
1565
+ }
1566
+ }
1567
+ let currentCellIdx = -1;
1568
+ for (let i = 0; i < cellPositions.length; i++) {
1569
+ const cell = cellPositions[i];
1570
+ const absStart = cell.lineFrom + cell.start;
1571
+ const absEnd = cell.lineFrom + cell.end;
1572
+ if (cursor >= absStart && cursor <= absEnd) {
1573
+ currentCellIdx = i;
1574
+ break;
1575
+ }
1576
+ }
1577
+ if (currentCellIdx === -1) return false;
1578
+ const nextIdx = backwards ? currentCellIdx - 1 : currentCellIdx + 1;
1579
+ if (nextIdx < 0 || nextIdx >= cellPositions.length) return false;
1580
+ const nextCell = cellPositions[nextIdx];
1581
+ const cellText = state.sliceDoc(nextCell.lineFrom + nextCell.start, nextCell.lineFrom + nextCell.end);
1582
+ const trimStart = cellText.length - cellText.trimStart().length;
1583
+ const trimEnd = cellText.length - cellText.trimEnd().length;
1584
+ const selectFrom = nextCell.lineFrom + nextCell.start + (trimStart > 0 ? 1 : 0);
1585
+ const selectTo = nextCell.lineFrom + nextCell.end - (trimEnd > 0 ? 1 : 0);
1586
+ view.dispatch({
1587
+ selection: {
1588
+ anchor: selectFrom,
1589
+ head: selectTo
1590
+ },
1591
+ scrollIntoView: true
1592
+ });
1593
+ return true;
1594
+ }
1595
+ // ============================================
1596
+ // Helpers
1597
+ // ============================================
1598
+ /**
1599
+ * Find the Table node at the cursor position
1600
+ */
1601
+ getTableAtCursor(view) {
1602
+ const tree = syntaxTree(view.state);
1603
+ const cursor = view.state.selection.main.head;
1604
+ let result = null;
1605
+ tree.iterate({
1606
+ enter: (node) => {
1607
+ if (node.name === "Table" && cursor >= node.from && cursor <= node.to) {
1608
+ result = { from: node.from, to: node.to };
1609
+ }
1610
+ }
1611
+ });
1612
+ return result;
1613
+ }
1614
+ /**
1615
+ * Split a table line into cells (keeping the whitespace around content)
1616
+ */
1617
+ splitLineToCells(line) {
1618
+ let trimmed = line.trim();
1619
+ if (trimmed.startsWith("|")) trimmed = trimmed.slice(1);
1620
+ if (trimmed.endsWith("|")) trimmed = trimmed.slice(0, -1);
1621
+ return trimmed.split("|");
1622
+ }
1623
+ // ============================================
1624
+ // Preview Rendering
1625
+ // ============================================
1626
+ async renderToHTML(node, _children, _ctx) {
1627
+ if (node.name === "Table") {
1628
+ const content = _ctx.sliceDoc(node.from, node.to);
1629
+ const parsed = parseTableMarkdown(content);
1630
+ if (!parsed) return null;
1631
+ return await renderTableToHtml(parsed, this.draftlyConfig);
1632
+ }
1633
+ if (node.name === "TableHeader" || node.name === "TableDelimiter" || node.name === "TableRow" || node.name === "TableCell") {
1634
+ return "";
1635
+ }
1636
+ return null;
1637
+ }
1638
+ };
1639
+ var theme6 = createTheme({
1640
+ default: {
1641
+ // Raw table lines — monospace when cursor is inside
1642
+ ".cm-draftly-table-line": {
1643
+ "--radius": "0.375rem",
1644
+ fontFamily: "var(--font-jetbrains-mono, monospace)",
1645
+ fontSize: "0.9rem",
1646
+ backgroundColor: "rgba(0, 0, 0, 0.02)",
1647
+ padding: "0 0.75rem !important",
1648
+ lineHeight: "1.6",
1649
+ borderLeft: "1px solid var(--color-border, #e2e8f0)",
1650
+ borderRight: "1px solid var(--color-border, #e2e8f0)"
1651
+ },
1652
+ ".cm-draftly-table-line-start": {
1653
+ borderTopLeftRadius: "var(--radius)",
1654
+ borderTopRightRadius: "var(--radius)",
1655
+ borderTop: "1px solid var(--color-border, #e2e8f0)"
1656
+ },
1657
+ ".cm-draftly-table-line-end": {
1658
+ borderBottomLeftRadius: "var(--radius)",
1659
+ borderBottomRightRadius: "var(--radius)",
1660
+ borderBottom: "1px solid var(--color-border, #e2e8f0)"
1661
+ },
1662
+ ".cm-draftly-table-delimiter-line": {
1663
+ opacity: "0.5"
1664
+ },
1665
+ // Hidden table text (when cursor is not in range)
1666
+ ".cm-draftly-table-hidden": {
1667
+ display: "none"
1668
+ },
1669
+ // Line decoration for rendered state — hide line breaks
1670
+ ".cm-draftly-table-rendered": {
1671
+ padding: "0 !important"
1672
+ },
1673
+ ".cm-draftly-table-rendered br": {
1674
+ display: "none"
1675
+ },
1676
+ // Rendered table widget container
1677
+ ".cm-draftly-table-widget": {
1678
+ cursor: "pointer",
1679
+ overflow: "auto",
1680
+ padding: "0.5rem 0"
1681
+ },
1682
+ // Table element
1683
+ ".cm-draftly-table": {
1684
+ width: "100%",
1685
+ borderCollapse: "separate",
1686
+ borderSpacing: "0",
1687
+ borderRadius: "0.5rem",
1688
+ overflow: "hidden",
1689
+ border: "1px solid var(--color-border, #e2e8f0)",
1690
+ fontFamily: "var(--font-sans, sans-serif)",
1691
+ fontSize: "0.9375rem",
1692
+ lineHeight: "1.5"
1693
+ },
1694
+ // Table header
1695
+ ".cm-draftly-table thead th": {
1696
+ padding: "0rem 0.875rem",
1697
+ fontWeight: "600",
1698
+ borderBottom: "2px solid var(--color-border, #e2e8f0)",
1699
+ backgroundColor: "rgba(0, 0, 0, 0.03)"
1700
+ },
1701
+ // Table cells
1702
+ ".cm-draftly-table td": {
1703
+ padding: "0rem 0.875rem",
1704
+ borderBottom: "1px solid var(--color-border, #e2e8f0)",
1705
+ borderRight: "1px solid var(--color-border, #e2e8f0)"
1706
+ },
1707
+ // Remove right border on last cell
1708
+ ".cm-draftly-table td:last-child, .cm-draftly-table th:last-child": {
1709
+ borderRight: "none"
1710
+ },
1711
+ // Remove bottom border on last row
1712
+ ".cm-draftly-table tbody tr:last-child td": {
1713
+ borderBottom: "none"
1714
+ },
1715
+ // Alternate row colors
1716
+ ".cm-draftly-table tbody tr:nth-child(even)": {
1717
+ backgroundColor: "rgba(0, 0, 0, 0.02)"
1718
+ },
1719
+ // Header cells right border
1720
+ ".cm-draftly-table thead th:not(:last-child)": {
1721
+ borderRight: "1px solid var(--color-border, #e2e8f0)"
1722
+ },
1723
+ // Hover effect on rows
1724
+ ".cm-draftly-table tbody tr:hover": {
1725
+ backgroundColor: "rgba(0, 0, 0, 0.04)"
1726
+ },
1727
+ // Inline code in table cells
1728
+ ".cm-draftly-table-inline-code": {
1729
+ fontFamily: "var(--font-jetbrains-mono, monospace)",
1730
+ fontSize: "0.85em",
1731
+ padding: "0.1em 0.35em",
1732
+ borderRadius: "0.25rem",
1733
+ backgroundColor: "rgba(0, 0, 0, 0.06)"
1734
+ },
1735
+ // Links in table cells
1736
+ ".cm-draftly-table-link": {
1737
+ color: "var(--color-link, #0969da)",
1738
+ textDecoration: "none"
1739
+ },
1740
+ ".cm-draftly-table-link:hover": {
1741
+ textDecoration: "underline"
1742
+ },
1743
+ // Math in table cells
1744
+ ".cm-draftly-table-math": {
1745
+ fontFamily: "var(--font-jetbrains-mono, monospace)",
1746
+ fontSize: "0.9em",
1747
+ color: "#6a737d"
1748
+ }
1749
+ },
1750
+ dark: {
1751
+ ".cm-draftly-table-line": {
1752
+ backgroundColor: "rgba(255, 255, 255, 0.03)"
1753
+ },
1754
+ ".cm-draftly-table thead th": {
1755
+ backgroundColor: "rgba(255, 255, 255, 0.05)"
1756
+ },
1757
+ ".cm-draftly-table tbody tr:nth-child(even)": {
1758
+ backgroundColor: "rgba(255, 255, 255, 0.02)"
1759
+ },
1760
+ ".cm-draftly-table tbody tr:hover": {
1761
+ backgroundColor: "rgba(255, 255, 255, 0.05)"
1762
+ },
1763
+ ".cm-draftly-table-inline-code": {
1764
+ backgroundColor: "rgba(255, 255, 255, 0.08)"
1765
+ },
1766
+ ".cm-draftly-table-link": {
1767
+ color: "var(--color-link, #58a6ff)"
1768
+ },
1769
+ ".cm-draftly-table-math": {
1770
+ color: "#8b949e"
1771
+ }
1772
+ }
1773
+ });
1113
1774
  var htmlMarkDecorations = {
1114
1775
  "html-tag": Decoration.mark({ class: "cm-draftly-html-tag" }),
1115
1776
  "html-comment": Decoration.mark({ class: "cm-draftly-html-comment" })
@@ -1176,7 +1837,7 @@ var HTMLPlugin = class extends DecorationPlugin {
1176
1837
  * Plugin theme
1177
1838
  */
1178
1839
  get theme() {
1179
- return theme6;
1840
+ return theme7;
1180
1841
  }
1181
1842
  buildDecorations(ctx) {
1182
1843
  const { view, decorations } = ctx;
@@ -1316,7 +1977,7 @@ var HTMLPlugin = class extends DecorationPlugin {
1316
1977
  }
1317
1978
  }
1318
1979
  };
1319
- var theme6 = createTheme({
1980
+ var theme7 = createTheme({
1320
1981
  default: {
1321
1982
  ".cm-draftly-html-tag": {
1322
1983
  color: "#6a737d",
@@ -1446,7 +2107,7 @@ var ImagePlugin = class extends DecorationPlugin {
1446
2107
  * Plugin theme
1447
2108
  */
1448
2109
  get theme() {
1449
- return theme7;
2110
+ return theme8;
1450
2111
  }
1451
2112
  /**
1452
2113
  * Keyboard shortcuts for image formatting
@@ -1601,7 +2262,7 @@ var ImagePlugin = class extends DecorationPlugin {
1601
2262
  return html;
1602
2263
  }
1603
2264
  };
1604
- var theme7 = createTheme({
2265
+ var theme8 = createTheme({
1605
2266
  default: {
1606
2267
  ".cm-draftly-image-block br": {
1607
2268
  display: "none"
@@ -1850,7 +2511,16 @@ var MathPlugin = class extends DecorationPlugin {
1850
2511
  * Plugin theme
1851
2512
  */
1852
2513
  get theme() {
1853
- return theme8;
2514
+ return theme9;
2515
+ }
2516
+ /**
2517
+ * Intercepts dollar typing to wrap selected text as inline math.
2518
+ *
2519
+ * If user types '$' while text is selected, wraps each selected range
2520
+ * with single dollars (selected -> $selected$).
2521
+ */
2522
+ getExtensions() {
2523
+ return [createWrapSelectionInputHandler({ "$": "$" })];
1854
2524
  }
1855
2525
  /**
1856
2526
  * Return markdown parser extensions for math syntax
@@ -1958,7 +2628,7 @@ var MathPlugin = class extends DecorationPlugin {
1958
2628
  return null;
1959
2629
  }
1960
2630
  };
1961
- var theme8 = createTheme({
2631
+ var theme9 = createTheme({
1962
2632
  default: {
1963
2633
  ".cm-draftly-math-block": {
1964
2634
  fontFamily: "var(--font-jetbrains-mono, monospace)"
@@ -2157,7 +2827,7 @@ var MermaidPlugin = class extends DecorationPlugin {
2157
2827
  * Plugin theme
2158
2828
  */
2159
2829
  get theme() {
2160
- return theme9;
2830
+ return theme10;
2161
2831
  }
2162
2832
  /**
2163
2833
  * Return markdown parser extensions for mermaid syntax
@@ -2265,7 +2935,7 @@ var MermaidPlugin = class extends DecorationPlugin {
2265
2935
  return null;
2266
2936
  }
2267
2937
  };
2268
- var theme9 = createTheme({
2938
+ var theme10 = createTheme({
2269
2939
  default: {
2270
2940
  // Raw mermaid block lines (monospace)
2271
2941
  ".cm-draftly-mermaid-block:not(.cm-draftly-mermaid-block-rendered)": {
@@ -2369,22 +3039,395 @@ var theme9 = createTheme({
2369
3039
  }
2370
3040
  }
2371
3041
  });
2372
- var COPY_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>`;
2373
- var CHECK_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>`;
2374
- var COPY_RESET_DELAY = 2e3;
2375
- var codeMarkDecorations = {
2376
- // Inline code
2377
- "inline-code": Decoration.mark({ class: "cm-draftly-code-inline" }),
2378
- "inline-mark": Decoration.replace({}),
2379
- // Fenced code block
2380
- "code-block-line": Decoration.line({ class: "cm-draftly-code-block-line" }),
2381
- "code-block-line-start": Decoration.line({ class: "cm-draftly-code-block-line-start" }),
3042
+
3043
+ // src/plugins/code-plugin.theme.ts
3044
+ var codePluginTheme = createTheme({
3045
+ default: {
3046
+ // Inline code
3047
+ ".cm-draftly-code-inline": {
3048
+ fontFamily: "var(--font-jetbrains-mono, monospace)",
3049
+ fontSize: "0.9rem",
3050
+ backgroundColor: "rgba(0, 0, 0, 0.05)",
3051
+ padding: "0.1rem 0.25rem",
3052
+ border: "1px solid var(--color-border)",
3053
+ borderRadius: "3px"
3054
+ },
3055
+ // Fenced code block lines
3056
+ ".cm-draftly-code-block-line": {
3057
+ "--radius": "0.375rem",
3058
+ fontFamily: "var(--font-jetbrains-mono, monospace)",
3059
+ fontSize: "0.9rem",
3060
+ backgroundColor: "rgba(0, 0, 0, 0.03)",
3061
+ padding: "0 1rem !important",
3062
+ lineHeight: "1.5",
3063
+ borderLeft: "1px solid var(--color-border)",
3064
+ borderRight: "1px solid var(--color-border)"
3065
+ },
3066
+ // First line of code block
3067
+ ".cm-draftly-code-block-line-start": {
3068
+ borderTopLeftRadius: "var(--radius)",
3069
+ borderTopRightRadius: "var(--radius)",
3070
+ position: "relative",
3071
+ overflow: "hidden",
3072
+ borderTop: "1px solid var(--color-border)",
3073
+ paddingBottom: "0.5rem !important"
3074
+ },
3075
+ // Remove top radius when header is present
3076
+ ".cm-draftly-code-block-has-header": {
3077
+ padding: "0 !important",
3078
+ paddingBottom: "0.5rem !important"
3079
+ },
3080
+ // Code block header widget
3081
+ ".cm-draftly-code-header": {
3082
+ display: "flex",
3083
+ justifyContent: "space-between",
3084
+ alignItems: "center",
3085
+ padding: "0.25rem 1rem",
3086
+ backgroundColor: "rgba(0, 0, 0, 0.06)",
3087
+ fontFamily: "var(--font-jetbrains-mono, monospace)",
3088
+ fontSize: "0.85rem",
3089
+ ".cm-draftly-code-header-left": {
3090
+ display: "flex",
3091
+ alignItems: "center",
3092
+ gap: "0.5rem",
3093
+ ".cm-draftly-code-header-title": {
3094
+ color: "var(--color-text, inherit)",
3095
+ fontWeight: "500"
3096
+ },
3097
+ ".cm-draftly-code-header-lang": {
3098
+ color: "#6a737d",
3099
+ opacity: "0.8"
3100
+ }
3101
+ },
3102
+ ".cm-draftly-code-header-right": {
3103
+ display: "flex",
3104
+ alignItems: "center",
3105
+ gap: "0.5rem",
3106
+ ".cm-draftly-code-copy-btn": {
3107
+ display: "flex",
3108
+ alignItems: "center",
3109
+ justifyContent: "center",
3110
+ padding: "0.25rem",
3111
+ backgroundColor: "transparent",
3112
+ border: "none",
3113
+ borderRadius: "4px",
3114
+ cursor: "pointer",
3115
+ color: "#6a737d",
3116
+ transition: "color 0.2s, background-color 0.2s",
3117
+ "&:hover": {
3118
+ backgroundColor: "rgba(0, 0, 0, 0.1)",
3119
+ color: "var(--color-text, inherit)"
3120
+ },
3121
+ "&.copied": {
3122
+ color: "#22c55e"
3123
+ }
3124
+ }
3125
+ }
3126
+ },
3127
+ // Caption (below code block)
3128
+ ".cm-draftly-code-block-has-caption": {
3129
+ padding: "0 !important",
3130
+ paddingTop: "0.5rem !important"
3131
+ },
3132
+ ".cm-draftly-code-caption": {
3133
+ textAlign: "center",
3134
+ fontSize: "0.85rem",
3135
+ color: "#6a737d",
3136
+ fontStyle: "italic",
3137
+ padding: "0.25rem 1rem",
3138
+ backgroundColor: "rgba(0, 0, 0, 0.06)"
3139
+ },
3140
+ // Last line of code block
3141
+ ".cm-draftly-code-block-line-end": {
3142
+ borderBottomLeftRadius: "var(--radius)",
3143
+ borderBottomRightRadius: "var(--radius)",
3144
+ borderBottom: "1px solid var(--color-border)",
3145
+ paddingTop: "0.5rem !important",
3146
+ "& br": {
3147
+ display: "none"
3148
+ }
3149
+ },
3150
+ // Fence markers (```)
3151
+ ".cm-draftly-code-fence": {
3152
+ color: "#6a737d",
3153
+ fontFamily: "var(--font-jetbrains-mono, monospace)"
3154
+ },
3155
+ // Line numbers
3156
+ ".cm-draftly-code-line-numbered": {
3157
+ paddingLeft: "calc(var(--line-num-width, 2ch) + 1rem) !important",
3158
+ position: "relative",
3159
+ "&::before": {
3160
+ content: "attr(data-line-num)",
3161
+ position: "absolute",
3162
+ left: "0.5rem",
3163
+ top: "0.2rem",
3164
+ width: "var(--line-num-width, 2ch)",
3165
+ textAlign: "right",
3166
+ color: "#6a737d",
3167
+ opacity: "0.6",
3168
+ fontFamily: "var(--font-jetbrains-mono, monospace)",
3169
+ fontSize: "0.85rem",
3170
+ userSelect: "none"
3171
+ }
3172
+ },
3173
+ ".cm-draftly-code-line-numbered-diff": {
3174
+ paddingLeft: "calc(var(--line-num-old-width, 2ch) + var(--line-num-new-width, 2ch) + 2.75rem) !important",
3175
+ position: "relative",
3176
+ "&::before": {
3177
+ content: "attr(data-line-num-old)",
3178
+ position: "absolute",
3179
+ left: "0.5rem",
3180
+ top: "0.2rem",
3181
+ width: "var(--line-num-old-width, 2ch)",
3182
+ textAlign: "right",
3183
+ color: "#6a737d",
3184
+ opacity: "0.6",
3185
+ fontFamily: "var(--font-jetbrains-mono, monospace)",
3186
+ fontSize: "0.85rem",
3187
+ userSelect: "none"
3188
+ },
3189
+ "&::after": {
3190
+ content: 'attr(data-line-num-new) " " attr(data-diff-marker)',
3191
+ position: "absolute",
3192
+ left: "calc(0.5rem + var(--line-num-old-width, 2ch) + 0.75rem)",
3193
+ top: "0.2rem",
3194
+ width: "calc(var(--line-num-new-width, 2ch) + 2ch)",
3195
+ textAlign: "right",
3196
+ color: "#6a737d",
3197
+ opacity: "0.6",
3198
+ fontFamily: "var(--font-jetbrains-mono, monospace)",
3199
+ fontSize: "0.85rem",
3200
+ userSelect: "none"
3201
+ },
3202
+ "&.cm-draftly-code-line-diff-gutter": {
3203
+ paddingLeft: "calc(var(--line-num-width, 2ch) + 2rem) !important",
3204
+ "&::after": {
3205
+ content: "attr(data-diff-marker)",
3206
+ position: "absolute",
3207
+ left: "calc(0.5rem + var(--line-num-width, 2ch) + 0.35rem)",
3208
+ top: "0.1rem",
3209
+ width: "1ch",
3210
+ textAlign: "right",
3211
+ fontFamily: "var(--font-jetbrains-mono, monospace)",
3212
+ fontSize: "0.85rem",
3213
+ fontWeight: "700",
3214
+ userSelect: "none"
3215
+ }
3216
+ }
3217
+ },
3218
+ // Preview: code lines (need block display for full-width highlights)
3219
+ ".cm-draftly-code-line": {
3220
+ display: "block",
3221
+ position: "relative",
3222
+ paddingLeft: "1rem",
3223
+ paddingRight: "1rem",
3224
+ lineHeight: "1.5",
3225
+ borderLeft: "3px solid transparent"
3226
+ },
3227
+ // Line highlight
3228
+ ".cm-draftly-code-line-highlight": {
3229
+ backgroundColor: "rgba(255, 220, 100, 0.2) !important",
3230
+ borderLeft: "3px solid #f0b429 !important"
3231
+ },
3232
+ ".cm-draftly-code-line-diff-add": {
3233
+ color: "inherit",
3234
+ backgroundColor: "rgba(34, 197, 94, 0.12) !important",
3235
+ borderLeft: "3px solid #22c55e !important",
3236
+ "&.cm-draftly-code-line-diff-gutter::after": {
3237
+ color: "#16a34a"
3238
+ }
3239
+ },
3240
+ ".cm-draftly-code-line-diff-del": {
3241
+ color: "inherit",
3242
+ backgroundColor: "rgba(239, 68, 68, 0.12) !important",
3243
+ borderLeft: "3px solid #ef4444 !important",
3244
+ "&.cm-draftly-code-line-diff-gutter::after": {
3245
+ color: "#dc2626"
3246
+ }
3247
+ },
3248
+ ".cm-draftly-code-diff-sign-add": {
3249
+ color: "#16a34a",
3250
+ fontWeight: "700"
3251
+ },
3252
+ ".cm-draftly-code-diff-sign-del": {
3253
+ color: "#dc2626",
3254
+ fontWeight: "700"
3255
+ },
3256
+ ".cm-draftly-code-diff-mod-add": {
3257
+ color: "inherit",
3258
+ backgroundColor: "rgba(34, 197, 94, 0.25)",
3259
+ borderRadius: "2px",
3260
+ padding: "0.1rem 0"
3261
+ },
3262
+ ".cm-draftly-code-diff-mod-del": {
3263
+ color: "inherit",
3264
+ backgroundColor: "rgba(239, 68, 68, 0.25)",
3265
+ borderRadius: "2px",
3266
+ padding: "0.1rem 0"
3267
+ },
3268
+ // Text highlight
3269
+ ".cm-draftly-code-text-highlight": {
3270
+ color: "inherit",
3271
+ backgroundColor: "rgba(255, 220, 100, 0.4)",
3272
+ borderRadius: "2px",
3273
+ padding: "0.1rem 0"
3274
+ },
3275
+ // Preview: container wrapper
3276
+ ".cm-draftly-code-container": {
3277
+ margin: "1rem 0",
3278
+ borderRadius: "var(--radius)",
3279
+ overflow: "hidden",
3280
+ border: "1px solid var(--color-border)",
3281
+ ".cm-draftly-code-header": {
3282
+ borderRadius: "0",
3283
+ border: "none",
3284
+ borderBottom: "1px solid var(--color-border)"
3285
+ },
3286
+ ".cm-draftly-code-block": {
3287
+ margin: "0",
3288
+ borderRadius: "0",
3289
+ border: "none",
3290
+ whiteSpace: "pre-wrap"
3291
+ },
3292
+ ".cm-draftly-code-caption": {
3293
+ borderTop: "1px solid var(--color-border)"
3294
+ }
3295
+ },
3296
+ // Preview: standalone code block (not in container)
3297
+ ".cm-draftly-code-block": {
3298
+ fontFamily: "var(--font-jetbrains-mono, monospace)",
3299
+ fontSize: "0.9rem",
3300
+ backgroundColor: "rgba(0, 0, 0, 0.03)",
3301
+ padding: "1rem",
3302
+ overflow: "auto",
3303
+ position: "relative",
3304
+ borderRadius: "var(--radius)",
3305
+ border: "1px solid var(--color-border)",
3306
+ "&.cm-draftly-code-block-has-header": {
3307
+ borderTopLeftRadius: "0",
3308
+ borderTopRightRadius: "0",
3309
+ borderTop: "none",
3310
+ margin: "0",
3311
+ paddingTop: "0.5rem !important"
3312
+ },
3313
+ "&.cm-draftly-code-block-has-caption": {
3314
+ borderBottomLeftRadius: "0",
3315
+ borderBottomRightRadius: "0",
3316
+ borderBottom: "none",
3317
+ paddingBottom: "0.5rem !important"
3318
+ }
3319
+ }
3320
+ },
3321
+ dark: {
3322
+ ".cm-draftly-code-inline": {
3323
+ backgroundColor: "rgba(255, 255, 255, 0.1)"
3324
+ },
3325
+ ".cm-draftly-code-block-line": {
3326
+ backgroundColor: "rgba(255, 255, 255, 0.05)"
3327
+ },
3328
+ ".cm-draftly-code-fence": {
3329
+ color: "#8b949e"
3330
+ },
3331
+ ".cm-draftly-code-block": {
3332
+ backgroundColor: "rgba(255, 255, 255, 0.05)"
3333
+ },
3334
+ ".cm-draftly-code-header": {
3335
+ backgroundColor: "rgba(255, 255, 255, 0.08)",
3336
+ ".cm-draftly-code-header-lang": {
3337
+ color: "#8b949e"
3338
+ },
3339
+ ".cm-draftly-code-copy-btn": {
3340
+ color: "#8b949e",
3341
+ "&:hover": {
3342
+ backgroundColor: "rgba(255, 255, 255, 0.1)"
3343
+ }
3344
+ }
3345
+ },
3346
+ ".cm-draftly-code-caption": {
3347
+ backgroundColor: "rgba(255, 255, 255, 0.05)"
3348
+ },
3349
+ ".cm-draftly-code-line-numbered": {
3350
+ "&::before": {
3351
+ color: "#8b949e"
3352
+ }
3353
+ },
3354
+ ".cm-draftly-code-line-numbered-diff": {
3355
+ "&::before": {
3356
+ color: "#8b949e"
3357
+ },
3358
+ "&::after": {
3359
+ color: "#8b949e"
3360
+ }
3361
+ },
3362
+ ".cm-draftly-code-line-diff-gutter": {
3363
+ "&::after": {
3364
+ color: "#8b949e"
3365
+ }
3366
+ },
3367
+ ".cm-draftly-code-line-highlight": {
3368
+ backgroundColor: "rgba(255, 220, 100, 0.15) !important",
3369
+ borderLeft: "3px solid #d9a520 !important"
3370
+ },
3371
+ ".cm-draftly-code-line-diff-add": {
3372
+ backgroundColor: "rgba(34, 197, 94, 0.15) !important",
3373
+ borderLeft: "3px solid #22c55e !important",
3374
+ "&.cm-draftly-code-line-diff-gutter::after": {
3375
+ color: "#4ade80"
3376
+ }
3377
+ },
3378
+ ".cm-draftly-code-line-diff-del": {
3379
+ backgroundColor: "rgba(239, 68, 68, 0.15) !important",
3380
+ borderLeft: "3px solid #ef4444 !important",
3381
+ "&.cm-draftly-code-line-diff-gutter::after": {
3382
+ color: "#f87171"
3383
+ }
3384
+ },
3385
+ ".cm-draftly-code-diff-sign-add": {
3386
+ color: "#4ade80"
3387
+ },
3388
+ ".cm-draftly-code-diff-sign-del": {
3389
+ color: "#f87171"
3390
+ },
3391
+ ".cm-draftly-code-diff-mod-add": {
3392
+ backgroundColor: "rgba(34, 197, 94, 0.3)"
3393
+ },
3394
+ ".cm-draftly-code-diff-mod-del": {
3395
+ backgroundColor: "rgba(239, 68, 68, 0.3)"
3396
+ },
3397
+ ".cm-draftly-code-text-highlight": {
3398
+ backgroundColor: "rgba(255, 220, 100, 0.3)"
3399
+ }
3400
+ }
3401
+ });
3402
+
3403
+ // src/plugins/code-plugin.ts
3404
+ 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>`;
3405
+ 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>`;
3406
+ var COPY_RESET_DELAY = 2e3;
3407
+ var CODE_FENCE = "```";
3408
+ var QUOTED_INFO_PATTERN = /(\w+)="([^"]*)"/g;
3409
+ var TEXT_HIGHLIGHT_PATTERN = /\/([^/]+)\/(?:(\d+(?:-\d+)?(?:,\d+(?:-\d+)?)*))?/g;
3410
+ var codeMarkDecorations = {
3411
+ // Inline code
3412
+ "inline-code": Decoration.mark({ class: "cm-draftly-code-inline" }),
3413
+ "inline-mark": Decoration.replace({}),
3414
+ // Fenced code block
3415
+ "code-block-line": Decoration.line({ class: "cm-draftly-code-block-line" }),
3416
+ "code-block-line-start": Decoration.line({ class: "cm-draftly-code-block-line-start" }),
2382
3417
  "code-block-line-end": Decoration.line({ class: "cm-draftly-code-block-line-end" }),
2383
3418
  "code-fence": Decoration.mark({ class: "cm-draftly-code-fence" }),
2384
3419
  "code-hidden": Decoration.replace({}),
2385
3420
  // Highlights
2386
3421
  "code-line-highlight": Decoration.line({ class: "cm-draftly-code-line-highlight" }),
2387
- "code-text-highlight": Decoration.mark({ class: "cm-draftly-code-text-highlight" })
3422
+ "code-text-highlight": Decoration.mark({ class: "cm-draftly-code-text-highlight" }),
3423
+ // Diff preview
3424
+ "diff-line-add": Decoration.line({ class: "cm-draftly-code-line-diff-add" }),
3425
+ "diff-line-del": Decoration.line({ class: "cm-draftly-code-line-diff-del" }),
3426
+ "diff-sign-add": Decoration.mark({ class: "cm-draftly-code-diff-sign-add" }),
3427
+ "diff-sign-del": Decoration.mark({ class: "cm-draftly-code-diff-sign-del" }),
3428
+ "diff-mod-add": Decoration.mark({ class: "cm-draftly-code-diff-mod-add" }),
3429
+ "diff-mod-del": Decoration.mark({ class: "cm-draftly-code-diff-mod-del" }),
3430
+ "diff-escape-hidden": Decoration.replace({})
2388
3431
  };
2389
3432
  var CodeBlockHeaderWidget = class extends WidgetType {
2390
3433
  constructor(props, codeContent) {
@@ -2470,11 +3513,12 @@ var CodePlugin = class extends DecorationPlugin {
2470
3513
  version = "1.0.0";
2471
3514
  decorationPriority = 25;
2472
3515
  requiredNodes = ["InlineCode", "FencedCode", "CodeMark", "CodeInfo", "CodeText"];
3516
+ parserCache = /* @__PURE__ */ new Map();
2473
3517
  /**
2474
3518
  * Plugin theme
2475
3519
  */
2476
3520
  get theme() {
2477
- return theme10;
3521
+ return codePluginTheme;
2478
3522
  }
2479
3523
  /**
2480
3524
  * Keyboard shortcuts for code formatting
@@ -2493,6 +3537,15 @@ var CodePlugin = class extends DecorationPlugin {
2493
3537
  }
2494
3538
  ];
2495
3539
  }
3540
+ /**
3541
+ * Intercepts backtick typing to wrap selected text as inline code.
3542
+ *
3543
+ * If user types '`' while text is selected, wraps each selected range
3544
+ * with backticks (selected -> `selected`).
3545
+ */
3546
+ getExtensions() {
3547
+ return [createWrapSelectionInputHandler({ "`": "`" })];
3548
+ }
2496
3549
  /**
2497
3550
  * Toggle code block on current line or selected lines
2498
3551
  */
@@ -2505,7 +3558,7 @@ var CodePlugin = class extends DecorationPlugin {
2505
3558
  const nextLineNum = endLine.number < state.doc.lines ? endLine.number + 1 : endLine.number;
2506
3559
  const prevLine = state.doc.line(prevLineNum);
2507
3560
  const nextLine = state.doc.line(nextLineNum);
2508
- const isWrapped = prevLine.text.trim().startsWith("```") && nextLine.text.trim() === "```" && prevLineNum !== startLine.number && nextLineNum !== endLine.number;
3561
+ const isWrapped = prevLine.text.trim().startsWith(CODE_FENCE) && nextLine.text.trim() === CODE_FENCE && prevLineNum !== startLine.number && nextLineNum !== endLine.number;
2509
3562
  if (isWrapped) {
2510
3563
  view.dispatch({
2511
3564
  changes: [
@@ -2516,8 +3569,10 @@ var CodePlugin = class extends DecorationPlugin {
2516
3569
  ]
2517
3570
  });
2518
3571
  } else {
2519
- const openFence = "```\n";
2520
- const closeFence = "\n```";
3572
+ const openFence = `${CODE_FENCE}
3573
+ `;
3574
+ const closeFence = `
3575
+ ${CODE_FENCE}`;
2521
3576
  view.dispatch({
2522
3577
  changes: [
2523
3578
  { from: startLine.from, insert: openFence },
@@ -2546,6 +3601,7 @@ var CodePlugin = class extends DecorationPlugin {
2546
3601
  * lineNumbers: 5,
2547
3602
  * title: "hello.tsx",
2548
3603
  * copy: true,
3604
+ * diff: false,
2549
3605
  * highlightLines: [2,3,4,5],
2550
3606
  * highlightText: [{ pattern: "Hello", instances: [3,4,5] }]
2551
3607
  * }
@@ -2557,14 +3613,21 @@ var CodePlugin = class extends DecorationPlugin {
2557
3613
  return props;
2558
3614
  }
2559
3615
  let remaining = codeInfo.trim();
2560
- const langMatch = remaining.match(/^(\w+)/);
2561
- if (langMatch && langMatch[1]) {
2562
- props.language = langMatch[1];
2563
- remaining = remaining.slice(langMatch[0].length).trim();
3616
+ const firstTokenMatch = remaining.match(/^([^\s]+)/);
3617
+ if (firstTokenMatch && firstTokenMatch[1]) {
3618
+ const firstToken = firstTokenMatch[1];
3619
+ const normalizedToken = firstToken.toLowerCase();
3620
+ const isLineNumberDirective = /^(?:line-numbers|linenumbers|showlinenumbers)(?:\{\d+\})?$/.test(
3621
+ normalizedToken
3622
+ );
3623
+ const isKnownDirective = isLineNumberDirective || normalizedToken === "copy" || normalizedToken === "diff" || normalizedToken.startsWith("{") || normalizedToken.startsWith("/");
3624
+ if (!isKnownDirective) {
3625
+ props.language = firstToken;
3626
+ remaining = remaining.slice(firstToken.length).trim();
3627
+ }
2564
3628
  }
2565
- const quotedPattern = /(\w+)="([^"]*)"/g;
2566
3629
  let quotedMatch;
2567
- while ((quotedMatch = quotedPattern.exec(remaining)) !== null) {
3630
+ while ((quotedMatch = QUOTED_INFO_PATTERN.exec(remaining)) !== null) {
2568
3631
  const key = quotedMatch[1]?.toLowerCase();
2569
3632
  const value = quotedMatch[2];
2570
3633
  if (key === "title" && value !== void 0) {
@@ -2573,13 +3636,13 @@ var CodePlugin = class extends DecorationPlugin {
2573
3636
  props.caption = value;
2574
3637
  }
2575
3638
  }
2576
- remaining = remaining.replace(quotedPattern, "").trim();
2577
- const lineNumbersMatch = remaining.match(/line-numbers(?:\{(\d+)\})?/);
3639
+ remaining = remaining.replace(QUOTED_INFO_PATTERN, "").trim();
3640
+ const lineNumbersMatch = remaining.match(/\b(?:line-numbers|lineNumbers|showLineNumbers)(?:\{(\d+)\})?/i);
2578
3641
  if (lineNumbersMatch) {
2579
3642
  if (lineNumbersMatch[1]) {
2580
- props.lineNumbers = parseInt(lineNumbersMatch[1], 10);
3643
+ props.showLineNumbers = parseInt(lineNumbersMatch[1], 10);
2581
3644
  } else {
2582
- props.lineNumbers = true;
3645
+ props.showLineNumbers = true;
2583
3646
  }
2584
3647
  remaining = remaining.replace(lineNumbersMatch[0], "").trim();
2585
3648
  }
@@ -2587,52 +3650,27 @@ var CodePlugin = class extends DecorationPlugin {
2587
3650
  props.copy = true;
2588
3651
  remaining = remaining.replace(/\bcopy\b/, "").trim();
2589
3652
  }
3653
+ if (/\bdiff\b/.test(remaining)) {
3654
+ props.diff = true;
3655
+ remaining = remaining.replace(/\bdiff\b/, "").trim();
3656
+ }
2590
3657
  const lineHighlightMatch = remaining.match(/\{([^}]+)\}/);
2591
3658
  if (lineHighlightMatch && lineHighlightMatch[1]) {
2592
- const highlightLines = [];
2593
- const parts = lineHighlightMatch[1].split(",");
2594
- for (const part of parts) {
2595
- const trimmed = part.trim();
2596
- const rangeMatch = trimmed.match(/^(\d+)-(\d+)$/);
2597
- if (rangeMatch && rangeMatch[1] && rangeMatch[2]) {
2598
- const start = parseInt(rangeMatch[1], 10);
2599
- const end = parseInt(rangeMatch[2], 10);
2600
- for (let i = start; i <= end; i++) {
2601
- highlightLines.push(i);
2602
- }
2603
- } else if (/^\d+$/.test(trimmed)) {
2604
- highlightLines.push(parseInt(trimmed, 10));
2605
- }
2606
- }
3659
+ const highlightLines = this.parseNumberList(lineHighlightMatch[1]);
2607
3660
  if (highlightLines.length > 0) {
2608
3661
  props.highlightLines = highlightLines;
2609
3662
  }
2610
3663
  remaining = remaining.replace(lineHighlightMatch[0], "").trim();
2611
3664
  }
2612
- const textHighlightPattern = /\/([^/]+)\/(?:(\d+(?:-\d+)?(?:,\d+(?:-\d+)?)*))?/g;
2613
3665
  let textMatch;
2614
3666
  const highlightText = [];
2615
- while ((textMatch = textHighlightPattern.exec(remaining)) !== null) {
3667
+ while ((textMatch = TEXT_HIGHLIGHT_PATTERN.exec(remaining)) !== null) {
2616
3668
  if (!textMatch[1]) continue;
2617
3669
  const highlight = {
2618
3670
  pattern: textMatch[1]
2619
3671
  };
2620
3672
  if (textMatch[2]) {
2621
- const instanceStr = textMatch[2];
2622
- const instances = [];
2623
- const instanceParts = instanceStr.split(",");
2624
- for (const part of instanceParts) {
2625
- const rangeMatch = part.match(/^(\d+)-(\d+)$/);
2626
- if (rangeMatch && rangeMatch[1] && rangeMatch[2]) {
2627
- const start = parseInt(rangeMatch[1], 10);
2628
- const end = parseInt(rangeMatch[2], 10);
2629
- for (let i = start; i <= end; i++) {
2630
- instances.push(i);
2631
- }
2632
- } else if (/^\d+$/.test(part)) {
2633
- instances.push(parseInt(part, 10));
2634
- }
2635
- }
3673
+ const instances = this.parseNumberList(textMatch[2]);
2636
3674
  if (instances.length > 0) {
2637
3675
  highlight.instances = instances;
2638
3676
  }
@@ -2649,148 +3687,244 @@ var CodePlugin = class extends DecorationPlugin {
2649
3687
  * Handles line numbers, highlights, header/caption widgets, and fence visibility.
2650
3688
  */
2651
3689
  buildDecorations(ctx) {
2652
- const { view, decorations } = ctx;
2653
- const tree = syntaxTree(view.state);
3690
+ const tree = syntaxTree(ctx.view.state);
2654
3691
  tree.iterate({
2655
3692
  enter: (node) => {
2656
- const { from, to, name } = node;
2657
- if (name === "InlineCode") {
2658
- decorations.push(codeMarkDecorations["inline-code"].range(from, to));
2659
- const cursorInRange = ctx.selectionOverlapsRange(from, to);
2660
- if (!cursorInRange) {
2661
- for (let child = node.node.firstChild; child; child = child.nextSibling) {
2662
- if (child.name === "CodeMark") {
2663
- decorations.push(codeMarkDecorations["inline-mark"].range(child.from, child.to));
2664
- }
2665
- }
2666
- }
3693
+ if (node.name === "InlineCode") {
3694
+ this.decorateInlineCode(node, ctx);
3695
+ return;
2667
3696
  }
2668
- if (name === "FencedCode") {
2669
- const nodeLineStart = view.state.doc.lineAt(from);
2670
- const nodeLineEnd = view.state.doc.lineAt(to);
2671
- const cursorInRange = ctx.selectionOverlapsRange(nodeLineStart.from, nodeLineEnd.to);
2672
- let infoProps = { language: "" };
2673
- for (let child = node.node.firstChild; child; child = child.nextSibling) {
2674
- if (child.name === "CodeInfo") {
2675
- infoProps = this.parseCodeInfo(view.state.sliceDoc(child.from, child.to).trim());
2676
- break;
3697
+ if (node.name === "FencedCode") {
3698
+ this.decorateFencedCode(node, ctx);
3699
+ }
3700
+ }
3701
+ });
3702
+ }
3703
+ decorateInlineCode(node, ctx) {
3704
+ const { from, to } = node;
3705
+ ctx.decorations.push(codeMarkDecorations["inline-code"].range(from, to));
3706
+ if (ctx.selectionOverlapsRange(from, to)) {
3707
+ return;
3708
+ }
3709
+ for (let child = node.node.firstChild; child; child = child.nextSibling) {
3710
+ if (child.name === "CodeMark") {
3711
+ ctx.decorations.push(codeMarkDecorations["inline-mark"].range(child.from, child.to));
3712
+ }
3713
+ }
3714
+ }
3715
+ decorateFencedCode(node, ctx) {
3716
+ const { view, decorations } = ctx;
3717
+ const nodeLineStart = view.state.doc.lineAt(node.from);
3718
+ const nodeLineEnd = view.state.doc.lineAt(node.to);
3719
+ const cursorInRange = ctx.selectionOverlapsRange(nodeLineStart.from, nodeLineEnd.to);
3720
+ let infoProps = { language: "" };
3721
+ let codeContent = "";
3722
+ for (let child = node.node.firstChild; child; child = child.nextSibling) {
3723
+ if (child.name === "CodeInfo") {
3724
+ infoProps = this.parseCodeInfo(view.state.sliceDoc(child.from, child.to).trim());
3725
+ }
3726
+ if (child.name === "CodeText") {
3727
+ codeContent = view.state.sliceDoc(child.from, child.to);
3728
+ }
3729
+ }
3730
+ const codeLines = [];
3731
+ for (let i = nodeLineStart.number + 1; i <= nodeLineEnd.number - 1; i++) {
3732
+ const codeLine = view.state.doc.line(i);
3733
+ codeLines.push(view.state.sliceDoc(codeLine.from, codeLine.to));
3734
+ }
3735
+ const totalCodeLines = nodeLineEnd.number - nodeLineStart.number - 1;
3736
+ const startLineNum = typeof infoProps.showLineNumbers === "number" ? infoProps.showLineNumbers : 1;
3737
+ const maxLineNum = startLineNum + totalCodeLines - 1;
3738
+ const lineNumWidth = Math.max(String(maxLineNum).length, String(startLineNum).length);
3739
+ const highlightInstanceCounters = new Array(infoProps.highlightText?.length ?? 0).fill(0);
3740
+ const diffStates = infoProps.diff ? this.analyzeDiffLines(codeLines) : [];
3741
+ const diffDisplayLineNumbers = infoProps.diff ? this.computeDiffDisplayLineNumbers(diffStates, startLineNum) : [];
3742
+ const displayLineNumbers = infoProps.diff ? diffDisplayLineNumbers.map((numbers, index) => numbers.newLine ?? numbers.oldLine ?? startLineNum + index) : codeLines.map((_, index) => startLineNum + index);
3743
+ const diffHighlightLineNumbers = infoProps.diff ? this.computeDiffDisplayLineNumbers(diffStates, startLineNum).map(
3744
+ (numbers, index) => numbers.newLine ?? numbers.oldLine ?? startLineNum + index
3745
+ ) : [];
3746
+ const maxOldDiffLineNum = diffDisplayLineNumbers.reduce((max, numbers) => {
3747
+ const oldLine = numbers.oldLine ?? 0;
3748
+ return oldLine > max ? oldLine : max;
3749
+ }, startLineNum);
3750
+ const maxNewDiffLineNum = diffDisplayLineNumbers.reduce((max, numbers) => {
3751
+ const newLine = numbers.newLine ?? 0;
3752
+ return newLine > max ? newLine : max;
3753
+ }, startLineNum);
3754
+ const diffOldLineNumWidth = Math.max(String(startLineNum).length, String(maxOldDiffLineNum).length);
3755
+ const diffNewLineNumWidth = Math.max(String(startLineNum).length, String(maxNewDiffLineNum).length);
3756
+ const shouldShowHeader = !cursorInRange && (infoProps.title || infoProps.copy || infoProps.language);
3757
+ const shouldShowCaption = !cursorInRange && !!infoProps.caption;
3758
+ if (shouldShowHeader) {
3759
+ decorations.push(
3760
+ Decoration.widget({
3761
+ widget: new CodeBlockHeaderWidget(infoProps, codeContent),
3762
+ block: false,
3763
+ side: -1
3764
+ }).range(nodeLineStart.from)
3765
+ );
3766
+ }
3767
+ let codeLineIndex = 0;
3768
+ for (let lineNumber = nodeLineStart.number; lineNumber <= nodeLineEnd.number; lineNumber++) {
3769
+ const line = view.state.doc.line(lineNumber);
3770
+ const isFenceLine = lineNumber === nodeLineStart.number || lineNumber === nodeLineEnd.number;
3771
+ const relativeLineNum = displayLineNumbers[codeLineIndex] ?? startLineNum + codeLineIndex;
3772
+ decorations.push(codeMarkDecorations["code-block-line"].range(line.from));
3773
+ if (lineNumber === nodeLineStart.number) {
3774
+ decorations.push(codeMarkDecorations["code-block-line-start"].range(line.from));
3775
+ if (shouldShowHeader) {
3776
+ decorations.push(Decoration.line({ class: "cm-draftly-code-block-has-header" }).range(line.from));
3777
+ }
3778
+ }
3779
+ if (lineNumber === nodeLineEnd.number) {
3780
+ decorations.push(codeMarkDecorations["code-block-line-end"].range(line.from));
3781
+ if (shouldShowCaption) {
3782
+ decorations.push(Decoration.line({ class: "cm-draftly-code-block-has-caption" }).range(line.from));
3783
+ }
3784
+ }
3785
+ if (!isFenceLine && infoProps.showLineNumbers && !infoProps.diff) {
3786
+ decorations.push(
3787
+ Decoration.line({
3788
+ class: "cm-draftly-code-line-numbered",
3789
+ attributes: {
3790
+ "data-line-num": String(relativeLineNum),
3791
+ style: `--line-num-width: ${lineNumWidth}ch`
2677
3792
  }
2678
- }
2679
- const totalCodeLines = nodeLineEnd.number - nodeLineStart.number - 1;
2680
- const startLineNum = typeof infoProps.lineNumbers === "number" ? infoProps.lineNumbers : 1;
2681
- const maxLineNum = startLineNum + totalCodeLines - 1;
2682
- const lineNumWidth = Math.max(String(maxLineNum).length, String(startLineNum).length);
2683
- let codeLineIndex = 0;
2684
- let codeContent = "";
2685
- for (let child = node.node.firstChild; child; child = child.nextSibling) {
2686
- if (child.name === "CodeText") {
2687
- codeContent = view.state.sliceDoc(child.from, child.to);
2688
- break;
2689
- }
2690
- }
2691
- const shouldShowHeader = !cursorInRange && (infoProps.title || infoProps.copy || infoProps.language);
2692
- const shouldShowCaption = !cursorInRange && infoProps.caption;
2693
- if (shouldShowHeader) {
2694
- decorations.push(
2695
- Decoration.widget({
2696
- widget: new CodeBlockHeaderWidget(infoProps, codeContent),
2697
- block: false
2698
- }).range(nodeLineStart.from)
2699
- );
2700
- }
2701
- for (let i = nodeLineStart.number; i <= nodeLineEnd.number; i++) {
2702
- const line = view.state.doc.line(i);
2703
- const isFenceLine = i === nodeLineStart.number || i === nodeLineEnd.number;
2704
- const relativeLineNum = startLineNum + codeLineIndex;
2705
- decorations.push(codeMarkDecorations["code-block-line"].range(line.from));
2706
- if (i === nodeLineStart.number) {
2707
- decorations.push(codeMarkDecorations["code-block-line-start"].range(line.from));
2708
- if (shouldShowHeader) {
2709
- decorations.push(
2710
- Decoration.line({
2711
- class: "cm-draftly-code-block-has-header"
2712
- }).range(line.from)
2713
- );
2714
- }
2715
- }
2716
- if (i === nodeLineEnd.number) {
2717
- decorations.push(codeMarkDecorations["code-block-line-end"].range(line.from));
2718
- if (shouldShowCaption) {
2719
- decorations.push(
2720
- Decoration.line({
2721
- class: "cm-draftly-code-block-has-caption"
2722
- }).range(line.from)
2723
- );
2724
- }
2725
- }
2726
- if (!isFenceLine && infoProps.lineNumbers) {
2727
- decorations.push(
2728
- Decoration.line({
2729
- class: "cm-draftly-code-line-numbered",
2730
- attributes: {
2731
- "data-line-num": String(relativeLineNum),
2732
- style: `--line-num-width: ${lineNumWidth}ch`
2733
- }
2734
- }).range(line.from)
2735
- );
2736
- }
2737
- if (!isFenceLine && infoProps.highlightLines) {
2738
- if (infoProps.highlightLines.includes(codeLineIndex + 1)) {
2739
- decorations.push(codeMarkDecorations["code-line-highlight"].range(line.from));
2740
- }
2741
- }
2742
- if (!isFenceLine && infoProps.highlightText && infoProps.highlightText.length > 0) {
2743
- const lineText = view.state.sliceDoc(line.from, line.to);
2744
- for (const textHighlight of infoProps.highlightText) {
2745
- try {
2746
- const regex = new RegExp(textHighlight.pattern, "g");
2747
- let match;
2748
- let matchIndex = 0;
2749
- while ((match = regex.exec(lineText)) !== null) {
2750
- matchIndex++;
2751
- const shouldHighlight = !textHighlight.instances || textHighlight.instances.includes(matchIndex);
2752
- if (shouldHighlight) {
2753
- const matchFrom = line.from + match.index;
2754
- const matchTo = matchFrom + match[0].length;
2755
- decorations.push(codeMarkDecorations["code-text-highlight"].range(matchFrom, matchTo));
2756
- }
2757
- }
2758
- } catch {
2759
- }
2760
- }
2761
- }
2762
- if (!isFenceLine) {
2763
- codeLineIndex++;
2764
- }
2765
- }
2766
- for (let child = node.node.firstChild; child; child = child.nextSibling) {
2767
- if (child.name === "CodeMark" || child.name === "CodeInfo") {
2768
- if (cursorInRange) {
2769
- decorations.push(codeMarkDecorations["code-fence"].range(child.from, child.to));
2770
- } else {
2771
- decorations.push(codeMarkDecorations["code-hidden"].range(child.from, child.to));
2772
- }
3793
+ }).range(line.from)
3794
+ );
3795
+ }
3796
+ if (!isFenceLine && infoProps.showLineNumbers && infoProps.diff) {
3797
+ const diffLineNumbers = diffDisplayLineNumbers[codeLineIndex];
3798
+ const diffState = diffStates[codeLineIndex];
3799
+ const diffMarker = diffState?.kind === "addition" ? "+" : diffState?.kind === "deletion" ? "-" : " ";
3800
+ decorations.push(
3801
+ Decoration.line({
3802
+ class: "cm-draftly-code-line-numbered-diff",
3803
+ attributes: {
3804
+ "data-line-num-old": diffLineNumbers?.oldLine != null ? String(diffLineNumbers.oldLine) : "",
3805
+ "data-line-num-new": diffLineNumbers?.newLine != null ? String(diffLineNumbers.newLine) : "",
3806
+ "data-diff-marker": diffMarker,
3807
+ style: `--line-num-old-width: ${diffOldLineNumWidth}ch; --line-num-new-width: ${diffNewLineNumWidth}ch`
2773
3808
  }
3809
+ }).range(line.from)
3810
+ );
3811
+ }
3812
+ if (!isFenceLine && infoProps.diff) {
3813
+ this.decorateDiffLine(
3814
+ line,
3815
+ codeLineIndex,
3816
+ diffStates,
3817
+ cursorInRange,
3818
+ !infoProps.showLineNumbers,
3819
+ decorations
3820
+ );
3821
+ }
3822
+ if (!isFenceLine && infoProps.highlightLines) {
3823
+ const highlightLineNumber = infoProps.diff ? diffHighlightLineNumbers[codeLineIndex] ?? codeLineIndex + 1 : startLineNum + codeLineIndex;
3824
+ if (infoProps.highlightLines.includes(highlightLineNumber)) {
3825
+ decorations.push(codeMarkDecorations["code-line-highlight"].range(line.from));
3826
+ }
3827
+ }
3828
+ if (!isFenceLine && infoProps.highlightText?.length) {
3829
+ this.decorateTextHighlights(
3830
+ line.from,
3831
+ view.state.sliceDoc(line.from, line.to),
3832
+ infoProps.highlightText,
3833
+ highlightInstanceCounters,
3834
+ decorations
3835
+ );
3836
+ }
3837
+ if (!isFenceLine) {
3838
+ codeLineIndex++;
3839
+ }
3840
+ }
3841
+ this.decorateFenceMarkers(node.node, cursorInRange, decorations);
3842
+ if (!cursorInRange && infoProps.caption) {
3843
+ decorations.push(
3844
+ Decoration.widget({
3845
+ widget: new CodeBlockCaptionWidget(infoProps.caption),
3846
+ block: false,
3847
+ side: 1
3848
+ }).range(nodeLineEnd.to)
3849
+ );
3850
+ }
3851
+ }
3852
+ decorateFenceMarkers(node, cursorInRange, decorations) {
3853
+ for (let child = node.firstChild; child; child = child.nextSibling) {
3854
+ if (child.name === "CodeMark" || child.name === "CodeInfo") {
3855
+ decorations.push(
3856
+ (cursorInRange ? codeMarkDecorations["code-fence"] : codeMarkDecorations["code-hidden"]).range(
3857
+ child.from,
3858
+ child.to
3859
+ )
3860
+ );
3861
+ }
3862
+ }
3863
+ }
3864
+ decorateDiffLine(line, codeLineIndex, diffStates, cursorInRange, showDiffMarkerGutter, decorations) {
3865
+ const diffState = diffStates[codeLineIndex];
3866
+ const diffMarker = diffState?.kind === "addition" ? "+" : diffState?.kind === "deletion" ? "-" : " ";
3867
+ if (showDiffMarkerGutter) {
3868
+ decorations.push(
3869
+ Decoration.line({
3870
+ class: "cm-draftly-code-line-diff-gutter",
3871
+ attributes: {
3872
+ "data-diff-marker": diffMarker
2774
3873
  }
2775
- if (!cursorInRange && infoProps.caption) {
2776
- decorations.push(
2777
- Decoration.widget({
2778
- widget: new CodeBlockCaptionWidget(infoProps.caption),
2779
- block: false,
2780
- side: 1
2781
- // After the content
2782
- }).range(nodeLineEnd.to)
2783
- );
3874
+ }).range(line.from)
3875
+ );
3876
+ }
3877
+ if (diffState?.kind === "addition") {
3878
+ decorations.push(codeMarkDecorations["diff-line-add"].range(line.from));
3879
+ if (cursorInRange && line.to > line.from) {
3880
+ decorations.push(codeMarkDecorations["diff-sign-add"].range(line.from, line.from + 1));
3881
+ }
3882
+ }
3883
+ if (diffState?.kind === "deletion") {
3884
+ decorations.push(codeMarkDecorations["diff-line-del"].range(line.from));
3885
+ if (cursorInRange && line.to > line.from) {
3886
+ decorations.push(codeMarkDecorations["diff-sign-del"].range(line.from, line.from + 1));
3887
+ }
3888
+ }
3889
+ if (!cursorInRange && line.to > line.from && (diffState?.escapedMarker || diffState?.kind === "addition" || diffState?.kind === "deletion")) {
3890
+ decorations.push(codeMarkDecorations["diff-escape-hidden"].range(line.from, line.from + 1));
3891
+ }
3892
+ if (diffState?.modificationRanges?.length) {
3893
+ for (const [start, end] of diffState.modificationRanges) {
3894
+ const rangeFrom = line.from + diffState.contentOffset + start;
3895
+ const rangeTo = line.from + diffState.contentOffset + end;
3896
+ if (rangeTo > rangeFrom) {
3897
+ decorations.push(
3898
+ (diffState.kind === "addition" ? codeMarkDecorations["diff-mod-add"] : codeMarkDecorations["diff-mod-del"]).range(rangeFrom, rangeTo)
3899
+ );
3900
+ }
3901
+ }
3902
+ }
3903
+ }
3904
+ decorateTextHighlights(lineFrom, lineText, highlights, instanceCounters, decorations) {
3905
+ for (const [highlightIndex, textHighlight] of highlights.entries()) {
3906
+ try {
3907
+ const regex = new RegExp(textHighlight.pattern, "g");
3908
+ let match;
3909
+ while ((match = regex.exec(lineText)) !== null) {
3910
+ instanceCounters[highlightIndex] = (instanceCounters[highlightIndex] ?? 0) + 1;
3911
+ const globalMatchIndex = instanceCounters[highlightIndex];
3912
+ const shouldHighlight = !textHighlight.instances || textHighlight.instances.includes(globalMatchIndex);
3913
+ if (shouldHighlight) {
3914
+ const matchFrom = lineFrom + match.index;
3915
+ const matchTo = matchFrom + match[0].length;
3916
+ decorations.push(codeMarkDecorations["code-text-highlight"].range(matchFrom, matchTo));
2784
3917
  }
2785
3918
  }
3919
+ } catch {
2786
3920
  }
2787
- });
3921
+ }
2788
3922
  }
2789
3923
  /**
2790
3924
  * Render code elements to HTML for static preview.
2791
3925
  * Applies syntax highlighting using @lezer/highlight.
2792
3926
  */
2793
- renderToHTML(node, children, ctx) {
3927
+ async renderToHTML(node, _children, ctx) {
2794
3928
  if (node.name === "CodeMark") {
2795
3929
  return "";
2796
3930
  }
@@ -2800,7 +3934,7 @@ var CodePlugin = class extends DecorationPlugin {
2800
3934
  if (match && match[1]) {
2801
3935
  content = match[1];
2802
3936
  }
2803
- return `<code class="cm-draftly-code-inline" style="padding: 0.1rem 0.25rem">${ctx.sanitize(content)}</code>`;
3937
+ return `<code class="cm-draftly-code-inline" style="padding: 0.1rem 0.25rem">${this.escapeHtml(content)}</code>`;
2804
3938
  }
2805
3939
  if (node.name === "FencedCode") {
2806
3940
  const content = ctx.sliceDoc(node.from, node.to);
@@ -2818,9 +3952,9 @@ var CodePlugin = class extends DecorationPlugin {
2818
3952
  html += `<div class="cm-draftly-code-header">`;
2819
3953
  html += `<div class="cm-draftly-code-header-left">`;
2820
3954
  if (props.title) {
2821
- html += `<span class="cm-draftly-code-header-title">${ctx.sanitize(props.title)}</span>`;
3955
+ html += `<span class="cm-draftly-code-header-title">${this.escapeHtml(props.title)}</span>`;
2822
3956
  } else if (props.language) {
2823
- html += `<span class="cm-draftly-code-header-lang">${ctx.sanitize(props.language)}</span>`;
3957
+ html += `<span class="cm-draftly-code-header-lang">${this.escapeHtml(props.language)}</span>`;
2824
3958
  }
2825
3959
  html += `</div>`;
2826
3960
  if (props.copy !== false) {
@@ -2833,32 +3967,83 @@ var CodePlugin = class extends DecorationPlugin {
2833
3967
  }
2834
3968
  html += `</div>`;
2835
3969
  }
2836
- const startLineNum = typeof props.lineNumbers === "number" ? props.lineNumbers : 1;
2837
- const lineNumWidth = String(startLineNum + codeLines.length - 1).length;
3970
+ const startLineNum = typeof props.showLineNumbers === "number" ? props.showLineNumbers : 1;
3971
+ const previewHighlightCounters = new Array(props.highlightText?.length ?? 0).fill(0);
3972
+ const diffStates = props.diff ? this.analyzeDiffLines(codeLines) : [];
3973
+ const previewDiffLineNumbers = props.diff ? this.computeDiffDisplayLineNumbers(diffStates, startLineNum) : [];
3974
+ const previewLineNumbers = props.diff ? previewDiffLineNumbers.map((numbers, index) => numbers.newLine ?? numbers.oldLine ?? startLineNum + index) : codeLines.map((_, index) => startLineNum + index);
3975
+ const previewHighlightLineNumbers = props.diff ? this.computeDiffDisplayLineNumbers(diffStates, startLineNum).map(
3976
+ (numbers, index) => numbers.newLine ?? numbers.oldLine ?? startLineNum + index
3977
+ ) : [];
3978
+ const lineNumWidth = String(Math.max(...previewLineNumbers, startLineNum)).length;
3979
+ const previewOldLineNumWidth = String(
3980
+ Math.max(
3981
+ ...previewDiffLineNumbers.map((numbers) => numbers.oldLine ?? 0),
3982
+ startLineNum
3983
+ )
3984
+ ).length;
3985
+ const previewNewLineNumWidth = String(
3986
+ Math.max(
3987
+ ...previewDiffLineNumbers.map((numbers) => numbers.newLine ?? 0),
3988
+ startLineNum
3989
+ )
3990
+ ).length;
3991
+ const previewContentLines = props.diff ? diffStates.map((state) => state.content) : codeLines;
3992
+ const highlightedLines = await this.highlightCodeLines(
3993
+ previewContentLines.join("\n"),
3994
+ props.language || "",
3995
+ ctx.syntaxHighlighters
3996
+ );
2838
3997
  const hasHeader = showHeader ? " cm-draftly-code-block-has-header" : "";
2839
3998
  const hasCaption = props.caption ? " cm-draftly-code-block-has-caption" : "";
2840
- html += `<pre class="cm-draftly-code-block${hasHeader}${hasCaption}"${props.language ? ` data-lang="${ctx.sanitize(props.language)}"` : ""}>`;
3999
+ html += `<pre class="cm-draftly-code-block${hasHeader}${hasCaption}"${props.language ? ` data-lang="${this.escapeAttribute(props.language)}"` : ""}>`;
2841
4000
  html += `<code>`;
2842
4001
  codeLines.forEach((line, index) => {
2843
- const lineNum = startLineNum + index;
2844
- const isHighlighted = props.highlightLines?.includes(index + 1);
4002
+ const lineNum = previewLineNumbers[index] ?? startLineNum + index;
4003
+ const highlightLineNumber = props.diff ? previewHighlightLineNumbers[index] ?? startLineNum + index : startLineNum + index;
4004
+ const isHighlighted = props.highlightLines?.includes(highlightLineNumber);
4005
+ const diffState = props.diff ? diffStates[index] : void 0;
4006
+ const diffLineNumbers = props.diff ? previewDiffLineNumbers[index] : void 0;
2845
4007
  const lineClasses = ["cm-draftly-code-line"];
2846
4008
  if (isHighlighted) lineClasses.push("cm-draftly-code-line-highlight");
2847
- if (props.lineNumbers) lineClasses.push("cm-draftly-code-line-numbered");
4009
+ if (props.showLineNumbers) {
4010
+ lineClasses.push(props.diff ? "cm-draftly-code-line-numbered-diff" : "cm-draftly-code-line-numbered");
4011
+ }
4012
+ if (diffState?.kind === "addition") lineClasses.push("cm-draftly-code-line-diff-add");
4013
+ if (diffState?.kind === "deletion") lineClasses.push("cm-draftly-code-line-diff-del");
2848
4014
  const lineAttrs = [`class="${lineClasses.join(" ")}"`];
2849
- if (props.lineNumbers) {
4015
+ if (props.showLineNumbers && !props.diff) {
2850
4016
  lineAttrs.push(`data-line-num="${lineNum}"`);
2851
4017
  lineAttrs.push(`style="--line-num-width: ${lineNumWidth}ch"`);
2852
4018
  }
2853
- let lineContent = this.highlightCodeLine(line, props.language || "", ctx);
4019
+ if (props.diff) {
4020
+ const diffMarker = diffState?.kind === "addition" ? "+" : diffState?.kind === "deletion" ? "-" : " ";
4021
+ if (props.showLineNumbers) {
4022
+ lineAttrs.push(`data-line-num-old="${diffLineNumbers?.oldLine != null ? diffLineNumbers.oldLine : ""}"`);
4023
+ lineAttrs.push(`data-line-num-new="${diffLineNumbers?.newLine != null ? diffLineNumbers.newLine : ""}"`);
4024
+ lineAttrs.push(`data-diff-marker="${diffMarker}"`);
4025
+ lineAttrs.push(
4026
+ `style="--line-num-old-width: ${previewOldLineNumWidth}ch; --line-num-new-width: ${previewNewLineNumWidth}ch"`
4027
+ );
4028
+ } else {
4029
+ lineAttrs.push(`data-diff-marker="${diffMarker}"`);
4030
+ lineClasses.push("cm-draftly-code-line-diff-gutter");
4031
+ lineAttrs[0] = `class="${lineClasses.join(" ")}"`;
4032
+ }
4033
+ }
4034
+ const highlightedLine = highlightedLines[index] ?? this.escapeHtml(previewContentLines[index] ?? line);
4035
+ let lineContent = highlightedLine;
4036
+ if (diffState) {
4037
+ lineContent = this.renderDiffPreviewLine(diffState, highlightedLine);
4038
+ }
2854
4039
  if (props.highlightText && props.highlightText.length > 0) {
2855
- lineContent = this.applyTextHighlights(lineContent, props.highlightText);
4040
+ lineContent = this.applyTextHighlights(lineContent, props.highlightText, previewHighlightCounters);
2856
4041
  }
2857
4042
  html += `<span ${lineAttrs.join(" ")}>${lineContent || " "}</span>`;
2858
4043
  });
2859
4044
  html += `</code></pre>`;
2860
4045
  if (props.caption) {
2861
- html += `<div class="cm-draftly-code-caption">${ctx.sanitize(props.caption)}</div>`;
4046
+ html += `<div class="cm-draftly-code-caption">${this.escapeHtml(props.caption)}</div>`;
2862
4047
  }
2863
4048
  html += `</div>`;
2864
4049
  return html;
@@ -2868,54 +4053,290 @@ var CodePlugin = class extends DecorationPlugin {
2868
4053
  }
2869
4054
  return null;
2870
4055
  }
4056
+ /** Parse comma-separated numbers and ranges (e.g. "1,3-5") into [1,3,4,5]. */
4057
+ parseNumberList(value) {
4058
+ const result = [];
4059
+ for (const part of value.split(",")) {
4060
+ const trimmed = part.trim();
4061
+ const rangeMatch = trimmed.match(/^(\d+)-(\d+)$/);
4062
+ if (rangeMatch && rangeMatch[1] && rangeMatch[2]) {
4063
+ const start = parseInt(rangeMatch[1], 10);
4064
+ const end = parseInt(rangeMatch[2], 10);
4065
+ for (let i = start; i <= end; i++) {
4066
+ result.push(i);
4067
+ }
4068
+ continue;
4069
+ }
4070
+ if (/^\d+$/.test(trimmed)) {
4071
+ result.push(parseInt(trimmed, 10));
4072
+ }
4073
+ }
4074
+ return result;
4075
+ }
2871
4076
  /**
2872
4077
  * Highlight a single line of code using the language's Lezer parser.
2873
4078
  * Falls back to sanitized plain text if the language is not supported.
2874
4079
  */
2875
- highlightCodeLine(line, lang, ctx) {
2876
- if (!lang || !line) {
2877
- return ctx.sanitize(line);
4080
+ async highlightCodeLines(code, lang, syntaxHighlighters) {
4081
+ const rawLines = code.split("\n");
4082
+ if (!lang || !code) {
4083
+ return rawLines.map((line) => this.escapeHtml(line));
2878
4084
  }
2879
- const langDesc = languages.find(
2880
- (l) => l.name.toLowerCase() === lang.toLowerCase() || l.alias && l.alias.includes(lang.toLowerCase())
2881
- );
2882
- if (!langDesc || !langDesc.support) {
2883
- return ctx.sanitize(line);
4085
+ const parser = await this.resolveLanguageParser(lang);
4086
+ if (!parser) {
4087
+ return rawLines.map((line) => this.escapeHtml(line));
2884
4088
  }
2885
4089
  try {
2886
- const parser = langDesc.support.language.parser;
2887
- const tree = parser.parse(line);
2888
- let result = "";
4090
+ const tree = parser.parse(code);
4091
+ const highlightedLines = [""];
2889
4092
  highlightCode(
2890
- line,
4093
+ code,
2891
4094
  tree,
2892
- classHighlighter,
4095
+ syntaxHighlighters && syntaxHighlighters.length > 0 ? syntaxHighlighters : [],
2893
4096
  (text, classes2) => {
2894
- if (classes2) {
2895
- result += `<span class="${classes2}">${ctx.sanitize(text)}</span>`;
2896
- } else {
2897
- result += ctx.sanitize(text);
2898
- }
4097
+ const chunk = classes2 ? `<span class="${this.escapeAttribute(classes2)}">${this.escapeHtml(text)}</span>` : this.escapeHtml(text);
4098
+ highlightedLines[highlightedLines.length - 1] += chunk;
2899
4099
  },
2900
4100
  () => {
4101
+ highlightedLines.push("");
2901
4102
  }
2902
- // No newlines for single line
2903
4103
  );
2904
- return result;
4104
+ return rawLines.map((line, index) => highlightedLines[index] || this.escapeHtml(line));
2905
4105
  } catch {
2906
- return ctx.sanitize(line);
4106
+ return rawLines.map((line) => this.escapeHtml(line));
2907
4107
  }
2908
4108
  }
4109
+ async resolveLanguageParser(lang) {
4110
+ const normalizedLang = this.normalizeLanguage(lang);
4111
+ if (!normalizedLang) return null;
4112
+ const cached = this.parserCache.get(normalizedLang);
4113
+ if (cached) return cached;
4114
+ const parserPromise = (async () => {
4115
+ const langDesc = LanguageDescription.matchLanguageName(languages, normalizedLang, true);
4116
+ if (!langDesc) return null;
4117
+ if (langDesc.support) {
4118
+ return langDesc.support.language.parser;
4119
+ }
4120
+ if (typeof langDesc.load === "function") {
4121
+ try {
4122
+ const support = await langDesc.load();
4123
+ return support.language.parser;
4124
+ } catch {
4125
+ return null;
4126
+ }
4127
+ }
4128
+ return null;
4129
+ })();
4130
+ this.parserCache.set(normalizedLang, parserPromise);
4131
+ return parserPromise;
4132
+ }
4133
+ normalizeLanguage(lang) {
4134
+ const normalized = lang.trim().toLowerCase();
4135
+ if (!normalized) return "";
4136
+ const normalizedMap = {
4137
+ "c++": "cpp",
4138
+ "c#": "csharp",
4139
+ "f#": "fsharp",
4140
+ py: "python",
4141
+ js: "javascript",
4142
+ ts: "typescript",
4143
+ sh: "shell"
4144
+ };
4145
+ return normalizedMap[normalized] ?? normalized;
4146
+ }
4147
+ escapeHtml(value) {
4148
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
4149
+ }
4150
+ escapeAttribute(value) {
4151
+ return this.escapeHtml(value).replace(/`/g, "&#96;");
4152
+ }
4153
+ analyzeDiffLines(lines) {
4154
+ const states = lines.map((line) => this.parseDiffLineState(line));
4155
+ let index = 0;
4156
+ while (index < states.length) {
4157
+ if (states[index]?.kind !== "deletion") {
4158
+ index++;
4159
+ continue;
4160
+ }
4161
+ const deletionStart = index;
4162
+ while (index < states.length && states[index]?.kind === "deletion") {
4163
+ index++;
4164
+ }
4165
+ const deletionEnd = index;
4166
+ const additionStart = index;
4167
+ while (index < states.length && states[index]?.kind === "addition") {
4168
+ index++;
4169
+ }
4170
+ const additionEnd = index;
4171
+ if (additionStart === additionEnd) {
4172
+ continue;
4173
+ }
4174
+ const pairCount = Math.min(deletionEnd - deletionStart, additionEnd - additionStart);
4175
+ for (let pairIndex = 0; pairIndex < pairCount; pairIndex++) {
4176
+ const deletionState = states[deletionStart + pairIndex];
4177
+ const additionState = states[additionStart + pairIndex];
4178
+ if (!deletionState || !additionState) {
4179
+ continue;
4180
+ }
4181
+ const ranges = this.computeChangedRanges(deletionState.content, additionState.content);
4182
+ if (ranges.oldRanges.length > 0) {
4183
+ deletionState.modificationRanges = ranges.oldRanges;
4184
+ }
4185
+ if (ranges.newRanges.length > 0) {
4186
+ additionState.modificationRanges = ranges.newRanges;
4187
+ }
4188
+ }
4189
+ }
4190
+ return states;
4191
+ }
4192
+ computeDiffDisplayLineNumbers(states, startLineNum) {
4193
+ const numbers = [];
4194
+ let oldLineNumber = startLineNum;
4195
+ let newLineNumber = startLineNum;
4196
+ for (const state of states) {
4197
+ if (state.kind === "deletion") {
4198
+ numbers.push({ oldLine: oldLineNumber, newLine: null });
4199
+ oldLineNumber++;
4200
+ continue;
4201
+ }
4202
+ if (state.kind === "addition") {
4203
+ numbers.push({ oldLine: null, newLine: newLineNumber });
4204
+ newLineNumber++;
4205
+ continue;
4206
+ }
4207
+ numbers.push({ oldLine: oldLineNumber, newLine: newLineNumber });
4208
+ oldLineNumber++;
4209
+ newLineNumber++;
4210
+ }
4211
+ return numbers;
4212
+ }
4213
+ parseDiffLineState(line) {
4214
+ const escapedMarker = line.startsWith("\\+") || line.startsWith("\\-");
4215
+ if (escapedMarker) {
4216
+ return {
4217
+ kind: "normal",
4218
+ content: line.slice(1),
4219
+ contentOffset: 1,
4220
+ escapedMarker: true
4221
+ };
4222
+ }
4223
+ if (line.startsWith("+")) {
4224
+ return {
4225
+ kind: "addition",
4226
+ content: line.slice(1),
4227
+ contentOffset: 1,
4228
+ escapedMarker: false
4229
+ };
4230
+ }
4231
+ if (line.startsWith("-")) {
4232
+ return {
4233
+ kind: "deletion",
4234
+ content: line.slice(1),
4235
+ contentOffset: 1,
4236
+ escapedMarker: false
4237
+ };
4238
+ }
4239
+ return {
4240
+ kind: "normal",
4241
+ content: line,
4242
+ contentOffset: 0,
4243
+ escapedMarker: false
4244
+ };
4245
+ }
4246
+ computeChangedRanges(oldText, newText) {
4247
+ let prefix = 0;
4248
+ while (prefix < oldText.length && prefix < newText.length && oldText[prefix] === newText[prefix]) {
4249
+ prefix++;
4250
+ }
4251
+ let oldSuffix = oldText.length;
4252
+ let newSuffix = newText.length;
4253
+ while (oldSuffix > prefix && newSuffix > prefix && oldText[oldSuffix - 1] === newText[newSuffix - 1]) {
4254
+ oldSuffix--;
4255
+ newSuffix--;
4256
+ }
4257
+ const oldRanges = [];
4258
+ const newRanges = [];
4259
+ if (oldSuffix > prefix) {
4260
+ oldRanges.push([prefix, oldSuffix]);
4261
+ }
4262
+ if (newSuffix > prefix) {
4263
+ newRanges.push([prefix, newSuffix]);
4264
+ }
4265
+ return { oldRanges, newRanges };
4266
+ }
4267
+ renderDiffPreviewLine(diffState, highlightedContent) {
4268
+ const modClass = diffState.kind === "addition" ? "cm-draftly-code-diff-mod-add" : diffState.kind === "deletion" ? "cm-draftly-code-diff-mod-del" : "";
4269
+ const baseHighlightedContent = highlightedContent || this.escapeHtml(diffState.content);
4270
+ const contentHtml = diffState.modificationRanges && modClass ? this.applyRangesToHighlightedHTML(baseHighlightedContent, diffState.modificationRanges, modClass) : baseHighlightedContent;
4271
+ return contentHtml || " ";
4272
+ }
4273
+ applyRangesToHighlightedHTML(htmlContent, ranges, className) {
4274
+ 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]);
4275
+ if (normalizedRanges.length === 0 || !htmlContent) {
4276
+ return htmlContent;
4277
+ }
4278
+ const isInsideRange = (position) => {
4279
+ for (const [start, end] of normalizedRanges) {
4280
+ if (position >= start && position < end) return true;
4281
+ if (position < start) return false;
4282
+ }
4283
+ return false;
4284
+ };
4285
+ let result = "";
4286
+ let htmlIndex = 0;
4287
+ let textPosition = 0;
4288
+ let markOpen = false;
4289
+ while (htmlIndex < htmlContent.length) {
4290
+ const char = htmlContent[htmlIndex];
4291
+ if (char === "<") {
4292
+ const tagEnd = htmlContent.indexOf(">", htmlIndex);
4293
+ if (tagEnd === -1) {
4294
+ result += htmlContent.slice(htmlIndex);
4295
+ break;
4296
+ }
4297
+ result += htmlContent.slice(htmlIndex, tagEnd + 1);
4298
+ htmlIndex = tagEnd + 1;
4299
+ continue;
4300
+ }
4301
+ let token = char;
4302
+ if (char === "&") {
4303
+ const entityEnd = htmlContent.indexOf(";", htmlIndex);
4304
+ if (entityEnd !== -1) {
4305
+ token = htmlContent.slice(htmlIndex, entityEnd + 1);
4306
+ htmlIndex = entityEnd + 1;
4307
+ } else {
4308
+ htmlIndex += 1;
4309
+ }
4310
+ } else {
4311
+ htmlIndex += 1;
4312
+ }
4313
+ const shouldMark = isInsideRange(textPosition);
4314
+ if (shouldMark && !markOpen) {
4315
+ result += `<mark class="${className}">`;
4316
+ markOpen = true;
4317
+ }
4318
+ if (!shouldMark && markOpen) {
4319
+ result += "</mark>";
4320
+ markOpen = false;
4321
+ }
4322
+ result += token;
4323
+ textPosition += 1;
4324
+ }
4325
+ if (markOpen) {
4326
+ result += "</mark>";
4327
+ }
4328
+ return result;
4329
+ }
2909
4330
  /**
2910
4331
  * Apply text highlights (regex patterns) to already syntax-highlighted HTML.
2911
4332
  * Wraps matched patterns in `<mark>` elements.
2912
4333
  */
2913
- applyTextHighlights(htmlContent, highlights) {
4334
+ applyTextHighlights(htmlContent, highlights, instanceCounters) {
2914
4335
  let result = htmlContent;
2915
- for (const highlight of highlights) {
4336
+ for (const [highlightIndex, highlight] of highlights.entries()) {
2916
4337
  try {
2917
4338
  const regex = new RegExp(`(${highlight.pattern})`, "g");
2918
- let matchCount = 0;
4339
+ let matchCount = instanceCounters?.[highlightIndex] ?? 0;
2919
4340
  result = result.replace(regex, (match) => {
2920
4341
  matchCount++;
2921
4342
  const shouldHighlight = !highlight.instances || highlight.instances.includes(matchCount);
@@ -2924,252 +4345,15 @@ var CodePlugin = class extends DecorationPlugin {
2924
4345
  }
2925
4346
  return match;
2926
4347
  });
4348
+ if (instanceCounters) {
4349
+ instanceCounters[highlightIndex] = matchCount;
4350
+ }
2927
4351
  } catch {
2928
4352
  }
2929
4353
  }
2930
4354
  return result;
2931
4355
  }
2932
4356
  };
2933
- var theme10 = createTheme({
2934
- default: {
2935
- // Inline code
2936
- ".cm-draftly-code-inline": {
2937
- fontFamily: "var(--font-jetbrains-mono, monospace)",
2938
- fontSize: "0.9rem",
2939
- backgroundColor: "rgba(0, 0, 0, 0.05)",
2940
- padding: "0.1rem 0.25rem",
2941
- border: "1px solid var(--color-border)",
2942
- borderRadius: "3px"
2943
- },
2944
- // Fenced code block lines
2945
- ".cm-draftly-code-block-line": {
2946
- "--radius": "0.375rem",
2947
- fontFamily: "var(--font-jetbrains-mono, monospace)",
2948
- fontSize: "0.9rem",
2949
- backgroundColor: "rgba(0, 0, 0, 0.03)",
2950
- padding: "0 1rem !important",
2951
- lineHeight: "1.5",
2952
- borderLeft: "1px solid var(--color-border)",
2953
- borderRight: "1px solid var(--color-border)"
2954
- },
2955
- // First line of code block
2956
- ".cm-draftly-code-block-line-start": {
2957
- borderTopLeftRadius: "var(--radius)",
2958
- borderTopRightRadius: "var(--radius)",
2959
- position: "relative",
2960
- overflow: "hidden",
2961
- borderTop: "1px solid var(--color-border)",
2962
- paddingBottom: "0.5rem !important"
2963
- },
2964
- // Remove top radius when header is present
2965
- ".cm-draftly-code-block-has-header": {
2966
- padding: "0 !important",
2967
- paddingBottom: "0.5rem !important"
2968
- },
2969
- // Code block header widget
2970
- ".cm-draftly-code-header": {
2971
- display: "flex",
2972
- justifyContent: "space-between",
2973
- alignItems: "center",
2974
- padding: "0.25rem 1rem",
2975
- backgroundColor: "rgba(0, 0, 0, 0.06)",
2976
- fontFamily: "var(--font-jetbrains-mono, monospace)",
2977
- fontSize: "0.85rem"
2978
- },
2979
- ".cm-draftly-code-header-left": {
2980
- display: "flex",
2981
- alignItems: "center",
2982
- gap: "0.5rem"
2983
- },
2984
- ".cm-draftly-code-header-title": {
2985
- color: "var(--color-text, inherit)",
2986
- fontWeight: "500"
2987
- },
2988
- ".cm-draftly-code-header-lang": {
2989
- color: "#6a737d",
2990
- opacity: "0.8"
2991
- },
2992
- ".cm-draftly-code-header-right": {
2993
- display: "flex",
2994
- alignItems: "center",
2995
- gap: "0.5rem"
2996
- },
2997
- ".cm-draftly-code-copy-btn": {
2998
- display: "flex",
2999
- alignItems: "center",
3000
- justifyContent: "center",
3001
- padding: "0.25rem",
3002
- backgroundColor: "transparent",
3003
- border: "none",
3004
- borderRadius: "4px",
3005
- cursor: "pointer",
3006
- color: "#6a737d",
3007
- transition: "color 0.2s, background-color 0.2s"
3008
- },
3009
- ".cm-draftly-code-copy-btn:hover": {
3010
- backgroundColor: "rgba(0, 0, 0, 0.1)",
3011
- color: "var(--color-text, inherit)"
3012
- },
3013
- ".cm-draftly-code-copy-btn.copied": {
3014
- color: "#22c55e"
3015
- },
3016
- // Caption (below code block)
3017
- ".cm-draftly-code-block-has-caption": {
3018
- padding: "0 !important",
3019
- paddingTop: "0.5rem !important"
3020
- },
3021
- ".cm-draftly-code-caption": {
3022
- textAlign: "center",
3023
- fontSize: "0.85rem",
3024
- color: "#6a737d",
3025
- fontStyle: "italic",
3026
- padding: "0.25rem 1rem",
3027
- backgroundColor: "rgba(0, 0, 0, 0.06)"
3028
- },
3029
- // Last line of code block
3030
- ".cm-draftly-code-block-line-end": {
3031
- borderBottomLeftRadius: "var(--radius)",
3032
- borderBottomRightRadius: "var(--radius)",
3033
- borderBottom: "1px solid var(--color-border)",
3034
- paddingTop: "0.5rem !important"
3035
- },
3036
- ".cm-draftly-code-block-line-end br": {
3037
- display: "none"
3038
- },
3039
- // Fence markers (```)
3040
- ".cm-draftly-code-fence": {
3041
- color: "#6a737d",
3042
- fontFamily: "var(--font-jetbrains-mono, monospace)"
3043
- },
3044
- // Line numbers
3045
- ".cm-draftly-code-line-numbered": {
3046
- paddingLeft: "calc(var(--line-num-width, 2ch) + 1rem) !important",
3047
- position: "relative"
3048
- },
3049
- ".cm-draftly-code-line-numbered::before": {
3050
- content: "attr(data-line-num)",
3051
- position: "absolute",
3052
- left: "0.5rem",
3053
- top: "0.2rem",
3054
- width: "var(--line-num-width, 2ch)",
3055
- textAlign: "right",
3056
- color: "#6a737d",
3057
- opacity: "0.6",
3058
- fontFamily: "var(--font-jetbrains-mono, monospace)",
3059
- fontSize: "0.85rem",
3060
- userSelect: "none"
3061
- },
3062
- // Preview: code lines (need block display for full-width highlights)
3063
- ".cm-draftly-code-line": {
3064
- display: "block",
3065
- position: "relative",
3066
- paddingLeft: "1rem",
3067
- paddingRight: "1rem",
3068
- lineHeight: "1.5",
3069
- borderLeft: "3px solid transparent"
3070
- },
3071
- // Line highlight
3072
- ".cm-draftly-code-line-highlight": {
3073
- backgroundColor: "rgba(255, 220, 100, 0.2) !important",
3074
- borderLeft: "3px solid #f0b429 !important"
3075
- },
3076
- // Text highlight
3077
- ".cm-draftly-code-text-highlight": {
3078
- backgroundColor: "rgba(255, 220, 100, 0.4)",
3079
- borderRadius: "2px",
3080
- padding: "0.1rem 0"
3081
- },
3082
- // Preview: container wrapper
3083
- ".cm-draftly-code-container": {
3084
- margin: "1rem 0",
3085
- borderRadius: "var(--radius)",
3086
- overflow: "hidden",
3087
- border: "1px solid var(--color-border)"
3088
- },
3089
- // Preview: header inside container
3090
- ".cm-draftly-code-container .cm-draftly-code-header": {
3091
- borderRadius: "0",
3092
- border: "none",
3093
- borderBottom: "1px solid var(--color-border)"
3094
- },
3095
- // Preview: code block inside container
3096
- ".cm-draftly-code-container .cm-draftly-code-block": {
3097
- margin: "0",
3098
- borderRadius: "0",
3099
- border: "none",
3100
- whiteSpace: "pre-wrap"
3101
- },
3102
- // Preview: caption inside container
3103
- ".cm-draftly-code-container .cm-draftly-code-caption": {
3104
- borderTop: "1px solid var(--color-border)"
3105
- },
3106
- // Preview: standalone code block (not in container)
3107
- ".cm-draftly-code-block": {
3108
- fontFamily: "var(--font-jetbrains-mono, monospace)",
3109
- fontSize: "0.9rem",
3110
- backgroundColor: "rgba(0, 0, 0, 0.03)",
3111
- padding: "1rem",
3112
- overflow: "auto",
3113
- position: "relative",
3114
- borderRadius: "var(--radius)",
3115
- border: "1px solid var(--color-border)"
3116
- },
3117
- // Preview: code block with header (remove top radius)
3118
- ".cm-draftly-code-block.cm-draftly-code-block-has-header": {
3119
- borderTopLeftRadius: "0",
3120
- borderTopRightRadius: "0",
3121
- borderTop: "none",
3122
- margin: "0",
3123
- paddingTop: "0.5rem !important"
3124
- },
3125
- // Preview: code block with caption (remove bottom radius)
3126
- ".cm-draftly-code-block.cm-draftly-code-block-has-caption": {
3127
- borderBottomLeftRadius: "0",
3128
- borderBottomRightRadius: "0",
3129
- borderBottom: "none",
3130
- paddingBottom: "0.5rem !important"
3131
- }
3132
- },
3133
- dark: {
3134
- ".cm-draftly-code-inline": {
3135
- backgroundColor: "rgba(255, 255, 255, 0.1)"
3136
- },
3137
- ".cm-draftly-code-block-line": {
3138
- backgroundColor: "rgba(255, 255, 255, 0.05)"
3139
- },
3140
- ".cm-draftly-code-fence": {
3141
- color: "#8b949e"
3142
- },
3143
- ".cm-draftly-code-block": {
3144
- backgroundColor: "rgba(255, 255, 255, 0.05)"
3145
- },
3146
- ".cm-draftly-code-header": {
3147
- backgroundColor: "rgba(255, 255, 255, 0.08)"
3148
- },
3149
- ".cm-draftly-code-header-lang": {
3150
- color: "#8b949e"
3151
- },
3152
- ".cm-draftly-code-copy-btn": {
3153
- color: "#8b949e"
3154
- },
3155
- ".cm-draftly-code-copy-btn:hover": {
3156
- backgroundColor: "rgba(255, 255, 255, 0.1)"
3157
- },
3158
- ".cm-draftly-code-caption": {
3159
- backgroundColor: "rgba(255, 255, 255, 0.05)"
3160
- },
3161
- ".cm-draftly-code-line-numbered::before": {
3162
- color: "#8b949e"
3163
- },
3164
- ".cm-draftly-code-line-highlight": {
3165
- backgroundColor: "rgba(255, 220, 100, 0.15) !important",
3166
- borderLeft: "3px solid #d9a520 !important"
3167
- },
3168
- ".cm-draftly-code-text-highlight": {
3169
- backgroundColor: "rgba(255, 220, 100, 0.3)"
3170
- }
3171
- }
3172
- });
3173
4357
  var quoteMarkDecorations = {
3174
4358
  /** Decoration for the > marker */
3175
4359
  "quote-mark": Decoration.replace({}),
@@ -3337,6 +4521,103 @@ var theme12 = createTheme({
3337
4521
  }
3338
4522
  }
3339
4523
  });
4524
+ function shortcodeToEmoji(raw) {
4525
+ const rendered = emoji.emojify(raw);
4526
+ return rendered !== raw ? rendered : null;
4527
+ }
4528
+ var EmojiWidget = class extends WidgetType {
4529
+ constructor(rendered) {
4530
+ super();
4531
+ this.rendered = rendered;
4532
+ }
4533
+ eq(other) {
4534
+ return other.rendered === this.rendered;
4535
+ }
4536
+ toDOM() {
4537
+ const span = document.createElement("span");
4538
+ span.className = "cm-draftly-emoji";
4539
+ span.textContent = this.rendered;
4540
+ return span;
4541
+ }
4542
+ ignoreEvent() {
4543
+ return false;
4544
+ }
4545
+ };
4546
+ var emojiMarkDecorations = {
4547
+ "emoji-source": Decoration.mark({ class: "cm-draftly-emoji-source" })
4548
+ };
4549
+ var EmojiPlugin = class extends DecorationPlugin {
4550
+ name = "emoji";
4551
+ version = "1.0.0";
4552
+ decorationPriority = 20;
4553
+ requiredNodes = ["Emoji", "EmojiMark"];
4554
+ constructor() {
4555
+ super();
4556
+ }
4557
+ /**
4558
+ * Plugin theme
4559
+ */
4560
+ get theme() {
4561
+ return theme13;
4562
+ }
4563
+ /**
4564
+ * Build emoji decorations by iterating the syntax tree
4565
+ */
4566
+ buildDecorations(ctx) {
4567
+ const { view, decorations } = ctx;
4568
+ const tree = syntaxTree(view.state);
4569
+ tree.iterate({
4570
+ enter: (node) => {
4571
+ const { from, to, name } = node;
4572
+ if (name !== "Emoji") {
4573
+ return;
4574
+ }
4575
+ const raw = view.state.sliceDoc(from, to);
4576
+ const rendered = shortcodeToEmoji(raw);
4577
+ if (!rendered) {
4578
+ return;
4579
+ }
4580
+ const cursorInNode = ctx.selectionOverlapsRange(from, to);
4581
+ if (cursorInNode) {
4582
+ decorations.push(emojiMarkDecorations["emoji-source"].range(from, to));
4583
+ return;
4584
+ }
4585
+ decorations.push(
4586
+ Decoration.replace({
4587
+ widget: new EmojiWidget(rendered)
4588
+ }).range(from, to)
4589
+ );
4590
+ }
4591
+ });
4592
+ }
4593
+ renderToHTML(node, children, ctx) {
4594
+ if (node.name === "EmojiMark") {
4595
+ return "";
4596
+ }
4597
+ if (node.name !== "Emoji") {
4598
+ return null;
4599
+ }
4600
+ const raw = ctx.sliceDoc(node.from, node.to);
4601
+ const rendered = shortcodeToEmoji(raw);
4602
+ if (!rendered) {
4603
+ return `<span class="cm-draftly-emoji-source">${children}</span>`;
4604
+ }
4605
+ return `<span class="cm-draftly-emoji">${rendered}</span>`;
4606
+ }
4607
+ };
4608
+ var theme13 = createTheme({
4609
+ default: {
4610
+ ".cm-draftly-emoji": {
4611
+ fontFamily: '"Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Segoe UI Symbol", sans-serif',
4612
+ fontVariantEmoji: "emoji",
4613
+ lineHeight: "1.2"
4614
+ },
4615
+ ".cm-draftly-emoji-source": {
4616
+ fontFamily: "inherit",
4617
+ lineHeight: "inherit"
4618
+ }
4619
+ }
4620
+ });
3340
4621
 
3341
4622
  // src/plugins/index.ts
3342
4623
  var essentialPlugins = [
@@ -3345,16 +4626,18 @@ var essentialPlugins = [
3345
4626
  new InlinePlugin(),
3346
4627
  new LinkPlugin(),
3347
4628
  new ListPlugin(),
4629
+ new TablePlugin(),
3348
4630
  new HTMLPlugin(),
3349
4631
  new ImagePlugin(),
3350
4632
  new MathPlugin(),
3351
4633
  new MermaidPlugin(),
3352
4634
  new CodePlugin(),
3353
4635
  new QuotePlugin(),
3354
- new HRPlugin()
4636
+ new HRPlugin(),
4637
+ new EmojiPlugin()
3355
4638
  ];
3356
4639
  var allPlugins = [...essentialPlugins];
3357
4640
 
3358
- export { CodePlugin, HRPlugin, HTMLPlugin, HeadingPlugin, ImagePlugin, InlinePlugin, LinkPlugin, ListPlugin, MathPlugin, MermaidPlugin, ParagraphPlugin, QuotePlugin, allPlugins, essentialPlugins };
3359
- //# sourceMappingURL=chunk-N3WL3XPB.js.map
3360
- //# sourceMappingURL=chunk-N3WL3XPB.js.map
4641
+ export { CodePlugin, EmojiPlugin, HRPlugin, HTMLPlugin, HeadingPlugin, ImagePlugin, InlinePlugin, LinkPlugin, ListPlugin, MathPlugin, MermaidPlugin, ParagraphPlugin, QuotePlugin, TablePlugin, allPlugins, essentialPlugins };
4642
+ //# sourceMappingURL=chunk-L2XSK57Y.js.map
4643
+ //# sourceMappingURL=chunk-L2XSK57Y.js.map