@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.
- package/editor/chunks/extensions.js +53 -16
- package/editor/extensions/markdown.d.ts +1 -1
- package/markdown/fixtures.js +18 -2
- package/markdown/index.d.ts +17 -2
- package/markdown/index.js +38 -4
- package/markdown/markdown-it.d.ts +10 -2
- package/markdown/markdown-it.js +15 -12
- package/package.json +2 -1
|
@@ -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
|
|
315
|
-
* line, and
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
package/markdown/fixtures.js
CHANGED
|
@@ -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
|
|
26
|
+
"../../markdown-fixtures/code-meta/notation-alone.html": "<pre><code class=\"language-ts\">const host = "localhost";\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 = "localhost"; // [\\!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="config.ts" {1,3-4} :line-numbers\">export default {\n name: "docs",\n base: "/docs/",\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\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",
|
package/markdown/index.d.ts
CHANGED
|
@@ -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
|
|
98
|
-
* line, and
|
|
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
|
|
210
|
-
* line, and
|
|
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
|
-
|
|
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;
|
package/markdown/markdown-it.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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.
|
|
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": {
|