comark-ui 0.7.1 → 0.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/client-Bjd6VcGQ.mjs +241 -0
- package/dist/client.d.mts +11 -1
- package/dist/client.mjs +3 -26
- package/dist/{editor-BAdok66p.mjs → editor-DmxNPK7K.mjs} +522 -68
- package/dist/{editor-CNivEuMH.d.mts → editor-KD97dL_1.d.mts} +21 -3
- package/dist/editor-vue.d.mts +1 -1
- package/dist/editor-vue.mjs +1 -1
- package/dist/editor.d.mts +2 -2
- package/dist/editor.mjs +1 -1
- package/dist/html.d.mts +7 -1
- package/dist/html.mjs +29 -9
- package/dist/{index-B0YvApci.d.mts → index-BUDHsvnm.d.mts} +11 -2
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +2 -2
- package/dist/react.d.mts +2 -2
- package/dist/react.mjs +20 -6
- package/dist/skills/before-after-evidence/SKILL.md +72 -0
- package/dist/skills/before-after-evidence/agents/openai.yaml +3 -0
- package/dist/skills/before-after-evidence/references/blocks.md +54 -0
- package/dist/skills/before-after-evidence/references/example.md +37 -0
- package/dist/skills/before-after-evidence/references/measuring.md +50 -0
- package/dist/skills/comark-ui/SKILL.md +9 -2
- package/dist/skills/comark-ui/references/annotate.md +4 -2
- package/dist/skills/comark-ui/references/comments.md +43 -0
- package/dist/skills/comark-ui/references/diagrams.md +7 -1
- package/dist/skills/comark-ui/references/lint.md +1 -1
- package/dist/skills/comark-ui/references/outline.md +9 -1
- package/dist/skills/comark-ui/references/quiz.md +1 -1
- package/dist/skills/comark-ui/references/ui.md +1 -1
- package/dist/skills/review-document/SKILL.md +64 -0
- package/dist/skills/review-document/agents/openai.yaml +3 -0
- package/dist/skills/review-document/references/blocks.md +69 -0
- package/dist/skills/review-document/references/example.md +51 -0
- package/dist/skills/teach-lesson/SKILL.md +78 -0
- package/dist/skills/teach-lesson/agents/openai.yaml +3 -0
- package/dist/skills/teach-lesson/references/blocks.md +91 -0
- package/dist/skills/teach-lesson/references/example.md +70 -0
- package/dist/skills/visual-explanation/SKILL.md +67 -0
- package/dist/skills/visual-explanation/agents/openai.yaml +3 -0
- package/dist/skills/visual-explanation/references/blocks.md +89 -0
- package/dist/skills/visual-explanation/references/example.md +25 -0
- package/dist/skills.d.mts +18 -0
- package/dist/skills.mjs +326 -0
- package/dist/{src-DROB05U6.mjs → src-PkmYCSNu.mjs} +1 -0
- package/dist/svelte/Mermaid.svelte +16 -0
- package/dist/svelte/index.d.ts +1 -1
- package/dist/svelte/index.js +4 -8
- package/dist/{typeset-css-DPB0S1bV.mjs → typeset-css-5aJ7cSmh.mjs} +14 -0
- package/dist/vue.d.mts +2 -2
- package/dist/vue.mjs +33 -6
- package/package.json +46 -44
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import cjk from "comark-cjk";
|
|
2
|
+
import critic from "comark-critic";
|
|
3
|
+
import deflist from "comark-deflist";
|
|
4
|
+
import footnotes from "comark-footnotes";
|
|
5
|
+
import { defineComarkPlugin, parseMarkdown } from "comark";
|
|
6
|
+
import { Extension, Mark, Node } from "@tiptap/core";
|
|
3
7
|
import Image from "@tiptap/extension-image";
|
|
4
8
|
import { TaskItem, TaskList } from "@tiptap/extension-list";
|
|
5
9
|
import { TableKit } from "@tiptap/extension-table";
|
|
6
10
|
import StarterKit from "@tiptap/starter-kit";
|
|
7
11
|
import { renderMarkdown } from "comark/render";
|
|
8
|
-
import { textContent } from "comark/utils";
|
|
12
|
+
import { indent, textContent } from "comark/utils";
|
|
9
13
|
import { Plugin, PluginKey } from "@tiptap/pm/state";
|
|
10
14
|
import { Decoration, DecorationSet } from "@tiptap/pm/view";
|
|
11
15
|
//#region src/editor/kit.ts
|
|
@@ -30,12 +34,26 @@ const typesWithProps = [
|
|
|
30
34
|
"italic",
|
|
31
35
|
"strike",
|
|
32
36
|
"code",
|
|
33
|
-
"link"
|
|
37
|
+
"link",
|
|
38
|
+
"definitionList",
|
|
39
|
+
"definitionTerm",
|
|
40
|
+
"definitionDescription"
|
|
34
41
|
];
|
|
35
|
-
|
|
42
|
+
function readProps(element) {
|
|
43
|
+
try {
|
|
44
|
+
return JSON.parse(element.getAttribute("data-comark-props") ?? "null");
|
|
45
|
+
} catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Comark attributes the editor doesn't show, carried as they were parsed so writing the node back keeps them. They're
|
|
51
|
+
* in the HTML too, since the clipboard copies nodes as HTML: a pasted `::screenshot` keeps its `src`.
|
|
52
|
+
*/
|
|
36
53
|
const props = {
|
|
37
54
|
default: null,
|
|
38
|
-
|
|
55
|
+
parseHTML: readProps,
|
|
56
|
+
renderHTML: (attrs) => attrs.props ? { "data-comark-props": JSON.stringify(attrs.props) } : {}
|
|
39
57
|
};
|
|
40
58
|
const name = (attribute) => ({
|
|
41
59
|
default: "",
|
|
@@ -123,9 +141,157 @@ const ComarkComment = Node.create({
|
|
|
123
141
|
parseHTML: () => [{ tag: "div[data-comark-comment]" }],
|
|
124
142
|
renderHTML: ({ HTMLAttributes }) => ["div", HTMLAttributes]
|
|
125
143
|
});
|
|
144
|
+
const label = {
|
|
145
|
+
default: "",
|
|
146
|
+
parseHTML: (element) => element.getAttribute("data-label") ?? "",
|
|
147
|
+
renderHTML: (attrs) => ({ "data-label": attrs.label })
|
|
148
|
+
};
|
|
149
|
+
/** A footnote reference, `[^label]`. */
|
|
150
|
+
const FootnoteReference = Node.create({
|
|
151
|
+
name: "footnoteReference",
|
|
152
|
+
group: "inline",
|
|
153
|
+
inline: true,
|
|
154
|
+
atom: true,
|
|
155
|
+
addAttributes: () => ({ label }),
|
|
156
|
+
parseHTML: () => [{ tag: "sup[data-footnote-reference]" }],
|
|
157
|
+
renderHTML: ({ node, HTMLAttributes }) => [
|
|
158
|
+
"sup",
|
|
159
|
+
{
|
|
160
|
+
...HTMLAttributes,
|
|
161
|
+
"data-footnote-reference": ""
|
|
162
|
+
},
|
|
163
|
+
node.attrs.label
|
|
164
|
+
]
|
|
165
|
+
});
|
|
166
|
+
/** A note written where it applies, `^[text]`. */
|
|
167
|
+
const InlineFootnote = Node.create({
|
|
168
|
+
name: "inlineFootnote",
|
|
169
|
+
group: "inline",
|
|
170
|
+
inline: true,
|
|
171
|
+
content: "inline*",
|
|
172
|
+
parseHTML: () => [{ tag: "span[data-inline-footnote]" }],
|
|
173
|
+
renderHTML: ({ HTMLAttributes }) => [
|
|
174
|
+
"span",
|
|
175
|
+
{
|
|
176
|
+
...HTMLAttributes,
|
|
177
|
+
"data-inline-footnote": ""
|
|
178
|
+
},
|
|
179
|
+
0
|
|
180
|
+
]
|
|
181
|
+
});
|
|
182
|
+
/** A footnote's text, `[^label]: text`, with the blocks indented under it. */
|
|
183
|
+
const FootnoteDefinition = Node.create({
|
|
184
|
+
name: "footnoteDefinition",
|
|
185
|
+
group: "block",
|
|
186
|
+
content: "block+",
|
|
187
|
+
defining: true,
|
|
188
|
+
addAttributes: () => ({ label }),
|
|
189
|
+
parseHTML: () => [{ tag: "div[data-footnote-definition]" }],
|
|
190
|
+
renderHTML: ({ HTMLAttributes }) => [
|
|
191
|
+
"div",
|
|
192
|
+
{
|
|
193
|
+
...HTMLAttributes,
|
|
194
|
+
"data-footnote-definition": ""
|
|
195
|
+
},
|
|
196
|
+
0
|
|
197
|
+
]
|
|
198
|
+
});
|
|
199
|
+
/** A definition list, `Term` then `: Definition`. */
|
|
200
|
+
const DefinitionList = Node.create({
|
|
201
|
+
name: "definitionList",
|
|
202
|
+
group: "block",
|
|
203
|
+
content: "(definitionTerm definitionDescription+)+",
|
|
204
|
+
parseHTML: () => [{ tag: "dl" }],
|
|
205
|
+
renderHTML: ({ HTMLAttributes }) => [
|
|
206
|
+
"dl",
|
|
207
|
+
HTMLAttributes,
|
|
208
|
+
0
|
|
209
|
+
]
|
|
210
|
+
});
|
|
211
|
+
const DefinitionTerm = Node.create({
|
|
212
|
+
name: "definitionTerm",
|
|
213
|
+
content: "inline*",
|
|
214
|
+
defining: true,
|
|
215
|
+
parseHTML: () => [{ tag: "dt" }],
|
|
216
|
+
renderHTML: ({ HTMLAttributes }) => [
|
|
217
|
+
"dt",
|
|
218
|
+
HTMLAttributes,
|
|
219
|
+
0
|
|
220
|
+
]
|
|
221
|
+
});
|
|
222
|
+
const DefinitionDescription = Node.create({
|
|
223
|
+
name: "definitionDescription",
|
|
224
|
+
content: "block+",
|
|
225
|
+
defining: true,
|
|
226
|
+
parseHTML: () => [{ tag: "dd" }],
|
|
227
|
+
renderHTML: ({ HTMLAttributes }) => [
|
|
228
|
+
"dd",
|
|
229
|
+
HTMLAttributes,
|
|
230
|
+
0
|
|
231
|
+
]
|
|
232
|
+
});
|
|
233
|
+
const substitution$2 = {
|
|
234
|
+
default: false,
|
|
235
|
+
parseHTML: (element) => element.hasAttribute("data-substitution"),
|
|
236
|
+
renderHTML: (attrs) => attrs.substitution ? { "data-substitution": "" } : {}
|
|
237
|
+
};
|
|
238
|
+
/** CriticMarkup's suggested insertion, `{++text++}`. */
|
|
239
|
+
const CriticInsertion = Mark.create({
|
|
240
|
+
name: "criticInsertion",
|
|
241
|
+
addAttributes: () => ({ substitution: substitution$2 }),
|
|
242
|
+
parseHTML: () => [{ tag: "ins" }],
|
|
243
|
+
renderHTML: ({ HTMLAttributes }) => [
|
|
244
|
+
"ins",
|
|
245
|
+
HTMLAttributes,
|
|
246
|
+
0
|
|
247
|
+
]
|
|
248
|
+
});
|
|
249
|
+
/** CriticMarkup's suggested deletion, `{--text--}`. */
|
|
250
|
+
const CriticDeletion = Mark.create({
|
|
251
|
+
name: "criticDeletion",
|
|
252
|
+
addAttributes: () => ({ substitution: substitution$2 }),
|
|
253
|
+
parseHTML: () => [{
|
|
254
|
+
tag: "del[data-critic]",
|
|
255
|
+
priority: 60
|
|
256
|
+
}],
|
|
257
|
+
renderHTML: ({ HTMLAttributes }) => [
|
|
258
|
+
"del",
|
|
259
|
+
{
|
|
260
|
+
...HTMLAttributes,
|
|
261
|
+
"data-critic": ""
|
|
262
|
+
},
|
|
263
|
+
0
|
|
264
|
+
]
|
|
265
|
+
});
|
|
266
|
+
/** CriticMarkup's highlight, `{==text==}`. */
|
|
267
|
+
const CriticHighlight = Mark.create({
|
|
268
|
+
name: "criticHighlight",
|
|
269
|
+
parseHTML: () => [{ tag: "mark" }],
|
|
270
|
+
renderHTML: ({ HTMLAttributes }) => [
|
|
271
|
+
"mark",
|
|
272
|
+
HTMLAttributes,
|
|
273
|
+
0
|
|
274
|
+
]
|
|
275
|
+
});
|
|
276
|
+
/** CriticMarkup's comment, `{>>text<<}`. */
|
|
277
|
+
const CriticComment = Node.create({
|
|
278
|
+
name: "criticComment",
|
|
279
|
+
group: "inline",
|
|
280
|
+
inline: true,
|
|
281
|
+
content: "inline*",
|
|
282
|
+
parseHTML: () => [{ tag: "span[data-critic-comment]" }],
|
|
283
|
+
renderHTML: ({ HTMLAttributes }) => [
|
|
284
|
+
"span",
|
|
285
|
+
{
|
|
286
|
+
...HTMLAttributes,
|
|
287
|
+
"data-critic-comment": ""
|
|
288
|
+
},
|
|
289
|
+
0
|
|
290
|
+
]
|
|
291
|
+
});
|
|
126
292
|
/**
|
|
127
293
|
* The schema `fromMarkdown` and `toMarkdown` read and write: StarterKit's nodes and marks, task lists, inline images,
|
|
128
|
-
* tables, and Comark's components, slots, and comments. It needs no DOM, so a server can load it to convert a document
|
|
294
|
+
* tables, footnotes, definition lists, CriticMarkup, and Comark's components, slots, and comments. It needs no DOM, so a server can load it to convert a document
|
|
129
295
|
* for a collaborative room.
|
|
130
296
|
*/
|
|
131
297
|
const ComarkKit = Extension.create({
|
|
@@ -137,7 +303,9 @@ const ComarkKit = Extension.create({
|
|
|
137
303
|
}],
|
|
138
304
|
addExtensions() {
|
|
139
305
|
return [
|
|
140
|
-
StarterKit.
|
|
306
|
+
StarterKit.extend({ addExtensions() {
|
|
307
|
+
return (this.parent?.() ?? []).map((extension) => extension.name === "code" ? extension.extend({ excludes: "" }) : extension);
|
|
308
|
+
} }).configure({
|
|
141
309
|
...this.options.starterKit,
|
|
142
310
|
underline: false
|
|
143
311
|
}),
|
|
@@ -148,16 +316,196 @@ const ComarkKit = Extension.create({
|
|
|
148
316
|
ComarkBlock,
|
|
149
317
|
ComarkSlot,
|
|
150
318
|
ComarkInline,
|
|
151
|
-
ComarkComment
|
|
319
|
+
ComarkComment,
|
|
320
|
+
FootnoteReference,
|
|
321
|
+
InlineFootnote,
|
|
322
|
+
FootnoteDefinition,
|
|
323
|
+
DefinitionList,
|
|
324
|
+
DefinitionTerm,
|
|
325
|
+
DefinitionDescription,
|
|
326
|
+
CriticInsertion,
|
|
327
|
+
CriticDeletion,
|
|
328
|
+
CriticHighlight,
|
|
329
|
+
CriticComment
|
|
152
330
|
];
|
|
153
331
|
}
|
|
154
332
|
});
|
|
155
333
|
//#endregion
|
|
334
|
+
//#region src/editor/handlers.ts
|
|
335
|
+
const handlers = /* @__PURE__ */ new WeakMap();
|
|
336
|
+
const isElement$1 = (node) => Array.isArray(node) && node[0] !== null;
|
|
337
|
+
/** Marks `node` to be written by `handler`. */
|
|
338
|
+
function own(node, handler) {
|
|
339
|
+
handlers.set(node, handler);
|
|
340
|
+
return node;
|
|
341
|
+
}
|
|
342
|
+
const writeOptions = {
|
|
343
|
+
headingIds: false,
|
|
344
|
+
maxInlineAttributes: Number.POSITIVE_INFINITY,
|
|
345
|
+
components: { owned: {
|
|
346
|
+
match: (node) => handlers.has(node),
|
|
347
|
+
handler: (node, state, parent) => handlers.get(node)?.(node, state, parent) ?? ""
|
|
348
|
+
} }
|
|
349
|
+
};
|
|
350
|
+
const TABLE = "comark-editor-table";
|
|
351
|
+
/**
|
|
352
|
+
* A block component, `::name`. Written with no parent, since Comark indents a component inside another, and authors
|
|
353
|
+
* write them flush: the colons already say how deep each one is.
|
|
354
|
+
*/
|
|
355
|
+
const blockComponent = async (node, state) => {
|
|
356
|
+
const [tag, attrs, ...children] = node;
|
|
357
|
+
if (tag !== "table") return state.handlers.mdc(node, state);
|
|
358
|
+
return (await state.handlers.mdc([
|
|
359
|
+
TABLE,
|
|
360
|
+
attrs,
|
|
361
|
+
...children
|
|
362
|
+
], state)).replace(TABLE, "table");
|
|
363
|
+
};
|
|
364
|
+
/** An inline component, `:name[text]{props}`, which Comark would write as a block when it's alone in its paragraph. */
|
|
365
|
+
const inlineComponent = async (node, state, parent) => {
|
|
366
|
+
const revert = state.applyContext({ inline: true });
|
|
367
|
+
try {
|
|
368
|
+
return await state.handlers.mdc(node, state, parent);
|
|
369
|
+
} finally {
|
|
370
|
+
state.applyContext(revert);
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
/**
|
|
374
|
+
* A task, written as a list item with its check box added after the marker. Comark's own writer indents the blocks
|
|
375
|
+
* under a task by the width of `- [ ] `, which reads them back as code.
|
|
376
|
+
*/
|
|
377
|
+
const task = (checked) => async (node, state, parent) => (await state.handlers.li(node, state, parent)).replace(/^(- |\d+\. )/, `$1[${checked ? "x" : " "}] `);
|
|
378
|
+
const END = "";
|
|
379
|
+
/** A code block that ends in blank lines, which Comark's writer trims off. */
|
|
380
|
+
const trailingLines = async ([tag, attrs, ...children], state) => {
|
|
381
|
+
const written = await state.handlers.pre([
|
|
382
|
+
tag,
|
|
383
|
+
attrs,
|
|
384
|
+
...children,
|
|
385
|
+
END
|
|
386
|
+
], state);
|
|
387
|
+
const at = written.lastIndexOf(END);
|
|
388
|
+
return written.slice(0, at) + written.slice(at + 1);
|
|
389
|
+
};
|
|
390
|
+
/** Inline content between two delimiters, such as `{++` and `++}`. */
|
|
391
|
+
const between = (open, close) => async (node, state) => `${open}${await state.flow(node, state)}${close}`;
|
|
392
|
+
/** `{~~old~>new~~}`, from its `del` and `ins` halves. */
|
|
393
|
+
const substitution$1 = async ([, , removed, added], state) => {
|
|
394
|
+
const flow = async (half) => isElement$1(half) ? state.flow(half, state) : "";
|
|
395
|
+
return `{~~${await flow(removed)}~>${await flow(added)}~~}`;
|
|
396
|
+
};
|
|
397
|
+
const footnoteReference = (label) => () => `[^${label}]`;
|
|
398
|
+
/** `[^label]: text`, with the rest of its blocks indented under it. */
|
|
399
|
+
const footnoteDefinition = (label) => async (node, state) => {
|
|
400
|
+
const content = (await state.flow(node, state)).trim();
|
|
401
|
+
return `[^${label}]: ${indent(content, {
|
|
402
|
+
width: 4,
|
|
403
|
+
ignoreFirstLine: true
|
|
404
|
+
})}${state.context.blockSeparator}`;
|
|
405
|
+
};
|
|
406
|
+
/** Each term on its line, each definition after `: ` with its blocks indented under it, and a blank line before the next term. */
|
|
407
|
+
const definitionList = async (node, state) => {
|
|
408
|
+
let written = "";
|
|
409
|
+
for (const item of node.slice(2)) {
|
|
410
|
+
if (!isElement$1(item)) continue;
|
|
411
|
+
const content = (await state.flow(item, state)).trim();
|
|
412
|
+
if (item[0] === "dt") written += `${written ? "\n" : ""}${content}\n`;
|
|
413
|
+
else written += `: ${indent(content, {
|
|
414
|
+
width: 4,
|
|
415
|
+
ignoreFirstLine: true
|
|
416
|
+
})}\n`;
|
|
417
|
+
}
|
|
418
|
+
return written.trimEnd() + state.context.blockSeparator;
|
|
419
|
+
};
|
|
420
|
+
//#endregion
|
|
421
|
+
//#region ../../shared/markdown-it.ts
|
|
422
|
+
/**
|
|
423
|
+
* An inline element and a block element, by the prefix of their `_open` and `_close` types. Not `mdc_block_slot` for
|
|
424
|
+
* blocks: directly inside a `::component` Comark reads those as named slots and splits the block apart.
|
|
425
|
+
*/
|
|
426
|
+
const SPAN = "mdc_inline_span";
|
|
427
|
+
/**
|
|
428
|
+
* Adds a rule before or after the first of `anchors` the parser has, else last. Comark's own rules exist only with
|
|
429
|
+
* its default plugins, and markdown-exit throws on a missing anchor, which would make every parse throw.
|
|
430
|
+
*/
|
|
431
|
+
function addRule(ruler, place, anchors, name, fn, options) {
|
|
432
|
+
for (const anchor of anchors) try {
|
|
433
|
+
ruler[place](anchor, name, fn, options);
|
|
434
|
+
return;
|
|
435
|
+
} catch {}
|
|
436
|
+
ruler.push(name, fn, options);
|
|
437
|
+
}
|
|
438
|
+
//#endregion
|
|
439
|
+
//#region src/editor/syntax.ts
|
|
440
|
+
/** The element around the two halves of `{~~old~>new~~}`, which comark-critic writes as a `del` beside an `ins`. */
|
|
441
|
+
const SUBSTITUTION = "critic-sub";
|
|
442
|
+
let inside = false;
|
|
443
|
+
function substitution(state, silent) {
|
|
444
|
+
if (silent || inside || !state.src.startsWith("{~~", state.pos)) return false;
|
|
445
|
+
const start = state.pos;
|
|
446
|
+
const max = state.posMax;
|
|
447
|
+
const rules = state.md.inline.ruler.getRules("");
|
|
448
|
+
const criticRule = rules[rules.indexOf(substitution) + 1];
|
|
449
|
+
if (!criticRule?.(state, true)) return false;
|
|
450
|
+
const end = state.pos;
|
|
451
|
+
state.pos = start;
|
|
452
|
+
state.push(`${SPAN}_open`, SUBSTITUTION, 1);
|
|
453
|
+
state.posMax = end;
|
|
454
|
+
inside = true;
|
|
455
|
+
try {
|
|
456
|
+
state.md.inline.tokenize(state);
|
|
457
|
+
} finally {
|
|
458
|
+
inside = false;
|
|
459
|
+
}
|
|
460
|
+
state.posMax = max;
|
|
461
|
+
state.push(`${SPAN}_close`, SUBSTITUTION, -1);
|
|
462
|
+
return true;
|
|
463
|
+
}
|
|
464
|
+
const substitutions = defineComarkPlugin(() => ({
|
|
465
|
+
name: "editor-critic-substitutions",
|
|
466
|
+
markdownItPlugins: [(md) => addRule(md.inline.ruler, "before", ["critic"], "critic_substitution", substitution)]
|
|
467
|
+
}));
|
|
468
|
+
const footnoteMarkers = defineComarkPlugin(() => ({
|
|
469
|
+
name: "editor-footnotes",
|
|
470
|
+
markdownItPlugins: footnotes().markdownItPlugins
|
|
471
|
+
}));
|
|
472
|
+
const noAlert = defineComarkPlugin(() => ({ name: "alert" }));
|
|
473
|
+
/** The prop on a component written `:name{props}` alone on its line, which Comark reads like an empty `::name`. */
|
|
474
|
+
const SHORTHAND = "data-comark-editor-shorthand";
|
|
475
|
+
function markShorthands(tokens) {
|
|
476
|
+
for (const token of tokens) {
|
|
477
|
+
if (token.type === "mdc_block_shorthand" || token.type === "mdc_block_shorthand_open") token.attrSet(SHORTHAND, "");
|
|
478
|
+
if (token.children) markShorthands(token.children);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
const shorthands = defineComarkPlugin(() => ({
|
|
482
|
+
name: "editor-shorthands",
|
|
483
|
+
markdownItPost: (state) => markShorthands(state.tokens)
|
|
484
|
+
}));
|
|
485
|
+
/**
|
|
486
|
+
* The syntax the codec reads beyond Comark's own: CriticMarkup, definition lists, footnotes, and emphasis next to CJK
|
|
487
|
+
* punctuation. Each would otherwise reach the tree as text, or not at all, and be written back escaped or dropped.
|
|
488
|
+
* It also marks the components written `:name` alone on their line, so they're written back that way.
|
|
489
|
+
*/
|
|
490
|
+
const syntax = [
|
|
491
|
+
noAlert(),
|
|
492
|
+
critic(),
|
|
493
|
+
substitutions(),
|
|
494
|
+
deflist(),
|
|
495
|
+
cjk(),
|
|
496
|
+
footnoteMarkers(),
|
|
497
|
+
shorthands()
|
|
498
|
+
];
|
|
499
|
+
//#endregion
|
|
156
500
|
//#region src/editor/markdown.ts
|
|
157
501
|
const parseOptions = {
|
|
158
502
|
autoUnwrap: false,
|
|
159
503
|
headingIds: false
|
|
160
504
|
};
|
|
505
|
+
const readOptions = {
|
|
506
|
+
...parseOptions,
|
|
507
|
+
plugins: syntax
|
|
508
|
+
};
|
|
161
509
|
const marks = /* @__PURE__ */ new Map([
|
|
162
510
|
["strong", "bold"],
|
|
163
511
|
["em", "italic"],
|
|
@@ -165,13 +513,20 @@ const marks = /* @__PURE__ */ new Map([
|
|
|
165
513
|
["code", "code"],
|
|
166
514
|
["a", "link"]
|
|
167
515
|
]);
|
|
516
|
+
const DEFINITION = "cfn-def:";
|
|
517
|
+
const REFERENCE = "cfn-ref:";
|
|
518
|
+
const NOTE = "cfn-note";
|
|
168
519
|
const markTags = new Map([...marks].map(([tag, mark]) => [mark, tag]));
|
|
169
520
|
const inlineTags = /* @__PURE__ */ new Set([
|
|
170
521
|
...marks.keys(),
|
|
171
522
|
"br",
|
|
172
523
|
"img",
|
|
173
524
|
"span",
|
|
174
|
-
"input"
|
|
525
|
+
"input",
|
|
526
|
+
"ins",
|
|
527
|
+
"mark",
|
|
528
|
+
SUBSTITUTION,
|
|
529
|
+
NOTE
|
|
175
530
|
]);
|
|
176
531
|
const headings = /* @__PURE__ */ new Set([
|
|
177
532
|
"h1",
|
|
@@ -181,24 +536,14 @@ const headings = /* @__PURE__ */ new Set([
|
|
|
181
536
|
"h5",
|
|
182
537
|
"h6"
|
|
183
538
|
]);
|
|
184
|
-
const builtIn = /* @__PURE__ */ new Set([
|
|
185
|
-
...headings,
|
|
186
|
-
"p",
|
|
187
|
-
"blockquote",
|
|
188
|
-
"hr",
|
|
189
|
-
"pre",
|
|
190
|
-
"table",
|
|
191
|
-
"ul",
|
|
192
|
-
"ol",
|
|
193
|
-
"li"
|
|
194
|
-
]);
|
|
195
539
|
const isText = (node) => !Array.isArray(node);
|
|
196
540
|
const isElement = (node) => Array.isArray(node) && node[0] !== null;
|
|
197
541
|
const childrenOf = (element) => element.slice(2);
|
|
198
542
|
/** A prop as text. Comark props are strings, numbers, and booleans, or parsed JSON for `:prop` bindings. */
|
|
199
543
|
const textOf = (value) => value === void 0 || value === null || value instanceof Object ? void 0 : String(value);
|
|
200
544
|
const inlineHtml = ({ $: meta }) => meta instanceof Object && "block" in meta && meta.block === 0;
|
|
201
|
-
const isInline = (node) => isText(node) || isElement(node) && (inlineTags.has(node[0]) || inlineHtml(node[1]));
|
|
545
|
+
const isInline = (node) => isText(node) || isElement(node) && (inlineTags.has(node[0]) || node[0].startsWith(REFERENCE) || inlineHtml(node[1]));
|
|
546
|
+
const isHtml = (props) => props.$ instanceof Object;
|
|
202
547
|
/** The props a node has no field for, or `undefined` when none are left. */
|
|
203
548
|
function rest(props, ...keys) {
|
|
204
549
|
const left = Object.fromEntries(Object.entries(props).filter(([key]) => !keys.includes(key)));
|
|
@@ -249,20 +594,38 @@ function readBlock(child, reading) {
|
|
|
249
594
|
if (!isElement(child)) return [node("comarkComment", { text: child[2] })];
|
|
250
595
|
const [tag, props] = child;
|
|
251
596
|
const children = childrenOf(child);
|
|
597
|
+
if ("data-comark-editor-shorthand" in props) {
|
|
598
|
+
const { [SHORTHAND]: _, ...attrs } = props;
|
|
599
|
+
return [node("paragraph", {}, readInline([[
|
|
600
|
+
tag,
|
|
601
|
+
attrs,
|
|
602
|
+
...children
|
|
603
|
+
]], [], reading))];
|
|
604
|
+
}
|
|
252
605
|
if (tag === "p") return [node("paragraph", withProps({}, rest(props)), readInline(children, [], reading))];
|
|
253
606
|
if (headings.has(tag)) return [node("heading", withProps({ level: Number(tag[1]) }, rest(props)), readInline(children, [], reading))];
|
|
254
607
|
if (tag === "blockquote") return [node("blockquote", withProps({}, rest(props)), readBlocks(children, reading))];
|
|
255
608
|
if (tag === "hr") return [node("horizontalRule", withProps({}, rest(props)))];
|
|
256
609
|
if (tag === "pre") return [readCode(props, children)];
|
|
257
|
-
if (tag === "table" && children.every((part) => isElement(part) && [
|
|
610
|
+
if (tag === "table" && children.length && children.every((part) => isElement(part) && [
|
|
258
611
|
"thead",
|
|
259
612
|
"tbody",
|
|
260
613
|
"tr"
|
|
261
614
|
].includes(part[0]))) return [readTable(props, children, reading)];
|
|
262
615
|
if (tag === "ul" || tag === "ol") return [readList(tag, props, children, reading)];
|
|
263
|
-
if (tag
|
|
616
|
+
if (tag.startsWith(DEFINITION)) return [node("footnoteDefinition", { label: tag.slice(8) }, filled(readBlocks(children, reading)))];
|
|
617
|
+
if (tag === "dl") return [readDefinitions(props, children, reading)];
|
|
618
|
+
if (tag === "template") reading.lossy = true;
|
|
264
619
|
return [node("comarkBlock", withProps({ name: tag }, rest(props)), readComponent(children, reading))];
|
|
265
620
|
}
|
|
621
|
+
/** Blocks that need at least one, as an empty item or definition writes nothing. */
|
|
622
|
+
const filled = (blocks) => blocks.length ? blocks : [{ type: "paragraph" }];
|
|
623
|
+
function readDefinitions(props, children, reading) {
|
|
624
|
+
return node("definitionList", withProps({}, rest(props)), children.flatMap((item) => {
|
|
625
|
+
if (!isElement(item)) return [];
|
|
626
|
+
return item[0] === "dt" ? [node("definitionTerm", withProps({}, rest(item[1])), readInline(childrenOf(item), [], reading))] : [node("definitionDescription", withProps({}, rest(item[1])), filled(readBlocks(childrenOf(item), reading)))];
|
|
627
|
+
}));
|
|
628
|
+
}
|
|
266
629
|
function readComponent(children, reading) {
|
|
267
630
|
const out = [];
|
|
268
631
|
let run = [];
|
|
@@ -326,6 +689,29 @@ function readTable(props, children, reading) {
|
|
|
326
689
|
const rows = children.filter(isElement).flatMap((section) => section[0] === "tr" ? [section] : childrenOf(section).filter(isElement));
|
|
327
690
|
return node("table", withProps({}, rest(props)), rows.map((row) => node("tableRow", withProps({}, rest(row[1])), childrenOf(row).filter(isElement).map((cell) => node(cell[0] === "th" ? "tableHeader" : "tableCell", withProps({}, rest(cell[1])), [node("paragraph", {}, readInline(childrenOf(cell), [], reading))])))));
|
|
328
691
|
}
|
|
692
|
+
const critics = /* @__PURE__ */ new Map([
|
|
693
|
+
["ins", {
|
|
694
|
+
className: "ccm-ins",
|
|
695
|
+
type: "criticInsertion"
|
|
696
|
+
}],
|
|
697
|
+
["del", {
|
|
698
|
+
className: "ccm-del",
|
|
699
|
+
type: "criticDeletion"
|
|
700
|
+
}],
|
|
701
|
+
["mark", {
|
|
702
|
+
className: "ccm-mark",
|
|
703
|
+
type: "criticHighlight"
|
|
704
|
+
}]
|
|
705
|
+
]);
|
|
706
|
+
const COMMENT = "ccm-comment";
|
|
707
|
+
function criticMark([tag, props]) {
|
|
708
|
+
const critic = critics.get(tag);
|
|
709
|
+
if (!critic || !classes(props).includes(critic.className)) return;
|
|
710
|
+
return {
|
|
711
|
+
className: critic.className,
|
|
712
|
+
mark: { type: critic.type }
|
|
713
|
+
};
|
|
714
|
+
}
|
|
329
715
|
function readInline(children, active, reading) {
|
|
330
716
|
const out = [];
|
|
331
717
|
const marked = (json) => active.length ? {
|
|
@@ -346,8 +732,24 @@ function readInline(children, active, reading) {
|
|
|
346
732
|
}
|
|
347
733
|
const [tag, props] = child;
|
|
348
734
|
const content = childrenOf(child);
|
|
735
|
+
const critic = criticMark(child);
|
|
349
736
|
const mark = marks.get(tag);
|
|
350
|
-
if (
|
|
737
|
+
if (critic) {
|
|
738
|
+
if (rest(withoutClass(props, critic.className))) reading.lossy = true;
|
|
739
|
+
out.push(...readInline(content, [...active, critic.mark], reading));
|
|
740
|
+
} else if (tag === "critic-sub") {
|
|
741
|
+
const [removed, added] = content.filter(isElement);
|
|
742
|
+
const half = (element, type) => element ? readInline(childrenOf(element), [...active, {
|
|
743
|
+
type,
|
|
744
|
+
attrs: { substitution: true }
|
|
745
|
+
}], reading) : [];
|
|
746
|
+
out.push(...half(removed, "criticDeletion"), ...half(added, "criticInsertion"));
|
|
747
|
+
} else if (tag === "span" && classes(props).includes(COMMENT)) {
|
|
748
|
+
if (rest(withoutClass(props, COMMENT))) reading.lossy = true;
|
|
749
|
+
out.push(marked(node("criticComment", {}, readInline(content, [], reading))));
|
|
750
|
+
} else if (tag.startsWith(REFERENCE)) out.push(marked(node("footnoteReference", { label: tag.slice(8) })));
|
|
751
|
+
else if (tag === NOTE) out.push(marked(node("inlineFootnote", {}, readInline(content, [], reading))));
|
|
752
|
+
else if (mark && !(tag === "code" && !content.every(isText))) {
|
|
351
753
|
const attrs = mark === "link" ? withProps({
|
|
352
754
|
href: props.href ?? null,
|
|
353
755
|
title: props.title ?? null
|
|
@@ -394,7 +796,8 @@ function writeBlock(json) {
|
|
|
394
796
|
case "horizontalRule": return [["hr", props]];
|
|
395
797
|
case "codeBlock": {
|
|
396
798
|
const language = textOf(attrs.language) || void 0;
|
|
397
|
-
|
|
799
|
+
const code = plainText(json.content);
|
|
800
|
+
const pre = [
|
|
398
801
|
"pre",
|
|
399
802
|
language ? {
|
|
400
803
|
...props,
|
|
@@ -403,9 +806,10 @@ function writeBlock(json) {
|
|
|
403
806
|
[
|
|
404
807
|
"code",
|
|
405
808
|
language ? { class: `language-${language}` } : {},
|
|
406
|
-
|
|
809
|
+
code
|
|
407
810
|
]
|
|
408
|
-
]
|
|
811
|
+
];
|
|
812
|
+
return [code.trimEnd() === code ? pre : own(pre, trailingLines)];
|
|
409
813
|
}
|
|
410
814
|
case "bulletList": return [[
|
|
411
815
|
"ul",
|
|
@@ -433,39 +837,48 @@ function writeBlock(json) {
|
|
|
433
837
|
withClass(props, "contains-task-list"),
|
|
434
838
|
...writeBlocks(json.content)
|
|
435
839
|
]];
|
|
436
|
-
case "taskItem":
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
else text.unshift(" ");
|
|
442
|
-
const box = {
|
|
443
|
-
class: "task-list-item-checkbox",
|
|
444
|
-
type: "checkbox",
|
|
445
|
-
":disabled": "true"
|
|
446
|
-
};
|
|
447
|
-
if (attrs.checked) box[":checked"] = "true";
|
|
448
|
-
return [[
|
|
449
|
-
"li",
|
|
450
|
-
withClass(props, "task-list-item"),
|
|
451
|
-
["input", box],
|
|
452
|
-
...text,
|
|
453
|
-
...writeBlocks(others)
|
|
454
|
-
]];
|
|
455
|
-
}
|
|
840
|
+
case "taskItem": return [own([
|
|
841
|
+
"li",
|
|
842
|
+
props,
|
|
843
|
+
...writeBlocks(json.content)
|
|
844
|
+
], task(Boolean(attrs.checked)))];
|
|
456
845
|
case "table": return [writeTable(json, props)];
|
|
457
|
-
case "comarkBlock":
|
|
458
|
-
|
|
846
|
+
case "comarkBlock": {
|
|
847
|
+
const component = [
|
|
848
|
+
String(attrs.name),
|
|
849
|
+
props,
|
|
850
|
+
...(json.content ?? []).flatMap((child) => child.type === "comarkSlot" ? [[
|
|
851
|
+
"template",
|
|
852
|
+
{
|
|
853
|
+
...propsOf(child.attrs),
|
|
854
|
+
name: child.attrs?.name
|
|
855
|
+
},
|
|
856
|
+
...writeBlocks(child.content)
|
|
857
|
+
]] : writeBlock(child))
|
|
858
|
+
];
|
|
859
|
+
return [isHtml(props) ? component : own(component, blockComponent)];
|
|
860
|
+
}
|
|
861
|
+
case "footnoteDefinition": {
|
|
862
|
+
const label = textOf(attrs.label) ?? "";
|
|
863
|
+
return [own([
|
|
864
|
+
"footnote",
|
|
865
|
+
{},
|
|
866
|
+
...writeBlocks(json.content)
|
|
867
|
+
], footnoteDefinition(label))];
|
|
868
|
+
}
|
|
869
|
+
case "definitionList": return [own([
|
|
870
|
+
"dl",
|
|
459
871
|
props,
|
|
460
|
-
...(json.content ?? []).
|
|
461
|
-
"
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
872
|
+
...(json.content ?? []).map((item) => item.type === "definitionTerm" ? [
|
|
873
|
+
"dt",
|
|
874
|
+
propsOf(item.attrs),
|
|
875
|
+
...writeInline(item.content)
|
|
876
|
+
] : [
|
|
877
|
+
"dd",
|
|
878
|
+
propsOf(item.attrs),
|
|
879
|
+
...writeBlocks(item.content)
|
|
880
|
+
])
|
|
881
|
+
], definitionList)];
|
|
469
882
|
case "comarkComment": return [[
|
|
470
883
|
null,
|
|
471
884
|
{},
|
|
@@ -505,7 +918,18 @@ function writeTable(json, props) {
|
|
|
505
918
|
];
|
|
506
919
|
}
|
|
507
920
|
const sameMark = (a, b) => a.type === b.type && JSON.stringify(a.attrs ?? {}) === JSON.stringify(b.attrs ?? {});
|
|
921
|
+
const criticDelimiters = /* @__PURE__ */ new Map([
|
|
922
|
+
["criticInsertion", ["{++", "++}"]],
|
|
923
|
+
["criticDeletion", ["{--", "--}"]],
|
|
924
|
+
["criticHighlight", ["{==", "==}"]]
|
|
925
|
+
]);
|
|
508
926
|
function writeMark(mark, children) {
|
|
927
|
+
const delimiters = criticDelimiters.get(mark.type);
|
|
928
|
+
if (delimiters) return own([
|
|
929
|
+
mark.attrs?.substitution ? mark.type : "critic",
|
|
930
|
+
{},
|
|
931
|
+
...children
|
|
932
|
+
], between(...delimiters));
|
|
509
933
|
const props = propsOf(mark.attrs);
|
|
510
934
|
if (mark.type !== "link") return [
|
|
511
935
|
markTags.get(mark.type) ?? "span",
|
|
@@ -537,7 +961,7 @@ function writeInline(content = []) {
|
|
|
537
961
|
for (let frame = open.pop(); frame; frame = open.length > depth ? open.pop() : void 0) append(open.at(-1)?.children ?? root, writeMark(frame.mark, frame.children));
|
|
538
962
|
};
|
|
539
963
|
for (const json of content) {
|
|
540
|
-
const active = json.marks ?? [];
|
|
964
|
+
const active = [...json.marks ?? []].sort((left, right) => markOrder(left) - markOrder(right));
|
|
541
965
|
const same = (index) => {
|
|
542
966
|
const frame = open[index];
|
|
543
967
|
const mark = active[index];
|
|
@@ -553,7 +977,23 @@ function writeInline(content = []) {
|
|
|
553
977
|
for (const child of writeLeaf(json)) append(open.at(-1)?.children ?? root, child);
|
|
554
978
|
}
|
|
555
979
|
if (open.length) close(0);
|
|
556
|
-
return root;
|
|
980
|
+
return pairSubstitutions(root);
|
|
981
|
+
}
|
|
982
|
+
const markOrder = (mark) => criticDelimiters.has(mark.type) ? -1 : mark.type === "code" ? 1 : 0;
|
|
983
|
+
/** Joins a deletion and the insertion right after it, both from one `{~~old~>new~~}`, back into one. */
|
|
984
|
+
function pairSubstitutions(children) {
|
|
985
|
+
const out = [];
|
|
986
|
+
for (const child of children) {
|
|
987
|
+
const last = out.at(-1);
|
|
988
|
+
if (isElement(child) && child[0] === "criticInsertion" && last !== void 0 && isElement(last) && last[0] === "criticDeletion") out[out.length - 1] = own([
|
|
989
|
+
SUBSTITUTION,
|
|
990
|
+
{},
|
|
991
|
+
last,
|
|
992
|
+
child
|
|
993
|
+
], substitution$1);
|
|
994
|
+
else out.push(child);
|
|
995
|
+
}
|
|
996
|
+
return out;
|
|
557
997
|
}
|
|
558
998
|
function writeLeaf(json) {
|
|
559
999
|
const props = propsOf(json.attrs);
|
|
@@ -569,11 +1009,25 @@ function writeLeaf(json) {
|
|
|
569
1009
|
if (attrs.title) image.title = attrs.title;
|
|
570
1010
|
return [["img", image]];
|
|
571
1011
|
}
|
|
572
|
-
if (json.type === "comarkInline")
|
|
573
|
-
|
|
574
|
-
|
|
1012
|
+
if (json.type === "comarkInline") {
|
|
1013
|
+
const component = [
|
|
1014
|
+
String(attrs.name),
|
|
1015
|
+
props,
|
|
1016
|
+
...writeInline(json.content)
|
|
1017
|
+
];
|
|
1018
|
+
return [isHtml(props) ? component : own(component, inlineComponent)];
|
|
1019
|
+
}
|
|
1020
|
+
if (json.type === "footnoteReference") return [own(["footnote", {}], footnoteReference(textOf(attrs.label) ?? ""))];
|
|
1021
|
+
if (json.type === "inlineFootnote") return [own([
|
|
1022
|
+
"footnote",
|
|
1023
|
+
{},
|
|
1024
|
+
...writeInline(json.content)
|
|
1025
|
+
], between("^[", "]"))];
|
|
1026
|
+
if (json.type === "criticComment") return [own([
|
|
1027
|
+
"span",
|
|
1028
|
+
{},
|
|
575
1029
|
...writeInline(json.content)
|
|
576
|
-
]];
|
|
1030
|
+
], between("{>>", "<<}"))];
|
|
577
1031
|
return [];
|
|
578
1032
|
}
|
|
579
1033
|
function equal(a, b) {
|
|
@@ -585,7 +1039,7 @@ function equal(a, b) {
|
|
|
585
1039
|
}
|
|
586
1040
|
/** Reads Markdown into the editor's document. */
|
|
587
1041
|
async function fromMarkdown(markdown) {
|
|
588
|
-
const tree = await parseMarkdown(markdown,
|
|
1042
|
+
const tree = await parseMarkdown(markdown, readOptions);
|
|
589
1043
|
const reading = { lossy: false };
|
|
590
1044
|
const content = readBlocks(tree.nodes, reading);
|
|
591
1045
|
return {
|
|
@@ -603,7 +1057,7 @@ async function toMarkdown(doc, options = {}) {
|
|
|
603
1057
|
nodes: writeBlocks(doc.content),
|
|
604
1058
|
frontmatter: options.frontmatter ?? {},
|
|
605
1059
|
meta: {}
|
|
606
|
-
},
|
|
1060
|
+
}, writeOptions);
|
|
607
1061
|
return markdown.trim() ? `${markdown.trimEnd()}\n` : "";
|
|
608
1062
|
}
|
|
609
1063
|
/**
|
|
@@ -832,4 +1286,4 @@ function createEditorCodec(options = {}) {
|
|
|
832
1286
|
};
|
|
833
1287
|
}
|
|
834
1288
|
//#endregion
|
|
835
|
-
export { uploadKey as a, fromMarkdown as c, ComarkComment as d, ComarkImage as f, ComarkSlot as h, startUploads as i, toMarkdown as l, ComarkKit as m, Upload as n, uploadPlugin as o, ComarkInline as p, findUpload as r, checkMarkdown as s, createEditorCodec as t, ComarkBlock as u };
|
|
1289
|
+
export { FootnoteDefinition as C, DefinitionTerm as S, InlineFootnote as T, CriticDeletion as _, uploadKey as a, DefinitionDescription as b, fromMarkdown as c, ComarkComment as d, ComarkImage as f, CriticComment as g, ComarkSlot as h, startUploads as i, toMarkdown as l, ComarkKit as m, Upload as n, uploadPlugin as o, ComarkInline as p, findUpload as r, checkMarkdown as s, createEditorCodec as t, ComarkBlock as u, CriticHighlight as v, FootnoteReference as w, DefinitionList as x, CriticInsertion as y };
|