draftly 0.1.0-alpha.1 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-2B3A3VSQ.cjs +3382 -0
- package/dist/chunk-2B3A3VSQ.cjs.map +1 -0
- package/dist/{chunk-ZDSZRRUY.cjs → chunk-72ZYRGRT.cjs} +48 -104
- package/dist/chunk-72ZYRGRT.cjs.map +1 -0
- package/dist/{chunk-MOG6E2LY.js → chunk-CG4M4TC7.js} +46 -103
- package/dist/chunk-CG4M4TC7.js.map +1 -0
- package/dist/{chunk-LCQALOEI.js → chunk-DFQYXFOP.js} +36 -3
- package/dist/chunk-DFQYXFOP.js.map +1 -0
- package/dist/{chunk-6LQ2VR4I.js → chunk-HPSMS2WB.js} +38 -22
- package/dist/chunk-HPSMS2WB.js.map +1 -0
- package/dist/{chunk-7Z3SRTPZ.cjs → chunk-KBQDZ5IW.cjs} +38 -22
- package/dist/chunk-KBQDZ5IW.cjs.map +1 -0
- package/dist/{chunk-RV2SYFA6.cjs → chunk-KDEDLC3D.cjs} +36 -2
- package/dist/chunk-KDEDLC3D.cjs.map +1 -0
- package/dist/chunk-N3WL3XPB.js +3360 -0
- package/dist/chunk-N3WL3XPB.js.map +1 -0
- package/dist/{draftly-Bxu_H4nw.d.ts → draftly-BLnx3uGX.d.cts} +12 -6
- package/dist/{draftly-Bxu_H4nw.d.cts → draftly-BLnx3uGX.d.ts} +12 -6
- package/dist/editor/index.cjs +20 -12
- package/dist/editor/index.d.cts +3 -2
- package/dist/editor/index.d.ts +3 -2
- package/dist/editor/index.js +2 -2
- package/dist/index.cjs +59 -27
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +4 -4
- package/dist/plugins/index.cjs +35 -11
- package/dist/plugins/index.d.cts +321 -3
- package/dist/plugins/index.d.ts +321 -3
- package/dist/plugins/index.js +3 -3
- package/dist/preview/index.cjs +7 -7
- package/dist/preview/index.d.cts +9 -4
- package/dist/preview/index.d.ts +9 -4
- package/dist/preview/index.js +2 -2
- package/package.json +2 -1
- package/src/editor/draftly.ts +9 -13
- package/src/editor/plugin.ts +4 -1
- package/src/editor/theme.ts +34 -1
- package/src/editor/utils.ts +49 -0
- package/src/editor/view-plugin.ts +6 -131
- package/src/plugins/code-plugin.ts +1119 -0
- package/src/plugins/heading-plugin.ts +23 -11
- package/src/plugins/hr-plugin.ts +102 -0
- package/src/plugins/image-plugin.ts +96 -2
- package/src/plugins/index.ts +57 -39
- package/src/plugins/inline-plugin.ts +125 -6
- package/src/plugins/link-plugin.ts +509 -0
- package/src/plugins/list-plugin.ts +116 -2
- package/src/plugins/math-plugin.ts +5 -1
- package/src/plugins/mermaid-plugin.ts +500 -0
- package/src/plugins/paragraph-plugin.ts +38 -0
- package/src/plugins/quote-plugin.ts +146 -0
- package/src/preview/context.ts +1 -1
- package/src/preview/css-generator.ts +3 -1
- package/src/preview/default-renderers.ts +0 -5
- package/src/preview/preview.ts +2 -2
- package/src/preview/renderer.ts +34 -12
- package/src/preview/types.ts +1 -1
- package/dist/chunk-6LQ2VR4I.js.map +0 -1
- package/dist/chunk-7Z3SRTPZ.cjs.map +0 -1
- package/dist/chunk-GA6NYY77.cjs +0 -1400
- package/dist/chunk-GA6NYY77.cjs.map +0 -1
- package/dist/chunk-LCQALOEI.js.map +0 -1
- package/dist/chunk-MOG6E2LY.js.map +0 -1
- package/dist/chunk-RV2SYFA6.cjs.map +0 -1
- package/dist/chunk-TKZNKWGF.js +0 -1385
- package/dist/chunk-TKZNKWGF.js.map +0 -1
- package/dist/chunk-ZDSZRRUY.cjs.map +0 -1
|
@@ -0,0 +1,3382 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunk72ZYRGRT_cjs = require('./chunk-72ZYRGRT.cjs');
|
|
4
|
+
var chunkKDEDLC3D_cjs = require('./chunk-KDEDLC3D.cjs');
|
|
5
|
+
var view = require('@codemirror/view');
|
|
6
|
+
var language = require('@codemirror/language');
|
|
7
|
+
var highlight = require('@lezer/highlight');
|
|
8
|
+
var DOMPurify = require('dompurify');
|
|
9
|
+
var katex = require('katex');
|
|
10
|
+
var katexCss = require('katex/dist/katex.min.css?raw');
|
|
11
|
+
var mermaid = require('mermaid');
|
|
12
|
+
var languageData = require('@codemirror/language-data');
|
|
13
|
+
|
|
14
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
15
|
+
|
|
16
|
+
var DOMPurify__default = /*#__PURE__*/_interopDefault(DOMPurify);
|
|
17
|
+
var katex__default = /*#__PURE__*/_interopDefault(katex);
|
|
18
|
+
var katexCss__default = /*#__PURE__*/_interopDefault(katexCss);
|
|
19
|
+
var mermaid__default = /*#__PURE__*/_interopDefault(mermaid);
|
|
20
|
+
|
|
21
|
+
// src/plugins/paragraph-plugin.ts
|
|
22
|
+
var ParagraphPlugin = class extends chunk72ZYRGRT_cjs.DraftlyPlugin {
|
|
23
|
+
name = "paragraph";
|
|
24
|
+
version = "1.0.0";
|
|
25
|
+
requiredNodes = ["Paragraph"];
|
|
26
|
+
/**
|
|
27
|
+
* Plugin theme for preview styling
|
|
28
|
+
*/
|
|
29
|
+
get theme() {
|
|
30
|
+
return theme;
|
|
31
|
+
}
|
|
32
|
+
renderToHTML(node, children) {
|
|
33
|
+
if (node.name !== "Paragraph") {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
return `<p class="cm-draftly-paragraph">${children}</p>`;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
var theme = chunkKDEDLC3D_cjs.createTheme({
|
|
40
|
+
default: {
|
|
41
|
+
".cm-draftly-paragraph": {
|
|
42
|
+
paddingTop: "0.5em",
|
|
43
|
+
paddingBottom: "0.5em"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
var HEADING_TYPES = ["ATXHeading1", "ATXHeading2", "ATXHeading3", "ATXHeading4", "ATXHeading5", "ATXHeading6"];
|
|
48
|
+
var headingMarkDecorations = {
|
|
49
|
+
"heading-1": view.Decoration.mark({ class: "cm-draftly-h1" }),
|
|
50
|
+
"heading-2": view.Decoration.mark({ class: "cm-draftly-h2" }),
|
|
51
|
+
"heading-3": view.Decoration.mark({ class: "cm-draftly-h3" }),
|
|
52
|
+
"heading-4": view.Decoration.mark({ class: "cm-draftly-h4" }),
|
|
53
|
+
"heading-5": view.Decoration.mark({ class: "cm-draftly-h5" }),
|
|
54
|
+
"heading-6": view.Decoration.mark({ class: "cm-draftly-h6" }),
|
|
55
|
+
"header-mark-class": view.Decoration.mark({ class: "cm-draftly-header-mark" }),
|
|
56
|
+
"heading-mark": view.Decoration.replace({})
|
|
57
|
+
};
|
|
58
|
+
var headingLineDecorations = {
|
|
59
|
+
"heading-1": view.Decoration.line({ class: "cm-draftly-line-h1" }),
|
|
60
|
+
"heading-2": view.Decoration.line({ class: "cm-draftly-line-h2" }),
|
|
61
|
+
"heading-3": view.Decoration.line({ class: "cm-draftly-line-h3" }),
|
|
62
|
+
"heading-4": view.Decoration.line({ class: "cm-draftly-line-h4" }),
|
|
63
|
+
"heading-5": view.Decoration.line({ class: "cm-draftly-line-h5" }),
|
|
64
|
+
"heading-6": view.Decoration.line({ class: "cm-draftly-line-h6" })
|
|
65
|
+
};
|
|
66
|
+
var HeadingPlugin = class extends chunk72ZYRGRT_cjs.DecorationPlugin {
|
|
67
|
+
name = "heading";
|
|
68
|
+
version = "1.0.0";
|
|
69
|
+
decorationPriority = 10;
|
|
70
|
+
requiredNodes = [
|
|
71
|
+
"ATXHeading1",
|
|
72
|
+
"ATXHeading2",
|
|
73
|
+
"ATXHeading3",
|
|
74
|
+
"ATXHeading4",
|
|
75
|
+
"ATXHeading5",
|
|
76
|
+
"ATXHeading6",
|
|
77
|
+
"HeaderMark"
|
|
78
|
+
];
|
|
79
|
+
/**
|
|
80
|
+
* Constructor - calls super constructor
|
|
81
|
+
*/
|
|
82
|
+
constructor() {
|
|
83
|
+
super();
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Plugin theme
|
|
87
|
+
*/
|
|
88
|
+
get theme() {
|
|
89
|
+
return theme2;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Build heading decorations by iterating the syntax tree
|
|
93
|
+
*/
|
|
94
|
+
buildDecorations(ctx) {
|
|
95
|
+
const { view, decorations } = ctx;
|
|
96
|
+
const tree = language.syntaxTree(view.state);
|
|
97
|
+
tree.iterate({
|
|
98
|
+
enter: (node) => {
|
|
99
|
+
const { from, to, name } = node;
|
|
100
|
+
if (!HEADING_TYPES.includes(name)) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const level = parseInt(name.slice(-1), 10);
|
|
104
|
+
const headingClass = `heading-${level}`;
|
|
105
|
+
const lineClass = `heading-${level}`;
|
|
106
|
+
const line = view.state.doc.lineAt(from);
|
|
107
|
+
decorations.push(headingLineDecorations[lineClass].range(line.from));
|
|
108
|
+
decorations.push(headingMarkDecorations[headingClass].range(from, to));
|
|
109
|
+
const headingMark = node.node.getChild("HeaderMark");
|
|
110
|
+
if (headingMark) {
|
|
111
|
+
const markEnd = Math.min(headingMark.to + 1, line.to);
|
|
112
|
+
const cursorInNode = ctx.selectionOverlapsRange(from, to);
|
|
113
|
+
if (!cursorInNode) {
|
|
114
|
+
decorations.push(headingMarkDecorations["heading-mark"].range(headingMark.from, markEnd));
|
|
115
|
+
} else {
|
|
116
|
+
decorations.push(headingMarkDecorations["header-mark-class"].range(headingMark.from, markEnd));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
renderToHTML(node, children) {
|
|
123
|
+
if (node.name === "HeaderMark") {
|
|
124
|
+
return "";
|
|
125
|
+
}
|
|
126
|
+
if (!HEADING_TYPES.includes(node.name)) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
const level = parseInt(node.name.slice(-1), 10);
|
|
130
|
+
const lineClass = headingLineDecorations[`heading-${level}`].spec.class;
|
|
131
|
+
const headingClass = headingMarkDecorations[`heading-${level}`].spec.class;
|
|
132
|
+
return `<div class="${lineClass}">
|
|
133
|
+
<h${level} class="${headingClass}">${children}</h${level}>
|
|
134
|
+
</div>
|
|
135
|
+
`;
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
var theme2 = chunkKDEDLC3D_cjs.createTheme({
|
|
139
|
+
default: {
|
|
140
|
+
".cm-draftly-h1": {
|
|
141
|
+
fontSize: "2em",
|
|
142
|
+
fontWeight: "bold",
|
|
143
|
+
fontFamily: "sans-serif",
|
|
144
|
+
textDecoration: "none"
|
|
145
|
+
},
|
|
146
|
+
".cm-draftly-h2": {
|
|
147
|
+
fontSize: "1.75em",
|
|
148
|
+
fontWeight: "bold",
|
|
149
|
+
fontFamily: "sans-serif",
|
|
150
|
+
textDecoration: "none"
|
|
151
|
+
},
|
|
152
|
+
".cm-draftly-h3": {
|
|
153
|
+
fontSize: "1.5em",
|
|
154
|
+
fontWeight: "bold",
|
|
155
|
+
fontFamily: "sans-serif",
|
|
156
|
+
textDecoration: "none"
|
|
157
|
+
},
|
|
158
|
+
".cm-draftly-h4": {
|
|
159
|
+
fontSize: "1.25em",
|
|
160
|
+
fontWeight: "bold",
|
|
161
|
+
fontFamily: "sans-serif",
|
|
162
|
+
textDecoration: "none"
|
|
163
|
+
},
|
|
164
|
+
".cm-draftly-h5": {
|
|
165
|
+
fontSize: "1em",
|
|
166
|
+
fontWeight: "bold",
|
|
167
|
+
fontFamily: "sans-serif",
|
|
168
|
+
textDecoration: "none"
|
|
169
|
+
},
|
|
170
|
+
".cm-draftly-h6": {
|
|
171
|
+
fontSize: "0.75em",
|
|
172
|
+
fontWeight: "bold",
|
|
173
|
+
fontFamily: "sans-serif",
|
|
174
|
+
textDecoration: "none"
|
|
175
|
+
},
|
|
176
|
+
// Heading line styles
|
|
177
|
+
".cm-draftly-line-h1": {
|
|
178
|
+
paddingTop: "1.5em",
|
|
179
|
+
paddingBottom: "0.5em"
|
|
180
|
+
},
|
|
181
|
+
".cm-draftly-line-h2": {
|
|
182
|
+
paddingTop: "1.25em",
|
|
183
|
+
paddingBottom: "0.5em"
|
|
184
|
+
},
|
|
185
|
+
".cm-draftly-line-h3, .cm-draftly-line-h4, .cm-draftly-line-h5, .cm-draftly-line-h6": {
|
|
186
|
+
paddingTop: "1em",
|
|
187
|
+
paddingBottom: "0.5em"
|
|
188
|
+
},
|
|
189
|
+
".cm-draftly-header-mark": {
|
|
190
|
+
opacity: 0.5
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
var INLINE_TYPES = {
|
|
195
|
+
Emphasis: "emphasis",
|
|
196
|
+
StrongEmphasis: "strong",
|
|
197
|
+
Strikethrough: "strikethrough",
|
|
198
|
+
Subscript: "subscript",
|
|
199
|
+
Superscript: "superscript",
|
|
200
|
+
Highlight: "highlight"
|
|
201
|
+
};
|
|
202
|
+
var inlineMarkDecorations = {
|
|
203
|
+
emphasis: view.Decoration.mark({ class: "cm-draftly-emphasis" }),
|
|
204
|
+
strong: view.Decoration.mark({ class: "cm-draftly-strong" }),
|
|
205
|
+
strikethrough: view.Decoration.mark({ class: "cm-draftly-strikethrough" }),
|
|
206
|
+
subscript: view.Decoration.mark({ class: "cm-draftly-subscript" }),
|
|
207
|
+
superscript: view.Decoration.mark({ class: "cm-draftly-superscript" }),
|
|
208
|
+
highlight: view.Decoration.mark({ class: "cm-draftly-highlight" }),
|
|
209
|
+
// Markers (* _ ~~ ^ ~ ==)
|
|
210
|
+
"inline-mark": view.Decoration.replace({})
|
|
211
|
+
};
|
|
212
|
+
var EQUALS = 61;
|
|
213
|
+
var Punctuation = /[!"#$%&'()*+,\-./:;<=>?@\[\\\]^_`{|}~\xA1\u2010-\u2027]/;
|
|
214
|
+
try {
|
|
215
|
+
Punctuation = new RegExp("[\\p{S}|\\p{P}]", "u");
|
|
216
|
+
} catch {
|
|
217
|
+
}
|
|
218
|
+
var HighlightDelim = { resolve: "Highlight", mark: "HighlightMark" };
|
|
219
|
+
var highlightParser = {
|
|
220
|
+
name: "Highlight",
|
|
221
|
+
parse(cx, next, pos) {
|
|
222
|
+
if (next !== EQUALS || cx.char(pos + 1) !== EQUALS) return -1;
|
|
223
|
+
if (cx.char(pos + 2) === EQUALS) return -1;
|
|
224
|
+
const before = cx.slice(pos - 1, pos);
|
|
225
|
+
const after = cx.slice(pos + 2, pos + 3);
|
|
226
|
+
const sBefore = /\s|^$/.test(before), sAfter = /\s|^$/.test(after);
|
|
227
|
+
const pBefore = Punctuation.test(before), pAfter = Punctuation.test(after);
|
|
228
|
+
return cx.addDelimiter(
|
|
229
|
+
HighlightDelim,
|
|
230
|
+
pos,
|
|
231
|
+
pos + 2,
|
|
232
|
+
!sAfter && (!pAfter || sBefore || pBefore),
|
|
233
|
+
!sBefore && (!pBefore || sAfter || pAfter)
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
var InlinePlugin = class extends chunk72ZYRGRT_cjs.DecorationPlugin {
|
|
238
|
+
name = "inline";
|
|
239
|
+
version = "1.0.0";
|
|
240
|
+
decorationPriority = 20;
|
|
241
|
+
requiredNodes = [
|
|
242
|
+
"Emphasis",
|
|
243
|
+
"StrongEmphasis",
|
|
244
|
+
"Strikethrough",
|
|
245
|
+
"Subscript",
|
|
246
|
+
"Superscript",
|
|
247
|
+
"Highlight",
|
|
248
|
+
"EmphasisMark",
|
|
249
|
+
"StrikethroughMark",
|
|
250
|
+
"SubscriptMark",
|
|
251
|
+
"SuperscriptMark",
|
|
252
|
+
"HighlightMark"
|
|
253
|
+
];
|
|
254
|
+
marks = [];
|
|
255
|
+
constructor() {
|
|
256
|
+
super();
|
|
257
|
+
for (const mark of Object.keys(INLINE_TYPES)) {
|
|
258
|
+
this.marks.push(...this.getMarkerNames(mark));
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Plugin theme
|
|
263
|
+
*/
|
|
264
|
+
get theme() {
|
|
265
|
+
return theme3;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Keyboard shortcuts for inline formatting
|
|
269
|
+
*/
|
|
270
|
+
getKeymap() {
|
|
271
|
+
return [
|
|
272
|
+
{
|
|
273
|
+
key: "Mod-b",
|
|
274
|
+
run: chunkKDEDLC3D_cjs.toggleMarkdownStyle("**"),
|
|
275
|
+
preventDefault: true
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
key: "Mod-i",
|
|
279
|
+
run: chunkKDEDLC3D_cjs.toggleMarkdownStyle("*"),
|
|
280
|
+
preventDefault: true
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
key: "Mod-Shift-s",
|
|
284
|
+
run: chunkKDEDLC3D_cjs.toggleMarkdownStyle("~~"),
|
|
285
|
+
preventDefault: true
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
key: "Mod-,",
|
|
289
|
+
run: chunkKDEDLC3D_cjs.toggleMarkdownStyle("~"),
|
|
290
|
+
preventDefault: true
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
key: "Mod-.",
|
|
294
|
+
run: chunkKDEDLC3D_cjs.toggleMarkdownStyle("^"),
|
|
295
|
+
preventDefault: true
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
key: "Mod-Shift-h",
|
|
299
|
+
run: chunkKDEDLC3D_cjs.toggleMarkdownStyle("=="),
|
|
300
|
+
preventDefault: true
|
|
301
|
+
}
|
|
302
|
+
];
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Return markdown parser extensions for highlight syntax (==text==)
|
|
306
|
+
*/
|
|
307
|
+
getMarkdownConfig() {
|
|
308
|
+
return {
|
|
309
|
+
defineNodes: [
|
|
310
|
+
{ name: "Highlight", style: highlight.tags.emphasis },
|
|
311
|
+
{ name: "HighlightMark", style: highlight.tags.processingInstruction }
|
|
312
|
+
],
|
|
313
|
+
parseInline: [highlightParser]
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Build inline decorations by iterating the syntax tree
|
|
318
|
+
*/
|
|
319
|
+
buildDecorations(ctx) {
|
|
320
|
+
const { view, decorations } = ctx;
|
|
321
|
+
const tree = language.syntaxTree(view.state);
|
|
322
|
+
tree.iterate({
|
|
323
|
+
enter: (node) => {
|
|
324
|
+
const { from, to, name } = node;
|
|
325
|
+
const inlineType = INLINE_TYPES[name];
|
|
326
|
+
if (!inlineType) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
decorations.push(inlineMarkDecorations[inlineType].range(from, to));
|
|
330
|
+
const cursorInNode = ctx.selectionOverlapsRange(from, to);
|
|
331
|
+
if (!cursorInNode) {
|
|
332
|
+
const markerNames = this.getMarkerNames(name);
|
|
333
|
+
for (const markerName of markerNames) {
|
|
334
|
+
const marks = node.node.getChildren(markerName);
|
|
335
|
+
for (const mark of marks) {
|
|
336
|
+
decorations.push(inlineMarkDecorations["inline-mark"].range(mark.from, mark.to));
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Get the marker node names for a given inline type
|
|
345
|
+
*/
|
|
346
|
+
getMarkerNames(nodeType) {
|
|
347
|
+
switch (nodeType) {
|
|
348
|
+
case "Emphasis":
|
|
349
|
+
case "StrongEmphasis":
|
|
350
|
+
return ["EmphasisMark"];
|
|
351
|
+
case "Strikethrough":
|
|
352
|
+
return ["StrikethroughMark"];
|
|
353
|
+
case "Subscript":
|
|
354
|
+
return ["SubscriptMark"];
|
|
355
|
+
case "Superscript":
|
|
356
|
+
return ["SuperscriptMark"];
|
|
357
|
+
case "Highlight":
|
|
358
|
+
return ["HighlightMark"];
|
|
359
|
+
default:
|
|
360
|
+
return [];
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
renderToHTML(node, children) {
|
|
364
|
+
if (this.marks.includes(node.name)) {
|
|
365
|
+
return "";
|
|
366
|
+
}
|
|
367
|
+
const inlineType = INLINE_TYPES[node.name];
|
|
368
|
+
if (!inlineType) {
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
const className = inlineMarkDecorations[inlineType].spec.class;
|
|
372
|
+
return `<span class="${className}">${children}</span>`;
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
var theme3 = chunkKDEDLC3D_cjs.createTheme({
|
|
376
|
+
default: {
|
|
377
|
+
// Emphasis (italic)
|
|
378
|
+
".cm-draftly-emphasis": {
|
|
379
|
+
fontStyle: "italic"
|
|
380
|
+
},
|
|
381
|
+
// Strong (bold)
|
|
382
|
+
".cm-draftly-strong": {
|
|
383
|
+
fontWeight: "bold"
|
|
384
|
+
},
|
|
385
|
+
// Strikethrough
|
|
386
|
+
".cm-draftly-strikethrough": {
|
|
387
|
+
textDecoration: "line-through",
|
|
388
|
+
opacity: "0.7"
|
|
389
|
+
},
|
|
390
|
+
// Subscript
|
|
391
|
+
".cm-draftly-subscript": {
|
|
392
|
+
fontSize: "0.75em",
|
|
393
|
+
verticalAlign: "sub"
|
|
394
|
+
},
|
|
395
|
+
// Superscript
|
|
396
|
+
".cm-draftly-superscript": {
|
|
397
|
+
fontSize: "0.75em",
|
|
398
|
+
verticalAlign: "super"
|
|
399
|
+
},
|
|
400
|
+
// Highlight
|
|
401
|
+
".cm-draftly-highlight": {
|
|
402
|
+
backgroundColor: "rgba(255, 213, 0, 0.35)",
|
|
403
|
+
borderRadius: "2px",
|
|
404
|
+
padding: "1px 2px"
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
var linkMarkDecorations = {
|
|
409
|
+
"link-text": view.Decoration.mark({ class: "cm-draftly-link-text" }),
|
|
410
|
+
"link-marker": view.Decoration.mark({ class: "cm-draftly-link-marker" }),
|
|
411
|
+
"link-url": view.Decoration.mark({ class: "cm-draftly-link-url" }),
|
|
412
|
+
"link-hidden": view.Decoration.mark({ class: "cm-draftly-link-hidden" })
|
|
413
|
+
};
|
|
414
|
+
function parseLinkMarkdown(content) {
|
|
415
|
+
const match = content.match(/^\[([^\]]*)\]\(([^"\s)]+)(?:\s+"([^"]*)")?\s*\)$/);
|
|
416
|
+
if (!match) return null;
|
|
417
|
+
const result = {
|
|
418
|
+
text: match[1] || "",
|
|
419
|
+
url: match[2]
|
|
420
|
+
};
|
|
421
|
+
if (match[3] !== void 0) {
|
|
422
|
+
result.title = match[3];
|
|
423
|
+
}
|
|
424
|
+
return result;
|
|
425
|
+
}
|
|
426
|
+
var LinkTooltipWidget = class extends view.WidgetType {
|
|
427
|
+
constructor(url, from, to) {
|
|
428
|
+
super();
|
|
429
|
+
this.url = url;
|
|
430
|
+
this.from = from;
|
|
431
|
+
this.to = to;
|
|
432
|
+
}
|
|
433
|
+
eq(other) {
|
|
434
|
+
return other.url === this.url && other.from === this.from && other.to === this.to;
|
|
435
|
+
}
|
|
436
|
+
toDOM(view) {
|
|
437
|
+
const wrapper = document.createElement("span");
|
|
438
|
+
wrapper.className = "cm-draftly-link-wrapper";
|
|
439
|
+
wrapper.style.cursor = "pointer";
|
|
440
|
+
const tooltip = document.createElement("span");
|
|
441
|
+
tooltip.className = "cm-draftly-link-tooltip";
|
|
442
|
+
tooltip.textContent = this.url;
|
|
443
|
+
wrapper.appendChild(tooltip);
|
|
444
|
+
wrapper.addEventListener("mouseenter", () => {
|
|
445
|
+
tooltip.classList.add("cm-draftly-link-tooltip-visible");
|
|
446
|
+
});
|
|
447
|
+
wrapper.addEventListener("mouseleave", () => {
|
|
448
|
+
tooltip.classList.remove("cm-draftly-link-tooltip-visible");
|
|
449
|
+
});
|
|
450
|
+
wrapper.addEventListener("click", (e) => {
|
|
451
|
+
if (e.ctrlKey || e.metaKey) {
|
|
452
|
+
e.preventDefault();
|
|
453
|
+
e.stopPropagation();
|
|
454
|
+
window.open(this.url, "_blank", "noopener,noreferrer");
|
|
455
|
+
} else {
|
|
456
|
+
e.preventDefault();
|
|
457
|
+
e.stopPropagation();
|
|
458
|
+
view.dispatch({
|
|
459
|
+
selection: { anchor: this.from, head: this.to },
|
|
460
|
+
scrollIntoView: true
|
|
461
|
+
});
|
|
462
|
+
view.focus();
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
return wrapper;
|
|
466
|
+
}
|
|
467
|
+
ignoreEvent(event) {
|
|
468
|
+
return event.type !== "click" && event.type !== "mouseenter" && event.type !== "mouseleave";
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
var LinkPlugin = class extends chunk72ZYRGRT_cjs.DecorationPlugin {
|
|
472
|
+
name = "link";
|
|
473
|
+
version = "1.0.0";
|
|
474
|
+
decorationPriority = 22;
|
|
475
|
+
requiredNodes = ["Link"];
|
|
476
|
+
constructor() {
|
|
477
|
+
super();
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Plugin theme
|
|
481
|
+
*/
|
|
482
|
+
get theme() {
|
|
483
|
+
return theme4;
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Keyboard shortcuts for link formatting
|
|
487
|
+
*/
|
|
488
|
+
getKeymap() {
|
|
489
|
+
return [
|
|
490
|
+
{
|
|
491
|
+
key: "Mod-k",
|
|
492
|
+
run: (view) => this.toggleLink(view),
|
|
493
|
+
preventDefault: true
|
|
494
|
+
}
|
|
495
|
+
];
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* URL regex pattern
|
|
499
|
+
*/
|
|
500
|
+
urlPattern = /^(https?:\/\/|www\.)[^\s]+$/i;
|
|
501
|
+
/**
|
|
502
|
+
* Toggle link on selection
|
|
503
|
+
* - If text is selected and is a URL: [](url) with cursor in brackets
|
|
504
|
+
* - If text is selected (not URL): [text]() with cursor in parentheses
|
|
505
|
+
* - If nothing selected: []() with cursor in brackets
|
|
506
|
+
* - If already a link: remove syntax, leave plain text
|
|
507
|
+
*/
|
|
508
|
+
toggleLink(view) {
|
|
509
|
+
const { state } = view;
|
|
510
|
+
const { from, to, empty } = state.selection.main;
|
|
511
|
+
const selectedText = state.sliceDoc(from, to);
|
|
512
|
+
const linkMatch = selectedText.match(/^\[([^\]]*)\]\(([^)]*)\)$/);
|
|
513
|
+
if (linkMatch) {
|
|
514
|
+
const linkText = linkMatch[1] || "";
|
|
515
|
+
view.dispatch({
|
|
516
|
+
changes: { from, to, insert: linkText },
|
|
517
|
+
selection: { anchor: from, head: from + linkText.length }
|
|
518
|
+
});
|
|
519
|
+
return true;
|
|
520
|
+
}
|
|
521
|
+
const lineStart = state.doc.lineAt(from).from;
|
|
522
|
+
const lineEnd = state.doc.lineAt(to).to;
|
|
523
|
+
const lineText = state.sliceDoc(lineStart, lineEnd);
|
|
524
|
+
const linkRegex = /\[([^\]]*)\]\(([^)]*)\)/g;
|
|
525
|
+
let match;
|
|
526
|
+
while ((match = linkRegex.exec(lineText)) !== null) {
|
|
527
|
+
const matchFrom = lineStart + match.index;
|
|
528
|
+
const matchTo = matchFrom + match[0].length;
|
|
529
|
+
if (from >= matchFrom && to <= matchTo) {
|
|
530
|
+
const linkText = match[1] || "";
|
|
531
|
+
view.dispatch({
|
|
532
|
+
changes: { from: matchFrom, to: matchTo, insert: linkText },
|
|
533
|
+
selection: { anchor: matchFrom, head: matchFrom + linkText.length }
|
|
534
|
+
});
|
|
535
|
+
return true;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
if (empty) {
|
|
539
|
+
view.dispatch({
|
|
540
|
+
changes: { from, insert: "[]()" },
|
|
541
|
+
selection: { anchor: from + 1 }
|
|
542
|
+
});
|
|
543
|
+
} else if (this.urlPattern.test(selectedText)) {
|
|
544
|
+
const newText = `[](${selectedText})`;
|
|
545
|
+
view.dispatch({
|
|
546
|
+
changes: { from, to, insert: newText },
|
|
547
|
+
selection: { anchor: from + 1 }
|
|
548
|
+
});
|
|
549
|
+
} else {
|
|
550
|
+
const newText = `[${selectedText}]()`;
|
|
551
|
+
view.dispatch({
|
|
552
|
+
changes: { from, to, insert: newText },
|
|
553
|
+
selection: { anchor: from + selectedText.length + 3 }
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
return true;
|
|
557
|
+
}
|
|
558
|
+
buildDecorations(ctx) {
|
|
559
|
+
const { view: view$1, decorations } = ctx;
|
|
560
|
+
const tree = language.syntaxTree(view$1.state);
|
|
561
|
+
tree.iterate({
|
|
562
|
+
enter: (node) => {
|
|
563
|
+
const { from, to, name } = node;
|
|
564
|
+
if (name === "Link") {
|
|
565
|
+
const content = view$1.state.sliceDoc(from, to);
|
|
566
|
+
const parsed = parseLinkMarkdown(content);
|
|
567
|
+
if (!parsed) return;
|
|
568
|
+
const cursorInRange = ctx.selectionOverlapsRange(from, to);
|
|
569
|
+
if (cursorInRange) {
|
|
570
|
+
this.decorateRawLink(node.node, decorations, view$1);
|
|
571
|
+
} else {
|
|
572
|
+
decorations.push(linkMarkDecorations["link-hidden"].range(from, to));
|
|
573
|
+
decorations.push(
|
|
574
|
+
view.Decoration.widget({
|
|
575
|
+
widget: new LinkTooltipWidget(parsed.url, from, to),
|
|
576
|
+
side: 1
|
|
577
|
+
}).range(to)
|
|
578
|
+
);
|
|
579
|
+
decorations.push(
|
|
580
|
+
view.Decoration.replace({
|
|
581
|
+
widget: new LinkTextWidget(parsed.text, parsed.url, from, to, parsed.title)
|
|
582
|
+
}).range(from, to)
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Decorate raw link markdown when cursor is in range
|
|
591
|
+
*/
|
|
592
|
+
decorateRawLink(node, decorations, view) {
|
|
593
|
+
const content = view.state.sliceDoc(node.from, node.to);
|
|
594
|
+
decorations.push(linkMarkDecorations["link-marker"].range(node.from, node.from + 1));
|
|
595
|
+
const bracketParen = content.indexOf("](");
|
|
596
|
+
if (bracketParen !== -1) {
|
|
597
|
+
if (bracketParen > 1) {
|
|
598
|
+
decorations.push(linkMarkDecorations["link-text"].range(node.from + 1, node.from + bracketParen));
|
|
599
|
+
}
|
|
600
|
+
decorations.push(
|
|
601
|
+
linkMarkDecorations["link-marker"].range(node.from + bracketParen, node.from + bracketParen + 2)
|
|
602
|
+
);
|
|
603
|
+
const urlChild = node.getChild("URL");
|
|
604
|
+
if (urlChild) {
|
|
605
|
+
decorations.push(linkMarkDecorations["link-url"].range(urlChild.from, urlChild.to));
|
|
606
|
+
}
|
|
607
|
+
decorations.push(linkMarkDecorations["link-marker"].range(node.to - 1, node.to));
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Render link to HTML for preview mode
|
|
612
|
+
*/
|
|
613
|
+
renderToHTML(node, _children, ctx) {
|
|
614
|
+
if (node.name !== "Link") return null;
|
|
615
|
+
const content = ctx.sliceDoc(node.from, node.to);
|
|
616
|
+
const parsed = parseLinkMarkdown(content);
|
|
617
|
+
if (!parsed) return null;
|
|
618
|
+
const textContent = ctx.sanitize(parsed.text);
|
|
619
|
+
const urlAttr = ctx.sanitize(parsed.url);
|
|
620
|
+
const titleAttr = parsed.title ? ` title="${ctx.sanitize(parsed.title)}"` : "";
|
|
621
|
+
return `<a class="cm-draftly-link" href="${urlAttr}"${titleAttr} target="_blank" rel="noopener noreferrer">${textContent}</a>`;
|
|
622
|
+
}
|
|
623
|
+
};
|
|
624
|
+
var LinkTextWidget = class extends view.WidgetType {
|
|
625
|
+
constructor(text, url, from, to, title) {
|
|
626
|
+
super();
|
|
627
|
+
this.text = text;
|
|
628
|
+
this.url = url;
|
|
629
|
+
this.from = from;
|
|
630
|
+
this.to = to;
|
|
631
|
+
this.title = title;
|
|
632
|
+
}
|
|
633
|
+
eq(other) {
|
|
634
|
+
return other.text === this.text && other.url === this.url && other.from === this.from && other.to === this.to && other.title === this.title;
|
|
635
|
+
}
|
|
636
|
+
toDOM(view) {
|
|
637
|
+
const span = document.createElement("span");
|
|
638
|
+
span.className = "cm-draftly-link-styled";
|
|
639
|
+
span.textContent = this.text;
|
|
640
|
+
span.style.cursor = "pointer";
|
|
641
|
+
if (this.title) {
|
|
642
|
+
span.title = this.title;
|
|
643
|
+
}
|
|
644
|
+
const tooltip = document.createElement("span");
|
|
645
|
+
tooltip.className = "cm-draftly-link-tooltip";
|
|
646
|
+
tooltip.textContent = this.url;
|
|
647
|
+
span.appendChild(tooltip);
|
|
648
|
+
span.addEventListener("mouseenter", () => {
|
|
649
|
+
tooltip.classList.add("cm-draftly-link-tooltip-visible");
|
|
650
|
+
});
|
|
651
|
+
span.addEventListener("mouseleave", () => {
|
|
652
|
+
tooltip.classList.remove("cm-draftly-link-tooltip-visible");
|
|
653
|
+
});
|
|
654
|
+
span.addEventListener("click", (e) => {
|
|
655
|
+
if (e.ctrlKey || e.metaKey) {
|
|
656
|
+
e.preventDefault();
|
|
657
|
+
e.stopPropagation();
|
|
658
|
+
window.open(this.url, "_blank", "noopener,noreferrer");
|
|
659
|
+
} else {
|
|
660
|
+
e.preventDefault();
|
|
661
|
+
e.stopPropagation();
|
|
662
|
+
view.dispatch({
|
|
663
|
+
selection: { anchor: this.from, head: this.to },
|
|
664
|
+
scrollIntoView: true
|
|
665
|
+
});
|
|
666
|
+
view.focus();
|
|
667
|
+
}
|
|
668
|
+
});
|
|
669
|
+
return span;
|
|
670
|
+
}
|
|
671
|
+
ignoreEvent(event) {
|
|
672
|
+
return event.type !== "click" && event.type !== "mouseenter" && event.type !== "mouseleave";
|
|
673
|
+
}
|
|
674
|
+
};
|
|
675
|
+
var theme4 = chunkKDEDLC3D_cjs.createTheme({
|
|
676
|
+
default: {
|
|
677
|
+
// Link text
|
|
678
|
+
".cm-draftly-link-text": {
|
|
679
|
+
color: "#0366d6"
|
|
680
|
+
},
|
|
681
|
+
// Link markers ([ ] ( ))
|
|
682
|
+
".cm-draftly-link-marker": {
|
|
683
|
+
color: "#6a737d",
|
|
684
|
+
fontFamily: "var(--font-jetbrains-mono, monospace)"
|
|
685
|
+
},
|
|
686
|
+
// URL in raw markdown
|
|
687
|
+
".cm-draftly-link-url": {
|
|
688
|
+
color: "#6a737d",
|
|
689
|
+
fontStyle: "italic"
|
|
690
|
+
},
|
|
691
|
+
// Hidden markdown syntax
|
|
692
|
+
".cm-draftly-link-hidden": {
|
|
693
|
+
display: "none"
|
|
694
|
+
},
|
|
695
|
+
// Styled link when cursor is not in range
|
|
696
|
+
".cm-draftly-link-styled": {
|
|
697
|
+
color: "#0366d6",
|
|
698
|
+
textDecoration: "underline",
|
|
699
|
+
position: "relative",
|
|
700
|
+
cursor: "pointer"
|
|
701
|
+
},
|
|
702
|
+
".cm-draftly-link-styled:hover": {
|
|
703
|
+
color: "#0056b3"
|
|
704
|
+
},
|
|
705
|
+
// Preview link styling
|
|
706
|
+
".cm-draftly-link": {
|
|
707
|
+
color: "#0366d6",
|
|
708
|
+
textDecoration: "underline"
|
|
709
|
+
},
|
|
710
|
+
".cm-draftly-link:hover": {
|
|
711
|
+
color: "#0056b3"
|
|
712
|
+
},
|
|
713
|
+
// Tooltip styling
|
|
714
|
+
".cm-draftly-link-tooltip": {
|
|
715
|
+
display: "none",
|
|
716
|
+
position: "absolute",
|
|
717
|
+
bottom: "100%",
|
|
718
|
+
left: "50%",
|
|
719
|
+
transform: "translateX(-50%)",
|
|
720
|
+
backgroundColor: "#24292e",
|
|
721
|
+
color: "#ffffff",
|
|
722
|
+
padding: "4px 8px",
|
|
723
|
+
borderRadius: "4px",
|
|
724
|
+
fontSize: "12px",
|
|
725
|
+
whiteSpace: "nowrap",
|
|
726
|
+
zIndex: "1000",
|
|
727
|
+
pointerEvents: "none",
|
|
728
|
+
marginBottom: "4px",
|
|
729
|
+
maxWidth: "300px",
|
|
730
|
+
overflow: "hidden",
|
|
731
|
+
textOverflow: "ellipsis"
|
|
732
|
+
},
|
|
733
|
+
".cm-draftly-link-tooltip-visible": {
|
|
734
|
+
display: "block"
|
|
735
|
+
}
|
|
736
|
+
},
|
|
737
|
+
dark: {
|
|
738
|
+
".cm-draftly-link-text": {
|
|
739
|
+
color: "#58a6ff"
|
|
740
|
+
},
|
|
741
|
+
".cm-draftly-link-marker": {
|
|
742
|
+
color: "#8b949e"
|
|
743
|
+
},
|
|
744
|
+
".cm-draftly-link-url": {
|
|
745
|
+
color: "#8b949e"
|
|
746
|
+
},
|
|
747
|
+
".cm-draftly-link-styled": {
|
|
748
|
+
color: "#58a6ff"
|
|
749
|
+
},
|
|
750
|
+
".cm-draftly-link-styled:hover": {
|
|
751
|
+
color: "#79c0ff"
|
|
752
|
+
},
|
|
753
|
+
".cm-draftly-link": {
|
|
754
|
+
color: "#58a6ff"
|
|
755
|
+
},
|
|
756
|
+
".cm-draftly-link:hover": {
|
|
757
|
+
color: "#79c0ff"
|
|
758
|
+
},
|
|
759
|
+
".cm-draftly-link-tooltip": {
|
|
760
|
+
backgroundColor: "#30363d",
|
|
761
|
+
color: "#c9d1d9"
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
});
|
|
765
|
+
var classes = {
|
|
766
|
+
// Unordered list classes
|
|
767
|
+
lineUL: "cm-draftly-list-line-ul",
|
|
768
|
+
markUL: "cm-draftly-list-mark-ul",
|
|
769
|
+
// Ordered list classes
|
|
770
|
+
lineOL: "cm-draftly-list-line-ol",
|
|
771
|
+
markOL: "cm-draftly-list-mark-ol",
|
|
772
|
+
// Task list classes
|
|
773
|
+
taskLine: "cm-draftly-task-line",
|
|
774
|
+
taskMarker: "cm-draftly-task-marker",
|
|
775
|
+
// Common classes
|
|
776
|
+
content: "cm-draftly-list-content",
|
|
777
|
+
indent: "cm-draftly-list-indent",
|
|
778
|
+
active: " cm-draftly-active",
|
|
779
|
+
preview: "cm-draftly-preview"
|
|
780
|
+
};
|
|
781
|
+
var TaskCheckboxWidget = class extends view.WidgetType {
|
|
782
|
+
constructor(checked) {
|
|
783
|
+
super();
|
|
784
|
+
this.checked = checked;
|
|
785
|
+
}
|
|
786
|
+
eq(other) {
|
|
787
|
+
return other.checked === this.checked;
|
|
788
|
+
}
|
|
789
|
+
toDOM(view) {
|
|
790
|
+
const wrap = document.createElement("span");
|
|
791
|
+
wrap.className = `cm-draftly-task-checkbox ${this.checked ? "checked" : ""}`;
|
|
792
|
+
wrap.setAttribute("aria-hidden", "true");
|
|
793
|
+
const checkbox = document.createElement("input");
|
|
794
|
+
checkbox.type = "checkbox";
|
|
795
|
+
checkbox.checked = this.checked;
|
|
796
|
+
checkbox.tabIndex = -1;
|
|
797
|
+
checkbox.addEventListener("mousedown", (e) => {
|
|
798
|
+
e.preventDefault();
|
|
799
|
+
this.toggleCheckbox(view, wrap);
|
|
800
|
+
});
|
|
801
|
+
wrap.appendChild(checkbox);
|
|
802
|
+
return wrap;
|
|
803
|
+
}
|
|
804
|
+
ignoreEvent() {
|
|
805
|
+
return false;
|
|
806
|
+
}
|
|
807
|
+
/** Toggle the checkbox state in the document */
|
|
808
|
+
toggleCheckbox(view, wrap) {
|
|
809
|
+
const pos = view.posAtDOM(wrap);
|
|
810
|
+
const line = view.state.doc.lineAt(pos);
|
|
811
|
+
const match = line.text.match(/^(\s*(?:[-*+]|\d+\.)\s*)\[([ xX])\]/);
|
|
812
|
+
if (match) {
|
|
813
|
+
const markerStart = line.from + match[1].length + 1;
|
|
814
|
+
const newChar = this.checked ? " " : "x";
|
|
815
|
+
view.dispatch({
|
|
816
|
+
changes: { from: markerStart, to: markerStart + 1, insert: newChar }
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
};
|
|
821
|
+
var ListPlugin = class extends chunk72ZYRGRT_cjs.DecorationPlugin {
|
|
822
|
+
name = "list";
|
|
823
|
+
version = "1.0.0";
|
|
824
|
+
decorationPriority = 20;
|
|
825
|
+
requiredNodes = [
|
|
826
|
+
"BulletList",
|
|
827
|
+
"OrderedList",
|
|
828
|
+
"ListItem",
|
|
829
|
+
"ListMark",
|
|
830
|
+
"Task",
|
|
831
|
+
"TaskMarker"
|
|
832
|
+
];
|
|
833
|
+
get theme() {
|
|
834
|
+
return theme5;
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Keyboard shortcuts for list formatting
|
|
838
|
+
*/
|
|
839
|
+
getKeymap() {
|
|
840
|
+
return [
|
|
841
|
+
{
|
|
842
|
+
key: "Mod-Shift-8",
|
|
843
|
+
run: (view) => this.toggleListOnLines(view, "- "),
|
|
844
|
+
preventDefault: true
|
|
845
|
+
},
|
|
846
|
+
{
|
|
847
|
+
key: "Mod-Shift-7",
|
|
848
|
+
run: (view) => this.toggleListOnLines(view, "1. "),
|
|
849
|
+
preventDefault: true
|
|
850
|
+
},
|
|
851
|
+
{
|
|
852
|
+
key: "Mod-Shift-9",
|
|
853
|
+
run: (view) => this.toggleListOnLines(view, "- [ ] "),
|
|
854
|
+
preventDefault: true
|
|
855
|
+
}
|
|
856
|
+
];
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* Toggle list marker on current line or selected lines
|
|
860
|
+
*/
|
|
861
|
+
toggleListOnLines(view, marker) {
|
|
862
|
+
const { state } = view;
|
|
863
|
+
const { from, to } = state.selection.main;
|
|
864
|
+
const startLine = state.doc.lineAt(from);
|
|
865
|
+
const endLine = state.doc.lineAt(to);
|
|
866
|
+
const changes = [];
|
|
867
|
+
const listMarkerRegex = /^(\s*)([-*+]|\d+\.)\s(\[[ xX]\]\s)?/;
|
|
868
|
+
const isOrderedMarker = marker === "1. ";
|
|
869
|
+
let orderNum = 1;
|
|
870
|
+
for (let lineNum = startLine.number; lineNum <= endLine.number; lineNum++) {
|
|
871
|
+
const line = state.doc.line(lineNum);
|
|
872
|
+
const match = line.text.match(listMarkerRegex);
|
|
873
|
+
const actualMarker = isOrderedMarker ? `${orderNum}. ` : marker;
|
|
874
|
+
if (match) {
|
|
875
|
+
const existingMarker = match[0];
|
|
876
|
+
const indent = match[1] || "";
|
|
877
|
+
const isUnordered = /^[-*+]$/.test(match[2]);
|
|
878
|
+
const isOrdered = /^\d+\.$/.test(match[2]);
|
|
879
|
+
const hasTask = !!match[3];
|
|
880
|
+
const wantUnordered = marker === "- ";
|
|
881
|
+
const wantOrdered = isOrderedMarker;
|
|
882
|
+
const wantTask = marker === "- [ ] ";
|
|
883
|
+
if (wantUnordered && isUnordered && !hasTask || wantOrdered && isOrdered && !hasTask || wantTask && hasTask) {
|
|
884
|
+
changes.push({
|
|
885
|
+
from: line.from,
|
|
886
|
+
to: line.from + existingMarker.length,
|
|
887
|
+
insert: indent
|
|
888
|
+
});
|
|
889
|
+
} else {
|
|
890
|
+
changes.push({
|
|
891
|
+
from: line.from,
|
|
892
|
+
to: line.from + existingMarker.length,
|
|
893
|
+
insert: indent + actualMarker
|
|
894
|
+
});
|
|
895
|
+
orderNum++;
|
|
896
|
+
}
|
|
897
|
+
} else {
|
|
898
|
+
const indentMatch = line.text.match(/^(\s*)/);
|
|
899
|
+
const indent = indentMatch ? indentMatch[1] : "";
|
|
900
|
+
changes.push({
|
|
901
|
+
from: line.from + indent.length,
|
|
902
|
+
to: line.from + indent.length,
|
|
903
|
+
insert: actualMarker
|
|
904
|
+
});
|
|
905
|
+
orderNum++;
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
if (changes.length > 0) {
|
|
909
|
+
view.dispatch({ changes });
|
|
910
|
+
}
|
|
911
|
+
return true;
|
|
912
|
+
}
|
|
913
|
+
buildDecorations(ctx) {
|
|
914
|
+
const { view, decorations } = ctx;
|
|
915
|
+
const tree = language.syntaxTree(view.state);
|
|
916
|
+
tree.iterate({
|
|
917
|
+
enter: (node) => {
|
|
918
|
+
const { from, to, name } = node;
|
|
919
|
+
const line = view.state.doc.lineAt(from);
|
|
920
|
+
const cursorInLine = ctx.cursorInRange(line.from, line.to);
|
|
921
|
+
switch (name) {
|
|
922
|
+
case "ListItem":
|
|
923
|
+
this.decorateListItem(node, line, decorations);
|
|
924
|
+
break;
|
|
925
|
+
case "ListMark":
|
|
926
|
+
this.decorateListMark(node, line, decorations, cursorInLine);
|
|
927
|
+
break;
|
|
928
|
+
case "TaskMarker":
|
|
929
|
+
this.decorateTaskMarker(from, to, view, decorations, cursorInLine);
|
|
930
|
+
break;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
/** Add line decoration for list items with nesting depth */
|
|
936
|
+
decorateListItem(node, line, decorations) {
|
|
937
|
+
const parent = node.node.parent;
|
|
938
|
+
const listType = parent?.name;
|
|
939
|
+
let depth = 0;
|
|
940
|
+
let ancestor = node.node.parent;
|
|
941
|
+
while (ancestor) {
|
|
942
|
+
if (ancestor.name === "ListItem") depth++;
|
|
943
|
+
ancestor = ancestor.parent;
|
|
944
|
+
}
|
|
945
|
+
const hasTask = this.hasTaskChild(node);
|
|
946
|
+
let lineClass;
|
|
947
|
+
if (hasTask) lineClass = classes.taskLine;
|
|
948
|
+
else if (listType === "OrderedList") lineClass = classes.lineOL;
|
|
949
|
+
else lineClass = classes.lineUL;
|
|
950
|
+
decorations.push(
|
|
951
|
+
view.Decoration.line({
|
|
952
|
+
class: lineClass,
|
|
953
|
+
attributes: { style: `--depth: ${depth}` }
|
|
954
|
+
}).range(line.from)
|
|
955
|
+
);
|
|
956
|
+
}
|
|
957
|
+
/** Check if a ListItem node has a Task child */
|
|
958
|
+
hasTaskChild(node) {
|
|
959
|
+
const cursor = node.node.cursor();
|
|
960
|
+
if (cursor.firstChild()) {
|
|
961
|
+
do {
|
|
962
|
+
if (cursor.name === "Task") return true;
|
|
963
|
+
} while (cursor.nextSibling());
|
|
964
|
+
}
|
|
965
|
+
return false;
|
|
966
|
+
}
|
|
967
|
+
/** Decorate list markers (bullets for UL, numbers for OL) */
|
|
968
|
+
decorateListMark(node, line, decorations, cursorInLine) {
|
|
969
|
+
const { from, to } = node;
|
|
970
|
+
const parent = node.node.parent;
|
|
971
|
+
const grandparent = parent?.parent;
|
|
972
|
+
const listType = grandparent?.name;
|
|
973
|
+
const activeClass = cursorInLine ? classes.active : "";
|
|
974
|
+
if (from > line.from) {
|
|
975
|
+
decorations.push(view.Decoration.mark({ class: classes.indent + activeClass }).range(line.from, from));
|
|
976
|
+
}
|
|
977
|
+
const markClass = listType === "OrderedList" ? classes.markOL : classes.markUL;
|
|
978
|
+
decorations.push(view.Decoration.mark({ class: markClass + activeClass }).range(from, to + 1));
|
|
979
|
+
const contentStart = to + 1;
|
|
980
|
+
if (contentStart < line.to) {
|
|
981
|
+
decorations.push(view.Decoration.mark({ class: classes.content }).range(contentStart, line.to));
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
/** Decorate task markers - show checkbox widget or raw text based on cursor */
|
|
985
|
+
decorateTaskMarker(from, to, view$1, decorations, cursorInLine) {
|
|
986
|
+
const text = view$1.state.sliceDoc(from, to);
|
|
987
|
+
const isChecked = text.includes("x") || text.includes("X");
|
|
988
|
+
if (cursorInLine) {
|
|
989
|
+
decorations.push(view.Decoration.mark({ class: classes.taskMarker }).range(from, to));
|
|
990
|
+
} else {
|
|
991
|
+
decorations.push(
|
|
992
|
+
view.Decoration.replace({
|
|
993
|
+
widget: new TaskCheckboxWidget(isChecked)
|
|
994
|
+
}).range(from, to)
|
|
995
|
+
);
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
/** Render list nodes to HTML */
|
|
999
|
+
renderToHTML(node, children, ctx) {
|
|
1000
|
+
switch (node.name) {
|
|
1001
|
+
case "BulletList":
|
|
1002
|
+
return `<ul class="${classes.lineUL} ${classes.preview}">${children}</ul>
|
|
1003
|
+
`;
|
|
1004
|
+
case "OrderedList":
|
|
1005
|
+
return `<ol class="${classes.lineOL} ${classes.preview}">${children}</ol>
|
|
1006
|
+
`;
|
|
1007
|
+
case "ListItem":
|
|
1008
|
+
return `<li>${children}</li>
|
|
1009
|
+
`;
|
|
1010
|
+
case "Task":
|
|
1011
|
+
return children;
|
|
1012
|
+
case "TaskMarker": {
|
|
1013
|
+
const text = ctx.sliceDoc(node.from, node.to);
|
|
1014
|
+
const isChecked = text.includes("x") || text.includes("X");
|
|
1015
|
+
return `<input type="checkbox" class="cm-draftly-task-checkbox" disabled ${isChecked ? "checked" : ""} />`;
|
|
1016
|
+
}
|
|
1017
|
+
case "ListMark":
|
|
1018
|
+
return "";
|
|
1019
|
+
default:
|
|
1020
|
+
return null;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
};
|
|
1024
|
+
var theme5 = chunkKDEDLC3D_cjs.createTheme({
|
|
1025
|
+
default: {
|
|
1026
|
+
// Indentation marker positioning
|
|
1027
|
+
".cm-draftly-list-indent": {
|
|
1028
|
+
overflow: "hidden",
|
|
1029
|
+
display: "inline-block",
|
|
1030
|
+
position: "absolute",
|
|
1031
|
+
left: "calc(1rem * (var(--depth, 0) + 1))",
|
|
1032
|
+
transform: "translateX(-100%)"
|
|
1033
|
+
},
|
|
1034
|
+
// List line layout (flexbox for marker alignment)
|
|
1035
|
+
".cm-draftly-list-line-ul, .cm-draftly-list-line-ol": {
|
|
1036
|
+
position: "relative",
|
|
1037
|
+
paddingLeft: "calc(1rem * (var(--depth, 0) + 1)) !important",
|
|
1038
|
+
display: "flex",
|
|
1039
|
+
alignItems: "start"
|
|
1040
|
+
},
|
|
1041
|
+
".cm-draftly-list-line-ul > :first-child, .cm-draftly-list-line-ol > :first-child": {
|
|
1042
|
+
flexShrink: 0
|
|
1043
|
+
},
|
|
1044
|
+
// List marker sizing
|
|
1045
|
+
".cm-draftly-list-line-ul .cm-draftly-list-mark-ul, .cm-draftly-list-line-ol .cm-draftly-list-mark-ol": {
|
|
1046
|
+
whiteSpace: "pre",
|
|
1047
|
+
position: "relative",
|
|
1048
|
+
width: "1rem",
|
|
1049
|
+
flexShrink: 0
|
|
1050
|
+
},
|
|
1051
|
+
// Hide raw marker text when not active
|
|
1052
|
+
".cm-draftly-list-mark-ul:not(.cm-draftly-active) > span, .cm-draftly-task-line .cm-draftly-list-mark-ol:not(.cm-draftly-active) > span": {
|
|
1053
|
+
visibility: "hidden",
|
|
1054
|
+
display: "none"
|
|
1055
|
+
},
|
|
1056
|
+
// Styled bullet for unordered lists
|
|
1057
|
+
".cm-draftly-list-line-ul .cm-draftly-list-mark-ul:not(.cm-draftly-active)::after": {
|
|
1058
|
+
content: '"\u2022"',
|
|
1059
|
+
color: "var(--color-link)",
|
|
1060
|
+
fontWeight: "bold",
|
|
1061
|
+
pointerEvents: "none"
|
|
1062
|
+
},
|
|
1063
|
+
// Task marker styling (visible when editing)
|
|
1064
|
+
".cm-draftly-task-marker": {
|
|
1065
|
+
color: "var(--draftly-highlight, #a4a4a4)",
|
|
1066
|
+
fontFamily: "monospace"
|
|
1067
|
+
},
|
|
1068
|
+
// Task checkbox container
|
|
1069
|
+
".cm-draftly-task-checkbox": {
|
|
1070
|
+
display: "inline-flex",
|
|
1071
|
+
verticalAlign: "middle",
|
|
1072
|
+
marginRight: "0.3em",
|
|
1073
|
+
cursor: "pointer",
|
|
1074
|
+
userSelect: "none",
|
|
1075
|
+
alignItems: "center",
|
|
1076
|
+
height: "1.2em"
|
|
1077
|
+
},
|
|
1078
|
+
// Task checkbox input styling
|
|
1079
|
+
".cm-draftly-task-checkbox input": {
|
|
1080
|
+
cursor: "pointer",
|
|
1081
|
+
margin: 0,
|
|
1082
|
+
width: "1.1em",
|
|
1083
|
+
height: "1.1em",
|
|
1084
|
+
appearance: "none",
|
|
1085
|
+
border: "1px solid",
|
|
1086
|
+
borderRadius: "0.25em",
|
|
1087
|
+
backgroundColor: "transparent",
|
|
1088
|
+
position: "relative"
|
|
1089
|
+
},
|
|
1090
|
+
// Checkmark for completed tasks
|
|
1091
|
+
".cm-draftly-task-checkbox.checked input::after": {
|
|
1092
|
+
content: '"\u2713"',
|
|
1093
|
+
position: "absolute",
|
|
1094
|
+
left: "1px",
|
|
1095
|
+
top: "-3px"
|
|
1096
|
+
},
|
|
1097
|
+
// Preview styles (override editor-specific layout)
|
|
1098
|
+
".cm-draftly-preview": {
|
|
1099
|
+
display: "block",
|
|
1100
|
+
paddingLeft: "1.5rem",
|
|
1101
|
+
margin: "0.5rem 0"
|
|
1102
|
+
},
|
|
1103
|
+
".cm-draftly-preview li": {
|
|
1104
|
+
display: "list-item",
|
|
1105
|
+
marginBottom: "0.25rem"
|
|
1106
|
+
},
|
|
1107
|
+
"ul.cm-draftly-preview": {
|
|
1108
|
+
listStyleType: "disc"
|
|
1109
|
+
},
|
|
1110
|
+
"ol.cm-draftly-preview": {
|
|
1111
|
+
listStyleType: "decimal"
|
|
1112
|
+
},
|
|
1113
|
+
// Hide list marker for task items
|
|
1114
|
+
".cm-draftly-preview li:has(.cm-draftly-task-checkbox)": {
|
|
1115
|
+
listStyleType: "none"
|
|
1116
|
+
},
|
|
1117
|
+
".cm-draftly-preview li .cm-draftly-paragraph": {
|
|
1118
|
+
padding: "0"
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
});
|
|
1122
|
+
var htmlMarkDecorations = {
|
|
1123
|
+
"html-tag": view.Decoration.mark({ class: "cm-draftly-html-tag" }),
|
|
1124
|
+
"html-comment": view.Decoration.mark({ class: "cm-draftly-html-comment" })
|
|
1125
|
+
};
|
|
1126
|
+
var htmlLineDecorations = {
|
|
1127
|
+
"html-block": view.Decoration.line({ class: "cm-draftly-line-html-block" }),
|
|
1128
|
+
"hidden-line": view.Decoration.line({ class: "cm-draftly-hidden-line" })
|
|
1129
|
+
};
|
|
1130
|
+
var HTMLPreviewWidget = class extends view.WidgetType {
|
|
1131
|
+
constructor(html) {
|
|
1132
|
+
super();
|
|
1133
|
+
this.html = html;
|
|
1134
|
+
}
|
|
1135
|
+
eq(other) {
|
|
1136
|
+
return other.html === this.html;
|
|
1137
|
+
}
|
|
1138
|
+
toDOM() {
|
|
1139
|
+
const div = document.createElement("div");
|
|
1140
|
+
div.className = "cm-draftly-html-preview";
|
|
1141
|
+
div.innerHTML = DOMPurify__default.default.sanitize(this.html);
|
|
1142
|
+
return div;
|
|
1143
|
+
}
|
|
1144
|
+
ignoreEvent() {
|
|
1145
|
+
return false;
|
|
1146
|
+
}
|
|
1147
|
+
};
|
|
1148
|
+
var InlineHTMLPreviewWidget = class extends view.WidgetType {
|
|
1149
|
+
constructor(html) {
|
|
1150
|
+
super();
|
|
1151
|
+
this.html = html;
|
|
1152
|
+
}
|
|
1153
|
+
eq(other) {
|
|
1154
|
+
return other.html === this.html;
|
|
1155
|
+
}
|
|
1156
|
+
toDOM() {
|
|
1157
|
+
const span = document.createElement("span");
|
|
1158
|
+
span.className = "cm-draftly-inline-html-preview";
|
|
1159
|
+
span.innerHTML = DOMPurify__default.default.sanitize(this.html);
|
|
1160
|
+
return span;
|
|
1161
|
+
}
|
|
1162
|
+
ignoreEvent() {
|
|
1163
|
+
return false;
|
|
1164
|
+
}
|
|
1165
|
+
};
|
|
1166
|
+
function parseHTMLTag(content) {
|
|
1167
|
+
const match = content.match(/^<\s*(\/?)([a-zA-Z][a-zA-Z0-9-]*)[^>]*(\/?)>$/);
|
|
1168
|
+
if (!match) return null;
|
|
1169
|
+
return {
|
|
1170
|
+
tagName: match[2].toLowerCase(),
|
|
1171
|
+
isClosing: match[1] === "/",
|
|
1172
|
+
isSelfClosing: match[3] === "/" || ["br", "hr", "img", "input", "meta", "link", "area", "base", "col", "embed", "source", "track", "wbr"].includes(
|
|
1173
|
+
match[2].toLowerCase()
|
|
1174
|
+
)
|
|
1175
|
+
};
|
|
1176
|
+
}
|
|
1177
|
+
var HTMLPlugin = class extends chunk72ZYRGRT_cjs.DecorationPlugin {
|
|
1178
|
+
name = "html";
|
|
1179
|
+
version = "1.0.0";
|
|
1180
|
+
decorationPriority = 30;
|
|
1181
|
+
constructor() {
|
|
1182
|
+
super();
|
|
1183
|
+
}
|
|
1184
|
+
/**
|
|
1185
|
+
* Plugin theme
|
|
1186
|
+
*/
|
|
1187
|
+
get theme() {
|
|
1188
|
+
return theme6;
|
|
1189
|
+
}
|
|
1190
|
+
buildDecorations(ctx) {
|
|
1191
|
+
const { view: view$1, decorations } = ctx;
|
|
1192
|
+
const tree = language.syntaxTree(view$1.state);
|
|
1193
|
+
const htmlGroups = [];
|
|
1194
|
+
const htmlTags = [];
|
|
1195
|
+
tree.iterate({
|
|
1196
|
+
enter: (node) => {
|
|
1197
|
+
const { from, to, name } = node;
|
|
1198
|
+
if (name === "Comment") {
|
|
1199
|
+
decorations.push(htmlMarkDecorations["html-comment"].range(from, to));
|
|
1200
|
+
return;
|
|
1201
|
+
}
|
|
1202
|
+
if (name === "HTMLTag") {
|
|
1203
|
+
const content = view$1.state.sliceDoc(from, to);
|
|
1204
|
+
const parsed = parseHTMLTag(content);
|
|
1205
|
+
if (parsed) {
|
|
1206
|
+
htmlTags.push({
|
|
1207
|
+
from,
|
|
1208
|
+
to,
|
|
1209
|
+
tagName: parsed.tagName,
|
|
1210
|
+
isClosing: parsed.isClosing,
|
|
1211
|
+
isSelfClosing: parsed.isSelfClosing
|
|
1212
|
+
});
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
if (name === "HTMLBlock") {
|
|
1216
|
+
const last = htmlGroups[htmlGroups.length - 1];
|
|
1217
|
+
if (last) {
|
|
1218
|
+
const gap = view$1.state.sliceDoc(last.to, from);
|
|
1219
|
+
if (!gap.trim()) {
|
|
1220
|
+
last.to = to;
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
htmlGroups.push({ from, to });
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
});
|
|
1228
|
+
const inlineElements = [];
|
|
1229
|
+
const usedTags = /* @__PURE__ */ new Set();
|
|
1230
|
+
for (let i = 0; i < htmlTags.length; i++) {
|
|
1231
|
+
if (usedTags.has(i)) continue;
|
|
1232
|
+
const openTag = htmlTags[i];
|
|
1233
|
+
if (openTag.isClosing) continue;
|
|
1234
|
+
if (openTag.isSelfClosing) {
|
|
1235
|
+
inlineElements.push({
|
|
1236
|
+
from: openTag.from,
|
|
1237
|
+
to: openTag.to,
|
|
1238
|
+
content: view$1.state.sliceDoc(openTag.from, openTag.to)
|
|
1239
|
+
});
|
|
1240
|
+
usedTags.add(i);
|
|
1241
|
+
continue;
|
|
1242
|
+
}
|
|
1243
|
+
const openLine = view$1.state.doc.lineAt(openTag.from);
|
|
1244
|
+
let depth = 1;
|
|
1245
|
+
let closeTagIndex = null;
|
|
1246
|
+
for (let j = i + 1; j < htmlTags.length && depth > 0; j++) {
|
|
1247
|
+
const tag = htmlTags[j];
|
|
1248
|
+
if (tag.from > openLine.to) break;
|
|
1249
|
+
if (tag.tagName === openTag.tagName) {
|
|
1250
|
+
if (tag.isClosing) {
|
|
1251
|
+
depth--;
|
|
1252
|
+
if (depth === 0) {
|
|
1253
|
+
closeTagIndex = j;
|
|
1254
|
+
}
|
|
1255
|
+
} else if (!tag.isSelfClosing) {
|
|
1256
|
+
depth++;
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
if (closeTagIndex !== null) {
|
|
1261
|
+
const closeTag = htmlTags[closeTagIndex];
|
|
1262
|
+
inlineElements.push({
|
|
1263
|
+
from: openTag.from,
|
|
1264
|
+
to: closeTag.to,
|
|
1265
|
+
content: view$1.state.sliceDoc(openTag.from, closeTag.to)
|
|
1266
|
+
});
|
|
1267
|
+
for (let k = i; k <= closeTagIndex; k++) {
|
|
1268
|
+
usedTags.add(k);
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
inlineElements.sort((a, b) => a.from - b.from);
|
|
1273
|
+
const filteredElements = [];
|
|
1274
|
+
let lastEnd = -1;
|
|
1275
|
+
for (const elem of inlineElements) {
|
|
1276
|
+
if (elem.from >= lastEnd) {
|
|
1277
|
+
filteredElements.push(elem);
|
|
1278
|
+
lastEnd = elem.to;
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
for (const elem of filteredElements) {
|
|
1282
|
+
const cursorInRange = ctx.cursorInRange(elem.from, elem.to);
|
|
1283
|
+
if (cursorInRange) {
|
|
1284
|
+
for (const tag of htmlTags) {
|
|
1285
|
+
if (tag.from >= elem.from && tag.to <= elem.to) {
|
|
1286
|
+
decorations.push(htmlMarkDecorations["html-tag"].range(tag.from, tag.to));
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
} else {
|
|
1290
|
+
decorations.push(
|
|
1291
|
+
view.Decoration.replace({
|
|
1292
|
+
widget: new InlineHTMLPreviewWidget(elem.content)
|
|
1293
|
+
}).range(elem.from, elem.to)
|
|
1294
|
+
);
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
for (let i = 0; i < htmlTags.length; i++) {
|
|
1298
|
+
if (!usedTags.has(i)) {
|
|
1299
|
+
const tag = htmlTags[i];
|
|
1300
|
+
decorations.push(htmlMarkDecorations["html-tag"].range(tag.from, tag.to));
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
for (const group of htmlGroups) {
|
|
1304
|
+
const { from, to } = group;
|
|
1305
|
+
const nodeLineStart = view$1.state.doc.lineAt(from);
|
|
1306
|
+
const nodeLineEnd = view$1.state.doc.lineAt(to);
|
|
1307
|
+
const cursorInRange = ctx.cursorInRange(nodeLineStart.from, nodeLineEnd.to);
|
|
1308
|
+
if (cursorInRange) {
|
|
1309
|
+
for (let i = nodeLineStart.number; i <= nodeLineEnd.number; i++) {
|
|
1310
|
+
const line = view$1.state.doc.line(i);
|
|
1311
|
+
decorations.push(htmlLineDecorations["html-block"].range(line.from));
|
|
1312
|
+
}
|
|
1313
|
+
} else {
|
|
1314
|
+
const htmlContent = view$1.state.sliceDoc(from, to);
|
|
1315
|
+
decorations.push(
|
|
1316
|
+
view.Decoration.replace({
|
|
1317
|
+
widget: new HTMLPreviewWidget(htmlContent.trim())
|
|
1318
|
+
}).range(from, nodeLineStart.to)
|
|
1319
|
+
);
|
|
1320
|
+
for (let i = nodeLineStart.number + 1; i <= nodeLineEnd.number; i++) {
|
|
1321
|
+
const line = view$1.state.doc.line(i);
|
|
1322
|
+
decorations.push(htmlLineDecorations["hidden-line"].range(line.from));
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
};
|
|
1328
|
+
var theme6 = chunkKDEDLC3D_cjs.createTheme({
|
|
1329
|
+
default: {
|
|
1330
|
+
".cm-draftly-html-tag": {
|
|
1331
|
+
color: "#6a737d",
|
|
1332
|
+
fontFamily: "var(--font-jetbrains-mono, monospace)",
|
|
1333
|
+
fontSize: "0.85em"
|
|
1334
|
+
},
|
|
1335
|
+
".cm-draftly-html-comment": {
|
|
1336
|
+
color: "#6a737d",
|
|
1337
|
+
fontStyle: "italic",
|
|
1338
|
+
fontFamily: "var(--font-jetbrains-mono, monospace)",
|
|
1339
|
+
fontSize: "0.85em",
|
|
1340
|
+
opacity: 0.5
|
|
1341
|
+
},
|
|
1342
|
+
".cm-draftly-line-html-block": {
|
|
1343
|
+
backgroundColor: "rgba(0, 0, 0, 0.02)"
|
|
1344
|
+
},
|
|
1345
|
+
".cm-draftly-hidden-line": {
|
|
1346
|
+
display: "none"
|
|
1347
|
+
},
|
|
1348
|
+
".cm-draftly-html-preview": {
|
|
1349
|
+
display: "inline-block",
|
|
1350
|
+
width: "100%",
|
|
1351
|
+
verticalAlign: "top",
|
|
1352
|
+
margin: "0",
|
|
1353
|
+
whiteSpace: "normal",
|
|
1354
|
+
lineHeight: "1.4"
|
|
1355
|
+
},
|
|
1356
|
+
".cm-draftly-html-preview > *:first-child": {
|
|
1357
|
+
marginTop: "0"
|
|
1358
|
+
},
|
|
1359
|
+
".cm-draftly-html-preview > *:last-child": {
|
|
1360
|
+
marginBottom: "0"
|
|
1361
|
+
},
|
|
1362
|
+
".cm-draftly-inline-html-preview": {
|
|
1363
|
+
display: "inline",
|
|
1364
|
+
whiteSpace: "normal"
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
});
|
|
1368
|
+
var imageMarkDecorations = {
|
|
1369
|
+
"image-block": view.Decoration.line({ class: "cm-draftly-image-block" }),
|
|
1370
|
+
"image-marker": view.Decoration.mark({ class: "cm-draftly-image-marker" }),
|
|
1371
|
+
"image-alt": view.Decoration.mark({ class: "cm-draftly-image-alt" }),
|
|
1372
|
+
"image-url": view.Decoration.mark({ class: "cm-draftly-image-url" }),
|
|
1373
|
+
"image-hidden": view.Decoration.mark({ class: "cm-draftly-image-hidden" })
|
|
1374
|
+
};
|
|
1375
|
+
function parseImageMarkdown(content) {
|
|
1376
|
+
const match = content.match(/^!\[([^\]]*)\]\(([^"\s)]+)(?:\s+"([^"]*)")?\s*\)$/);
|
|
1377
|
+
if (!match) return null;
|
|
1378
|
+
const result = {
|
|
1379
|
+
alt: match[1] || "",
|
|
1380
|
+
url: match[2]
|
|
1381
|
+
};
|
|
1382
|
+
if (match[3] !== void 0) {
|
|
1383
|
+
result.title = match[3];
|
|
1384
|
+
}
|
|
1385
|
+
return result;
|
|
1386
|
+
}
|
|
1387
|
+
var ImageWidget = class extends view.WidgetType {
|
|
1388
|
+
constructor(url, alt, from, to, title) {
|
|
1389
|
+
super();
|
|
1390
|
+
this.url = url;
|
|
1391
|
+
this.alt = alt;
|
|
1392
|
+
this.from = from;
|
|
1393
|
+
this.to = to;
|
|
1394
|
+
this.title = title;
|
|
1395
|
+
}
|
|
1396
|
+
eq(other) {
|
|
1397
|
+
return other.url === this.url && other.alt === this.alt && other.from === this.from && other.to === this.to && other.title === this.title;
|
|
1398
|
+
}
|
|
1399
|
+
toDOM(view) {
|
|
1400
|
+
const figure = document.createElement("figure");
|
|
1401
|
+
figure.className = "cm-draftly-image-figure";
|
|
1402
|
+
figure.setAttribute("role", "figure");
|
|
1403
|
+
figure.style.cursor = "pointer";
|
|
1404
|
+
if (this.title) {
|
|
1405
|
+
figure.setAttribute("aria-label", this.title);
|
|
1406
|
+
}
|
|
1407
|
+
figure.addEventListener("click", (e) => {
|
|
1408
|
+
e.preventDefault();
|
|
1409
|
+
e.stopPropagation();
|
|
1410
|
+
view.dispatch({
|
|
1411
|
+
selection: { anchor: this.from, head: this.to },
|
|
1412
|
+
scrollIntoView: true
|
|
1413
|
+
});
|
|
1414
|
+
view.focus();
|
|
1415
|
+
});
|
|
1416
|
+
const img = document.createElement("img");
|
|
1417
|
+
img.className = "cm-draftly-image";
|
|
1418
|
+
img.src = this.url;
|
|
1419
|
+
img.alt = this.alt;
|
|
1420
|
+
img.setAttribute("loading", "lazy");
|
|
1421
|
+
img.setAttribute("decoding", "async");
|
|
1422
|
+
if (this.title) {
|
|
1423
|
+
img.title = this.title;
|
|
1424
|
+
}
|
|
1425
|
+
img.onerror = () => {
|
|
1426
|
+
img.style.display = "none";
|
|
1427
|
+
const errorSpan = document.createElement("span");
|
|
1428
|
+
errorSpan.className = "cm-draftly-image-error";
|
|
1429
|
+
errorSpan.setAttribute("role", "alert");
|
|
1430
|
+
errorSpan.textContent = `[Image not found: ${this.alt || this.url}]`;
|
|
1431
|
+
figure.appendChild(errorSpan);
|
|
1432
|
+
};
|
|
1433
|
+
figure.appendChild(img);
|
|
1434
|
+
if (this.title) {
|
|
1435
|
+
const figcaption = document.createElement("figcaption");
|
|
1436
|
+
figcaption.className = "cm-draftly-image-caption";
|
|
1437
|
+
figcaption.textContent = this.title;
|
|
1438
|
+
figure.appendChild(figcaption);
|
|
1439
|
+
}
|
|
1440
|
+
return figure;
|
|
1441
|
+
}
|
|
1442
|
+
ignoreEvent(event) {
|
|
1443
|
+
return event.type !== "click";
|
|
1444
|
+
}
|
|
1445
|
+
};
|
|
1446
|
+
var ImagePlugin = class extends chunk72ZYRGRT_cjs.DecorationPlugin {
|
|
1447
|
+
name = "image";
|
|
1448
|
+
version = "1.0.0";
|
|
1449
|
+
decorationPriority = 25;
|
|
1450
|
+
requiredNodes = ["Image"];
|
|
1451
|
+
constructor() {
|
|
1452
|
+
super();
|
|
1453
|
+
}
|
|
1454
|
+
/**
|
|
1455
|
+
* Plugin theme
|
|
1456
|
+
*/
|
|
1457
|
+
get theme() {
|
|
1458
|
+
return theme7;
|
|
1459
|
+
}
|
|
1460
|
+
/**
|
|
1461
|
+
* Keyboard shortcuts for image formatting
|
|
1462
|
+
*/
|
|
1463
|
+
getKeymap() {
|
|
1464
|
+
return [
|
|
1465
|
+
{
|
|
1466
|
+
key: "Mod-Shift-i",
|
|
1467
|
+
run: (view) => this.toggleImage(view),
|
|
1468
|
+
preventDefault: true
|
|
1469
|
+
}
|
|
1470
|
+
];
|
|
1471
|
+
}
|
|
1472
|
+
/**
|
|
1473
|
+
* URL regex pattern
|
|
1474
|
+
*/
|
|
1475
|
+
urlPattern = /^(https?:\/\/|www\.)[^\s]+$/i;
|
|
1476
|
+
/**
|
|
1477
|
+
* Toggle image on selection
|
|
1478
|
+
* - If text selected and is a URL:  with cursor in brackets
|
|
1479
|
+
* - If text selected (not URL): ![text]() with cursor in parentheses
|
|
1480
|
+
* - If nothing selected: ![Alt Text]() with cursor in parentheses
|
|
1481
|
+
* - If already an image: remove syntax, leave just the URL
|
|
1482
|
+
*/
|
|
1483
|
+
toggleImage(view) {
|
|
1484
|
+
const { state } = view;
|
|
1485
|
+
const { from, to, empty } = state.selection.main;
|
|
1486
|
+
const selectedText = state.sliceDoc(from, to);
|
|
1487
|
+
const imageMatch = selectedText.match(/^!\[([^\]]*)\]\(([^)]*)\)$/);
|
|
1488
|
+
if (imageMatch) {
|
|
1489
|
+
const imageUrl = imageMatch[2] || "";
|
|
1490
|
+
view.dispatch({
|
|
1491
|
+
changes: { from, to, insert: imageUrl },
|
|
1492
|
+
selection: { anchor: from, head: from + imageUrl.length }
|
|
1493
|
+
});
|
|
1494
|
+
return true;
|
|
1495
|
+
}
|
|
1496
|
+
const lineStart = state.doc.lineAt(from).from;
|
|
1497
|
+
const lineEnd = state.doc.lineAt(to).to;
|
|
1498
|
+
const lineText = state.sliceDoc(lineStart, lineEnd);
|
|
1499
|
+
const imageRegex = /!\[([^\]]*)\]\(([^)]*)\)/g;
|
|
1500
|
+
let match;
|
|
1501
|
+
while ((match = imageRegex.exec(lineText)) !== null) {
|
|
1502
|
+
const matchFrom = lineStart + match.index;
|
|
1503
|
+
const matchTo = matchFrom + match[0].length;
|
|
1504
|
+
if (from >= matchFrom && to <= matchTo) {
|
|
1505
|
+
const imageUrl = match[2] || "";
|
|
1506
|
+
view.dispatch({
|
|
1507
|
+
changes: { from: matchFrom, to: matchTo, insert: imageUrl },
|
|
1508
|
+
selection: { anchor: matchFrom, head: matchFrom + imageUrl.length }
|
|
1509
|
+
});
|
|
1510
|
+
return true;
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
if (empty) {
|
|
1514
|
+
const defaultAlt = "Alt Text";
|
|
1515
|
+
const newText = `![${defaultAlt}]()`;
|
|
1516
|
+
view.dispatch({
|
|
1517
|
+
changes: { from, insert: newText },
|
|
1518
|
+
selection: { anchor: from + defaultAlt.length + 4 }
|
|
1519
|
+
// After ;
|
|
1521
|
+
} else if (this.urlPattern.test(selectedText)) {
|
|
1522
|
+
const defaultAlt = "Alt Text";
|
|
1523
|
+
const newText = ``;
|
|
1524
|
+
view.dispatch({
|
|
1525
|
+
changes: { from, to, insert: newText },
|
|
1526
|
+
selection: { anchor: from + 2, head: from + 2 + defaultAlt.length }
|
|
1527
|
+
// Select "Alt Text"
|
|
1528
|
+
});
|
|
1529
|
+
} else {
|
|
1530
|
+
const newText = `![${selectedText}]()`;
|
|
1531
|
+
view.dispatch({
|
|
1532
|
+
changes: { from, to, insert: newText },
|
|
1533
|
+
selection: { anchor: from + selectedText.length + 4 }
|
|
1534
|
+
// After ;
|
|
1536
|
+
}
|
|
1537
|
+
return true;
|
|
1538
|
+
}
|
|
1539
|
+
buildDecorations(ctx) {
|
|
1540
|
+
const { view: view$1, decorations } = ctx;
|
|
1541
|
+
const tree = language.syntaxTree(view$1.state);
|
|
1542
|
+
tree.iterate({
|
|
1543
|
+
enter: (node) => {
|
|
1544
|
+
const { from, to, name } = node;
|
|
1545
|
+
if (name === "Image") {
|
|
1546
|
+
const content = view$1.state.sliceDoc(from, to);
|
|
1547
|
+
const parsed = parseImageMarkdown(content);
|
|
1548
|
+
if (!parsed) return;
|
|
1549
|
+
const cursorInRange = ctx.selectionOverlapsRange(from, to);
|
|
1550
|
+
decorations.push(imageMarkDecorations["image-block"].range(from));
|
|
1551
|
+
decorations.push(
|
|
1552
|
+
view.Decoration.widget({
|
|
1553
|
+
widget: new ImageWidget(parsed.url, parsed.alt, from, to, parsed.title),
|
|
1554
|
+
side: 1,
|
|
1555
|
+
// Place after the position
|
|
1556
|
+
block: false
|
|
1557
|
+
// Don't create a new line
|
|
1558
|
+
}).range(to)
|
|
1559
|
+
);
|
|
1560
|
+
if (cursorInRange) {
|
|
1561
|
+
this.decorateRawImage(node.node, decorations, view$1);
|
|
1562
|
+
} else {
|
|
1563
|
+
decorations.push(imageMarkDecorations["image-hidden"].range(from, to));
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
});
|
|
1568
|
+
}
|
|
1569
|
+
/**
|
|
1570
|
+
* Decorate raw image markdown when cursor is in range
|
|
1571
|
+
*/
|
|
1572
|
+
decorateRawImage(node, decorations, view) {
|
|
1573
|
+
for (let child = node.firstChild; child; child = child.nextSibling) {
|
|
1574
|
+
if (child.name === "URL") {
|
|
1575
|
+
decorations.push(imageMarkDecorations["image-url"].range(child.from, child.to));
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
const content = view.state.sliceDoc(node.from, node.to);
|
|
1579
|
+
const bangBracket = node.from;
|
|
1580
|
+
if (content.startsWith("![")) {
|
|
1581
|
+
decorations.push(imageMarkDecorations["image-marker"].range(bangBracket, bangBracket + 2));
|
|
1582
|
+
}
|
|
1583
|
+
const altEnd = content.indexOf("](");
|
|
1584
|
+
if (altEnd !== -1) {
|
|
1585
|
+
const altStart = 2;
|
|
1586
|
+
if (altEnd > altStart) {
|
|
1587
|
+
decorations.push(imageMarkDecorations["image-alt"].range(node.from + altStart, node.from + altEnd));
|
|
1588
|
+
}
|
|
1589
|
+
decorations.push(imageMarkDecorations["image-marker"].range(node.from + altEnd, node.from + altEnd + 2));
|
|
1590
|
+
decorations.push(imageMarkDecorations["image-marker"].range(node.to - 1, node.to));
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
/**
|
|
1594
|
+
* Render image to HTML for preview mode using figure/figcaption
|
|
1595
|
+
*/
|
|
1596
|
+
renderToHTML(node, _children, ctx) {
|
|
1597
|
+
if (node.name !== "Image") return null;
|
|
1598
|
+
const content = ctx.sliceDoc(node.from, node.to);
|
|
1599
|
+
const parsed = parseImageMarkdown(content);
|
|
1600
|
+
if (!parsed) return null;
|
|
1601
|
+
const altAttr = ctx.sanitize(parsed.alt);
|
|
1602
|
+
const titleAttr = parsed.title ? ` title="${ctx.sanitize(parsed.title)}"` : "";
|
|
1603
|
+
const ariaLabel = parsed.title ? ` aria-label="${ctx.sanitize(parsed.title)}"` : "";
|
|
1604
|
+
let html = `<figure class="cm-draftly-image-figure" role="figure"${ariaLabel}>`;
|
|
1605
|
+
html += `<img class="cm-draftly-image" src="${ctx.sanitize(parsed.url)}" alt="${altAttr}"${titleAttr} loading="lazy" decoding="async" />`;
|
|
1606
|
+
if (parsed.title) {
|
|
1607
|
+
html += `<figcaption class="cm-draftly-image-caption">${ctx.sanitize(parsed.title)}</figcaption>`;
|
|
1608
|
+
}
|
|
1609
|
+
html += `</figure>`;
|
|
1610
|
+
return html;
|
|
1611
|
+
}
|
|
1612
|
+
};
|
|
1613
|
+
var theme7 = chunkKDEDLC3D_cjs.createTheme({
|
|
1614
|
+
default: {
|
|
1615
|
+
".cm-draftly-image-block br": {
|
|
1616
|
+
display: "none"
|
|
1617
|
+
},
|
|
1618
|
+
// Image markers (! [ ] ( ))
|
|
1619
|
+
".cm-draftly-image-marker": {
|
|
1620
|
+
color: "#6a737d",
|
|
1621
|
+
fontFamily: "var(--font-jetbrains-mono, monospace)"
|
|
1622
|
+
},
|
|
1623
|
+
// Alt text
|
|
1624
|
+
".cm-draftly-image-alt": {
|
|
1625
|
+
color: "#22863a",
|
|
1626
|
+
fontStyle: "italic"
|
|
1627
|
+
},
|
|
1628
|
+
// URL
|
|
1629
|
+
".cm-draftly-image-url": {
|
|
1630
|
+
color: "#0366d6",
|
|
1631
|
+
textDecoration: "underline"
|
|
1632
|
+
},
|
|
1633
|
+
// Hidden markdown syntax (when cursor is not in range)
|
|
1634
|
+
".cm-draftly-image-hidden": {
|
|
1635
|
+
display: "none"
|
|
1636
|
+
},
|
|
1637
|
+
// Figure container
|
|
1638
|
+
".cm-draftly-image-figure": {
|
|
1639
|
+
display: "flex",
|
|
1640
|
+
flexDirection: "column",
|
|
1641
|
+
alignItems: "start",
|
|
1642
|
+
maxWidth: "100%",
|
|
1643
|
+
padding: "0"
|
|
1644
|
+
},
|
|
1645
|
+
// Image element
|
|
1646
|
+
".cm-draftly-image": {
|
|
1647
|
+
maxWidth: "100%",
|
|
1648
|
+
maxHeight: "800px",
|
|
1649
|
+
height: "auto",
|
|
1650
|
+
borderRadius: "4px"
|
|
1651
|
+
},
|
|
1652
|
+
// Figcaption
|
|
1653
|
+
".cm-draftly-image-caption": {
|
|
1654
|
+
display: "block",
|
|
1655
|
+
width: "100%",
|
|
1656
|
+
fontSize: "0.875em",
|
|
1657
|
+
color: "#6a737d",
|
|
1658
|
+
marginTop: "0.5em",
|
|
1659
|
+
textAlign: "center",
|
|
1660
|
+
fontStyle: "italic"
|
|
1661
|
+
},
|
|
1662
|
+
// Error state
|
|
1663
|
+
".cm-draftly-image-error": {
|
|
1664
|
+
display: "inline-block",
|
|
1665
|
+
padding: "0.5em 1em",
|
|
1666
|
+
backgroundColor: "rgba(255, 0, 0, 0.1)",
|
|
1667
|
+
color: "#d73a49",
|
|
1668
|
+
borderRadius: "4px",
|
|
1669
|
+
fontSize: "0.875em",
|
|
1670
|
+
fontStyle: "italic"
|
|
1671
|
+
}
|
|
1672
|
+
},
|
|
1673
|
+
dark: {
|
|
1674
|
+
".cm-draftly-image-marker": {
|
|
1675
|
+
color: "#8b949e"
|
|
1676
|
+
},
|
|
1677
|
+
".cm-draftly-image-alt": {
|
|
1678
|
+
color: "#7ee787"
|
|
1679
|
+
},
|
|
1680
|
+
".cm-draftly-image-url": {
|
|
1681
|
+
color: "#58a6ff"
|
|
1682
|
+
},
|
|
1683
|
+
".cm-draftly-image-caption": {
|
|
1684
|
+
color: "#8b949e"
|
|
1685
|
+
},
|
|
1686
|
+
".cm-draftly-image-error": {
|
|
1687
|
+
backgroundColor: "rgba(255, 0, 0, 0.15)",
|
|
1688
|
+
color: "#f85149"
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
});
|
|
1692
|
+
function injectKatexStyles() {
|
|
1693
|
+
if (typeof document === "undefined") return;
|
|
1694
|
+
if (document.getElementById("draftly-katex-styles")) return;
|
|
1695
|
+
const style = document.createElement("style");
|
|
1696
|
+
style.id = "draftly-katex-styles";
|
|
1697
|
+
style.textContent = katexCss__default.default;
|
|
1698
|
+
document.head.appendChild(style);
|
|
1699
|
+
}
|
|
1700
|
+
injectKatexStyles();
|
|
1701
|
+
var DOLLAR = 36;
|
|
1702
|
+
var mathMarkDecorations = {
|
|
1703
|
+
"math-block": view.Decoration.line({ class: "cm-draftly-math-block" }),
|
|
1704
|
+
"math-inline": view.Decoration.mark({ class: "cm-draftly-math-inline" }),
|
|
1705
|
+
"math-marker": view.Decoration.mark({ class: "cm-draftly-math-marker" }),
|
|
1706
|
+
"math-hidden": view.Decoration.mark({ class: "cm-draftly-math-hidden" })
|
|
1707
|
+
};
|
|
1708
|
+
function renderMath(latex, displayMode) {
|
|
1709
|
+
try {
|
|
1710
|
+
const html = katex__default.default.renderToString(latex, {
|
|
1711
|
+
displayMode,
|
|
1712
|
+
throwOnError: false,
|
|
1713
|
+
errorColor: "#d73a49",
|
|
1714
|
+
trust: false,
|
|
1715
|
+
strict: false
|
|
1716
|
+
});
|
|
1717
|
+
return { html, error: null };
|
|
1718
|
+
} catch (e) {
|
|
1719
|
+
const errorMsg = e instanceof Error ? e.message : "Unknown error";
|
|
1720
|
+
return { html: "", error: errorMsg };
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
var InlineMathWidget = class extends view.WidgetType {
|
|
1724
|
+
constructor(latex, from, to) {
|
|
1725
|
+
super();
|
|
1726
|
+
this.latex = latex;
|
|
1727
|
+
this.from = from;
|
|
1728
|
+
this.to = to;
|
|
1729
|
+
}
|
|
1730
|
+
eq(other) {
|
|
1731
|
+
return other.latex === this.latex && other.from === this.from && other.to === this.to;
|
|
1732
|
+
}
|
|
1733
|
+
toDOM(view) {
|
|
1734
|
+
const span = document.createElement("span");
|
|
1735
|
+
span.className = "cm-draftly-math-rendered cm-draftly-math-rendered-inline";
|
|
1736
|
+
span.style.cursor = "pointer";
|
|
1737
|
+
const { html, error } = renderMath(this.latex, false);
|
|
1738
|
+
if (error) {
|
|
1739
|
+
span.className += " cm-draftly-math-error";
|
|
1740
|
+
span.textContent = `[Math Error: ${error}]`;
|
|
1741
|
+
} else {
|
|
1742
|
+
span.innerHTML = html;
|
|
1743
|
+
}
|
|
1744
|
+
span.addEventListener("click", (e) => {
|
|
1745
|
+
e.preventDefault();
|
|
1746
|
+
e.stopPropagation();
|
|
1747
|
+
view.dispatch({
|
|
1748
|
+
selection: { anchor: this.from, head: this.to },
|
|
1749
|
+
scrollIntoView: true
|
|
1750
|
+
});
|
|
1751
|
+
view.focus();
|
|
1752
|
+
});
|
|
1753
|
+
return span;
|
|
1754
|
+
}
|
|
1755
|
+
ignoreEvent(event) {
|
|
1756
|
+
return event.type !== "click";
|
|
1757
|
+
}
|
|
1758
|
+
};
|
|
1759
|
+
var MathBlockWidget = class extends view.WidgetType {
|
|
1760
|
+
constructor(latex, from, to) {
|
|
1761
|
+
super();
|
|
1762
|
+
this.latex = latex;
|
|
1763
|
+
this.from = from;
|
|
1764
|
+
this.to = to;
|
|
1765
|
+
}
|
|
1766
|
+
eq(other) {
|
|
1767
|
+
return other.latex === this.latex && other.from === this.from && other.to === this.to;
|
|
1768
|
+
}
|
|
1769
|
+
toDOM(view) {
|
|
1770
|
+
const div = document.createElement("div");
|
|
1771
|
+
div.className = "cm-draftly-math-rendered cm-draftly-math-rendered-block";
|
|
1772
|
+
div.style.cursor = "pointer";
|
|
1773
|
+
const { html, error } = renderMath(this.latex, true);
|
|
1774
|
+
if (error) {
|
|
1775
|
+
div.className += " cm-draftly-math-error";
|
|
1776
|
+
div.textContent = `[Math Error: ${error}]`;
|
|
1777
|
+
} else {
|
|
1778
|
+
div.innerHTML = html;
|
|
1779
|
+
}
|
|
1780
|
+
div.addEventListener("click", (e) => {
|
|
1781
|
+
e.preventDefault();
|
|
1782
|
+
e.stopPropagation();
|
|
1783
|
+
view.dispatch({
|
|
1784
|
+
selection: { anchor: this.from, head: this.to },
|
|
1785
|
+
scrollIntoView: true
|
|
1786
|
+
});
|
|
1787
|
+
view.focus();
|
|
1788
|
+
});
|
|
1789
|
+
return div;
|
|
1790
|
+
}
|
|
1791
|
+
ignoreEvent(event) {
|
|
1792
|
+
return event.type !== "click";
|
|
1793
|
+
}
|
|
1794
|
+
};
|
|
1795
|
+
var inlineMathParser = {
|
|
1796
|
+
name: "InlineMath",
|
|
1797
|
+
parse(cx, next, pos) {
|
|
1798
|
+
if (next !== DOLLAR) return -1;
|
|
1799
|
+
if (cx.char(pos + 1) === DOLLAR) return -1;
|
|
1800
|
+
let end = pos + 1;
|
|
1801
|
+
while (end < cx.end) {
|
|
1802
|
+
const char = cx.char(end);
|
|
1803
|
+
if (char === DOLLAR) {
|
|
1804
|
+
if (cx.char(end + 1) !== DOLLAR) {
|
|
1805
|
+
const content = cx.slice(pos + 1, end);
|
|
1806
|
+
if (content.trim().length === 0) return -1;
|
|
1807
|
+
const openMark = cx.elt("InlineMathMark", pos, pos + 1);
|
|
1808
|
+
const closeMark = cx.elt("InlineMathMark", end, end + 1);
|
|
1809
|
+
const inlineMath = cx.elt("InlineMath", pos, end + 1, [openMark, closeMark]);
|
|
1810
|
+
return cx.addElement(inlineMath);
|
|
1811
|
+
}
|
|
1812
|
+
return -1;
|
|
1813
|
+
}
|
|
1814
|
+
if (char === 92) {
|
|
1815
|
+
end += 2;
|
|
1816
|
+
continue;
|
|
1817
|
+
}
|
|
1818
|
+
end++;
|
|
1819
|
+
}
|
|
1820
|
+
return -1;
|
|
1821
|
+
}
|
|
1822
|
+
};
|
|
1823
|
+
var mathBlockParser = {
|
|
1824
|
+
name: "MathBlock",
|
|
1825
|
+
parse(cx, line) {
|
|
1826
|
+
const text = line.text;
|
|
1827
|
+
const trimmed = text.slice(line.pos).trimStart();
|
|
1828
|
+
if (!trimmed.startsWith("$$")) return false;
|
|
1829
|
+
const startLine = cx.lineStart;
|
|
1830
|
+
let endPos = -1;
|
|
1831
|
+
let lastLineEnd = startLine + line.text.length;
|
|
1832
|
+
while (cx.nextLine()) {
|
|
1833
|
+
const currentText = line.text;
|
|
1834
|
+
lastLineEnd = cx.lineStart + currentText.length;
|
|
1835
|
+
if (currentText.trimEnd().endsWith("$$")) {
|
|
1836
|
+
endPos = lastLineEnd;
|
|
1837
|
+
cx.nextLine();
|
|
1838
|
+
break;
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
if (endPos === -1) {
|
|
1842
|
+
return false;
|
|
1843
|
+
}
|
|
1844
|
+
const openMark = cx.elt("MathBlockMark", startLine, startLine + text.indexOf("$$") + 2);
|
|
1845
|
+
const closeMark = cx.elt("MathBlockMark", endPos - 2, endPos);
|
|
1846
|
+
cx.addElement(cx.elt("MathBlock", startLine, endPos, [openMark, closeMark]));
|
|
1847
|
+
return true;
|
|
1848
|
+
}
|
|
1849
|
+
};
|
|
1850
|
+
var MathPlugin = class extends chunk72ZYRGRT_cjs.DecorationPlugin {
|
|
1851
|
+
name = "math";
|
|
1852
|
+
version = "1.0.0";
|
|
1853
|
+
decorationPriority = 25;
|
|
1854
|
+
requiredNodes = ["InlineMath", "MathBlock", "InlineMathMark", "MathBlockMark"];
|
|
1855
|
+
constructor() {
|
|
1856
|
+
super();
|
|
1857
|
+
}
|
|
1858
|
+
/**
|
|
1859
|
+
* Plugin theme
|
|
1860
|
+
*/
|
|
1861
|
+
get theme() {
|
|
1862
|
+
return theme8;
|
|
1863
|
+
}
|
|
1864
|
+
/**
|
|
1865
|
+
* Return markdown parser extensions for math syntax
|
|
1866
|
+
*/
|
|
1867
|
+
getMarkdownConfig() {
|
|
1868
|
+
return {
|
|
1869
|
+
defineNodes: [
|
|
1870
|
+
{ name: "InlineMath", style: highlight.tags.emphasis },
|
|
1871
|
+
{ name: "InlineMathMark", style: highlight.tags.processingInstruction },
|
|
1872
|
+
{ name: "MathBlock", block: true },
|
|
1873
|
+
{ name: "MathBlockMark", style: highlight.tags.processingInstruction }
|
|
1874
|
+
],
|
|
1875
|
+
parseInline: [inlineMathParser],
|
|
1876
|
+
parseBlock: [mathBlockParser]
|
|
1877
|
+
};
|
|
1878
|
+
}
|
|
1879
|
+
/**
|
|
1880
|
+
* Build decorations for math expressions
|
|
1881
|
+
*/
|
|
1882
|
+
buildDecorations(ctx) {
|
|
1883
|
+
const { view: view$1, decorations } = ctx;
|
|
1884
|
+
const tree = language.syntaxTree(view$1.state);
|
|
1885
|
+
tree.iterate({
|
|
1886
|
+
enter: (node) => {
|
|
1887
|
+
const { from, to, name } = node;
|
|
1888
|
+
if (name === "InlineMath") {
|
|
1889
|
+
const content = view$1.state.sliceDoc(from, to);
|
|
1890
|
+
const latex = content.slice(1, -1);
|
|
1891
|
+
const cursorInRange = ctx.selectionOverlapsRange(from, to);
|
|
1892
|
+
if (cursorInRange) {
|
|
1893
|
+
decorations.push(mathMarkDecorations["math-inline"].range(from, to));
|
|
1894
|
+
for (let child = node.node.firstChild; child; child = child.nextSibling) {
|
|
1895
|
+
if (child.name === "InlineMathMark") {
|
|
1896
|
+
decorations.push(mathMarkDecorations["math-marker"].range(child.from, child.to));
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
} else {
|
|
1900
|
+
decorations.push(
|
|
1901
|
+
view.Decoration.replace({
|
|
1902
|
+
widget: new InlineMathWidget(latex, from, to)
|
|
1903
|
+
}).range(from, to)
|
|
1904
|
+
);
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
if (name === "MathBlock") {
|
|
1908
|
+
const content = view$1.state.sliceDoc(from, to);
|
|
1909
|
+
const lines = content.split("\n");
|
|
1910
|
+
const latex = lines.slice(1, -1).join("\n").trim();
|
|
1911
|
+
const singleLine = !content.includes("\n");
|
|
1912
|
+
const latexContent = singleLine ? content.slice(2, -2).trim() : latex;
|
|
1913
|
+
const nodeLineStart = view$1.state.doc.lineAt(from);
|
|
1914
|
+
const nodeLineEnd = view$1.state.doc.lineAt(to);
|
|
1915
|
+
const cursorInRange = ctx.selectionOverlapsRange(nodeLineStart.from, nodeLineEnd.to);
|
|
1916
|
+
decorations.push(mathMarkDecorations["math-block"].range(from));
|
|
1917
|
+
decorations.push(
|
|
1918
|
+
view.Decoration.widget({
|
|
1919
|
+
widget: new MathBlockWidget(latexContent, from, to),
|
|
1920
|
+
side: 1,
|
|
1921
|
+
block: false
|
|
1922
|
+
}).range(to)
|
|
1923
|
+
);
|
|
1924
|
+
for (let i = nodeLineStart.number; i <= nodeLineEnd.number; i++) {
|
|
1925
|
+
const line = view$1.state.doc.line(i);
|
|
1926
|
+
decorations.push(mathMarkDecorations["math-block"].range(line.from));
|
|
1927
|
+
}
|
|
1928
|
+
if (cursorInRange) {
|
|
1929
|
+
for (let child = node.node.firstChild; child; child = child.nextSibling) {
|
|
1930
|
+
if (child.name === "MathBlockMark") {
|
|
1931
|
+
decorations.push(mathMarkDecorations["math-marker"].range(child.from, child.to));
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
} else {
|
|
1935
|
+
decorations.push(mathMarkDecorations["math-hidden"].range(from, to));
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
});
|
|
1940
|
+
}
|
|
1941
|
+
/**
|
|
1942
|
+
* Render math to HTML for preview mode
|
|
1943
|
+
*/
|
|
1944
|
+
renderToHTML(node, _children, ctx) {
|
|
1945
|
+
if (node.name === "InlineMath") {
|
|
1946
|
+
const content = ctx.sliceDoc(node.from, node.to);
|
|
1947
|
+
const latex = content.slice(1, -1);
|
|
1948
|
+
const { html, error } = renderMath(latex, false);
|
|
1949
|
+
if (error) {
|
|
1950
|
+
return `<span class="cm-draftly-math-error">[Math Error: ${ctx.sanitize(error)}]</span>`;
|
|
1951
|
+
}
|
|
1952
|
+
return `<span class="cm-draftly-math-rendered cm-draftly-math-rendered-inline">${html}</span>`;
|
|
1953
|
+
}
|
|
1954
|
+
if (node.name === "MathBlock") {
|
|
1955
|
+
const content = ctx.sliceDoc(node.from, node.to);
|
|
1956
|
+
const lines = content.split("\n");
|
|
1957
|
+
const latex = lines.length > 1 ? lines.slice(1, -1).join("\n").trim() : content.slice(2, -2).trim();
|
|
1958
|
+
const { html, error } = renderMath(latex, true);
|
|
1959
|
+
if (error) {
|
|
1960
|
+
return `<div class="cm-draftly-math-error">[Math Error: ${ctx.sanitize(error)}]</div>`;
|
|
1961
|
+
}
|
|
1962
|
+
return `<div class="cm-draftly-math-rendered cm-draftly-math-rendered-block">${html}</div>`;
|
|
1963
|
+
}
|
|
1964
|
+
if (node.name === "InlineMathMark" || node.name === "MathBlockMark") {
|
|
1965
|
+
return "";
|
|
1966
|
+
}
|
|
1967
|
+
return null;
|
|
1968
|
+
}
|
|
1969
|
+
};
|
|
1970
|
+
var theme8 = chunkKDEDLC3D_cjs.createTheme({
|
|
1971
|
+
default: {
|
|
1972
|
+
".cm-draftly-math-block": {
|
|
1973
|
+
fontFamily: "var(--font-jetbrains-mono, monospace)"
|
|
1974
|
+
},
|
|
1975
|
+
".cm-draftly-math-block br": {
|
|
1976
|
+
display: "none"
|
|
1977
|
+
},
|
|
1978
|
+
// Math markers ($ $$)
|
|
1979
|
+
".cm-draftly-math-marker": {
|
|
1980
|
+
color: "#6a737d",
|
|
1981
|
+
fontFamily: "var(--font-jetbrains-mono, monospace)"
|
|
1982
|
+
},
|
|
1983
|
+
// Inline math styling when editing
|
|
1984
|
+
".cm-draftly-math-inline": {
|
|
1985
|
+
fontFamily: "var(--font-jetbrains-mono, monospace)",
|
|
1986
|
+
fontSize: "0.9em"
|
|
1987
|
+
},
|
|
1988
|
+
// Hidden math syntax (when cursor is not in range)
|
|
1989
|
+
".cm-draftly-math-hidden": {
|
|
1990
|
+
display: "none"
|
|
1991
|
+
},
|
|
1992
|
+
// Hidden line (for multi-line blocks)
|
|
1993
|
+
".cm-draftly-hidden-line": {
|
|
1994
|
+
display: "none"
|
|
1995
|
+
},
|
|
1996
|
+
// Rendered math container (both inline and block)
|
|
1997
|
+
".cm-draftly-math-rendered": {
|
|
1998
|
+
fontFamily: "KaTeX_Main, 'Times New Roman', serif"
|
|
1999
|
+
},
|
|
2000
|
+
// Inline rendered math
|
|
2001
|
+
".cm-draftly-math-rendered-inline": {
|
|
2002
|
+
display: "inline",
|
|
2003
|
+
verticalAlign: "baseline"
|
|
2004
|
+
},
|
|
2005
|
+
// Block rendered math (display mode)
|
|
2006
|
+
".cm-draftly-math-rendered-block": {
|
|
2007
|
+
display: "flex",
|
|
2008
|
+
justifyContent: "center",
|
|
2009
|
+
alignItems: "center",
|
|
2010
|
+
padding: "1em 0",
|
|
2011
|
+
backgroundColor: "rgba(0, 0, 0, 0.02)",
|
|
2012
|
+
borderRadius: "4px",
|
|
2013
|
+
overflow: "auto"
|
|
2014
|
+
},
|
|
2015
|
+
// Math error styling
|
|
2016
|
+
".cm-draftly-math-error": {
|
|
2017
|
+
display: "inline-block",
|
|
2018
|
+
padding: "0.25em 0.5em",
|
|
2019
|
+
backgroundColor: "rgba(255, 0, 0, 0.1)",
|
|
2020
|
+
color: "#d73a49",
|
|
2021
|
+
borderRadius: "4px",
|
|
2022
|
+
fontSize: "0.875em",
|
|
2023
|
+
fontStyle: "italic",
|
|
2024
|
+
fontFamily: "var(--font-jetbrains-mono, monospace)"
|
|
2025
|
+
}
|
|
2026
|
+
},
|
|
2027
|
+
dark: {
|
|
2028
|
+
".cm-draftly-math-marker": {
|
|
2029
|
+
color: "#8b949e"
|
|
2030
|
+
},
|
|
2031
|
+
".cm-draftly-math-rendered-block": {
|
|
2032
|
+
backgroundColor: "rgba(255, 255, 255, 0.02)"
|
|
2033
|
+
},
|
|
2034
|
+
".cm-draftly-math-error": {
|
|
2035
|
+
backgroundColor: "rgba(255, 0, 0, 0.15)",
|
|
2036
|
+
color: "#f85149"
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
});
|
|
2040
|
+
mermaid__default.default.initialize({
|
|
2041
|
+
startOnLoad: false,
|
|
2042
|
+
theme: "default",
|
|
2043
|
+
suppressErrorRendering: true
|
|
2044
|
+
});
|
|
2045
|
+
var mermaidCounter = 0;
|
|
2046
|
+
async function renderMermaid(definition, options = {}, defaultTheme = "default") {
|
|
2047
|
+
try {
|
|
2048
|
+
const id = `draftly-mermaid-${mermaidCounter++}`;
|
|
2049
|
+
let finalDefinition = definition;
|
|
2050
|
+
const mermaidConfig = {};
|
|
2051
|
+
if (options.theme) {
|
|
2052
|
+
mermaidConfig.theme = options.theme;
|
|
2053
|
+
} else {
|
|
2054
|
+
mermaidConfig.theme = defaultTheme;
|
|
2055
|
+
}
|
|
2056
|
+
if (Object.keys(mermaidConfig).length > 0) {
|
|
2057
|
+
const jsonConfig = JSON.stringify(mermaidConfig);
|
|
2058
|
+
finalDefinition = `%%{init: ${jsonConfig} }%%
|
|
2059
|
+
${definition}`;
|
|
2060
|
+
}
|
|
2061
|
+
const { svg } = await mermaid__default.default.render(id, finalDefinition);
|
|
2062
|
+
return { svg, error: null };
|
|
2063
|
+
} catch (e) {
|
|
2064
|
+
const errorMsg = e instanceof Error ? e.message : "Unknown error";
|
|
2065
|
+
return { svg: "", error: errorMsg };
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
function parseAttributes(fenceLine) {
|
|
2069
|
+
const attributes = {};
|
|
2070
|
+
const regex = /(\w+)=["']([^"']*)["']/g;
|
|
2071
|
+
let match;
|
|
2072
|
+
while ((match = regex.exec(fenceLine)) !== null && match[1] && match[2]) {
|
|
2073
|
+
attributes[match[1]] = match[2];
|
|
2074
|
+
}
|
|
2075
|
+
return attributes;
|
|
2076
|
+
}
|
|
2077
|
+
var mermaidMarkDecorations = {
|
|
2078
|
+
"mermaid-block-start": view.Decoration.line({ class: "cm-draftly-mermaid-block-start" }),
|
|
2079
|
+
"mermaid-block-end": view.Decoration.line({ class: "cm-draftly-mermaid-block-end" }),
|
|
2080
|
+
"mermaid-block": view.Decoration.line({ class: "cm-draftly-mermaid-block" }),
|
|
2081
|
+
"mermaid-block-rendered": view.Decoration.line({ class: "cm-draftly-mermaid-block-rendered" }),
|
|
2082
|
+
"mermaid-marker": view.Decoration.mark({ class: "cm-draftly-mermaid-marker" }),
|
|
2083
|
+
"mermaid-hidden": view.Decoration.mark({ class: "cm-draftly-mermaid-hidden" })
|
|
2084
|
+
};
|
|
2085
|
+
var MermaidBlockWidget = class extends view.WidgetType {
|
|
2086
|
+
constructor(definition, attributes, defaultTheme, from, to) {
|
|
2087
|
+
super();
|
|
2088
|
+
this.definition = definition;
|
|
2089
|
+
this.attributes = attributes;
|
|
2090
|
+
this.defaultTheme = defaultTheme;
|
|
2091
|
+
this.from = from;
|
|
2092
|
+
this.to = to;
|
|
2093
|
+
}
|
|
2094
|
+
eq(other) {
|
|
2095
|
+
return other.definition === this.definition && JSON.stringify(other.attributes) === JSON.stringify(this.attributes) && other.defaultTheme === this.defaultTheme && other.from === this.from && other.to === this.to;
|
|
2096
|
+
}
|
|
2097
|
+
toDOM(view) {
|
|
2098
|
+
const div = document.createElement("div");
|
|
2099
|
+
div.className = "cm-draftly-mermaid-rendered";
|
|
2100
|
+
div.style.cursor = "pointer";
|
|
2101
|
+
div.innerHTML = `<div class="cm-draftly-mermaid-loading">Rendering diagram\u2026</div>`;
|
|
2102
|
+
renderMermaid(this.definition, this.attributes, this.defaultTheme).then(({ svg, error }) => {
|
|
2103
|
+
if (error) {
|
|
2104
|
+
div.className += " cm-draftly-mermaid-error";
|
|
2105
|
+
div.innerHTML = `<span>[Mermaid Error: ${error}]</span>`;
|
|
2106
|
+
} else {
|
|
2107
|
+
div.innerHTML = svg;
|
|
2108
|
+
}
|
|
2109
|
+
});
|
|
2110
|
+
div.addEventListener("click", (e) => {
|
|
2111
|
+
e.preventDefault();
|
|
2112
|
+
e.stopPropagation();
|
|
2113
|
+
view.dispatch({
|
|
2114
|
+
selection: { anchor: this.from, head: this.to },
|
|
2115
|
+
scrollIntoView: true
|
|
2116
|
+
});
|
|
2117
|
+
view.focus();
|
|
2118
|
+
});
|
|
2119
|
+
return div;
|
|
2120
|
+
}
|
|
2121
|
+
ignoreEvent(event) {
|
|
2122
|
+
return event.type !== "click";
|
|
2123
|
+
}
|
|
2124
|
+
};
|
|
2125
|
+
var mermaidBlockParser = {
|
|
2126
|
+
name: "MermaidBlock",
|
|
2127
|
+
before: "FencedCode",
|
|
2128
|
+
parse(cx, line) {
|
|
2129
|
+
const text = line.text;
|
|
2130
|
+
const trimmed = text.slice(line.pos).trimStart();
|
|
2131
|
+
if (!trimmed.startsWith("```mermaid")) return false;
|
|
2132
|
+
const startLine = cx.lineStart;
|
|
2133
|
+
let endPos = -1;
|
|
2134
|
+
let closeBacktickStart = -1;
|
|
2135
|
+
while (cx.nextLine()) {
|
|
2136
|
+
const currentText = line.text;
|
|
2137
|
+
const currentLineStart = cx.lineStart;
|
|
2138
|
+
const lastLineEnd = currentLineStart + currentText.length;
|
|
2139
|
+
const trimmedLine = currentText.trim();
|
|
2140
|
+
if (trimmedLine === "```") {
|
|
2141
|
+
endPos = lastLineEnd;
|
|
2142
|
+
closeBacktickStart = currentLineStart + currentText.indexOf("```");
|
|
2143
|
+
cx.nextLine();
|
|
2144
|
+
break;
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
if (endPos === -1) {
|
|
2148
|
+
return false;
|
|
2149
|
+
}
|
|
2150
|
+
const openMarkEnd = startLine + text.indexOf("```mermaid") + 10;
|
|
2151
|
+
const openMark = cx.elt("MermaidBlockMark", startLine, openMarkEnd);
|
|
2152
|
+
const closeMark = cx.elt("MermaidBlockMark", closeBacktickStart, closeBacktickStart + 3);
|
|
2153
|
+
cx.addElement(cx.elt("MermaidBlock", startLine, endPos, [openMark, closeMark]));
|
|
2154
|
+
return true;
|
|
2155
|
+
}
|
|
2156
|
+
};
|
|
2157
|
+
var MermaidPlugin = class extends chunk72ZYRGRT_cjs.DecorationPlugin {
|
|
2158
|
+
name = "mermaid";
|
|
2159
|
+
version = "1.0.0";
|
|
2160
|
+
decorationPriority = 25;
|
|
2161
|
+
requiredNodes = ["MermaidBlock", "MermaidBlockMark"];
|
|
2162
|
+
constructor() {
|
|
2163
|
+
super();
|
|
2164
|
+
}
|
|
2165
|
+
/**
|
|
2166
|
+
* Plugin theme
|
|
2167
|
+
*/
|
|
2168
|
+
get theme() {
|
|
2169
|
+
return theme9;
|
|
2170
|
+
}
|
|
2171
|
+
/**
|
|
2172
|
+
* Return markdown parser extensions for mermaid syntax
|
|
2173
|
+
*/
|
|
2174
|
+
getMarkdownConfig() {
|
|
2175
|
+
return {
|
|
2176
|
+
defineNodes: [
|
|
2177
|
+
{ name: "MermaidBlock", block: true },
|
|
2178
|
+
{ name: "MermaidBlockMark", style: highlight.tags.processingInstruction }
|
|
2179
|
+
],
|
|
2180
|
+
parseBlock: [mermaidBlockParser]
|
|
2181
|
+
};
|
|
2182
|
+
}
|
|
2183
|
+
/**
|
|
2184
|
+
* Build decorations for mermaid blocks
|
|
2185
|
+
*/
|
|
2186
|
+
buildDecorations(ctx) {
|
|
2187
|
+
const { view: view$1, decorations } = ctx;
|
|
2188
|
+
const tree = language.syntaxTree(view$1.state);
|
|
2189
|
+
const config = this.context?.config;
|
|
2190
|
+
const currentTheme = config?.theme === "dark" /* DARK */ ? "dark" : "default";
|
|
2191
|
+
tree.iterate({
|
|
2192
|
+
enter: (node) => {
|
|
2193
|
+
const { from, to, name } = node;
|
|
2194
|
+
if (name === "MermaidBlock") {
|
|
2195
|
+
const content = view$1.state.sliceDoc(from, to);
|
|
2196
|
+
const lines = content.split("\n");
|
|
2197
|
+
const definition = lines.slice(1, -1).join("\n").trim();
|
|
2198
|
+
const docLines = content.split("\n");
|
|
2199
|
+
const fenceLine = docLines[0] || "";
|
|
2200
|
+
const attributes = parseAttributes(fenceLine);
|
|
2201
|
+
const nodeLineStart = view$1.state.doc.lineAt(from);
|
|
2202
|
+
const nodeLineEnd = view$1.state.doc.lineAt(to);
|
|
2203
|
+
const cursorInRange = ctx.selectionOverlapsRange(nodeLineStart.from, nodeLineEnd.to);
|
|
2204
|
+
const totalCodeLines = nodeLineEnd.number - nodeLineStart.number - 1;
|
|
2205
|
+
const lineNumWidth = String(totalCodeLines).length;
|
|
2206
|
+
let codeLineIndex = 1;
|
|
2207
|
+
for (let i = nodeLineStart.number; i <= nodeLineEnd.number; i++) {
|
|
2208
|
+
const line = view$1.state.doc.line(i);
|
|
2209
|
+
const isFenceLine = i === nodeLineStart.number || i === nodeLineEnd.number;
|
|
2210
|
+
const relativeLineNum = codeLineIndex;
|
|
2211
|
+
decorations.push(mermaidMarkDecorations["mermaid-block"].range(line.from));
|
|
2212
|
+
if (!cursorInRange) decorations.push(mermaidMarkDecorations["mermaid-block-rendered"].range(line.from));
|
|
2213
|
+
if (i === nodeLineStart.number)
|
|
2214
|
+
decorations.push(mermaidMarkDecorations["mermaid-block-start"].range(line.from));
|
|
2215
|
+
if (i === nodeLineEnd.number)
|
|
2216
|
+
decorations.push(mermaidMarkDecorations["mermaid-block-end"].range(line.from));
|
|
2217
|
+
if (!isFenceLine) {
|
|
2218
|
+
decorations.push(
|
|
2219
|
+
view.Decoration.line({
|
|
2220
|
+
attributes: {
|
|
2221
|
+
"data-line-num": String(relativeLineNum),
|
|
2222
|
+
style: `--line-num-width: ${lineNumWidth}ch`
|
|
2223
|
+
}
|
|
2224
|
+
}).range(line.from)
|
|
2225
|
+
);
|
|
2226
|
+
}
|
|
2227
|
+
if (!isFenceLine) {
|
|
2228
|
+
codeLineIndex++;
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
decorations.push(
|
|
2232
|
+
view.Decoration.widget({
|
|
2233
|
+
widget: new MermaidBlockWidget(definition, attributes, currentTheme, from, to),
|
|
2234
|
+
side: 1,
|
|
2235
|
+
block: false
|
|
2236
|
+
}).range(to)
|
|
2237
|
+
);
|
|
2238
|
+
if (cursorInRange) {
|
|
2239
|
+
for (let child = node.node.firstChild; child; child = child.nextSibling) {
|
|
2240
|
+
if (child.name === "MermaidBlockMark") {
|
|
2241
|
+
decorations.push(mermaidMarkDecorations["mermaid-marker"].range(child.from, child.to));
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
} else {
|
|
2245
|
+
decorations.push(mermaidMarkDecorations["mermaid-hidden"].range(from, to));
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
});
|
|
2250
|
+
}
|
|
2251
|
+
/**
|
|
2252
|
+
* Render mermaid to HTML for preview mode
|
|
2253
|
+
*
|
|
2254
|
+
* Renders the actual mermaid diagram to SVG HTML
|
|
2255
|
+
*/
|
|
2256
|
+
async renderToHTML(node, _children, ctx) {
|
|
2257
|
+
if (node.name === "MermaidBlock") {
|
|
2258
|
+
const content = ctx.sliceDoc(node.from, node.to);
|
|
2259
|
+
const lines = content.split("\n");
|
|
2260
|
+
const definition = lines.length > 1 ? lines.slice(1, -1).join("\n").trim() : "";
|
|
2261
|
+
const fenceLine = lines[0] || "";
|
|
2262
|
+
const attributes = parseAttributes(fenceLine);
|
|
2263
|
+
const config = this.context?.config;
|
|
2264
|
+
const currentTheme = config?.theme === "dark" /* DARK */ ? "dark" : "default";
|
|
2265
|
+
const { svg, error } = await renderMermaid(definition, attributes, currentTheme);
|
|
2266
|
+
if (error) {
|
|
2267
|
+
return `<div class="cm-draftly-mermaid-error">${ctx.sanitize(`[Mermaid Error: ${error}]`)}</div>`;
|
|
2268
|
+
}
|
|
2269
|
+
return `<div class="cm-draftly-mermaid-rendered">${svg}</div>`;
|
|
2270
|
+
}
|
|
2271
|
+
if (node.name === "MermaidBlockMark") {
|
|
2272
|
+
return "";
|
|
2273
|
+
}
|
|
2274
|
+
return null;
|
|
2275
|
+
}
|
|
2276
|
+
};
|
|
2277
|
+
var theme9 = chunkKDEDLC3D_cjs.createTheme({
|
|
2278
|
+
default: {
|
|
2279
|
+
// Raw mermaid block lines (monospace)
|
|
2280
|
+
".cm-draftly-mermaid-block:not(.cm-draftly-mermaid-block-rendered)": {
|
|
2281
|
+
"--radius": "0.375rem",
|
|
2282
|
+
position: "relative",
|
|
2283
|
+
fontFamily: "var(--font-jetbrains-mono, monospace)",
|
|
2284
|
+
fontSize: "0.9rem",
|
|
2285
|
+
backgroundColor: "rgba(0, 0, 0, 0.03)",
|
|
2286
|
+
padding: "0 1rem !important",
|
|
2287
|
+
paddingLeft: "calc(var(--line-num-width, 2ch) + 1rem) !important",
|
|
2288
|
+
lineHeight: "1.5",
|
|
2289
|
+
borderLeft: "1px solid var(--color-border)",
|
|
2290
|
+
borderRight: "1px solid var(--color-border)"
|
|
2291
|
+
},
|
|
2292
|
+
".cm-draftly-mermaid-block-start:not(.cm-draftly-mermaid-block-rendered)": {
|
|
2293
|
+
overflow: "hidden",
|
|
2294
|
+
borderTopLeftRadius: "var(--radius)",
|
|
2295
|
+
borderTopRightRadius: "var(--radius)",
|
|
2296
|
+
borderTop: "1px solid var(--color-border)"
|
|
2297
|
+
},
|
|
2298
|
+
".cm-draftly-mermaid-block-end:not(.cm-draftly-mermaid-block-rendered)": {
|
|
2299
|
+
overflow: "hidden",
|
|
2300
|
+
borderBottomLeftRadius: "var(--radius)",
|
|
2301
|
+
borderBottomRightRadius: "var(--radius)",
|
|
2302
|
+
borderBottom: "1px solid var(--color-border)"
|
|
2303
|
+
},
|
|
2304
|
+
".cm-draftly-mermaid-block:not(.cm-draftly-mermaid-block-rendered)::before": {
|
|
2305
|
+
content: "attr(data-line-num)",
|
|
2306
|
+
position: "absolute",
|
|
2307
|
+
left: "0.5rem",
|
|
2308
|
+
top: "0.2rem",
|
|
2309
|
+
width: "var(--line-num-width, 2ch)",
|
|
2310
|
+
textAlign: "right",
|
|
2311
|
+
color: "#6a737d",
|
|
2312
|
+
opacity: "0.6",
|
|
2313
|
+
fontFamily: "var(--font-jetbrains-mono, monospace)",
|
|
2314
|
+
fontSize: "0.85rem",
|
|
2315
|
+
userSelect: "none"
|
|
2316
|
+
},
|
|
2317
|
+
".cm-draftly-mermaid-block.cm-draftly-mermaid-block-rendered br": {
|
|
2318
|
+
display: "none"
|
|
2319
|
+
},
|
|
2320
|
+
// Mermaid markers (```mermaid / ```)
|
|
2321
|
+
".cm-draftly-mermaid-marker": {
|
|
2322
|
+
color: "#6a737d",
|
|
2323
|
+
fontFamily: "var(--font-jetbrains-mono, monospace)"
|
|
2324
|
+
},
|
|
2325
|
+
// Hidden mermaid syntax (when cursor is not in range)
|
|
2326
|
+
".cm-draftly-mermaid-hidden": {
|
|
2327
|
+
display: "none"
|
|
2328
|
+
},
|
|
2329
|
+
// Rendered mermaid container
|
|
2330
|
+
".cm-draftly-mermaid-rendered": {
|
|
2331
|
+
display: "flex",
|
|
2332
|
+
justifyContent: "center",
|
|
2333
|
+
alignItems: "center",
|
|
2334
|
+
padding: "1em 0",
|
|
2335
|
+
borderRadius: "4px",
|
|
2336
|
+
overflow: "auto"
|
|
2337
|
+
},
|
|
2338
|
+
// SVG inside rendered container
|
|
2339
|
+
".cm-draftly-mermaid-rendered svg": {
|
|
2340
|
+
maxWidth: "100%",
|
|
2341
|
+
height: "auto",
|
|
2342
|
+
aspectRatio: "auto"
|
|
2343
|
+
},
|
|
2344
|
+
// Loading state
|
|
2345
|
+
".cm-draftly-mermaid-loading": {
|
|
2346
|
+
display: "inline-block",
|
|
2347
|
+
padding: "0.5em 1em",
|
|
2348
|
+
color: "#6a737d",
|
|
2349
|
+
fontSize: "0.875em",
|
|
2350
|
+
fontStyle: "italic",
|
|
2351
|
+
fontFamily: "var(--font-jetbrains-mono, monospace)"
|
|
2352
|
+
},
|
|
2353
|
+
// Error styling
|
|
2354
|
+
".cm-draftly-mermaid-error": {
|
|
2355
|
+
display: "inline-block",
|
|
2356
|
+
padding: "0.25em 0.5em",
|
|
2357
|
+
backgroundColor: "rgba(255, 0, 0, 0.1)",
|
|
2358
|
+
color: "#d73a49",
|
|
2359
|
+
borderRadius: "4px",
|
|
2360
|
+
fontSize: "0.875em",
|
|
2361
|
+
fontStyle: "italic",
|
|
2362
|
+
fontFamily: "var(--font-jetbrains-mono, monospace)"
|
|
2363
|
+
}
|
|
2364
|
+
},
|
|
2365
|
+
dark: {
|
|
2366
|
+
".cm-draftly-mermaid-block:not(.cm-draftly-mermaid-block-rendered)": {
|
|
2367
|
+
backgroundColor: "rgba(255, 255, 255, 0.03)"
|
|
2368
|
+
},
|
|
2369
|
+
".cm-draftly-mermaid-marker": {
|
|
2370
|
+
color: "#8b949e"
|
|
2371
|
+
},
|
|
2372
|
+
".cm-draftly-mermaid-loading": {
|
|
2373
|
+
color: "#8b949e"
|
|
2374
|
+
},
|
|
2375
|
+
".cm-draftly-mermaid-error": {
|
|
2376
|
+
backgroundColor: "rgba(255, 0, 0, 0.15)",
|
|
2377
|
+
color: "#f85149"
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
});
|
|
2381
|
+
var COPY_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>`;
|
|
2382
|
+
var CHECK_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>`;
|
|
2383
|
+
var COPY_RESET_DELAY = 2e3;
|
|
2384
|
+
var codeMarkDecorations = {
|
|
2385
|
+
// Inline code
|
|
2386
|
+
"inline-code": view.Decoration.mark({ class: "cm-draftly-code-inline" }),
|
|
2387
|
+
"inline-mark": view.Decoration.replace({}),
|
|
2388
|
+
// Fenced code block
|
|
2389
|
+
"code-block-line": view.Decoration.line({ class: "cm-draftly-code-block-line" }),
|
|
2390
|
+
"code-block-line-start": view.Decoration.line({ class: "cm-draftly-code-block-line-start" }),
|
|
2391
|
+
"code-block-line-end": view.Decoration.line({ class: "cm-draftly-code-block-line-end" }),
|
|
2392
|
+
"code-fence": view.Decoration.mark({ class: "cm-draftly-code-fence" }),
|
|
2393
|
+
"code-hidden": view.Decoration.replace({}),
|
|
2394
|
+
// Highlights
|
|
2395
|
+
"code-line-highlight": view.Decoration.line({ class: "cm-draftly-code-line-highlight" }),
|
|
2396
|
+
"code-text-highlight": view.Decoration.mark({ class: "cm-draftly-code-text-highlight" })
|
|
2397
|
+
};
|
|
2398
|
+
var CodeBlockHeaderWidget = class extends view.WidgetType {
|
|
2399
|
+
constructor(props, codeContent) {
|
|
2400
|
+
super();
|
|
2401
|
+
this.props = props;
|
|
2402
|
+
this.codeContent = codeContent;
|
|
2403
|
+
}
|
|
2404
|
+
/** Creates the header DOM element with title/language and optional copy button. */
|
|
2405
|
+
toDOM() {
|
|
2406
|
+
const header = document.createElement("div");
|
|
2407
|
+
header.className = "cm-draftly-code-header";
|
|
2408
|
+
const leftSide = document.createElement("div");
|
|
2409
|
+
leftSide.className = "cm-draftly-code-header-left";
|
|
2410
|
+
if (this.props.title) {
|
|
2411
|
+
const title = document.createElement("span");
|
|
2412
|
+
title.className = "cm-draftly-code-header-title";
|
|
2413
|
+
title.textContent = this.props.title;
|
|
2414
|
+
leftSide.appendChild(title);
|
|
2415
|
+
} else if (this.props.language) {
|
|
2416
|
+
const lang = document.createElement("span");
|
|
2417
|
+
lang.className = "cm-draftly-code-header-lang";
|
|
2418
|
+
lang.textContent = this.props.language;
|
|
2419
|
+
leftSide.appendChild(lang);
|
|
2420
|
+
}
|
|
2421
|
+
header.appendChild(leftSide);
|
|
2422
|
+
if (this.props.copy !== false) {
|
|
2423
|
+
const rightSide = document.createElement("div");
|
|
2424
|
+
rightSide.className = "cm-draftly-code-header-right";
|
|
2425
|
+
const copyBtn = document.createElement("button");
|
|
2426
|
+
copyBtn.className = "cm-draftly-code-copy-btn";
|
|
2427
|
+
copyBtn.type = "button";
|
|
2428
|
+
copyBtn.title = "Copy code";
|
|
2429
|
+
copyBtn.innerHTML = COPY_ICON;
|
|
2430
|
+
copyBtn.addEventListener("click", (e) => {
|
|
2431
|
+
e.preventDefault();
|
|
2432
|
+
e.stopPropagation();
|
|
2433
|
+
navigator.clipboard.writeText(this.codeContent).then(() => {
|
|
2434
|
+
copyBtn.classList.add("copied");
|
|
2435
|
+
copyBtn.innerHTML = CHECK_ICON;
|
|
2436
|
+
setTimeout(() => {
|
|
2437
|
+
copyBtn.classList.remove("copied");
|
|
2438
|
+
copyBtn.innerHTML = COPY_ICON;
|
|
2439
|
+
}, COPY_RESET_DELAY);
|
|
2440
|
+
});
|
|
2441
|
+
});
|
|
2442
|
+
rightSide.appendChild(copyBtn);
|
|
2443
|
+
header.appendChild(rightSide);
|
|
2444
|
+
}
|
|
2445
|
+
return header;
|
|
2446
|
+
}
|
|
2447
|
+
/** Checks equality for widget reuse optimization. */
|
|
2448
|
+
eq(other) {
|
|
2449
|
+
return this.props.title === other.props.title && this.props.language === other.props.language && this.props.copy === other.props.copy && this.codeContent === other.codeContent;
|
|
2450
|
+
}
|
|
2451
|
+
/** Allow click events to propagate for copy button interaction. */
|
|
2452
|
+
ignoreEvent() {
|
|
2453
|
+
return false;
|
|
2454
|
+
}
|
|
2455
|
+
};
|
|
2456
|
+
var CodeBlockCaptionWidget = class extends view.WidgetType {
|
|
2457
|
+
constructor(caption) {
|
|
2458
|
+
super();
|
|
2459
|
+
this.caption = caption;
|
|
2460
|
+
}
|
|
2461
|
+
/** Creates the caption DOM element. */
|
|
2462
|
+
toDOM() {
|
|
2463
|
+
const captionEl = document.createElement("div");
|
|
2464
|
+
captionEl.className = "cm-draftly-code-caption";
|
|
2465
|
+
captionEl.textContent = this.caption;
|
|
2466
|
+
return captionEl;
|
|
2467
|
+
}
|
|
2468
|
+
/** Checks equality for widget reuse optimization. */
|
|
2469
|
+
eq(other) {
|
|
2470
|
+
return this.caption === other.caption;
|
|
2471
|
+
}
|
|
2472
|
+
/** Allow click events to propagate for caption interaction. */
|
|
2473
|
+
ignoreEvent() {
|
|
2474
|
+
return false;
|
|
2475
|
+
}
|
|
2476
|
+
};
|
|
2477
|
+
var CodePlugin = class extends chunk72ZYRGRT_cjs.DecorationPlugin {
|
|
2478
|
+
name = "code";
|
|
2479
|
+
version = "1.0.0";
|
|
2480
|
+
decorationPriority = 25;
|
|
2481
|
+
requiredNodes = ["InlineCode", "FencedCode", "CodeMark", "CodeInfo", "CodeText"];
|
|
2482
|
+
/**
|
|
2483
|
+
* Plugin theme
|
|
2484
|
+
*/
|
|
2485
|
+
get theme() {
|
|
2486
|
+
return theme10;
|
|
2487
|
+
}
|
|
2488
|
+
/**
|
|
2489
|
+
* Keyboard shortcuts for code formatting
|
|
2490
|
+
*/
|
|
2491
|
+
getKeymap() {
|
|
2492
|
+
return [
|
|
2493
|
+
{
|
|
2494
|
+
key: "Mod-e",
|
|
2495
|
+
run: chunkKDEDLC3D_cjs.toggleMarkdownStyle("`"),
|
|
2496
|
+
preventDefault: true
|
|
2497
|
+
},
|
|
2498
|
+
{
|
|
2499
|
+
key: "Mod-Shift-e",
|
|
2500
|
+
run: (view) => this.toggleCodeBlock(view),
|
|
2501
|
+
preventDefault: true
|
|
2502
|
+
}
|
|
2503
|
+
];
|
|
2504
|
+
}
|
|
2505
|
+
/**
|
|
2506
|
+
* Toggle code block on current line or selected lines
|
|
2507
|
+
*/
|
|
2508
|
+
toggleCodeBlock(view) {
|
|
2509
|
+
const { state } = view;
|
|
2510
|
+
const { from, to } = state.selection.main;
|
|
2511
|
+
const startLine = state.doc.lineAt(from);
|
|
2512
|
+
const endLine = state.doc.lineAt(to);
|
|
2513
|
+
const prevLineNum = startLine.number > 1 ? startLine.number - 1 : startLine.number;
|
|
2514
|
+
const nextLineNum = endLine.number < state.doc.lines ? endLine.number + 1 : endLine.number;
|
|
2515
|
+
const prevLine = state.doc.line(prevLineNum);
|
|
2516
|
+
const nextLine = state.doc.line(nextLineNum);
|
|
2517
|
+
const isWrapped = prevLine.text.trim().startsWith("```") && nextLine.text.trim() === "```" && prevLineNum !== startLine.number && nextLineNum !== endLine.number;
|
|
2518
|
+
if (isWrapped) {
|
|
2519
|
+
view.dispatch({
|
|
2520
|
+
changes: [
|
|
2521
|
+
{ from: prevLine.from, to: prevLine.to + 1, insert: "" },
|
|
2522
|
+
// Remove opening fence + newline
|
|
2523
|
+
{ from: nextLine.from - 1, to: nextLine.to, insert: "" }
|
|
2524
|
+
// Remove newline + closing fence
|
|
2525
|
+
]
|
|
2526
|
+
});
|
|
2527
|
+
} else {
|
|
2528
|
+
const openFence = "```\n";
|
|
2529
|
+
const closeFence = "\n```";
|
|
2530
|
+
view.dispatch({
|
|
2531
|
+
changes: [
|
|
2532
|
+
{ from: startLine.from, insert: openFence },
|
|
2533
|
+
{ from: endLine.to, insert: closeFence }
|
|
2534
|
+
],
|
|
2535
|
+
selection: { anchor: startLine.from + openFence.length, head: endLine.to + openFence.length }
|
|
2536
|
+
});
|
|
2537
|
+
}
|
|
2538
|
+
return true;
|
|
2539
|
+
}
|
|
2540
|
+
/**
|
|
2541
|
+
* Parse CodeInfo string into structured properties
|
|
2542
|
+
*
|
|
2543
|
+
* @param codeInfo - The raw CodeInfo string (e.g., "tsx line-numbers{5} title=\"hello.tsx\" copy {2-4,5} /Hello/3-5")
|
|
2544
|
+
* @returns Parsed CodeBlockProperties object
|
|
2545
|
+
*
|
|
2546
|
+
* @example
|
|
2547
|
+
* ```typescript
|
|
2548
|
+
* parseCodeInfo("tsx line-numbers{5} title=\"hello.tsx\" copy {2-4,5} /Hello/3-5")
|
|
2549
|
+
* ```
|
|
2550
|
+
*
|
|
2551
|
+
* Returns:
|
|
2552
|
+
* ```json
|
|
2553
|
+
* {
|
|
2554
|
+
* language: "tsx",
|
|
2555
|
+
* lineNumbers: 5,
|
|
2556
|
+
* title: "hello.tsx",
|
|
2557
|
+
* copy: true,
|
|
2558
|
+
* highlightLines: [2,3,4,5],
|
|
2559
|
+
* highlightText: [{ pattern: "Hello", instances: [3,4,5] }]
|
|
2560
|
+
* }
|
|
2561
|
+
* ```
|
|
2562
|
+
*/
|
|
2563
|
+
parseCodeInfo(codeInfo) {
|
|
2564
|
+
const props = { language: "" };
|
|
2565
|
+
if (!codeInfo || !codeInfo.trim()) {
|
|
2566
|
+
return props;
|
|
2567
|
+
}
|
|
2568
|
+
let remaining = codeInfo.trim();
|
|
2569
|
+
const langMatch = remaining.match(/^(\w+)/);
|
|
2570
|
+
if (langMatch && langMatch[1]) {
|
|
2571
|
+
props.language = langMatch[1];
|
|
2572
|
+
remaining = remaining.slice(langMatch[0].length).trim();
|
|
2573
|
+
}
|
|
2574
|
+
const quotedPattern = /(\w+)="([^"]*)"/g;
|
|
2575
|
+
let quotedMatch;
|
|
2576
|
+
while ((quotedMatch = quotedPattern.exec(remaining)) !== null) {
|
|
2577
|
+
const key = quotedMatch[1]?.toLowerCase();
|
|
2578
|
+
const value = quotedMatch[2];
|
|
2579
|
+
if (key === "title" && value !== void 0) {
|
|
2580
|
+
props.title = value;
|
|
2581
|
+
} else if (key === "caption" && value !== void 0) {
|
|
2582
|
+
props.caption = value;
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
remaining = remaining.replace(quotedPattern, "").trim();
|
|
2586
|
+
const lineNumbersMatch = remaining.match(/line-numbers(?:\{(\d+)\})?/);
|
|
2587
|
+
if (lineNumbersMatch) {
|
|
2588
|
+
if (lineNumbersMatch[1]) {
|
|
2589
|
+
props.lineNumbers = parseInt(lineNumbersMatch[1], 10);
|
|
2590
|
+
} else {
|
|
2591
|
+
props.lineNumbers = true;
|
|
2592
|
+
}
|
|
2593
|
+
remaining = remaining.replace(lineNumbersMatch[0], "").trim();
|
|
2594
|
+
}
|
|
2595
|
+
if (/\bcopy\b/.test(remaining)) {
|
|
2596
|
+
props.copy = true;
|
|
2597
|
+
remaining = remaining.replace(/\bcopy\b/, "").trim();
|
|
2598
|
+
}
|
|
2599
|
+
const lineHighlightMatch = remaining.match(/\{([^}]+)\}/);
|
|
2600
|
+
if (lineHighlightMatch && lineHighlightMatch[1]) {
|
|
2601
|
+
const highlightLines = [];
|
|
2602
|
+
const parts = lineHighlightMatch[1].split(",");
|
|
2603
|
+
for (const part of parts) {
|
|
2604
|
+
const trimmed = part.trim();
|
|
2605
|
+
const rangeMatch = trimmed.match(/^(\d+)-(\d+)$/);
|
|
2606
|
+
if (rangeMatch && rangeMatch[1] && rangeMatch[2]) {
|
|
2607
|
+
const start = parseInt(rangeMatch[1], 10);
|
|
2608
|
+
const end = parseInt(rangeMatch[2], 10);
|
|
2609
|
+
for (let i = start; i <= end; i++) {
|
|
2610
|
+
highlightLines.push(i);
|
|
2611
|
+
}
|
|
2612
|
+
} else if (/^\d+$/.test(trimmed)) {
|
|
2613
|
+
highlightLines.push(parseInt(trimmed, 10));
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
if (highlightLines.length > 0) {
|
|
2617
|
+
props.highlightLines = highlightLines;
|
|
2618
|
+
}
|
|
2619
|
+
remaining = remaining.replace(lineHighlightMatch[0], "").trim();
|
|
2620
|
+
}
|
|
2621
|
+
const textHighlightPattern = /\/([^/]+)\/(?:(\d+(?:-\d+)?(?:,\d+(?:-\d+)?)*))?/g;
|
|
2622
|
+
let textMatch;
|
|
2623
|
+
const highlightText = [];
|
|
2624
|
+
while ((textMatch = textHighlightPattern.exec(remaining)) !== null) {
|
|
2625
|
+
if (!textMatch[1]) continue;
|
|
2626
|
+
const highlight = {
|
|
2627
|
+
pattern: textMatch[1]
|
|
2628
|
+
};
|
|
2629
|
+
if (textMatch[2]) {
|
|
2630
|
+
const instanceStr = textMatch[2];
|
|
2631
|
+
const instances = [];
|
|
2632
|
+
const instanceParts = instanceStr.split(",");
|
|
2633
|
+
for (const part of instanceParts) {
|
|
2634
|
+
const rangeMatch = part.match(/^(\d+)-(\d+)$/);
|
|
2635
|
+
if (rangeMatch && rangeMatch[1] && rangeMatch[2]) {
|
|
2636
|
+
const start = parseInt(rangeMatch[1], 10);
|
|
2637
|
+
const end = parseInt(rangeMatch[2], 10);
|
|
2638
|
+
for (let i = start; i <= end; i++) {
|
|
2639
|
+
instances.push(i);
|
|
2640
|
+
}
|
|
2641
|
+
} else if (/^\d+$/.test(part)) {
|
|
2642
|
+
instances.push(parseInt(part, 10));
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
if (instances.length > 0) {
|
|
2646
|
+
highlight.instances = instances;
|
|
2647
|
+
}
|
|
2648
|
+
}
|
|
2649
|
+
highlightText.push(highlight);
|
|
2650
|
+
}
|
|
2651
|
+
if (highlightText.length > 0) {
|
|
2652
|
+
props.highlightText = highlightText;
|
|
2653
|
+
}
|
|
2654
|
+
return props;
|
|
2655
|
+
}
|
|
2656
|
+
/**
|
|
2657
|
+
* Build decorations for inline code and fenced code blocks.
|
|
2658
|
+
* Handles line numbers, highlights, header/caption widgets, and fence visibility.
|
|
2659
|
+
*/
|
|
2660
|
+
buildDecorations(ctx) {
|
|
2661
|
+
const { view: view$1, decorations } = ctx;
|
|
2662
|
+
const tree = language.syntaxTree(view$1.state);
|
|
2663
|
+
tree.iterate({
|
|
2664
|
+
enter: (node) => {
|
|
2665
|
+
const { from, to, name } = node;
|
|
2666
|
+
if (name === "InlineCode") {
|
|
2667
|
+
decorations.push(codeMarkDecorations["inline-code"].range(from, to));
|
|
2668
|
+
const cursorInRange = ctx.selectionOverlapsRange(from, to);
|
|
2669
|
+
if (!cursorInRange) {
|
|
2670
|
+
for (let child = node.node.firstChild; child; child = child.nextSibling) {
|
|
2671
|
+
if (child.name === "CodeMark") {
|
|
2672
|
+
decorations.push(codeMarkDecorations["inline-mark"].range(child.from, child.to));
|
|
2673
|
+
}
|
|
2674
|
+
}
|
|
2675
|
+
}
|
|
2676
|
+
}
|
|
2677
|
+
if (name === "FencedCode") {
|
|
2678
|
+
const nodeLineStart = view$1.state.doc.lineAt(from);
|
|
2679
|
+
const nodeLineEnd = view$1.state.doc.lineAt(to);
|
|
2680
|
+
const cursorInRange = ctx.selectionOverlapsRange(nodeLineStart.from, nodeLineEnd.to);
|
|
2681
|
+
let infoProps = { language: "" };
|
|
2682
|
+
for (let child = node.node.firstChild; child; child = child.nextSibling) {
|
|
2683
|
+
if (child.name === "CodeInfo") {
|
|
2684
|
+
infoProps = this.parseCodeInfo(view$1.state.sliceDoc(child.from, child.to).trim());
|
|
2685
|
+
break;
|
|
2686
|
+
}
|
|
2687
|
+
}
|
|
2688
|
+
const totalCodeLines = nodeLineEnd.number - nodeLineStart.number - 1;
|
|
2689
|
+
const startLineNum = typeof infoProps.lineNumbers === "number" ? infoProps.lineNumbers : 1;
|
|
2690
|
+
const maxLineNum = startLineNum + totalCodeLines - 1;
|
|
2691
|
+
const lineNumWidth = Math.max(String(maxLineNum).length, String(startLineNum).length);
|
|
2692
|
+
let codeLineIndex = 0;
|
|
2693
|
+
let codeContent = "";
|
|
2694
|
+
for (let child = node.node.firstChild; child; child = child.nextSibling) {
|
|
2695
|
+
if (child.name === "CodeText") {
|
|
2696
|
+
codeContent = view$1.state.sliceDoc(child.from, child.to);
|
|
2697
|
+
break;
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2700
|
+
const shouldShowHeader = !cursorInRange && (infoProps.title || infoProps.copy || infoProps.language);
|
|
2701
|
+
const shouldShowCaption = !cursorInRange && infoProps.caption;
|
|
2702
|
+
if (shouldShowHeader) {
|
|
2703
|
+
decorations.push(
|
|
2704
|
+
view.Decoration.widget({
|
|
2705
|
+
widget: new CodeBlockHeaderWidget(infoProps, codeContent),
|
|
2706
|
+
block: false
|
|
2707
|
+
}).range(nodeLineStart.from)
|
|
2708
|
+
);
|
|
2709
|
+
}
|
|
2710
|
+
for (let i = nodeLineStart.number; i <= nodeLineEnd.number; i++) {
|
|
2711
|
+
const line = view$1.state.doc.line(i);
|
|
2712
|
+
const isFenceLine = i === nodeLineStart.number || i === nodeLineEnd.number;
|
|
2713
|
+
const relativeLineNum = startLineNum + codeLineIndex;
|
|
2714
|
+
decorations.push(codeMarkDecorations["code-block-line"].range(line.from));
|
|
2715
|
+
if (i === nodeLineStart.number) {
|
|
2716
|
+
decorations.push(codeMarkDecorations["code-block-line-start"].range(line.from));
|
|
2717
|
+
if (shouldShowHeader) {
|
|
2718
|
+
decorations.push(
|
|
2719
|
+
view.Decoration.line({
|
|
2720
|
+
class: "cm-draftly-code-block-has-header"
|
|
2721
|
+
}).range(line.from)
|
|
2722
|
+
);
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
if (i === nodeLineEnd.number) {
|
|
2726
|
+
decorations.push(codeMarkDecorations["code-block-line-end"].range(line.from));
|
|
2727
|
+
if (shouldShowCaption) {
|
|
2728
|
+
decorations.push(
|
|
2729
|
+
view.Decoration.line({
|
|
2730
|
+
class: "cm-draftly-code-block-has-caption"
|
|
2731
|
+
}).range(line.from)
|
|
2732
|
+
);
|
|
2733
|
+
}
|
|
2734
|
+
}
|
|
2735
|
+
if (!isFenceLine && infoProps.lineNumbers) {
|
|
2736
|
+
decorations.push(
|
|
2737
|
+
view.Decoration.line({
|
|
2738
|
+
class: "cm-draftly-code-line-numbered",
|
|
2739
|
+
attributes: {
|
|
2740
|
+
"data-line-num": String(relativeLineNum),
|
|
2741
|
+
style: `--line-num-width: ${lineNumWidth}ch`
|
|
2742
|
+
}
|
|
2743
|
+
}).range(line.from)
|
|
2744
|
+
);
|
|
2745
|
+
}
|
|
2746
|
+
if (!isFenceLine && infoProps.highlightLines) {
|
|
2747
|
+
if (infoProps.highlightLines.includes(codeLineIndex + 1)) {
|
|
2748
|
+
decorations.push(codeMarkDecorations["code-line-highlight"].range(line.from));
|
|
2749
|
+
}
|
|
2750
|
+
}
|
|
2751
|
+
if (!isFenceLine && infoProps.highlightText && infoProps.highlightText.length > 0) {
|
|
2752
|
+
const lineText = view$1.state.sliceDoc(line.from, line.to);
|
|
2753
|
+
for (const textHighlight of infoProps.highlightText) {
|
|
2754
|
+
try {
|
|
2755
|
+
const regex = new RegExp(textHighlight.pattern, "g");
|
|
2756
|
+
let match;
|
|
2757
|
+
let matchIndex = 0;
|
|
2758
|
+
while ((match = regex.exec(lineText)) !== null) {
|
|
2759
|
+
matchIndex++;
|
|
2760
|
+
const shouldHighlight = !textHighlight.instances || textHighlight.instances.includes(matchIndex);
|
|
2761
|
+
if (shouldHighlight) {
|
|
2762
|
+
const matchFrom = line.from + match.index;
|
|
2763
|
+
const matchTo = matchFrom + match[0].length;
|
|
2764
|
+
decorations.push(codeMarkDecorations["code-text-highlight"].range(matchFrom, matchTo));
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
} catch {
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2771
|
+
if (!isFenceLine) {
|
|
2772
|
+
codeLineIndex++;
|
|
2773
|
+
}
|
|
2774
|
+
}
|
|
2775
|
+
for (let child = node.node.firstChild; child; child = child.nextSibling) {
|
|
2776
|
+
if (child.name === "CodeMark" || child.name === "CodeInfo") {
|
|
2777
|
+
if (cursorInRange) {
|
|
2778
|
+
decorations.push(codeMarkDecorations["code-fence"].range(child.from, child.to));
|
|
2779
|
+
} else {
|
|
2780
|
+
decorations.push(codeMarkDecorations["code-hidden"].range(child.from, child.to));
|
|
2781
|
+
}
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2784
|
+
if (!cursorInRange && infoProps.caption) {
|
|
2785
|
+
decorations.push(
|
|
2786
|
+
view.Decoration.widget({
|
|
2787
|
+
widget: new CodeBlockCaptionWidget(infoProps.caption),
|
|
2788
|
+
block: false,
|
|
2789
|
+
side: 1
|
|
2790
|
+
// After the content
|
|
2791
|
+
}).range(nodeLineEnd.to)
|
|
2792
|
+
);
|
|
2793
|
+
}
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2796
|
+
});
|
|
2797
|
+
}
|
|
2798
|
+
/**
|
|
2799
|
+
* Render code elements to HTML for static preview.
|
|
2800
|
+
* Applies syntax highlighting using @lezer/highlight.
|
|
2801
|
+
*/
|
|
2802
|
+
renderToHTML(node, children, ctx) {
|
|
2803
|
+
if (node.name === "CodeMark") {
|
|
2804
|
+
return "";
|
|
2805
|
+
}
|
|
2806
|
+
if (node.name === "InlineCode") {
|
|
2807
|
+
let content = ctx.sliceDoc(node.from, node.to);
|
|
2808
|
+
const match = content.match(/^`+(.+?)`+$/s);
|
|
2809
|
+
if (match && match[1]) {
|
|
2810
|
+
content = match[1];
|
|
2811
|
+
}
|
|
2812
|
+
return `<code class="cm-draftly-code-inline" style="padding: 0.1rem 0.25rem">${ctx.sanitize(content)}</code>`;
|
|
2813
|
+
}
|
|
2814
|
+
if (node.name === "FencedCode") {
|
|
2815
|
+
const content = ctx.sliceDoc(node.from, node.to);
|
|
2816
|
+
const lines = content.split("\n");
|
|
2817
|
+
const firstLine = lines[0] || "";
|
|
2818
|
+
const infoMatch = firstLine.match(/^```(.*)$/);
|
|
2819
|
+
const infoString = infoMatch?.[1]?.trim() || "";
|
|
2820
|
+
const props = this.parseCodeInfo(infoString);
|
|
2821
|
+
const codeLines = lines.slice(1, -1);
|
|
2822
|
+
const code = codeLines.join("\n");
|
|
2823
|
+
let html = "";
|
|
2824
|
+
html += `<div class="cm-draftly-code-container">`;
|
|
2825
|
+
const showHeader = props.title || props.copy || props.language;
|
|
2826
|
+
if (showHeader) {
|
|
2827
|
+
html += `<div class="cm-draftly-code-header">`;
|
|
2828
|
+
html += `<div class="cm-draftly-code-header-left">`;
|
|
2829
|
+
if (props.title) {
|
|
2830
|
+
html += `<span class="cm-draftly-code-header-title">${ctx.sanitize(props.title)}</span>`;
|
|
2831
|
+
} else if (props.language) {
|
|
2832
|
+
html += `<span class="cm-draftly-code-header-lang">${ctx.sanitize(props.language)}</span>`;
|
|
2833
|
+
}
|
|
2834
|
+
html += `</div>`;
|
|
2835
|
+
if (props.copy !== false) {
|
|
2836
|
+
html += `<div class="cm-draftly-code-header-right">`;
|
|
2837
|
+
const encodedCode = typeof btoa !== "undefined" ? btoa(encodeURIComponent(code)) : Buffer.from(code).toString("base64");
|
|
2838
|
+
html += `<button class="cm-draftly-code-copy-btn" type="button" title="Copy code" data-code="${encodedCode}" data-encoded="true">`;
|
|
2839
|
+
html += `<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>`;
|
|
2840
|
+
html += `</button>`;
|
|
2841
|
+
html += `</div>`;
|
|
2842
|
+
}
|
|
2843
|
+
html += `</div>`;
|
|
2844
|
+
}
|
|
2845
|
+
const startLineNum = typeof props.lineNumbers === "number" ? props.lineNumbers : 1;
|
|
2846
|
+
const lineNumWidth = String(startLineNum + codeLines.length - 1).length;
|
|
2847
|
+
const hasHeader = showHeader ? " cm-draftly-code-block-has-header" : "";
|
|
2848
|
+
const hasCaption = props.caption ? " cm-draftly-code-block-has-caption" : "";
|
|
2849
|
+
html += `<pre class="cm-draftly-code-block${hasHeader}${hasCaption}"${props.language ? ` data-lang="${ctx.sanitize(props.language)}"` : ""}>`;
|
|
2850
|
+
html += `<code>`;
|
|
2851
|
+
codeLines.forEach((line, index) => {
|
|
2852
|
+
const lineNum = startLineNum + index;
|
|
2853
|
+
const isHighlighted = props.highlightLines?.includes(index + 1);
|
|
2854
|
+
const lineClasses = ["cm-draftly-code-line"];
|
|
2855
|
+
if (isHighlighted) lineClasses.push("cm-draftly-code-line-highlight");
|
|
2856
|
+
if (props.lineNumbers) lineClasses.push("cm-draftly-code-line-numbered");
|
|
2857
|
+
const lineAttrs = [`class="${lineClasses.join(" ")}"`];
|
|
2858
|
+
if (props.lineNumbers) {
|
|
2859
|
+
lineAttrs.push(`data-line-num="${lineNum}"`);
|
|
2860
|
+
lineAttrs.push(`style="--line-num-width: ${lineNumWidth}ch"`);
|
|
2861
|
+
}
|
|
2862
|
+
let lineContent = this.highlightCodeLine(line, props.language || "", ctx);
|
|
2863
|
+
if (props.highlightText && props.highlightText.length > 0) {
|
|
2864
|
+
lineContent = this.applyTextHighlights(lineContent, props.highlightText);
|
|
2865
|
+
}
|
|
2866
|
+
html += `<span ${lineAttrs.join(" ")}>${lineContent || " "}</span>`;
|
|
2867
|
+
});
|
|
2868
|
+
html += `</code></pre>`;
|
|
2869
|
+
if (props.caption) {
|
|
2870
|
+
html += `<div class="cm-draftly-code-caption">${ctx.sanitize(props.caption)}</div>`;
|
|
2871
|
+
}
|
|
2872
|
+
html += `</div>`;
|
|
2873
|
+
return html;
|
|
2874
|
+
}
|
|
2875
|
+
if (node.name === "CodeInfo" || node.name === "CodeText") {
|
|
2876
|
+
return "";
|
|
2877
|
+
}
|
|
2878
|
+
return null;
|
|
2879
|
+
}
|
|
2880
|
+
/**
|
|
2881
|
+
* Highlight a single line of code using the language's Lezer parser.
|
|
2882
|
+
* Falls back to sanitized plain text if the language is not supported.
|
|
2883
|
+
*/
|
|
2884
|
+
highlightCodeLine(line, lang, ctx) {
|
|
2885
|
+
if (!lang || !line) {
|
|
2886
|
+
return ctx.sanitize(line);
|
|
2887
|
+
}
|
|
2888
|
+
const langDesc = languageData.languages.find(
|
|
2889
|
+
(l) => l.name.toLowerCase() === lang.toLowerCase() || l.alias && l.alias.includes(lang.toLowerCase())
|
|
2890
|
+
);
|
|
2891
|
+
if (!langDesc || !langDesc.support) {
|
|
2892
|
+
return ctx.sanitize(line);
|
|
2893
|
+
}
|
|
2894
|
+
try {
|
|
2895
|
+
const parser = langDesc.support.language.parser;
|
|
2896
|
+
const tree = parser.parse(line);
|
|
2897
|
+
let result = "";
|
|
2898
|
+
highlight.highlightCode(
|
|
2899
|
+
line,
|
|
2900
|
+
tree,
|
|
2901
|
+
highlight.classHighlighter,
|
|
2902
|
+
(text, classes2) => {
|
|
2903
|
+
if (classes2) {
|
|
2904
|
+
result += `<span class="${classes2}">${ctx.sanitize(text)}</span>`;
|
|
2905
|
+
} else {
|
|
2906
|
+
result += ctx.sanitize(text);
|
|
2907
|
+
}
|
|
2908
|
+
},
|
|
2909
|
+
() => {
|
|
2910
|
+
}
|
|
2911
|
+
// No newlines for single line
|
|
2912
|
+
);
|
|
2913
|
+
return result;
|
|
2914
|
+
} catch {
|
|
2915
|
+
return ctx.sanitize(line);
|
|
2916
|
+
}
|
|
2917
|
+
}
|
|
2918
|
+
/**
|
|
2919
|
+
* Apply text highlights (regex patterns) to already syntax-highlighted HTML.
|
|
2920
|
+
* Wraps matched patterns in `<mark>` elements.
|
|
2921
|
+
*/
|
|
2922
|
+
applyTextHighlights(htmlContent, highlights) {
|
|
2923
|
+
let result = htmlContent;
|
|
2924
|
+
for (const highlight of highlights) {
|
|
2925
|
+
try {
|
|
2926
|
+
const regex = new RegExp(`(${highlight.pattern})`, "g");
|
|
2927
|
+
let matchCount = 0;
|
|
2928
|
+
result = result.replace(regex, (match) => {
|
|
2929
|
+
matchCount++;
|
|
2930
|
+
const shouldHighlight = !highlight.instances || highlight.instances.includes(matchCount);
|
|
2931
|
+
if (shouldHighlight) {
|
|
2932
|
+
return `<mark class="cm-draftly-code-text-highlight">${match}</mark>`;
|
|
2933
|
+
}
|
|
2934
|
+
return match;
|
|
2935
|
+
});
|
|
2936
|
+
} catch {
|
|
2937
|
+
}
|
|
2938
|
+
}
|
|
2939
|
+
return result;
|
|
2940
|
+
}
|
|
2941
|
+
};
|
|
2942
|
+
var theme10 = chunkKDEDLC3D_cjs.createTheme({
|
|
2943
|
+
default: {
|
|
2944
|
+
// Inline code
|
|
2945
|
+
".cm-draftly-code-inline": {
|
|
2946
|
+
fontFamily: "var(--font-jetbrains-mono, monospace)",
|
|
2947
|
+
fontSize: "0.9rem",
|
|
2948
|
+
backgroundColor: "rgba(0, 0, 0, 0.05)",
|
|
2949
|
+
padding: "0.1rem 0.25rem",
|
|
2950
|
+
border: "1px solid var(--color-border)",
|
|
2951
|
+
borderRadius: "3px"
|
|
2952
|
+
},
|
|
2953
|
+
// Fenced code block lines
|
|
2954
|
+
".cm-draftly-code-block-line": {
|
|
2955
|
+
"--radius": "0.375rem",
|
|
2956
|
+
fontFamily: "var(--font-jetbrains-mono, monospace)",
|
|
2957
|
+
fontSize: "0.9rem",
|
|
2958
|
+
backgroundColor: "rgba(0, 0, 0, 0.03)",
|
|
2959
|
+
padding: "0 1rem !important",
|
|
2960
|
+
lineHeight: "1.5",
|
|
2961
|
+
borderLeft: "1px solid var(--color-border)",
|
|
2962
|
+
borderRight: "1px solid var(--color-border)"
|
|
2963
|
+
},
|
|
2964
|
+
// First line of code block
|
|
2965
|
+
".cm-draftly-code-block-line-start": {
|
|
2966
|
+
borderTopLeftRadius: "var(--radius)",
|
|
2967
|
+
borderTopRightRadius: "var(--radius)",
|
|
2968
|
+
position: "relative",
|
|
2969
|
+
overflow: "hidden",
|
|
2970
|
+
borderTop: "1px solid var(--color-border)",
|
|
2971
|
+
paddingBottom: "0.5rem !important"
|
|
2972
|
+
},
|
|
2973
|
+
// Remove top radius when header is present
|
|
2974
|
+
".cm-draftly-code-block-has-header": {
|
|
2975
|
+
padding: "0 !important",
|
|
2976
|
+
paddingBottom: "0.5rem !important"
|
|
2977
|
+
},
|
|
2978
|
+
// Code block header widget
|
|
2979
|
+
".cm-draftly-code-header": {
|
|
2980
|
+
display: "flex",
|
|
2981
|
+
justifyContent: "space-between",
|
|
2982
|
+
alignItems: "center",
|
|
2983
|
+
padding: "0.25rem 1rem",
|
|
2984
|
+
backgroundColor: "rgba(0, 0, 0, 0.06)",
|
|
2985
|
+
fontFamily: "var(--font-jetbrains-mono, monospace)",
|
|
2986
|
+
fontSize: "0.85rem"
|
|
2987
|
+
},
|
|
2988
|
+
".cm-draftly-code-header-left": {
|
|
2989
|
+
display: "flex",
|
|
2990
|
+
alignItems: "center",
|
|
2991
|
+
gap: "0.5rem"
|
|
2992
|
+
},
|
|
2993
|
+
".cm-draftly-code-header-title": {
|
|
2994
|
+
color: "var(--color-text, inherit)",
|
|
2995
|
+
fontWeight: "500"
|
|
2996
|
+
},
|
|
2997
|
+
".cm-draftly-code-header-lang": {
|
|
2998
|
+
color: "#6a737d",
|
|
2999
|
+
opacity: "0.8"
|
|
3000
|
+
},
|
|
3001
|
+
".cm-draftly-code-header-right": {
|
|
3002
|
+
display: "flex",
|
|
3003
|
+
alignItems: "center",
|
|
3004
|
+
gap: "0.5rem"
|
|
3005
|
+
},
|
|
3006
|
+
".cm-draftly-code-copy-btn": {
|
|
3007
|
+
display: "flex",
|
|
3008
|
+
alignItems: "center",
|
|
3009
|
+
justifyContent: "center",
|
|
3010
|
+
padding: "0.25rem",
|
|
3011
|
+
backgroundColor: "transparent",
|
|
3012
|
+
border: "none",
|
|
3013
|
+
borderRadius: "4px",
|
|
3014
|
+
cursor: "pointer",
|
|
3015
|
+
color: "#6a737d",
|
|
3016
|
+
transition: "color 0.2s, background-color 0.2s"
|
|
3017
|
+
},
|
|
3018
|
+
".cm-draftly-code-copy-btn:hover": {
|
|
3019
|
+
backgroundColor: "rgba(0, 0, 0, 0.1)",
|
|
3020
|
+
color: "var(--color-text, inherit)"
|
|
3021
|
+
},
|
|
3022
|
+
".cm-draftly-code-copy-btn.copied": {
|
|
3023
|
+
color: "#22c55e"
|
|
3024
|
+
},
|
|
3025
|
+
// Caption (below code block)
|
|
3026
|
+
".cm-draftly-code-block-has-caption": {
|
|
3027
|
+
padding: "0 !important",
|
|
3028
|
+
paddingTop: "0.5rem !important"
|
|
3029
|
+
},
|
|
3030
|
+
".cm-draftly-code-caption": {
|
|
3031
|
+
textAlign: "center",
|
|
3032
|
+
fontSize: "0.85rem",
|
|
3033
|
+
color: "#6a737d",
|
|
3034
|
+
fontStyle: "italic",
|
|
3035
|
+
padding: "0.25rem 1rem",
|
|
3036
|
+
backgroundColor: "rgba(0, 0, 0, 0.06)"
|
|
3037
|
+
},
|
|
3038
|
+
// Last line of code block
|
|
3039
|
+
".cm-draftly-code-block-line-end": {
|
|
3040
|
+
borderBottomLeftRadius: "var(--radius)",
|
|
3041
|
+
borderBottomRightRadius: "var(--radius)",
|
|
3042
|
+
borderBottom: "1px solid var(--color-border)",
|
|
3043
|
+
paddingTop: "0.5rem !important"
|
|
3044
|
+
},
|
|
3045
|
+
".cm-draftly-code-block-line-end br": {
|
|
3046
|
+
display: "none"
|
|
3047
|
+
},
|
|
3048
|
+
// Fence markers (```)
|
|
3049
|
+
".cm-draftly-code-fence": {
|
|
3050
|
+
color: "#6a737d",
|
|
3051
|
+
fontFamily: "var(--font-jetbrains-mono, monospace)"
|
|
3052
|
+
},
|
|
3053
|
+
// Line numbers
|
|
3054
|
+
".cm-draftly-code-line-numbered": {
|
|
3055
|
+
paddingLeft: "calc(var(--line-num-width, 2ch) + 1rem) !important",
|
|
3056
|
+
position: "relative"
|
|
3057
|
+
},
|
|
3058
|
+
".cm-draftly-code-line-numbered::before": {
|
|
3059
|
+
content: "attr(data-line-num)",
|
|
3060
|
+
position: "absolute",
|
|
3061
|
+
left: "0.5rem",
|
|
3062
|
+
top: "0.2rem",
|
|
3063
|
+
width: "var(--line-num-width, 2ch)",
|
|
3064
|
+
textAlign: "right",
|
|
3065
|
+
color: "#6a737d",
|
|
3066
|
+
opacity: "0.6",
|
|
3067
|
+
fontFamily: "var(--font-jetbrains-mono, monospace)",
|
|
3068
|
+
fontSize: "0.85rem",
|
|
3069
|
+
userSelect: "none"
|
|
3070
|
+
},
|
|
3071
|
+
// Preview: code lines (need block display for full-width highlights)
|
|
3072
|
+
".cm-draftly-code-line": {
|
|
3073
|
+
display: "block",
|
|
3074
|
+
position: "relative",
|
|
3075
|
+
paddingLeft: "1rem",
|
|
3076
|
+
paddingRight: "1rem",
|
|
3077
|
+
lineHeight: "1.5",
|
|
3078
|
+
borderLeft: "3px solid transparent"
|
|
3079
|
+
},
|
|
3080
|
+
// Line highlight
|
|
3081
|
+
".cm-draftly-code-line-highlight": {
|
|
3082
|
+
backgroundColor: "rgba(255, 220, 100, 0.2) !important",
|
|
3083
|
+
borderLeft: "3px solid #f0b429 !important"
|
|
3084
|
+
},
|
|
3085
|
+
// Text highlight
|
|
3086
|
+
".cm-draftly-code-text-highlight": {
|
|
3087
|
+
backgroundColor: "rgba(255, 220, 100, 0.4)",
|
|
3088
|
+
borderRadius: "2px",
|
|
3089
|
+
padding: "0.1rem 0"
|
|
3090
|
+
},
|
|
3091
|
+
// Preview: container wrapper
|
|
3092
|
+
".cm-draftly-code-container": {
|
|
3093
|
+
margin: "1rem 0",
|
|
3094
|
+
borderRadius: "var(--radius)",
|
|
3095
|
+
overflow: "hidden",
|
|
3096
|
+
border: "1px solid var(--color-border)"
|
|
3097
|
+
},
|
|
3098
|
+
// Preview: header inside container
|
|
3099
|
+
".cm-draftly-code-container .cm-draftly-code-header": {
|
|
3100
|
+
borderRadius: "0",
|
|
3101
|
+
border: "none",
|
|
3102
|
+
borderBottom: "1px solid var(--color-border)"
|
|
3103
|
+
},
|
|
3104
|
+
// Preview: code block inside container
|
|
3105
|
+
".cm-draftly-code-container .cm-draftly-code-block": {
|
|
3106
|
+
margin: "0",
|
|
3107
|
+
borderRadius: "0",
|
|
3108
|
+
border: "none",
|
|
3109
|
+
whiteSpace: "pre-wrap"
|
|
3110
|
+
},
|
|
3111
|
+
// Preview: caption inside container
|
|
3112
|
+
".cm-draftly-code-container .cm-draftly-code-caption": {
|
|
3113
|
+
borderTop: "1px solid var(--color-border)"
|
|
3114
|
+
},
|
|
3115
|
+
// Preview: standalone code block (not in container)
|
|
3116
|
+
".cm-draftly-code-block": {
|
|
3117
|
+
fontFamily: "var(--font-jetbrains-mono, monospace)",
|
|
3118
|
+
fontSize: "0.9rem",
|
|
3119
|
+
backgroundColor: "rgba(0, 0, 0, 0.03)",
|
|
3120
|
+
padding: "1rem",
|
|
3121
|
+
overflow: "auto",
|
|
3122
|
+
position: "relative",
|
|
3123
|
+
borderRadius: "var(--radius)",
|
|
3124
|
+
border: "1px solid var(--color-border)"
|
|
3125
|
+
},
|
|
3126
|
+
// Preview: code block with header (remove top radius)
|
|
3127
|
+
".cm-draftly-code-block.cm-draftly-code-block-has-header": {
|
|
3128
|
+
borderTopLeftRadius: "0",
|
|
3129
|
+
borderTopRightRadius: "0",
|
|
3130
|
+
borderTop: "none",
|
|
3131
|
+
margin: "0",
|
|
3132
|
+
paddingTop: "0.5rem !important"
|
|
3133
|
+
},
|
|
3134
|
+
// Preview: code block with caption (remove bottom radius)
|
|
3135
|
+
".cm-draftly-code-block.cm-draftly-code-block-has-caption": {
|
|
3136
|
+
borderBottomLeftRadius: "0",
|
|
3137
|
+
borderBottomRightRadius: "0",
|
|
3138
|
+
borderBottom: "none",
|
|
3139
|
+
paddingBottom: "0.5rem !important"
|
|
3140
|
+
}
|
|
3141
|
+
},
|
|
3142
|
+
dark: {
|
|
3143
|
+
".cm-draftly-code-inline": {
|
|
3144
|
+
backgroundColor: "rgba(255, 255, 255, 0.1)"
|
|
3145
|
+
},
|
|
3146
|
+
".cm-draftly-code-block-line": {
|
|
3147
|
+
backgroundColor: "rgba(255, 255, 255, 0.05)"
|
|
3148
|
+
},
|
|
3149
|
+
".cm-draftly-code-fence": {
|
|
3150
|
+
color: "#8b949e"
|
|
3151
|
+
},
|
|
3152
|
+
".cm-draftly-code-block": {
|
|
3153
|
+
backgroundColor: "rgba(255, 255, 255, 0.05)"
|
|
3154
|
+
},
|
|
3155
|
+
".cm-draftly-code-header": {
|
|
3156
|
+
backgroundColor: "rgba(255, 255, 255, 0.08)"
|
|
3157
|
+
},
|
|
3158
|
+
".cm-draftly-code-header-lang": {
|
|
3159
|
+
color: "#8b949e"
|
|
3160
|
+
},
|
|
3161
|
+
".cm-draftly-code-copy-btn": {
|
|
3162
|
+
color: "#8b949e"
|
|
3163
|
+
},
|
|
3164
|
+
".cm-draftly-code-copy-btn:hover": {
|
|
3165
|
+
backgroundColor: "rgba(255, 255, 255, 0.1)"
|
|
3166
|
+
},
|
|
3167
|
+
".cm-draftly-code-caption": {
|
|
3168
|
+
backgroundColor: "rgba(255, 255, 255, 0.05)"
|
|
3169
|
+
},
|
|
3170
|
+
".cm-draftly-code-line-numbered::before": {
|
|
3171
|
+
color: "#8b949e"
|
|
3172
|
+
},
|
|
3173
|
+
".cm-draftly-code-line-highlight": {
|
|
3174
|
+
backgroundColor: "rgba(255, 220, 100, 0.15) !important",
|
|
3175
|
+
borderLeft: "3px solid #d9a520 !important"
|
|
3176
|
+
},
|
|
3177
|
+
".cm-draftly-code-text-highlight": {
|
|
3178
|
+
backgroundColor: "rgba(255, 220, 100, 0.3)"
|
|
3179
|
+
}
|
|
3180
|
+
}
|
|
3181
|
+
});
|
|
3182
|
+
var quoteMarkDecorations = {
|
|
3183
|
+
/** Decoration for the > marker */
|
|
3184
|
+
"quote-mark": view.Decoration.replace({}),
|
|
3185
|
+
/** Decoration for the quote content */
|
|
3186
|
+
"quote-content": view.Decoration.mark({ class: "cm-draftly-quote-content" })
|
|
3187
|
+
};
|
|
3188
|
+
var quoteLineDecorations = {
|
|
3189
|
+
/** Decoration for blockquote lines */
|
|
3190
|
+
"quote-line": view.Decoration.line({ class: "cm-draftly-quote-line" })
|
|
3191
|
+
};
|
|
3192
|
+
var QuotePlugin = class extends chunk72ZYRGRT_cjs.DecorationPlugin {
|
|
3193
|
+
name = "quote";
|
|
3194
|
+
version = "1.0.0";
|
|
3195
|
+
decorationPriority = 10;
|
|
3196
|
+
requiredNodes = ["Blockquote", "QuoteMark"];
|
|
3197
|
+
/**
|
|
3198
|
+
* Constructor - calls super constructor
|
|
3199
|
+
*/
|
|
3200
|
+
constructor() {
|
|
3201
|
+
super();
|
|
3202
|
+
}
|
|
3203
|
+
/**
|
|
3204
|
+
* Plugin theme
|
|
3205
|
+
*/
|
|
3206
|
+
get theme() {
|
|
3207
|
+
return theme11;
|
|
3208
|
+
}
|
|
3209
|
+
/**
|
|
3210
|
+
* Build blockquote decorations by iterating the syntax tree
|
|
3211
|
+
*/
|
|
3212
|
+
buildDecorations(ctx) {
|
|
3213
|
+
const { view, decorations } = ctx;
|
|
3214
|
+
const tree = language.syntaxTree(view.state);
|
|
3215
|
+
tree.iterate({
|
|
3216
|
+
enter: (node) => {
|
|
3217
|
+
const { from, to, name } = node;
|
|
3218
|
+
if (name !== "Blockquote") {
|
|
3219
|
+
return;
|
|
3220
|
+
}
|
|
3221
|
+
const startLine = view.state.doc.lineAt(from);
|
|
3222
|
+
const endLine = view.state.doc.lineAt(to);
|
|
3223
|
+
for (let lineNum = startLine.number; lineNum <= endLine.number; lineNum++) {
|
|
3224
|
+
const line = view.state.doc.line(lineNum);
|
|
3225
|
+
decorations.push(quoteLineDecorations["quote-line"].range(line.from));
|
|
3226
|
+
}
|
|
3227
|
+
decorations.push(quoteMarkDecorations["quote-content"].range(from, to));
|
|
3228
|
+
const cursorInNode = ctx.selectionOverlapsRange(from, to);
|
|
3229
|
+
if (!cursorInNode) {
|
|
3230
|
+
this.hideQuoteMarks(node.node, decorations, view);
|
|
3231
|
+
}
|
|
3232
|
+
}
|
|
3233
|
+
});
|
|
3234
|
+
}
|
|
3235
|
+
/**
|
|
3236
|
+
* Recursively find and hide quote marks
|
|
3237
|
+
*/
|
|
3238
|
+
hideQuoteMarks(node, decorations, view) {
|
|
3239
|
+
let child = node.firstChild;
|
|
3240
|
+
while (child) {
|
|
3241
|
+
if (child.name === "QuoteMark") {
|
|
3242
|
+
const line = view.state.doc.lineAt(child.from);
|
|
3243
|
+
const markEnd = Math.min(child.to + 1, line.to);
|
|
3244
|
+
decorations.push(quoteMarkDecorations["quote-mark"].range(child.from, markEnd));
|
|
3245
|
+
}
|
|
3246
|
+
if (child.name === "Blockquote") {
|
|
3247
|
+
this.hideQuoteMarks(child, decorations, view);
|
|
3248
|
+
}
|
|
3249
|
+
child = child.nextSibling;
|
|
3250
|
+
}
|
|
3251
|
+
}
|
|
3252
|
+
renderToHTML(node, children) {
|
|
3253
|
+
if (node.name === "QuoteMark") {
|
|
3254
|
+
return "";
|
|
3255
|
+
}
|
|
3256
|
+
if (node.name !== "Blockquote") {
|
|
3257
|
+
return null;
|
|
3258
|
+
}
|
|
3259
|
+
return `<blockquote class="cm-draftly-quote-line"><div class="cm-draftly-quote-content">${children}</div></blockquote>
|
|
3260
|
+
`;
|
|
3261
|
+
}
|
|
3262
|
+
};
|
|
3263
|
+
var theme11 = chunkKDEDLC3D_cjs.createTheme({
|
|
3264
|
+
default: {
|
|
3265
|
+
// Line styling with left border
|
|
3266
|
+
".cm-draftly-quote-line": {
|
|
3267
|
+
borderLeft: "3px solid currentColor",
|
|
3268
|
+
paddingLeft: "1em !important",
|
|
3269
|
+
paddingTop: "0.25em !important",
|
|
3270
|
+
paddingBottom: "0.25em !important",
|
|
3271
|
+
marginLeft: "0.25em",
|
|
3272
|
+
opacity: "0.85"
|
|
3273
|
+
},
|
|
3274
|
+
// Quote content styling
|
|
3275
|
+
".cm-draftly-quote-content": {
|
|
3276
|
+
fontStyle: "italic"
|
|
3277
|
+
}
|
|
3278
|
+
}
|
|
3279
|
+
});
|
|
3280
|
+
var hrLineDecoration = view.Decoration.line({ class: "cm-draftly-hr-line" });
|
|
3281
|
+
var hrMarkDecoration = view.Decoration.replace({});
|
|
3282
|
+
var HRPlugin = class extends chunk72ZYRGRT_cjs.DecorationPlugin {
|
|
3283
|
+
name = "hr";
|
|
3284
|
+
version = "1.0.0";
|
|
3285
|
+
decorationPriority = 10;
|
|
3286
|
+
requiredNodes = ["HorizontalRule"];
|
|
3287
|
+
/**
|
|
3288
|
+
* Constructor - calls super constructor
|
|
3289
|
+
*/
|
|
3290
|
+
constructor() {
|
|
3291
|
+
super();
|
|
3292
|
+
}
|
|
3293
|
+
/**
|
|
3294
|
+
* Plugin theme
|
|
3295
|
+
*/
|
|
3296
|
+
get theme() {
|
|
3297
|
+
return theme12;
|
|
3298
|
+
}
|
|
3299
|
+
/**
|
|
3300
|
+
* Build horizontal rule decorations by iterating the syntax tree
|
|
3301
|
+
*/
|
|
3302
|
+
buildDecorations(ctx) {
|
|
3303
|
+
const { view, decorations } = ctx;
|
|
3304
|
+
const tree = language.syntaxTree(view.state);
|
|
3305
|
+
tree.iterate({
|
|
3306
|
+
enter: (node) => {
|
|
3307
|
+
const { from, to, name } = node;
|
|
3308
|
+
if (name !== "HorizontalRule") {
|
|
3309
|
+
return;
|
|
3310
|
+
}
|
|
3311
|
+
const line = view.state.doc.lineAt(from);
|
|
3312
|
+
decorations.push(hrLineDecoration.range(line.from));
|
|
3313
|
+
const cursorInNode = ctx.selectionOverlapsRange(from, to);
|
|
3314
|
+
if (!cursorInNode) {
|
|
3315
|
+
const markEnd = Math.min(to, line.to);
|
|
3316
|
+
decorations.push(hrMarkDecoration.range(from, markEnd));
|
|
3317
|
+
}
|
|
3318
|
+
}
|
|
3319
|
+
});
|
|
3320
|
+
}
|
|
3321
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
3322
|
+
renderToHTML(node, _children) {
|
|
3323
|
+
if (node.name !== "HorizontalRule") {
|
|
3324
|
+
return null;
|
|
3325
|
+
}
|
|
3326
|
+
return `<hr class="cm-draftly-hr-line" />
|
|
3327
|
+
`;
|
|
3328
|
+
}
|
|
3329
|
+
};
|
|
3330
|
+
var theme12 = chunkKDEDLC3D_cjs.createTheme({
|
|
3331
|
+
default: {
|
|
3332
|
+
// Line styling — displays a centered horizontal line
|
|
3333
|
+
".cm-draftly-hr-line": {
|
|
3334
|
+
display: "flex",
|
|
3335
|
+
alignItems: "center",
|
|
3336
|
+
paddingTop: "0.75em",
|
|
3337
|
+
paddingBottom: "0.75em",
|
|
3338
|
+
border: "none",
|
|
3339
|
+
"&::after": {
|
|
3340
|
+
content: '""',
|
|
3341
|
+
flex: "1",
|
|
3342
|
+
height: "2px",
|
|
3343
|
+
background: "currentColor",
|
|
3344
|
+
opacity: "0.3"
|
|
3345
|
+
}
|
|
3346
|
+
}
|
|
3347
|
+
}
|
|
3348
|
+
});
|
|
3349
|
+
|
|
3350
|
+
// src/plugins/index.ts
|
|
3351
|
+
var essentialPlugins = [
|
|
3352
|
+
new ParagraphPlugin(),
|
|
3353
|
+
new HeadingPlugin(),
|
|
3354
|
+
new InlinePlugin(),
|
|
3355
|
+
new LinkPlugin(),
|
|
3356
|
+
new ListPlugin(),
|
|
3357
|
+
new HTMLPlugin(),
|
|
3358
|
+
new ImagePlugin(),
|
|
3359
|
+
new MathPlugin(),
|
|
3360
|
+
new MermaidPlugin(),
|
|
3361
|
+
new CodePlugin(),
|
|
3362
|
+
new QuotePlugin(),
|
|
3363
|
+
new HRPlugin()
|
|
3364
|
+
];
|
|
3365
|
+
var allPlugins = [...essentialPlugins];
|
|
3366
|
+
|
|
3367
|
+
exports.CodePlugin = CodePlugin;
|
|
3368
|
+
exports.HRPlugin = HRPlugin;
|
|
3369
|
+
exports.HTMLPlugin = HTMLPlugin;
|
|
3370
|
+
exports.HeadingPlugin = HeadingPlugin;
|
|
3371
|
+
exports.ImagePlugin = ImagePlugin;
|
|
3372
|
+
exports.InlinePlugin = InlinePlugin;
|
|
3373
|
+
exports.LinkPlugin = LinkPlugin;
|
|
3374
|
+
exports.ListPlugin = ListPlugin;
|
|
3375
|
+
exports.MathPlugin = MathPlugin;
|
|
3376
|
+
exports.MermaidPlugin = MermaidPlugin;
|
|
3377
|
+
exports.ParagraphPlugin = ParagraphPlugin;
|
|
3378
|
+
exports.QuotePlugin = QuotePlugin;
|
|
3379
|
+
exports.allPlugins = allPlugins;
|
|
3380
|
+
exports.essentialPlugins = essentialPlugins;
|
|
3381
|
+
//# sourceMappingURL=chunk-2B3A3VSQ.cjs.map
|
|
3382
|
+
//# sourceMappingURL=chunk-2B3A3VSQ.cjs.map
|