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