@threadlabs/looma 0.28.17 → 0.28.18

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,46 @@ 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
+ /**
315
+ * Applies code notation: a line ending in a `[!code highlight]` comment is highlighted and loses
316
+ * the comment, `[!code highlight:3]` covers three lines, and a marker alone on its line is removed
317
+ * with the line and applies to the lines after it.
318
+ * @contract Returns the code a reader sees and its highlighted line numbers, from 1, ascending.
319
+ * ponytail: matched on source lines, so a string that ends in the marker counts too.
320
+ */
321
+ function applyLoomaCodeNotation(code) {
322
+ const kept = [];
323
+ const lines = /* @__PURE__ */ new Set();
324
+ for (const line of code.split("\n")) {
325
+ const match = NOTATION.exec(line);
326
+ if (!match) {
327
+ kept.push(line);
328
+ continue;
329
+ }
330
+ const rest = line.slice(0, match.index);
331
+ const own = rest.trim() !== "";
332
+ if (own) kept.push(rest);
333
+ for (let count = 0; count < Number(match[1] ?? 1); count++) lines.add(kept.length + (own ? 0 : 1) + count);
334
+ }
335
+ return {
336
+ code: kept.join("\n"),
337
+ lines: [...lines].sort((a, b) => a - b)
338
+ };
339
+ }
312
340
  /**
313
341
  * 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.
342
+ * @contract Front matter opens with a `---` line at the very start and closes at a `---` or `...`
343
+ * line, and what lies between is a YAML mapping: its first line that is not blank or a comment
344
+ * starts with a key, whose value may be a scalar, a list or a mapping. `source` is the block as
345
+ * written, delimiters included.
316
346
  * @failure Returns null when the document has none.
317
347
  */
318
348
  function splitLoomaFrontmatter(document) {
319
349
  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;
350
+ const first = match?.[1].split(/\r?\n/).find((line) => line.trim() && !line.trimStart().startsWith("#"));
351
+ if (!match || !first || !MAPPING_KEY.test(first)) return null;
321
352
  return {
322
353
  source: match[0].trimEnd(),
323
354
  yaml: match[1],
@@ -9695,7 +9726,10 @@ function taskLists(state) {
9695
9726
  inline.content = inline.content.replace(TASK, "");
9696
9727
  item.attrSet("data-type", "taskItem");
9697
9728
  item.attrSet("data-checked", String(checked));
9698
- item.meta = { checked };
9729
+ inline.meta = {
9730
+ ...inline.meta,
9731
+ task: checked
9732
+ };
9699
9733
  }
9700
9734
  }
9701
9735
  }
