@threadlabs/looma 0.28.17 → 0.28.19

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.
@@ -309,15 +309,49 @@ function parseLoomaFenceInfo(info) {
309
309
  ...numbers ? { lineNumbers: numbers[1] === void 0 } : {}
310
310
  };
311
311
  }
312
+ var MAPPING_KEY = /^(?:[^\s#:'"\-?,[\]{}&*!|>%@`][^:\n]*?|"[^"\n]*"|'[^'\n]*')[ \t]*:(?:[ \t]|$)/;
313
+ var NOTATION = /[ \t]*(?:\/\/|#|--|<!--|\{?\/\*)[ \t]*\[!code highlight(?::(\d+))?\][ \t]*(?:-->|\*\/\}?)?[ \t]*$/;
314
+ var ESCAPED_NOTATION = /\[\\(\\*!code highlight)/g;
315
+ /**
316
+ * Applies code notation: a line ending in a `[!code highlight]` comment is highlighted and loses
317
+ * the comment, `[!code highlight:3]` covers three lines, and a marker alone on its line is removed
318
+ * with the line and applies to the lines after it. A backslash between `[` and `!` escapes a
319
+ * marker, `[\!code highlight]`, which then shows as text with one backslash fewer and highlights
320
+ * nothing.
321
+ * @contract Returns the code a reader sees and its highlighted line numbers, from 1, ascending.
322
+ * ponytail: matched on source lines, so a string that ends in the marker counts too.
323
+ */
324
+ function applyLoomaCodeNotation(code) {
325
+ const kept = [];
326
+ const lines = /* @__PURE__ */ new Set();
327
+ for (const line of code.split("\n")) {
328
+ const match = NOTATION.exec(line);
329
+ if (!match) {
330
+ kept.push(line);
331
+ continue;
332
+ }
333
+ const rest = line.slice(0, match.index);
334
+ const own = rest.trim() !== "";
335
+ if (own) kept.push(rest);
336
+ for (let count = 0; count < Number(match[1] ?? 1); count++) lines.add(kept.length + (own ? 0 : 1) + count);
337
+ }
338
+ return {
339
+ code: kept.join("\n").replace(ESCAPED_NOTATION, "[$1"),
340
+ lines: [...lines].sort((a, b) => a - b)
341
+ };
342
+ }
312
343
  /**
313
344
  * Splits YAML front matter from the start of a document.
314
- * @contract Front matter opens with a `---` line at the very start, holds at least one `key: value`
315
- * line, and closes at a `---` or `...` line. `source` is the block as written, delimiters included.
345
+ * @contract Front matter opens with a `---` line at the very start and closes at a `---` or `...`
346
+ * line, and what lies between is a YAML mapping: its first line that is not blank or a comment
347
+ * starts with a key, whose value may be a scalar, a list or a mapping. `source` is the block as
348
+ * written, delimiters included.
316
349
  * @failure Returns null when the document has none.
317
350
  */
318
351
  function splitLoomaFrontmatter(document) {
319
352
  const match = /^---[ \t]*\r?\n([\s\S]*?)\r?\n(?:---|\.\.\.)[ \t]*(?:\r?\n|$)/.exec(document.slice(0, 8192));
320
- if (!match || !/^[A-Za-z][\w-]*:[ \t]*\S/m.test(match[1])) return null;
353
+ const first = match?.[1].split(/\r?\n/).find((line) => line.trim() && !line.trimStart().startsWith("#"));
354
+ if (!match || !first || !MAPPING_KEY.test(first)) return null;
321
355
  return {
322
356
  source: match[0].trimEnd(),
323
357
  yaml: match[1],
@@ -9695,7 +9729,10 @@ function taskLists(state) {
9695
9729
  inline.content = inline.content.replace(TASK, "");
9696
9730
  item.attrSet("data-type", "taskItem");
9697
9731
  item.attrSet("data-checked", String(checked));
9698
- item.meta = { checked };
9732
+ inline.meta = {
9733
+ ...inline.meta,
9734
+ task: checked
9735
+ };
9699
9736
  }
9700
9737
  }
9701
9738
  }
@@ -9737,6 +9774,11 @@ function attributes(state) {
9737
9774
  const token = tokens[index];
9738
9775
  if (token.type !== "inline" || !token.children) continue;
9739
9776
  const children = token.children;
9777
+ if (typeof token.meta?.task === "boolean") {
9778
+ const html = (content) => Object.assign(new state.Token("html_inline", "", 0), { content });
9779
+ children.unshift(html(`<label><input type="checkbox" disabled=""${token.meta.task ? " checked=\"\"" : ""}> `));
9780
+ children.push(html("</label>"));
9781
+ }
9740
9782
  for (let child = children.length - 3; child >= 0; child--) {
9741
9783
  const text = children[child + 1];
9742
9784
  if (children[child].type !== "link_open" || children[child].markup !== "linkify" || /^(?:[a-z][\w+.-]*:|www\.)/i.test(text.content)) continue;
@@ -9813,7 +9855,7 @@ function tableOfContents(tokens, format, depth, escape) {
9813
9855
  * @contract Renders the canonical HTML, which is the HTML LoomaEditor saves. It is not sanitized.
9814
9856
  * Linkify and raw HTML are instance options; `createLoomaMarkdownIt` turns both on.
9815
9857
  */
9816
- function loomaMarkdown$1(md) {
9858
+ function loomaMarkdown$1(md, { codeNotation = "apply" } = {}) {
9817
9859
  const escape = md.utils.escapeHtml;
9818
9860
  md.block.ruler.before("table", "looma_frontmatter", frontmatter);
9819
9861
  md.block.ruler.before("fence", "looma_container", container, { alt: [
@@ -9864,15 +9906,10 @@ function loomaMarkdown$1(md) {
9864
9906
  const id = String(attributes.id);
9865
9907
  return `<span data-type="mention" class="looma-mention" data-id="${escape(id)}" data-label="${escape(label || id)}">@${escape(label || id)}</span>`;
9866
9908
  };
9867
- const listItem = rules.list_item_open ?? ((tokens, index, options, _env, self) => self.renderToken(tokens, index, options));
9868
- rules.list_item_open = (tokens, index, options, env, self) => {
9869
- const html = listItem(tokens, index, options, env, self);
9870
- const task = tokens[index].meta;
9871
- return task && typeof task.checked === "boolean" ? `${html}<input type="checkbox" disabled=""${task.checked ? " checked=\"\"" : ""}>` : html;
9872
- };
9873
9909
  const code = (content, info) => {
9874
9910
  const { language, meta } = parseLoomaFenceInfo(info);
9875
- return `<pre><code${language ? ` class="language-${escape(language)}"` : ""}${meta ? ` data-meta="${escape(meta)}"` : ""}>${escape(content.replace(/\n$/, ""))}</code></pre>\n`;
9911
+ const source = content.replace(/\n$/, "");
9912
+ return `<pre><code${language ? ` class="language-${escape(language)}"` : ""}${meta ? ` data-meta="${escape(meta)}"` : ""}>${escape(codeNotation === "keep" ? source : applyLoomaCodeNotation(source).code)}</code></pre>\n`;
9876
9913
  };
9877
9914
  rules.fence = (tokens, index) => code(tokens[index].content, tokens[index].info);
9878
9915
  rules.code_block = (tokens, index) => code(tokens[index].content, "");
@@ -9881,17 +9918,17 @@ function loomaMarkdown$1(md) {
9881
9918
  * A markdown-it instance for Looma Markdown: raw HTML and GFM autolinks on, with `loomaMarkdown`.
9882
9919
  * @contract Each call returns a new instance that callers may extend.
9883
9920
  */
9884
- function createLoomaMarkdownIt() {
9921
+ function createLoomaMarkdownIt(options = {}) {
9885
9922
  return new MarkdownIt({
9886
9923
  html: true,
9887
9924
  linkify: true,
9888
9925
  typographer: false
9889
- }).use(loomaMarkdown$1);
9926
+ }).use(loomaMarkdown$1, options);
9890
9927
  }
9891
9928
  //#endregion
9892
9929
  //#region src/editor/extensions/markdown.ts
9893
- /** The markdown-it instance LoomaEditor reads Looma Markdown with. */
9894
- var loomaMarkdown = createLoomaMarkdownIt();
9930
+ /** The markdown-it instance LoomaEditor reads Looma Markdown with; code keeps its notation as source. */
9931
+ var loomaMarkdown = createLoomaMarkdownIt({ codeNotation: "keep" });
9895
9932
  /**
9896
9933
  * Renders Looma Markdown as the HTML the editor parses.
9897
9934
  * @contract The result is not sanitized; pass it to the editor's `setContent`, whose schema
@@ -1,5 +1,5 @@
1
1
  import { type Node as ProseMirrorNode } from "@tiptap/pm/model";
2
- /** The markdown-it instance LoomaEditor reads Looma Markdown with. */
2
+ /** The markdown-it instance LoomaEditor reads Looma Markdown with; code keeps its notation as source. */
3
3
  export declare const loomaMarkdown: import("markdown-it").default;
4
4
  /**
5
5
  * Renders Looma Markdown as the HTML the editor parses.
@@ -23,7 +23,11 @@ var files = /* #__PURE__ */ Object.assign({
23
23
  "../../markdown-fixtures/code-blocks/fences.md": "```ts\nconst answer = 42;\n```\n\n```\nplain text\n```\n\n indented\n",
24
24
  "../../markdown-fixtures/code-blocks/long-fence.html": "<pre><code class=\"language-md\">```ts\nconst a = 1;\n```</code></pre>\n",
25
25
  "../../markdown-fixtures/code-blocks/long-fence.md": "````md\n```ts\nconst a = 1;\n```\n````\n",
26
- "../../markdown-fixtures/code-meta/notation.html": "<pre><code class=\"language-ts\">const port = 8080; // [!code highlight]</code></pre>\n",
26
+ "../../markdown-fixtures/code-meta/notation-alone.html": "<pre><code class=\"language-ts\">const host = &quot;localhost&quot;;\nconst port = 8080;</code></pre>\n",
27
+ "../../markdown-fixtures/code-meta/notation-alone.md": "```ts\n// [!code highlight:2]\nconst host = \"localhost\";\nconst port = 8080;\n```\n",
28
+ "../../markdown-fixtures/code-meta/notation-escaped.html": "<pre><code class=\"language-md\">const port = 8080; // [!code highlight]\nconst host = &quot;localhost&quot;; // [\\!code highlight:2]</code></pre>\n",
29
+ "../../markdown-fixtures/code-meta/notation-escaped.md": "```md\nconst port = 8080; // [\\!code highlight]\nconst host = \"localhost\"; // [\\\\!code highlight:2]\n```\n",
30
+ "../../markdown-fixtures/code-meta/notation.html": "<pre><code class=\"language-ts\">const port = 8080;</code></pre>\n",
27
31
  "../../markdown-fixtures/code-meta/notation.md": "```ts\nconst port = 8080; // [!code highlight]\n```\n",
28
32
  "../../markdown-fixtures/code-meta/title-lines.html": "<pre><code class=\"language-ts\" data-meta=\"title=&quot;config.ts&quot; {1,3-4} :line-numbers\">export default {\n name: &quot;docs&quot;,\n base: &quot;/docs/&quot;,\n};</code></pre>\n",
29
33
  "../../markdown-fixtures/code-meta/title-lines.md": "```ts title=\"config.ts\" {1,3-4} :line-numbers\nexport default {\n name: \"docs\",\n base: \"/docs/\",\n};\n```\n",
@@ -47,9 +51,18 @@ var files = /* #__PURE__ */ Object.assign({
47
51
  "../../markdown-fixtures/details/open.md": "::: details Show the full log {open}\nThe build finished in 42 seconds.\n:::\n",
48
52
  "../../markdown-fixtures/figures/caption.html": "<figure>\n<img src=\"./pipeline.svg\" alt=\"The build pipeline\">\n<figcaption>The build, from <em>source</em> to release</figcaption>\n</figure>\n",
49
53
  "../../markdown-fixtures/figures/caption.md": "::: figure The build, from *source* to release\n![The build pipeline](./pipeline.svg)\n:::\n",
54
+ "../../markdown-fixtures/front-matter/list-only.canonical.md": "# Notes\n",
55
+ "../../markdown-fixtures/front-matter/list-only.html": "<h1 id=\"notes\">Notes</h1>\n",
56
+ "../../markdown-fixtures/front-matter/list-only.md": "---\naliases:\n - /old/\n - /older/\n---\n\n# Notes\n",
57
+ "../../markdown-fixtures/front-matter/mapping-only.canonical.md": "# Notes\n",
58
+ "../../markdown-fixtures/front-matter/mapping-only.html": "<h1 id=\"notes\">Notes</h1>\n",
59
+ "../../markdown-fixtures/front-matter/mapping-only.md": "---\nhead:\n meta:\n robots: noindex\n---\n\n# Notes\n",
50
60
  "../../markdown-fixtures/front-matter/metadata.canonical.md": "# Notes\n",
51
61
  "../../markdown-fixtures/front-matter/metadata.html": "<h1 id=\"notes\">Notes</h1>\n",
52
62
  "../../markdown-fixtures/front-matter/metadata.md": "---\ntitle: Release notes\n---\n\n# Notes\n",
63
+ "../../markdown-fixtures/front-matter/nested.canonical.md": "# Notes\n",
64
+ "../../markdown-fixtures/front-matter/nested.html": "<h1 id=\"notes\">Notes</h1>\n",
65
+ "../../markdown-fixtures/front-matter/nested.md": "---\n# Page settings\nsidebar:\n position: 2\n items:\n - label: Intro\n href: /intro/\ntitle: Notes\n---\n\n# Notes\n",
53
66
  "../../markdown-fixtures/heading-ids/accented.html": "<h2 id=\"über-uns\">Über uns</h2>\n<h2 id=\"café-crème\">Café crème</h2>\n",
54
67
  "../../markdown-fixtures/heading-ids/accented.md": "## Über uns\n\n## Café crème\n",
55
68
  "../../markdown-fixtures/heading-ids/cjk.html": "<h2 id=\"はじめに\">はじめに</h2>\n<h2 id=\"設定-と-使い方\">設定 と 使い方</h2>\n",
@@ -85,8 +98,11 @@ var files = /* #__PURE__ */ Object.assign({
85
98
  "../../markdown-fixtures/tabs/code-group.md": "::: code-group\n```js title=\"looma.config.js\"\nexport default { theme: \"light\" };\n```\n```ts\nexport default { theme: \"light\" } satisfies Config;\n```\n:::\n",
86
99
  "../../markdown-fixtures/tabs/labelled.html": "<ui-tabs group=\"install\">\n<section aria-label=\"npm\">\n<pre><code class=\"language-sh\">npm install @threadlabs/looma</code></pre>\n</section>\n<section aria-label=\"pnpm\">\n<pre><code class=\"language-sh\">pnpm add @threadlabs/looma</code></pre>\n</section>\n</ui-tabs>\n",
87
100
  "../../markdown-fixtures/tabs/labelled.md": "::: tabs install\n== npm\n```sh\nnpm install @threadlabs/looma\n```\n== pnpm\n```sh\npnpm add @threadlabs/looma\n```\n:::\n",
88
- "../../markdown-fixtures/task-lists/checked.html": "<ul data-type=\"taskList\">\n<li data-type=\"taskItem\" data-checked=\"true\"><input type=\"checkbox\" disabled checked>Read the changelog</li>\n<li data-type=\"taskItem\" data-checked=\"false\"><input type=\"checkbox\" disabled>Run the migration</li>\n</ul>\n",
101
+ "../../markdown-fixtures/task-lists/checked.html": "<ul data-type=\"taskList\">\n<li data-type=\"taskItem\" data-checked=\"true\"><label><input type=\"checkbox\" disabled checked> Read the changelog</label></li>\n<li data-type=\"taskItem\" data-checked=\"false\"><label><input type=\"checkbox\" disabled> Run the migration</label></li>\n</ul>\n",
89
102
  "../../markdown-fixtures/task-lists/checked.md": "- [x] Read the changelog\n- [ ] Run the migration\n",
103
+ "../../markdown-fixtures/task-lists/loose.canonical.md": "- [x] Read the changelog\n\n Then tell the team.\n- [ ] Run the migration\n",
104
+ "../../markdown-fixtures/task-lists/loose.html": "<ul data-type=\"taskList\">\n<li data-type=\"taskItem\" data-checked=\"true\">\n<p><label><input type=\"checkbox\" disabled checked> Read the changelog</label></p>\n<p>Then tell the team.</p>\n</li>\n<li data-type=\"taskItem\" data-checked=\"false\">\n<p><label><input type=\"checkbox\" disabled> Run the migration</label></p>\n</li>\n</ul>\n",
105
+ "../../markdown-fixtures/task-lists/loose.md": "- [x] Read the changelog\n\n Then tell the team.\n\n- [ ] Run the migration\n",
90
106
  "../../markdown-fixtures/task-lists/mixed.canonical.md": "- \\[x\\] Done\n- Not a task\n",
91
107
  "../../markdown-fixtures/task-lists/mixed.html": "<ul>\n<li>[x] Done</li>\n<li>Not a task</li>\n</ul>\n",
92
108
  "../../markdown-fixtures/task-lists/mixed.md": "- [x] Done\n- Not a task\n",
@@ -92,10 +92,25 @@ export interface LoomaFenceInfo {
92
92
  }
93
93
  /** Reads a fence's info string: ```` ```ts title="a.ts" {1,3-5} :line-numbers ````. */
94
94
  export declare function parseLoomaFenceInfo(info: string): LoomaFenceInfo;
95
+ /**
96
+ * Applies code notation: a line ending in a `[!code highlight]` comment is highlighted and loses
97
+ * the comment, `[!code highlight:3]` covers three lines, and a marker alone on its line is removed
98
+ * with the line and applies to the lines after it. A backslash between `[` and `!` escapes a
99
+ * marker, `[\!code highlight]`, which then shows as text with one backslash fewer and highlights
100
+ * nothing.
101
+ * @contract Returns the code a reader sees and its highlighted line numbers, from 1, ascending.
102
+ * ponytail: matched on source lines, so a string that ends in the marker counts too.
103
+ */
104
+ export declare function applyLoomaCodeNotation(code: string): {
105
+ code: string;
106
+ lines: number[];
107
+ };
95
108
  /**
96
109
  * Splits YAML front matter from the start of a document.
97
- * @contract Front matter opens with a `---` line at the very start, holds at least one `key: value`
98
- * line, and closes at a `---` or `...` line. `source` is the block as written, delimiters included.
110
+ * @contract Front matter opens with a `---` line at the very start and closes at a `---` or `...`
111
+ * line, and what lies between is a YAML mapping: its first line that is not blank or a comment
112
+ * starts with a key, whose value may be a scalar, a list or a mapping. `source` is the block as
113
+ * written, delimiters included.
99
114
  * @failure Returns null when the document has none.
100
115
  */
101
116
  export declare function splitLoomaFrontmatter(document: string): {
package/markdown/index.js CHANGED
@@ -204,15 +204,49 @@ function parseLoomaFenceInfo(info) {
204
204
  ...numbers ? { lineNumbers: numbers[1] === void 0 } : {}
205
205
  };
206
206
  }
207
+ var MAPPING_KEY = /^(?:[^\s#:'"\-?,[\]{}&*!|>%@`][^:\n]*?|"[^"\n]*"|'[^'\n]*')[ \t]*:(?:[ \t]|$)/;
208
+ var NOTATION = /[ \t]*(?:\/\/|#|--|<!--|\{?\/\*)[ \t]*\[!code highlight(?::(\d+))?\][ \t]*(?:-->|\*\/\}?)?[ \t]*$/;
209
+ var ESCAPED_NOTATION = /\[\\(\\*!code highlight)/g;
210
+ /**
211
+ * Applies code notation: a line ending in a `[!code highlight]` comment is highlighted and loses
212
+ * the comment, `[!code highlight:3]` covers three lines, and a marker alone on its line is removed
213
+ * with the line and applies to the lines after it. A backslash between `[` and `!` escapes a
214
+ * marker, `[\!code highlight]`, which then shows as text with one backslash fewer and highlights
215
+ * nothing.
216
+ * @contract Returns the code a reader sees and its highlighted line numbers, from 1, ascending.
217
+ * ponytail: matched on source lines, so a string that ends in the marker counts too.
218
+ */
219
+ function applyLoomaCodeNotation(code) {
220
+ const kept = [];
221
+ const lines = /* @__PURE__ */ new Set();
222
+ for (const line of code.split("\n")) {
223
+ const match = NOTATION.exec(line);
224
+ if (!match) {
225
+ kept.push(line);
226
+ continue;
227
+ }
228
+ const rest = line.slice(0, match.index);
229
+ const own = rest.trim() !== "";
230
+ if (own) kept.push(rest);
231
+ for (let count = 0; count < Number(match[1] ?? 1); count++) lines.add(kept.length + (own ? 0 : 1) + count);
232
+ }
233
+ return {
234
+ code: kept.join("\n").replace(ESCAPED_NOTATION, "[$1"),
235
+ lines: [...lines].sort((a, b) => a - b)
236
+ };
237
+ }
207
238
  /**
208
239
  * Splits YAML front matter from the start of a document.
209
- * @contract Front matter opens with a `---` line at the very start, holds at least one `key: value`
210
- * line, and closes at a `---` or `...` line. `source` is the block as written, delimiters included.
240
+ * @contract Front matter opens with a `---` line at the very start and closes at a `---` or `...`
241
+ * line, and what lies between is a YAML mapping: its first line that is not blank or a comment
242
+ * starts with a key, whose value may be a scalar, a list or a mapping. `source` is the block as
243
+ * written, delimiters included.
211
244
  * @failure Returns null when the document has none.
212
245
  */
213
246
  function splitLoomaFrontmatter(document) {
214
247
  const match = /^---[ \t]*\r?\n([\s\S]*?)\r?\n(?:---|\.\.\.)[ \t]*(?:\r?\n|$)/.exec(document.slice(0, 8192));
215
- if (!match || !/^[A-Za-z][\w-]*:[ \t]*\S/m.test(match[1])) return null;
248
+ const first = match?.[1].split(/\r?\n/).find((line) => line.trim() && !line.trimStart().startsWith("#"));
249
+ if (!match || !first || !MAPPING_KEY.test(first)) return null;
216
250
  return {
217
251
  source: match[0].trimEnd(),
218
252
  yaml: match[1],
@@ -229,4 +263,4 @@ function loomaCodeFence(code) {
229
263
  return "`".repeat(Math.max(3, longest + 1));
230
264
  }
231
265
  //#endregion
232
- export { LOOMA_BADGE_TONES, LOOMA_CALLOUT_TONES, LOOMA_CALLOUT_TONE_ALIASES, LOOMA_CHIP_TONES, LOOMA_CONTAINER_NAMES, LOOMA_MEANING_TONES, assignLoomaHeadingIds, formatLoomaAttributes, loomaCodeFence, loomaContainerMarker, loomaHeadingSlug, normalizeLoomaCalloutTone, normalizeLoomaChipTone, parseLoomaAttributes, parseLoomaFenceInfo, readLoomaAttributes, splitLoomaFrontmatter };
266
+ export { LOOMA_BADGE_TONES, LOOMA_CALLOUT_TONES, LOOMA_CALLOUT_TONE_ALIASES, LOOMA_CHIP_TONES, LOOMA_CONTAINER_NAMES, LOOMA_MEANING_TONES, applyLoomaCodeNotation, assignLoomaHeadingIds, formatLoomaAttributes, loomaCodeFence, loomaContainerMarker, loomaHeadingSlug, normalizeLoomaCalloutTone, normalizeLoomaChipTone, parseLoomaAttributes, parseLoomaFenceInfo, readLoomaAttributes, splitLoomaFrontmatter };
@@ -1,13 +1,21 @@
1
1
  import MarkdownIt from "markdown-it";
2
+ /** How a Looma Markdown instance renders. */
3
+ export interface LoomaMarkdownOptions {
4
+ /**
5
+ * `apply` (default) renders code as a reader sees it, without its `[!code highlight]` comments;
6
+ * `keep` keeps them in the code, for an editor that edits the source.
7
+ */
8
+ codeNotation?: "apply" | "keep";
9
+ }
2
10
  /**
3
11
  * Adds Looma Markdown to a markdown-it instance: containers, alerts, task lists, the table of
4
12
  * contents, chips, mentions, brace attributes, heading anchors, code meta and front matter.
5
13
  * @contract Renders the canonical HTML, which is the HTML LoomaEditor saves. It is not sanitized.
6
14
  * Linkify and raw HTML are instance options; `createLoomaMarkdownIt` turns both on.
7
15
  */
8
- export declare function loomaMarkdown(md: MarkdownIt): void;
16
+ export declare function loomaMarkdown(md: MarkdownIt, { codeNotation }?: LoomaMarkdownOptions): void;
9
17
  /**
10
18
  * A markdown-it instance for Looma Markdown: raw HTML and GFM autolinks on, with `loomaMarkdown`.
11
19
  * @contract Each call returns a new instance that callers may extend.
12
20
  */
13
- export declare function createLoomaMarkdownIt(): MarkdownIt;
21
+ export declare function createLoomaMarkdownIt(options?: LoomaMarkdownOptions): MarkdownIt;
@@ -1,4 +1,4 @@
1
- import { LOOMA_CONTAINER_NAMES, assignLoomaHeadingIds, normalizeLoomaCalloutTone, normalizeLoomaChipTone, parseLoomaAttributes, parseLoomaFenceInfo, readLoomaAttributes, splitLoomaFrontmatter } from "./index.js";
1
+ import { LOOMA_CONTAINER_NAMES, applyLoomaCodeNotation, assignLoomaHeadingIds, normalizeLoomaCalloutTone, normalizeLoomaChipTone, parseLoomaAttributes, parseLoomaFenceInfo, readLoomaAttributes, splitLoomaFrontmatter } from "./index.js";
2
2
  import MarkdownIt from "markdown-it";
3
3
  //#region src/markdown/markdown-it.ts
4
4
  var CONTAINER_OPEN = /^(:{3,})[ \t]*([A-Za-z][\w-]*)(?:[ \t]+(.*?))?[ \t]*$/;
@@ -200,7 +200,10 @@ function taskLists(state) {
200
200
  inline.content = inline.content.replace(TASK, "");
201
201
  item.attrSet("data-type", "taskItem");
202
202
  item.attrSet("data-checked", String(checked));
203
- item.meta = { checked };
203
+ inline.meta = {
204
+ ...inline.meta,
205
+ task: checked
206
+ };
204
207
  }
205
208
  }
206
209
  }
@@ -242,6 +245,11 @@ function attributes(state) {
242
245
  const token = tokens[index];
243
246
  if (token.type !== "inline" || !token.children) continue;
244
247
  const children = token.children;
248
+ if (typeof token.meta?.task === "boolean") {
249
+ const html = (content) => Object.assign(new state.Token("html_inline", "", 0), { content });
250
+ children.unshift(html(`<label><input type="checkbox" disabled=""${token.meta.task ? " checked=\"\"" : ""}> `));
251
+ children.push(html("</label>"));
252
+ }
245
253
  for (let child = children.length - 3; child >= 0; child--) {
246
254
  const text = children[child + 1];
247
255
  if (children[child].type !== "link_open" || children[child].markup !== "linkify" || /^(?:[a-z][\w+.-]*:|www\.)/i.test(text.content)) continue;
@@ -318,7 +326,7 @@ function tableOfContents(tokens, format, depth, escape) {
318
326
  * @contract Renders the canonical HTML, which is the HTML LoomaEditor saves. It is not sanitized.
319
327
  * Linkify and raw HTML are instance options; `createLoomaMarkdownIt` turns both on.
320
328
  */
321
- function loomaMarkdown(md) {
329
+ function loomaMarkdown(md, { codeNotation = "apply" } = {}) {
322
330
  const escape = md.utils.escapeHtml;
323
331
  md.block.ruler.before("table", "looma_frontmatter", frontmatter);
324
332
  md.block.ruler.before("fence", "looma_container", container, { alt: [
@@ -369,15 +377,10 @@ function loomaMarkdown(md) {
369
377
  const id = String(attributes.id);
370
378
  return `<span data-type="mention" class="looma-mention" data-id="${escape(id)}" data-label="${escape(label || id)}">@${escape(label || id)}</span>`;
371
379
  };
372
- const listItem = rules.list_item_open ?? ((tokens, index, options, _env, self) => self.renderToken(tokens, index, options));
373
- rules.list_item_open = (tokens, index, options, env, self) => {
374
- const html = listItem(tokens, index, options, env, self);
375
- const task = tokens[index].meta;
376
- return task && typeof task.checked === "boolean" ? `${html}<input type="checkbox" disabled=""${task.checked ? " checked=\"\"" : ""}>` : html;
377
- };
378
380
  const code = (content, info) => {
379
381
  const { language, meta } = parseLoomaFenceInfo(info);
380
- return `<pre><code${language ? ` class="language-${escape(language)}"` : ""}${meta ? ` data-meta="${escape(meta)}"` : ""}>${escape(content.replace(/\n$/, ""))}</code></pre>\n`;
382
+ const source = content.replace(/\n$/, "");
383
+ return `<pre><code${language ? ` class="language-${escape(language)}"` : ""}${meta ? ` data-meta="${escape(meta)}"` : ""}>${escape(codeNotation === "keep" ? source : applyLoomaCodeNotation(source).code)}</code></pre>\n`;
381
384
  };
382
385
  rules.fence = (tokens, index) => code(tokens[index].content, tokens[index].info);
383
386
  rules.code_block = (tokens, index) => code(tokens[index].content, "");
@@ -386,12 +389,12 @@ function loomaMarkdown(md) {
386
389
  * A markdown-it instance for Looma Markdown: raw HTML and GFM autolinks on, with `loomaMarkdown`.
387
390
  * @contract Each call returns a new instance that callers may extend.
388
391
  */
389
- function createLoomaMarkdownIt() {
392
+ function createLoomaMarkdownIt(options = {}) {
390
393
  return new MarkdownIt({
391
394
  html: true,
392
395
  linkify: true,
393
396
  typographer: false
394
- }).use(loomaMarkdown);
397
+ }).use(loomaMarkdown, options);
395
398
  }
396
399
  //#endregion
397
400
  export { createLoomaMarkdownIt, loomaMarkdown };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@threadlabs/looma",
3
- "version": "0.28.17",
3
+ "version": "0.28.19",
4
4
  "type": "module",
5
5
  "description": "Looma components as HTML, Vue, and plain DOM, with design tokens.",
6
6
  "license": "MIT",
@@ -171,6 +171,7 @@
171
171
  "@tiptap/vue-3": "^2.11.5",
172
172
  "@types/markdown-it": "14.2.0",
173
173
  "@vitest/browser": "~3.2.6",
174
+ "axe-core": "^4.10.3",
174
175
  "jsdom": "^27.0.0"
175
176
  },
176
177
  "engines": {