comark-ui 0.7.6 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +3 -3
  2. package/dist/{client-Bjd6VcGQ.mjs → client-kUEiRbtp.mjs} +25 -8
  3. package/dist/client.mjs +2 -2
  4. package/dist/{editor-DmxNPK7K.mjs → editor-9IcfELjG.mjs} +108 -16
  5. package/dist/editor-vue.d.mts +21 -5
  6. package/dist/editor-vue.mjs +40 -9
  7. package/dist/{editor-KD97dL_1.d.mts → editor-yoVL7Ajq.d.mts} +27 -3
  8. package/dist/editor.d.mts +2 -2
  9. package/dist/editor.mjs +1 -1
  10. package/dist/html.d.mts +1 -1
  11. package/dist/html.mjs +7 -3
  12. package/dist/{index-Dbqxt4Qg.d.mts → index-DVGSDzBS.d.mts} +1 -1
  13. package/dist/index.d.mts +1 -1
  14. package/dist/index.mjs +1 -1
  15. package/dist/react.d.mts +1 -1
  16. package/dist/react.mjs +2 -2
  17. package/dist/skills/before-after-evidence/SKILL.md +11 -13
  18. package/dist/skills/before-after-evidence/references/blocks.md +1 -1
  19. package/dist/skills/comark-ui/SKILL.md +13 -9
  20. package/dist/skills/comark-ui/references/annotate.md +1 -1
  21. package/dist/skills/comark-ui/references/badge.md +1 -1
  22. package/dist/skills/comark-ui/references/cards.md +2 -1
  23. package/dist/skills/comark-ui/references/code-notes.md +1 -1
  24. package/dist/skills/comark-ui/references/code.md +3 -3
  25. package/dist/skills/comark-ui/references/comments.md +3 -3
  26. package/dist/skills/comark-ui/references/embed.md +3 -2
  27. package/dist/skills/comark-ui/references/keys.md +1 -1
  28. package/dist/skills/comark-ui/references/links.md +1 -1
  29. package/dist/skills/comark-ui/references/lint.md +21 -12
  30. package/dist/skills/comark-ui/references/magic-move.md +1 -1
  31. package/dist/skills/comark-ui/references/media.md +2 -2
  32. package/dist/skills/comark-ui/references/metrics.md +1 -0
  33. package/dist/skills/comark-ui/references/outline.md +2 -2
  34. package/dist/skills/comark-ui/references/properties.md +1 -1
  35. package/dist/skills/comark-ui/references/quiz.md +15 -3
  36. package/dist/skills/comark-ui/references/reading-time.md +1 -1
  37. package/dist/skills/comark-ui/references/remark-to-comark.md +29 -0
  38. package/dist/skills/comark-ui/references/steps.md +1 -1
  39. package/dist/skills/comark-ui/references/table.md +1 -0
  40. package/dist/skills/comark-ui/references/tabs.md +2 -2
  41. package/dist/skills/comark-ui/references/tests.md +1 -1
  42. package/dist/skills/comark-ui/references/ui.md +7 -6
  43. package/dist/skills/teach-lesson/SKILL.md +4 -4
  44. package/dist/skills/teach-lesson/references/blocks.md +6 -6
  45. package/dist/skills.mjs +34 -30
  46. package/dist/text-D8j9clg3.mjs +19 -0
  47. package/dist/{typeset-css-5aJ7cSmh.mjs → typeset-css-blSNpFNK.mjs} +2 -2
  48. package/dist/vue.d.mts +1 -1
  49. package/dist/vue.mjs +3 -3
  50. package/package.json +45 -45
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  [![license](https://comark-ui.onmax.me/badge.svg?label=license&value=MIT)](https://npmx.dev/package/comark-ui)
6
6
  [![docs](https://comark-ui.onmax.me/badge.svg?label=docs&value=comark-ui.onmax.me)](https://comark-ui.onmax.me/getting-started/)
7
7
 
8
- Every [Comark](https://comark.dev) block from [comark-ui](https://comark-ui.onmax.me) in one install, registered in the order they need, with one icon resolver shared by `:icon`, cards, details, file trees, tabs, and code actions. It ships in lockstep with the plugins, so updating it brings their latest defaults.
8
+ Every [Comark](https://comark.dev) block from [comark-ui](https://comark-ui.onmax.me) in one install, registered in the order they need, with one icon resolver shared by `:icon`, cards, details, file trees, tabs, and code actions.
9
9
 
10
10
  ```sh
11
11
  pnpm add comark @comark/html comark-ui
@@ -53,8 +53,8 @@ Vue, React, and Svelte leave out annotate and diff, which render only to HTML.
53
53
  | `ComarkKit` | The schema: StarterKit, task lists, inline images (drawn as players for video and audio), tables, footnotes, definition lists, CriticMarkup, and Comark's components, slots, and comments, each keeping the attributes it has no field for. |
54
54
  | `fromMarkdown`, `toMarkdown` | Convert between Markdown and the editor's JSON with Comark's own parser and writer. Front matter stays beside the document. |
55
55
  | `checkMarkdown(markdown, { plugins })` | Whether the editor can save the document without changing what readers see, judged with the plugins the page renders with. A document it can't keep opens read-only. |
56
- | `Upload` | Uploads pasted and dropped files. Each shows a placeholder with its progress until `upload(file, progress)` returns its URL. The placeholder is a decoration, so a collaborative document never holds a file that hasn't arrived. |
57
- | `createEditorCodec({ plugins })` | `{ extensions, fromMarkdown, toMarkdown, verify }` for a collaborative room, such as ViteHub Realtime's `editor` option. |
56
+ | `Upload` | Uploads pasted and dropped files. Each shows a placeholder with its progress until `upload(file, progress)` returns its URL. |
57
+ | `createEditorCodec({ plugins })` | `{ extensions, split, join, fromMarkdown, toMarkdown, verify }` for a collaborative room, such as ViteHub Realtime's `editor` option. A room keeps the body as the editor's document and the frontmatter as its fields, so a text edit never overwrites the metadata. |
58
58
 
59
59
  `comark-ui/editor/vue` binds the editor to a Markdown string with `useComarkEditor(markdown)` or `<ComarkEditor v-model="markdown" />`. Run `pnpm playground` in this package to try it.
60
60
 
@@ -165,7 +165,19 @@ function start() {
165
165
  return state.stop;
166
166
  }
167
167
  //#endregion
168
- //#region src/client.ts
168
+ //#region ../../shared/styles.ts
169
+ /**
170
+ * Adds a package's styles to the page once, unless the page already carries them as a `style` or a `link` with
171
+ * `data-comark-<name>`, which is how the HTML renderer inlines them and how an app that imports `style.css` marks them.
172
+ */
173
+ function addStyles(name, css) {
174
+ const attribute = `data-comark-${name}`;
175
+ if (document.querySelector(`style[${attribute}],link[${attribute}]`)) return;
176
+ const style = document.createElement("style");
177
+ style.setAttribute(attribute, "");
178
+ style.textContent = css;
179
+ document.head.append(style);
180
+ }
169
181
  /**
170
182
  * comark-pele's diagrams on the shared `--comark-*` tokens, on a card like comark-diagrams'. Pele paints with its
171
183
  * `--pele-*` variables, so setting them on the wrapper themes every diagram, light and dark, without drawing it
@@ -173,8 +185,17 @@ function start() {
173
185
  */
174
186
  const mermaidCss = `@layer comark{${[
175
187
  theme(".cmm,.pele-diagram"),
176
- theme(":root"),
177
- `:where(:root){--pele-bg:var(--_comark-background);--pele-fg:var(--_comark-foreground);--pele-muted:var(--_comark-muted);--pele-line:var(--_comark-muted);--pele-surface:var(--_comark-accent);--pele-surface-alt:color-mix(in srgb,var(--_comark-accent),var(--_comark-foreground) 6%);--pele-border:var(--_comark-border);--pele-accent:var(--ui-primary,var(--primary,var(--_comark-foreground)));--pele-critical:var(--ui-error,var(--destructive,#dc2626));--pele-error:var(--ui-error,var(--destructive,#dc2626));--pele-font:var(--_comark-font);--pele-radius:calc(var(--_comark-radius) - 4px);--pele-series-1:var(--comark-chart-1,var(--ui-chart-1,var(--chart-1,#3b82f6)));--pele-series-2:var(--comark-chart-2,var(--ui-chart-2,var(--chart-2,#f97316)));--pele-series-3:var(--comark-chart-3,var(--ui-chart-3,var(--chart-3,#10b981)));--pele-series-4:var(--comark-chart-4,var(--ui-chart-4,var(--chart-4,#a855f7)));--pele-series-5:var(--comark-chart-5,var(--ui-chart-5,var(--chart-5,#f43f5e)));--pele-series-6:var(--comark-chart-6,var(--ui-chart-6,var(--chart-6,#eab308)));--pele-series-7:var(--comark-chart-7,var(--ui-chart-7,var(--chart-7,#06b6d4)));--pele-series-8:var(--comark-chart-8,var(--ui-chart-8,var(--chart-8,#ec4899)))}`,
188
+ theme(":root", "status", "primary"),
189
+ `:where(:root){--pele-bg:var(--_comark-background);--pele-fg:var(--_comark-foreground);--pele-muted:var(--_comark-muted);--pele-line:var(--_comark-muted);--pele-surface:var(--_comark-accent);--pele-surface-alt:color-mix(in srgb,var(--_comark-accent),var(--_comark-foreground) 6%);--pele-border:var(--_comark-border);--pele-accent:var(--_comark-primary);--pele-critical:var(--_comark-danger);--pele-error:var(--_comark-danger);--pele-font:var(--_comark-font);--pele-radius:calc(var(--_comark-radius) - 4px);${[
190
+ "#3b82f6",
191
+ "#f97316",
192
+ "#10b981",
193
+ "#a855f7",
194
+ "#f43f5e",
195
+ "#eab308",
196
+ "#06b6d4",
197
+ "#ec4899"
198
+ ].map((hex, index) => `--pele-series-${index + 1}:var(--comark-chart-${index + 1},var(--ui-chart-${index + 1},var(--chart-${index + 1},${hex})))`).join(";")}}`,
178
199
  ".pele-diagram{box-sizing:border-box;margin-block:1.5rem;padding:1rem;overflow-x:auto;border:1px solid var(--_comark-border);border-radius:var(--_comark-radius);background:var(--_comark-background);color:var(--_comark-foreground);text-align:center}",
179
200
  ".pele-diagram>svg{min-width:min(28rem,var(--cmm-svg,28rem))}",
180
201
  ".pele-source{margin:0;color:var(--_comark-muted);text-align:left}",
@@ -214,11 +235,7 @@ function addMermaidStyles() {
214
235
  block.style.removeProperty("--cmm-svg");
215
236
  }
216
237
  }
217
- if (document.querySelector("style[data-comark-mermaid]")) return;
218
- const style = document.createElement("style");
219
- style.dataset.comarkMermaid = "";
220
- style.textContent = mermaidCss;
221
- document.head.append(style);
238
+ addStyles("mermaid", mermaidCss);
222
239
  }
223
240
  const icon = (name, path) => `<svg class="cmm-${name}" viewBox="0 0 24 24" aria-hidden="true">${path}</svg>`;
224
241
  const done = icon("done", "<path d=\"M20 6 9 17l-5-5\"/>");
package/dist/client.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { t as typesetCss } from "./typeset-css-5aJ7cSmh.mjs";
2
- import { i as startMermaid, n as mermaidBar, r as mermaidCss, t as addMermaidStyles } from "./client-Bjd6VcGQ.mjs";
1
+ import { t as typesetCss } from "./typeset-css-blSNpFNK.mjs";
2
+ import { i as startMermaid, n as mermaidBar, r as mermaidCss, t as addMermaidStyles } from "./client-kUEiRbtp.mjs";
3
3
  export { addMermaidStyles, mermaidBar, mermaidCss, startMermaid, typesetCss };
@@ -1,3 +1,4 @@
1
+ import { n as scalar } from "./text-D8j9clg3.mjs";
1
2
  import cjk from "comark-cjk";
2
3
  import critic from "comark-critic";
3
4
  import deflist from "comark-deflist";
@@ -8,7 +9,8 @@ import Image from "@tiptap/extension-image";
8
9
  import { TaskItem, TaskList } from "@tiptap/extension-list";
9
10
  import { TableKit } from "@tiptap/extension-table";
10
11
  import StarterKit from "@tiptap/starter-kit";
11
- import { renderMarkdown } from "comark/render";
12
+ import { parseFrontmatter } from "comark/parse";
13
+ import { renderFrontmatter, renderMarkdown } from "comark/render";
12
14
  import { indent, textContent } from "comark/utils";
13
15
  import { Plugin, PluginKey } from "@tiptap/pm/state";
14
16
  import { Decoration, DecorationSet } from "@tiptap/pm/view";
@@ -539,8 +541,6 @@ const headings = /* @__PURE__ */ new Set([
539
541
  const isText = (node) => !Array.isArray(node);
540
542
  const isElement = (node) => Array.isArray(node) && node[0] !== null;
541
543
  const childrenOf = (element) => element.slice(2);
542
- /** A prop as text. Comark props are strings, numbers, and booleans, or parsed JSON for `:prop` bindings. */
543
- const textOf = (value) => value === void 0 || value === null || value instanceof Object ? void 0 : String(value);
544
544
  const inlineHtml = ({ $: meta }) => meta instanceof Object && "block" in meta && meta.block === 0;
545
545
  const isInline = (node) => isText(node) || isElement(node) && (inlineTags.has(node[0]) || node[0].startsWith(REFERENCE) || inlineHtml(node[1]));
546
546
  const isHtml = (props) => props.$ instanceof Object;
@@ -549,7 +549,7 @@ function rest(props, ...keys) {
549
549
  const left = Object.fromEntries(Object.entries(props).filter(([key]) => !keys.includes(key)));
550
550
  return Object.keys(left).length ? left : void 0;
551
551
  }
552
- const classes = (props) => textOf(props.class)?.split(/\s+/) ?? [];
552
+ const classes = (props) => scalar(props.class)?.split(/\s+/) ?? [];
553
553
  function withoutClass(props, name) {
554
554
  const left = classes(props).filter((value) => value && value !== name);
555
555
  const { class: _, ...others } = props;
@@ -636,7 +636,7 @@ function readComponent(children, reading) {
636
636
  for (const child of children) if (isElement(child) && child[0] === "template") {
637
637
  flush();
638
638
  const { name, ...props } = child[1];
639
- out.push(node("comarkSlot", withProps({ name: textOf(name) ?? "default" }, rest(props)), readBlocks(childrenOf(child), reading)));
639
+ out.push(node("comarkSlot", withProps({ name: scalar(name) ?? "default" }, rest(props)), readBlocks(childrenOf(child), reading)));
640
640
  } else run.push(child);
641
641
  flush();
642
642
  return out;
@@ -644,7 +644,7 @@ function readComponent(children, reading) {
644
644
  function readCode(props, children) {
645
645
  const code = children.find(isElement);
646
646
  const text = (code ? childrenOf(code) : children).filter(isText).join("");
647
- const language = textOf(props.language) ?? null;
647
+ const language = scalar(props.language) ?? null;
648
648
  return node("codeBlock", withProps(language ? { language } : {}, rest(props, "language")), text ? [{
649
649
  type: "text",
650
650
  text
@@ -795,7 +795,7 @@ function writeBlock(json) {
795
795
  ]];
796
796
  case "horizontalRule": return [["hr", props]];
797
797
  case "codeBlock": {
798
- const language = textOf(attrs.language) || void 0;
798
+ const language = scalar(attrs.language) || void 0;
799
799
  const code = plainText(json.content);
800
800
  const pre = [
801
801
  "pre",
@@ -859,7 +859,7 @@ function writeBlock(json) {
859
859
  return [isHtml(props) ? component : own(component, blockComponent)];
860
860
  }
861
861
  case "footnoteDefinition": {
862
- const label = textOf(attrs.label) ?? "";
862
+ const label = scalar(attrs.label) ?? "";
863
863
  return [own([
864
864
  "footnote",
865
865
  {},
@@ -882,7 +882,7 @@ function writeBlock(json) {
882
882
  case "comarkComment": return [[
883
883
  null,
884
884
  {},
885
- textOf(attrs.text) ?? ""
885
+ scalar(attrs.text) ?? ""
886
886
  ]];
887
887
  default: return [];
888
888
  }
@@ -936,8 +936,8 @@ function writeMark(mark, children) {
936
936
  props,
937
937
  ...children
938
938
  ];
939
- const href = textOf(mark.attrs?.href);
940
- const title = textOf(mark.attrs?.title);
939
+ const href = scalar(mark.attrs?.href);
940
+ const title = scalar(mark.attrs?.title);
941
941
  const link = {
942
942
  ...props,
943
943
  href
@@ -1017,7 +1017,7 @@ function writeLeaf(json) {
1017
1017
  ];
1018
1018
  return [isHtml(props) ? component : own(component, inlineComponent)];
1019
1019
  }
1020
- if (json.type === "footnoteReference") return [own(["footnote", {}], footnoteReference(textOf(attrs.label) ?? ""))];
1020
+ if (json.type === "footnoteReference") return [own(["footnote", {}], footnoteReference(scalar(attrs.label) ?? ""))];
1021
1021
  if (json.type === "inlineFootnote") return [own([
1022
1022
  "footnote",
1023
1023
  {},
@@ -1055,16 +1055,90 @@ async function fromMarkdown(markdown) {
1055
1055
  async function toMarkdown(doc, options = {}) {
1056
1056
  const markdown = await renderMarkdown({
1057
1057
  nodes: writeBlocks(doc.content),
1058
- frontmatter: options.frontmatter ?? {},
1058
+ frontmatter: {},
1059
1059
  meta: {}
1060
1060
  }, writeOptions);
1061
- return markdown.trim() ? `${markdown.trimEnd()}\n` : "";
1061
+ return withFrontmatter(markdown.trim() ? `${markdown.trimEnd()}\n` : "", options.frontmatter);
1062
+ }
1063
+ /** Finds the frontmatter block where Comark does, and says why a save wouldn't write it back. */
1064
+ function readFrontmatter(markdown) {
1065
+ if (!markdown.startsWith("---") || !markdown.includes("\n---")) return { kind: "none" };
1066
+ let read;
1067
+ try {
1068
+ read = parseFrontmatter(markdown);
1069
+ } catch (error) {
1070
+ return {
1071
+ kind: "lost",
1072
+ problem: `The frontmatter isn't valid YAML (${String(error instanceof Error && "reason" in error ? error.reason : error)}); fix it to edit the document.`,
1073
+ body: markdown.slice(markdown.indexOf("\n---") + 4).replace(/^(?:\r?\n)+/, "")
1074
+ };
1075
+ }
1076
+ const { data, frontmatterText: yaml } = read;
1077
+ const value = data;
1078
+ if (!(value instanceof Object) || Array.isArray(value)) return {
1079
+ kind: "lost",
1080
+ problem: `The frontmatter is a YAML ${Array.isArray(value) ? "list" : "scalar"}, which a save would rewrite; turn it into \`key: value\` lines to edit the document.`
1081
+ };
1082
+ const comments = hasComment(yaml);
1083
+ if (!Object.keys(data).length) return {
1084
+ kind: "lost",
1085
+ problem: comments ? "The frontmatter holds only YAML comments, which a save would drop; delete them or move them into the text to edit the document." : "The frontmatter block is empty, so a save wouldn't write it back; delete its `---` lines or add a `key: value` line to edit the document."
1086
+ };
1087
+ if (comments) return {
1088
+ kind: "lost",
1089
+ problem: "The frontmatter has YAML comments, which a save would drop; delete them or move them into the text to edit the document."
1090
+ };
1091
+ if (hasProtoKey(data)) return {
1092
+ kind: "lost",
1093
+ problem: "The frontmatter has a `__proto__` key, which the room's fields would drop on save; rename it to edit the document."
1094
+ };
1095
+ return {
1096
+ kind: "kept",
1097
+ body: read.content.replace(/^(?:\r?\n)+/, ""),
1098
+ fields: data
1099
+ };
1100
+ }
1101
+ function hasProtoKey(value, seen = /* @__PURE__ */ new Set()) {
1102
+ if (!(value instanceof Object) || seen.has(value)) return false;
1103
+ seen.add(value);
1104
+ return Object.hasOwn(value, "__proto__") || Object.values(value).some((child) => hasProtoKey(child, seen));
1105
+ }
1106
+ /**
1107
+ * Whether the YAML has a comment. A `#` after a space may sit inside a quoted value, so each candidate counts only when
1108
+ * cutting the line there leaves the same data.
1109
+ */
1110
+ function hasComment(yaml) {
1111
+ const lines = yaml.split("\n");
1112
+ const data = (text) => {
1113
+ try {
1114
+ return { value: parseFrontmatter(`---\n${text}\n---\n`).data };
1115
+ } catch {
1116
+ return;
1117
+ }
1118
+ };
1119
+ const original = data(yaml);
1120
+ return lines.some((line, index) => [...line.matchAll(/(?:^|\s)#/g)].some((match) => {
1121
+ const cut = [...lines];
1122
+ cut[index] = line.slice(0, match.index);
1123
+ const read = data(cut.join("\n"));
1124
+ return read !== void 0 && equal(read.value, original?.value);
1125
+ }));
1126
+ }
1127
+ /** Writes the frontmatter above the body. Comark's own writer leaves out the `---` lines when the body is empty. */
1128
+ function withFrontmatter(body, frontmatter = {}) {
1129
+ if (!Object.keys(frontmatter).length) return body;
1130
+ return `---\n${renderFrontmatter(frontmatter, "")}\n---\n${body ? `\n${body}` : ""}`;
1062
1131
  }
1063
1132
  /**
1064
1133
  * Whether the editor can open this Markdown and save it without changing what readers see. Pass the parse plugins the
1065
1134
  * page renders with, so syntax only they read, such as footnote definitions, counts too.
1066
1135
  */
1067
1136
  async function checkMarkdown(markdown, options = {}) {
1137
+ const frontmatter = readFrontmatter(markdown);
1138
+ if (frontmatter.kind === "lost") return {
1139
+ editable: false,
1140
+ reason: frontmatter.problem
1141
+ };
1068
1142
  const first = await fromMarkdown(markdown);
1069
1143
  if (first.lossy) return { editable: false };
1070
1144
  const written = await toMarkdown(first.doc, first);
@@ -1273,10 +1347,28 @@ function createEditorCodec(options = {}) {
1273
1347
  });
1274
1348
  return {
1275
1349
  extensions: () => [kit],
1276
- fromMarkdown: async (markdown) => (await fromMarkdown(markdown)).doc,
1350
+ async fromMarkdown(markdown) {
1351
+ const frontmatter = readFrontmatter(markdown);
1352
+ return (await fromMarkdown(frontmatter.kind === "lost" ? frontmatter.body ?? markdown : markdown)).doc;
1353
+ },
1277
1354
  toMarkdown: (doc) => toMarkdown(doc),
1355
+ split(markdown) {
1356
+ const frontmatter = readFrontmatter(markdown);
1357
+ return frontmatter.kind === "kept" ? {
1358
+ body: frontmatter.body,
1359
+ fields: frontmatter.fields
1360
+ } : {
1361
+ body: markdown,
1362
+ fields: {}
1363
+ };
1364
+ },
1365
+ join: ({ body, fields }) => withFrontmatter(body, fields),
1278
1366
  async verify(markdown) {
1279
- if (Object.keys((await fromMarkdown(markdown)).frontmatter).length) return { writable: false };
1367
+ const frontmatter = readFrontmatter(markdown);
1368
+ if (frontmatter.kind !== "none") return {
1369
+ writable: false,
1370
+ issues: [{ message: frontmatter.kind === "lost" ? frontmatter.problem : "The frontmatter is in the room's body, where a save would drop it; open the room with the codec's `split()` so it stays in the room's fields." }]
1371
+ };
1280
1372
  const check = await checkMarkdown(markdown, { plugins: options.plugins });
1281
1373
  return check.editable ? {
1282
1374
  writable: true,
@@ -1,8 +1,9 @@
1
- import { x as ComarkKitOptions } from "./editor-KD97dL_1.mjs";
1
+ import { S as ComarkKitOptions } from "./editor-yoVL7Ajq.mjs";
2
2
  import { ComarkPlugin } from "comark";
3
3
  import { AnyExtension } from "@tiptap/core";
4
4
  import { Editor } from "@tiptap/vue-3";
5
- import { PropType, Ref } from "vue";
5
+ import { PropertiesOptions } from "comark-properties";
6
+ import { PropType, Ref, VNode } from "vue";
6
7
  //#region src/editor-vue.d.ts
7
8
  export interface UseComarkEditorOptions {
8
9
  /** Added after ComarkKit, such as a placeholder. */
@@ -18,9 +19,17 @@ export interface UseComarkEditorOptions {
18
19
  export declare function useComarkEditor(markdown: Ref<string>, options?: UseComarkEditorOptions): {
19
20
  editor: import("vue").ShallowRef<Editor | undefined, Editor | undefined>;
20
21
  editable: Ref<boolean, boolean>;
22
+ frontmatter: import("vue").ShallowRef<Record<string, unknown>, Record<string, unknown>>;
21
23
  };
22
- /** `useComarkEditor` as a component: `<ComarkEditor v-model="markdown" />`. */
24
+ /**
25
+ * `useComarkEditor` as a component: `<ComarkEditor v-model="markdown" />`. The frontmatter keys that comark-properties
26
+ * shows on the page show above the text the same way, read-only. Pass its options in `properties`, or `false` to hide them.
27
+ */
23
28
  export declare const ComarkEditor: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
29
+ properties: {
30
+ type: PropType<PropertiesOptions | boolean>;
31
+ default: boolean;
32
+ };
24
33
  modelValue: {
25
34
  type: StringConstructor;
26
35
  required: true;
@@ -33,9 +42,15 @@ export declare const ComarkEditor: import("vue").DefineComponent<import("vue").E
33
42
  type: PropType<ComarkPlugin<any, any>[]>;
34
43
  default: undefined;
35
44
  };
36
- }>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
45
+ }>, () => VNode<import("vue").RendererNode, import("vue").RendererElement, {
37
46
  [key: string]: any;
38
- }> | null, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, "update:modelValue"[], "update:modelValue", import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
47
+ }> | VNode<import("vue").RendererNode, import("vue").RendererElement, {
48
+ [key: string]: any;
49
+ }>[] | null, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, "update:modelValue"[], "update:modelValue", import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
50
+ properties: {
51
+ type: PropType<PropertiesOptions | boolean>;
52
+ default: boolean;
53
+ };
39
54
  modelValue: {
40
55
  type: StringConstructor;
41
56
  required: true;
@@ -51,6 +66,7 @@ export declare const ComarkEditor: import("vue").DefineComponent<import("vue").E
51
66
  }>> & Readonly<{
52
67
  "onUpdate:modelValue"?: ((...args: any[]) => any) | undefined;
53
68
  }>, {
69
+ properties: boolean | PropertiesOptions;
54
70
  extensions: AnyExtension[];
55
71
  plugins: ComarkPlugin<any, any>[];
56
72
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
@@ -1,6 +1,12 @@
1
- import { c as fromMarkdown, l as toMarkdown, m as ComarkKit, s as checkMarkdown } from "./editor-DmxNPK7K.mjs";
1
+ import { c as fromMarkdown, l as toMarkdown, m as ComarkKit, s as checkMarkdown } from "./editor-9IcfELjG.mjs";
2
2
  import { Editor, EditorContent } from "@tiptap/vue-3";
3
+ import { rowsOf } from "comark-properties";
4
+ import { Properties } from "comark-properties/vue";
3
5
  import { defineComponent, h, onScopeDispose, ref, shallowRef, watch } from "vue";
6
+ //#region ../../shared/nodes.ts
7
+ const isElement = (node) => Array.isArray(node) && node[0] !== null;
8
+ const childrenOf = ([, , ...rest]) => rest;
9
+ //#endregion
4
10
  //#region src/editor-vue.ts
5
11
  /**
6
12
  * A Tiptap editor bound to a Markdown string: an edit writes the Markdown back, and a change from outside reloads the
@@ -9,7 +15,8 @@ import { defineComponent, h, onScopeDispose, ref, shallowRef, watch } from "vue"
9
15
  function useComarkEditor(markdown, options = {}) {
10
16
  const editor = shallowRef();
11
17
  const editable = ref(false);
12
- let frontmatter = {};
18
+ /** The document's frontmatter, which the editor keeps beside the text and writes back on each save. */
19
+ const frontmatter = shallowRef({});
13
20
  let written;
14
21
  let loads = 0;
15
22
  let saves = 0;
@@ -17,7 +24,7 @@ function useComarkEditor(markdown, options = {}) {
17
24
  const current = editor.value;
18
25
  if (!current) return;
19
26
  const run = ++saves;
20
- const next = await toMarkdown(current.getJSON(), { frontmatter });
27
+ const next = await toMarkdown(current.getJSON(), { frontmatter: frontmatter.value });
21
28
  if (run !== saves) return;
22
29
  written = next;
23
30
  markdown.value = next;
@@ -27,7 +34,7 @@ function useComarkEditor(markdown, options = {}) {
27
34
  ++saves;
28
35
  const [check, read] = await Promise.all([checkMarkdown(source, { plugins: options.plugins }), fromMarkdown(source)]);
29
36
  if (run !== loads) return;
30
- frontmatter = read.frontmatter;
37
+ frontmatter.value = read.frontmatter;
31
38
  editable.value = check.editable;
32
39
  if (editor.value) {
33
40
  editor.value.commands.setContent(read.doc, { emitUpdate: false });
@@ -47,13 +54,27 @@ function useComarkEditor(markdown, options = {}) {
47
54
  onScopeDispose(() => editor.value?.destroy());
48
55
  return {
49
56
  editor,
50
- editable
57
+ editable,
58
+ frontmatter
51
59
  };
52
60
  }
53
- /** `useComarkEditor` as a component: `<ComarkEditor v-model="markdown" />`. */
61
+ /** A comark node as a Vue node, for the few elements the properties rows hold. */
62
+ function vnode(node) {
63
+ if (!isElement(node)) return Array.isArray(node) ? null : node;
64
+ return h(node[0], node[1], childrenOf(node).map(vnode));
65
+ }
66
+ /**
67
+ * `useComarkEditor` as a component: `<ComarkEditor v-model="markdown" />`. The frontmatter keys that comark-properties
68
+ * shows on the page show above the text the same way, read-only. Pass its options in `properties`, or `false` to hide them.
69
+ */
54
70
  const ComarkEditor = defineComponent({
55
71
  name: "ComarkEditor",
72
+ inheritAttrs: false,
56
73
  props: {
74
+ properties: {
75
+ type: [Object, Boolean],
76
+ default: true
77
+ },
57
78
  modelValue: {
58
79
  type: String,
59
80
  required: true
@@ -68,17 +89,27 @@ const ComarkEditor = defineComponent({
68
89
  }
69
90
  },
70
91
  emits: ["update:modelValue"],
71
- setup(props, { emit }) {
92
+ setup(props, { attrs, emit }) {
72
93
  const markdown = ref(props.modelValue);
73
94
  watch(() => props.modelValue, (value) => markdown.value = value);
74
95
  watch(markdown, (value) => {
75
96
  if (value !== props.modelValue) emit("update:modelValue", value);
76
97
  });
77
- const { editor } = useComarkEditor(markdown, {
98
+ const { editor, frontmatter } = useComarkEditor(markdown, {
78
99
  extensions: props.extensions,
79
100
  plugins: props.plugins
80
101
  });
81
- return () => editor.value ? h(EditorContent, { editor: editor.value }) : null;
102
+ return () => {
103
+ if (!editor.value) return null;
104
+ const content = h(EditorContent, {
105
+ ...attrs,
106
+ editor: editor.value
107
+ });
108
+ if (props.properties === false) return content;
109
+ const options = props.properties === true ? {} : props.properties;
110
+ const { rows } = rowsOf(frontmatter.value, options);
111
+ return rows.length ? [h(Properties, null, () => rows.map(vnode)), content] : content;
112
+ };
82
113
  }
83
114
  });
84
115
  //#endregion
@@ -50,7 +50,7 @@ declare const ComarkKit: Extension<ComarkKitOptions, any>;
50
50
  interface EditorDocument {
51
51
  /** The document as Tiptap JSON, for `setContent` or a collaborative room. */
52
52
  doc: JSONContent;
53
- /** The front matter, which stays outside the editor: a collaborative room keeps only the body. */
53
+ /** The front matter, which stays outside the editor. A collaborative room keeps it beside the body, in its fields. */
54
54
  frontmatter: Record<string, unknown>;
55
55
  /** The Markdown holds something the editor can't keep, so writing it back would change more than the edit. */
56
56
  lossy: boolean;
@@ -61,11 +61,17 @@ declare function fromMarkdown(markdown: string): Promise<EditorDocument>;
61
61
  declare function toMarkdown(doc: JSONContent, options?: {
62
62
  frontmatter?: Record<string, unknown>;
63
63
  }): Promise<string>;
64
+ /** A Markdown file as a collaborative room holds it: the body the editor shows, and the frontmatter beside it. */
65
+ interface SplitMarkdown {
66
+ body: string;
67
+ fields: Record<string, unknown>;
68
+ }
64
69
  type MarkdownCheck = {
65
70
  editable: true;
66
71
  /** The Markdown as the editor writes it, which the first save produces. */ markdown: string;
67
72
  } | {
68
73
  editable: false;
74
+ /** Why, when it's the frontmatter the editor can't keep. */ reason?: string;
69
75
  };
70
76
  /**
71
77
  * Whether the editor can open this Markdown and save it without changing what readers see. Pass the parse plugins the
@@ -128,15 +134,33 @@ declare const Upload: Extension<UploadOptions, any>;
128
134
  * A schema and Markdown codec that a server and its browsers share, as a collaborative room needs: the server converts
129
135
  * the stored Markdown with it, and every editor that joins builds the same schema from `extensions()`. ViteHub
130
136
  * Realtime's `editor` option takes it as it is.
137
+ *
138
+ * A room holds the body as the editor's document and the frontmatter as its fields, a map of one value per key, so an
139
+ * edit to the text never writes over the metadata. `split()` and `join()` convert between a stored file and the two.
131
140
  */
132
141
  interface EditorCodec {
133
142
  extensions(): AnyExtension[];
143
+ /** Reads a room's body, without frontmatter. */
134
144
  fromMarkdown(markdown: string): Promise<JSONContent>;
145
+ /** Writes a room's body, without frontmatter. */
135
146
  toMarkdown(doc: JSONContent): Promise<string>;
136
- /** Whether the room may save this document. `canonical` is what the first save writes for it unchanged. */
147
+ /**
148
+ * Splits a stored file into the room's body and fields. Frontmatter a save couldn't write back stays in the body,
149
+ * where `verify()` says why the room can't save it.
150
+ */
151
+ split(markdown: string): SplitMarkdown;
152
+ /** Joins a room's body and fields into the file to store. */
153
+ join(document: SplitMarkdown): string;
154
+ /**
155
+ * Whether the room may save this body. `canonical` is what the first save writes for it unchanged, and `issues` says
156
+ * why a body that still holds frontmatter can't be saved. The fields need no check: every JSON value survives YAML.
157
+ */
137
158
  verify(markdown: string): Promise<{
138
159
  writable: boolean;
139
160
  canonical?: string;
161
+ issues?: {
162
+ message: string;
163
+ }[];
140
164
  }>;
141
165
  }
142
166
  interface EditorCodecOptions {
@@ -146,4 +170,4 @@ interface EditorCodecOptions {
146
170
  }
147
171
  declare function createEditorCodec(options?: EditorCodecOptions): EditorCodec;
148
172
  //#endregion
149
- export { FootnoteDefinition as A, CriticComment as C, DefinitionDescription as D, CriticInsertion as E, InlineFootnote as M, DefinitionList as O, ComarkSlot as S, CriticHighlight as T, ComarkComment as _, Upload as a, ComarkKit as b, startUploads as c, EditorDocument as d, MarkdownCheck as f, ComarkBlock as g, toMarkdown as h, PendingUpload as i, FootnoteReference as j, DefinitionTerm as k, uploadKey as l, fromMarkdown as m, EditorCodecOptions as n, UploadOptions as o, checkMarkdown as p, createEditorCodec as r, findUpload as s, EditorCodec as t, uploadPlugin as u, ComarkImage as v, CriticDeletion as w, ComarkKitOptions as x, ComarkInline as y };
173
+ export { DefinitionTerm as A, ComarkSlot as C, CriticInsertion as D, CriticHighlight as E, FootnoteReference as M, InlineFootnote as N, DefinitionDescription as O, ComarkKitOptions as S, CriticDeletion as T, ComarkBlock as _, Upload as a, ComarkInline as b, startUploads as c, EditorDocument as d, MarkdownCheck as f, toMarkdown as g, fromMarkdown as h, PendingUpload as i, FootnoteDefinition as j, DefinitionList as k, uploadKey as l, checkMarkdown as m, EditorCodecOptions as n, UploadOptions as o, SplitMarkdown as p, createEditorCodec as r, findUpload as s, EditorCodec as t, uploadPlugin as u, ComarkComment as v, CriticComment as w, ComarkKit as x, ComarkImage as y };
package/dist/editor.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { A as FootnoteDefinition, C as CriticComment, D as DefinitionDescription, E as CriticInsertion, M as InlineFootnote, O as DefinitionList, S as ComarkSlot, T as CriticHighlight, _ as ComarkComment, a as Upload, b as ComarkKit, c as startUploads, d as EditorDocument, f as MarkdownCheck, g as ComarkBlock, h as toMarkdown, i as PendingUpload, j as FootnoteReference, k as DefinitionTerm, l as uploadKey, m as fromMarkdown, n as EditorCodecOptions, o as UploadOptions, p as checkMarkdown, r as createEditorCodec, s as findUpload, t as EditorCodec, u as uploadPlugin, v as ComarkImage, w as CriticDeletion, x as ComarkKitOptions, y as ComarkInline } from "./editor-KD97dL_1.mjs";
2
- export { ComarkBlock, ComarkComment, ComarkImage, ComarkInline, ComarkKit, type ComarkKitOptions, ComarkSlot, CriticComment, CriticDeletion, CriticHighlight, CriticInsertion, DefinitionDescription, DefinitionList, DefinitionTerm, type EditorCodec, type EditorCodecOptions, type EditorDocument, FootnoteDefinition, FootnoteReference, InlineFootnote, type MarkdownCheck, type PendingUpload, Upload, type UploadOptions, checkMarkdown, createEditorCodec, findUpload, fromMarkdown, startUploads, toMarkdown, uploadKey, uploadPlugin };
1
+ import { A as DefinitionTerm, C as ComarkSlot, D as CriticInsertion, E as CriticHighlight, M as FootnoteReference, N as InlineFootnote, O as DefinitionDescription, S as ComarkKitOptions, T as CriticDeletion, _ as ComarkBlock, a as Upload, b as ComarkInline, c as startUploads, d as EditorDocument, f as MarkdownCheck, g as toMarkdown, h as fromMarkdown, i as PendingUpload, j as FootnoteDefinition, k as DefinitionList, l as uploadKey, m as checkMarkdown, n as EditorCodecOptions, o as UploadOptions, p as SplitMarkdown, r as createEditorCodec, s as findUpload, t as EditorCodec, u as uploadPlugin, v as ComarkComment, w as CriticComment, x as ComarkKit, y as ComarkImage } from "./editor-yoVL7Ajq.mjs";
2
+ export { ComarkBlock, ComarkComment, ComarkImage, ComarkInline, ComarkKit, type ComarkKitOptions, ComarkSlot, CriticComment, CriticDeletion, CriticHighlight, CriticInsertion, DefinitionDescription, DefinitionList, DefinitionTerm, type EditorCodec, type EditorCodecOptions, type EditorDocument, FootnoteDefinition, FootnoteReference, InlineFootnote, type MarkdownCheck, type PendingUpload, type SplitMarkdown, Upload, type UploadOptions, checkMarkdown, createEditorCodec, findUpload, fromMarkdown, startUploads, toMarkdown, uploadKey, uploadPlugin };
package/dist/editor.mjs CHANGED
@@ -1 +1 @@
1
- export { u as ComarkBlock, d as ComarkComment, f as ComarkImage, p as ComarkInline, m as ComarkKit, h as ComarkSlot, g as CriticComment, _ as CriticDeletion, v as CriticHighlight, y as CriticInsertion, b as DefinitionDescription, x as DefinitionList, S as DefinitionTerm, C as FootnoteDefinition, w as FootnoteReference, T as InlineFootnote, n as Upload, s as checkMarkdown, t as createEditorCodec, r as findUpload, c as fromMarkdown, i as startUploads, l as toMarkdown, a as uploadKey, o as uploadPlugin } from "./editor-DmxNPK7K.mjs";
1
+ export { u as ComarkBlock, d as ComarkComment, f as ComarkImage, p as ComarkInline, m as ComarkKit, h as ComarkSlot, g as CriticComment, _ as CriticDeletion, v as CriticHighlight, y as CriticInsertion, b as DefinitionDescription, x as DefinitionList, S as DefinitionTerm, C as FootnoteDefinition, w as FootnoteReference, T as InlineFootnote, n as Upload, s as checkMarkdown, t as createEditorCodec, r as findUpload, c as fromMarkdown, i as startUploads, l as toMarkdown, a as uploadKey, o as uploadPlugin } from "./editor-9IcfELjG.mjs";
package/dist/html.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as criticCss, n as Preset, r as PresetOptions } from "./index-Dbqxt4Qg.mjs";
1
+ import { a as criticCss, n as Preset, r as PresetOptions } from "./index-DVGSDzBS.mjs";
2
2
  import { NodeHandler } from "comark";
3
3
  //#region src/html.d.ts
4
4
  /**
package/dist/html.mjs CHANGED
@@ -1,6 +1,7 @@
1
- import { t as typesetCss } from "./typeset-css-5aJ7cSmh.mjs";
1
+ import { t as typesetCss } from "./typeset-css-blSNpFNK.mjs";
2
2
  import { n as preset, t as criticCss } from "./src-PkmYCSNu.mjs";
3
- import { a as start, n as mermaidBar, r as mermaidCss } from "./client-Bjd6VcGQ.mjs";
3
+ import { a as start, n as mermaidBar, r as mermaidCss } from "./client-kUEiRbtp.mjs";
4
+ import { t as rawText } from "./text-D8j9clg3.mjs";
4
5
  import { escapeHtml } from "comark/utils";
5
6
  import * as annotate from "comark-annotate/html";
6
7
  import * as badge from "comark-badge/html";
@@ -38,6 +39,10 @@ import * as table from "comark-table/html";
38
39
  import * as tabs from "comark-tabs/html";
39
40
  import * as tests from "comark-tests/html";
40
41
  import * as zoom from "comark-zoom/html";
42
+ //#region ../../shared/html.ts
43
+ /** A value as the inside of a double-quoted HTML attribute. */
44
+ const attr = (value) => escapeHtml(rawText(value), { "\"": "&quot;" });
45
+ //#endregion
41
46
  //#region src/html.ts
42
47
  const styled = /* @__PURE__ */ new WeakSet();
43
48
  /**
@@ -46,7 +51,6 @@ const styled = /* @__PURE__ */ new WeakSet();
46
51
  * hash without rendering a document first.
47
52
  */
48
53
  const mermaidScript = `(${start.toString()})()`;
49
- const attr = (value) => escapeHtml(value, { "\"": "&quot;" });
50
54
  /**
51
55
  * Wraps Pele's card in `.cmm` with the bar of buttons and the source they copy. The wrapper is a wide block with the
52
56
  * diagram's own width, so a page that gives wide blocks room shows a long flowchart at its size rather than shrinking
@@ -7,6 +7,7 @@ import { KeysOptions } from "comark-keys";
7
7
  import { ReadingTimeOptions } from "comark-reading-time";
8
8
  import { XrefOptions } from "comark-xref";
9
9
  import { ComarkPlugin } from "comark";
10
+ import { PropertiesOptions } from "comark-properties";
10
11
  import { BadgeOptions } from "comark-badge";
11
12
  import { AnnotateOptions } from "comark-annotate";
12
13
  import { CastOptions } from "comark-cast";
@@ -23,7 +24,6 @@ import { MathOptions } from "comark-mathml";
23
24
  import { MediaOptions } from "comark-media";
24
25
  import { OutlineOptions } from "comark-outline";
25
26
  import { PeleConfig } from "comark-pele";
26
- import { PropertiesOptions } from "comark-properties";
27
27
  import { QuizOptions } from "comark-quiz";
28
28
  import { TableOptions } from "comark-table";
29
29
  import { TabsOptions } from "comark-tabs";
package/dist/index.d.mts CHANGED
@@ -1,3 +1,3 @@
1
1
  import { t as typesetCss } from "./typeset-css-DdOGL7WF.mjs";
2
- import { a as criticCss, c as typeset, i as Renderer, n as Preset, o as preset, r as PresetOptions, s as TYPESET_TAG, t as AnyPlugin } from "./index-Dbqxt4Qg.mjs";
2
+ import { a as criticCss, c as typeset, i as Renderer, n as Preset, o as preset, r as PresetOptions, s as TYPESET_TAG, t as AnyPlugin } from "./index-DVGSDzBS.mjs";
3
3
  export { AnyPlugin, Preset, PresetOptions, Renderer, TYPESET_TAG, criticCss, preset, typeset, typesetCss };
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { t as typesetCss } from "./typeset-css-5aJ7cSmh.mjs";
1
+ import { t as typesetCss } from "./typeset-css-blSNpFNK.mjs";
2
2
  import { i as typeset, n as preset, r as TYPESET_TAG, t as criticCss } from "./src-PkmYCSNu.mjs";
3
3
  export { TYPESET_TAG, criticCss, preset, typeset, typesetCss };
package/dist/react.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as criticCss, n as Preset, r as PresetOptions } from "./index-Dbqxt4Qg.mjs";
1
+ import { a as criticCss, n as Preset, r as PresetOptions } from "./index-DVGSDzBS.mjs";
2
2
  import { ComponentType } from "react";
3
3
  //#region src/react.d.ts
4
4
  type AnyComponent = ComponentType<any>;
package/dist/react.mjs CHANGED
@@ -1,6 +1,6 @@
1
- import { t as typesetCss } from "./typeset-css-5aJ7cSmh.mjs";
1
+ import { t as typesetCss } from "./typeset-css-blSNpFNK.mjs";
2
2
  import { n as preset, t as criticCss } from "./src-PkmYCSNu.mjs";
3
- import { i as startMermaid, n as mermaidBar, t as addMermaidStyles } from "./client-Bjd6VcGQ.mjs";
3
+ import { i as startMermaid, n as mermaidBar, t as addMermaidStyles } from "./client-kUEiRbtp.mjs";
4
4
  import { createElement, useEffect } from "react";
5
5
  import * as badge from "comark-badge/react";
6
6
  import * as cards from "comark-cards/react";