@@ -9737,6 +9771,11 @@ function attributes(state) {
9737
9771
  const token = tokens[index];
9738
9772
  if (token.type !== "inline" || !token.children) continue;
9739
9773
  const children = token.children;
9774
+ if (typeof token.meta?.task === "boolean") {
9775
+ const html = (content) => Object.assign(new state.Token("html_inline", "", 0), { content });
9776
+ children.unshift(html(`<label><input type="checkbox" disabled=""${token.meta.task ? " checked=\"\"" : ""}> `));
9777
+ children.push(html("</label>"));
9778
+ }
9740
9779
  for (let child = children.length - 3; child >= 0; child--) {
9741
9780
  const text = children[child + 1];
9742
9781
  if (children[child].type !== "link_open" || children[child].markup !== "linkify" || /^(?:[a-z][\w+.-]*:|www\.)/i.test(text.content)) continue;
@@ -9813,7 +9852,7 @@ function tableOfContents(tokens, format, depth, escape) {
9813
9852
  * @contract Renders the canonical HTML, which is the HTML LoomaEditor saves. It is not sanitized.
9814
9853
  * Linkify and raw HTML are instance options; `createLoomaMarkdownIt` turns both on.
9815
9854
  */
9816
- function loomaMarkdown$1(md) {
9855
+ function loomaMarkdown$1(md, { codeNotation = "apply" } = {}) {
9817
9856
  const escape = md.utils.escapeHtml;
9818
9857
  md.block.ruler.before("table", "looma_frontmatter", frontmatter);
9819
9858
  md.block.ruler.before("fence", "looma_container", container, { alt: [
@@ -9864,15 +9903,10 @@ function loomaMarkdown$1(md) {
9864
9903
  const id = String(attributes.id);
9865
9904
  return `<span data-type="mention" class="looma-mention" data-id="${escape(id)}" data-label="${escape(label || id)}">@${escape(label || id)}</span>`;
9866
9905
  };
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
9906
  const code = (content, info) => {
9874
9907
  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`;
9908
+ const source = content.replace(/\n$/, "");
9909
+ return `<pre><code${language ? ` class="language-${escape(language)}"` : ""}${meta ? ` data-meta="${escape(meta)}"` : ""}>${escape(codeNotation === "keep" ? source : applyLoomaCodeNotation(source).code)}</code></pre>\n`;
9876
9910
  };
9877
9911
  rules.fence = (tokens, index) => code(tokens[index].content, tokens[index].info);
9878
9912
  rules.code_block = (tokens, index) => code(tokens[index].content, "");
@@ -9881,17 +9915,17 @@ function loomaMarkdown$1(md) {
9881
9915
  * A markdown-it instance for Looma Markdown: raw HTML and GFM autolinks on, with `loomaMarkdown`.
9882
9916
  * @contract Each call returns a new instance that callers may extend.
9883
9917
  */
9884
- function createLoomaMarkdownIt() {
9918
+ function createLoomaMarkdownIt(options = {}) {
9885
9919
  return new MarkdownIt({
9886
9920
  html: true,
9887
9921
  linkify: true,
9888
9922
  typographer: false
9889
- }).use(loomaMarkdown$1);
9923
+ }).use(loomaMarkdown$1, options);
9890
9924
  }
9891
9925
  //#endregion
9892
9926
  //#region src/editor/extensions/markdown.ts
9893
- /** The markdown-it instance LoomaEditor reads Looma Markdown with. */
9894
- var loomaMarkdown = createLoomaMarkdownIt();
9927
+ /** The markdown-it instance LoomaEditor reads Looma Markdown with; code keeps its notation as source. */
9928
+ var loomaMarkdown = createLoomaMarkdownIt({ codeNotation: "keep" });
9895
9929
  /**
9896
9930
  * Renders Looma Markdown as the HTML the editor parses.
9897
9931
  * @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,9 @@ 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.html": "<pre><code class=\"language-ts\">const port = 8080;</code></pre>\n",
27
29
  "../../markdown-fixtures/code-meta/notation.md": "```ts\nconst port = 8080; // [!code highlight]\n```\n",
28
30
  "../../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
31
  "../../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 +49,18 @@ var files = /* #__PURE__ */ Object.assign({
47
49
  "../../markdown-fixtures/details/open.md": "::: details Show the full log {open}\nThe build finished in 42 seconds.\n:::\n",
48
50
  "../../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
51
  "../../markdown-fixtures/figures/caption.md": "::: figure The build, from *source* to release\n![The build pipeline](./pipeline.svg)\n:::\n",
52
+ "../../markdown-fixtures/front-matter/list-only.canonical.md": "# Notes\n",
53
+ "../../markdown-fixtures/front-matter/list-only.html": "<h1 id=\"notes\">Notes</h1>\n",
54
+ "../../markdown-fixtures/front-matter/list-only.md": "---\naliases:\n - /old/\n - /older/\n---\n\n# Notes\n",
55
+ "../../markdown-fixtures/front-matter/mapping-only.canonical.md": "# Notes\n",
56
+ "../../markdown-fixtures/front-matter/mapping-only.html": "<h1 id=\"notes\">Notes</h1>\n",
57
+ "../../markdown-fixtures/front-matter/mapping-only.md": "---\nhead:\n meta:\n robots: noindex\n---\n\n# Notes\n",
50
58
  "../../markdown-fixtures/front-matter/metadata.canonical.md": "# Notes\n",
51
59
  "../../markdown-fixtures/front-matter/metadata.html": "<h1 id=\"notes\">Notes</h1>\n",
52
60
  "../../markdown-fixtures/front-matter/metadata.md": "---\ntitle: Release notes\n---\n\n# Notes\n",
61
+ "../../markdown-fixtures/front-matter/nested.canonical.md": "# Notes\n",
62
+ "../../markdown-fixtures/front-matter/nested.html": "<h1 id=\"notes\">Notes</h1>\n",
63
+ "../../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
64
  "../../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
65
  "../../markdown-fixtures/heading-ids/accented.md": "## Über uns\n\n## Café crème\n",
55
66
  "../../markdown-fixtures/heading-ids/cjk.html": "<h2 id=\"はじめに\">はじめに</h2>\n<h2 id=\"設定-と-使い方\">設定 と 使い方</h2>\n",
@@ -85,8 +96,11 @@ var files = /* #__PURE__ */ Object.assign({
85
96
  "../../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
97
  "../../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
98
  "../../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",
99
+ "../../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
100
  "../../markdown-fixtures/task-lists/checked.md": "- [x] Read the changelog\n- [ ] Run the migration\n",
101
+ "../../markdown-fixtures/task-lists/loose.canonical.md": "- [x] Read the changelog\n\n Then tell the team.\n- [ ] Run the migration\n",
102
+ "../../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",
103
+ "../../markdown-fixtures/task-lists/loose.md": "- [x] Read the changelog\n\n Then tell the team.\n\n- [ ] Run the migration\n",
90
104
  "../../markdown-fixtures/task-lists/mixed.canonical.md": "- \\[x\\] Done\n- Not a task\n",
91
105
  "../../markdown-fixtures/task-lists/mixed.html": "<ul>\n<li>[x] Done</li>\n<li>Not a task</li>\n</ul>\n",
92
106
  "../../markdown-fixtures/task-lists/mixed.md": "- [x] Done\n- Not a task\n",
@@ -92,10 +92,23 @@ 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.
99
+ * @contract Returns the code a reader sees and its highlighted line numbers, from 1, ascending.
100
+ * ponytail: matched on source lines, so a string that ends in the marker counts too.
101
+ */
102
+ export declare function applyLoomaCodeNotation(code: string): {
103
+ code: string;
104
+ lines: number[];
105
+ };
95
106
  /**
96
107
  * 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.
108
+ * @contract Front matter opens with a `---` line at the very start and closes at a `---` or `...`
109
+ * line, and what lies between is a YAML mapping: its first line that is not blank or a comment
110
+ * starts with a key, whose value may be a scalar, a list or a mapping. `source` is the block as
111
+ * written, delimiters included.
99
112
  * @failure Returns null when the document has none.
100
113
  */
101
114
  export declare function splitLoomaFrontmatter(document: string): {
package/markdown/index.js CHANGED
@@ -204,15 +204,46 @@ 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
+ /**
210
+ * Applies code notation: a line ending in a `[!code highlight]` comment is highlighted and loses
211
+ * the comment, `[!code highlight:3]` covers three lines, and a marker alone on its line is removed
212
+ * with the line and applies to the lines after it.
213
+ * @contract Returns the code a reader sees and its highlighted line numbers, from 1, ascending.
214
+ * ponytail: matched on source lines, so a string that ends in the marker counts too.
215
+ */
216
+ function applyLoomaCodeNotation(code) {
217
+ const kept = [];
218
+ const lines = /* @__PURE__ */ new Set();
219
+ for (const line of code.split("\n")) {
220
+ const match = NOTATION.exec(line);
221
+ if (!match) {
222
+ kept.push(line);
223
+ continue;
224
+ }
225
+ const rest = line.slice(0, match.index);
226
+ const own = rest.trim() !== "";
227
+ if (own) kept.push(rest);
228
+ for (let count = 0; count < Number(match[1] ?? 1); count++) lines.add(kept.length + (own ? 0 : 1) + count);
229
+ }
230
+ return {
231
+ code: kept.join("\n"),
232
+ lines: [...lines].sort((a, b) => a - b)
233
+ };
234
+ }
207
235
  /**
208
236
  * 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.
237
+ * @contract Front matter opens with a `---` line at the very start and closes at a `---` or `...`
238
+ * line, and what lies between is a YAML mapping: its first line that is not blank or a comment
239
+ * starts with a key, whose value may be a scalar, a list or a mapping. `source` is the block as
240
+ * written, delimiters included.
211
241
  * @failure Returns null when the document has none.
212
242
  */
213
243
  function splitLoomaFrontmatter(document) {
214
244
  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;
245
+ const first = match?.[1].split(/\r?\n/).find((line) => line.trim() && !line.trimStart().startsWith("#"));
246
+ if (!match || !first || !MAPPING_KEY.test(first)) return null;
216
247
  return {
217
248
  source: match[0].trimEnd(),
218
249
  yaml: match[1],
@@ -229,4 +260,4 @@ function loomaCodeFence(code) {
229
260
  return "`".repeat(Math.max(3, longest + 1));
230
261
  }
231
262
  //#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 };
263
+ 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.18",
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": {