@threadlabs/looma 0.30.7 → 0.30.9
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/EXPRESSIVE-MARKDOWN.md +3 -1
- package/components/ui-dialog/ui-dialog.html +5 -0
- package/dist/index.js +1 -1
- package/editor/chunks/extensions.js +268 -2
- package/editor/expressmark.js +33 -0
- package/editor/extensions/footnote.d.ts +18 -0
- package/editor/extensions/index.d.ts +1 -0
- package/editor/extensions/index.js +2 -2
- package/editor/index.js +2 -2
- package/markdown/fixtures.js +10 -0
- package/markdown/markdown-it.d.ts +1 -1
- package/markdown/markdown-it.js +133 -1
- package/package.json +1 -1
- package/styles/ui-dialog.css +7 -0
- package/vue/UiDialog.vue +5 -0
- package/vue/chunks/UiDialog.js +1 -1
- package/vue/components.css +9 -4
package/markdown/markdown-it.js
CHANGED
|
@@ -296,6 +296,119 @@ function applyImageAttributes(image, attributes) {
|
|
|
296
296
|
if (typeof placement === "string" && PLACEMENTS.has(placement) && placement !== "block") image.attrSet("data-placement", placement);
|
|
297
297
|
if (attributes.responsive === true) image.attrSet("data-looma-responsive", "");
|
|
298
298
|
}
|
|
299
|
+
var FOOTNOTE_DEFINITION = /^\[\^([^\s\]]+)\]:/;
|
|
300
|
+
var FOOTNOTE_REFERENCE = /^\[\^([^\s\]]+)\]/;
|
|
301
|
+
function footnoteDefinition(state, startLine, endLine, silent) {
|
|
302
|
+
if (state.sCount[startLine] - state.blkIndent >= 4) return false;
|
|
303
|
+
const start = state.bMarks[startLine] + state.tShift[startLine];
|
|
304
|
+
const match = FOOTNOTE_DEFINITION.exec(state.src.slice(start, state.eMarks[startLine]));
|
|
305
|
+
if (!match) return false;
|
|
306
|
+
if (silent) return true;
|
|
307
|
+
(state.env.footnotes ??= /* @__PURE__ */ new Set()).add(match[1]);
|
|
308
|
+
const open = state.push("looma_footnote_open", "li", 1);
|
|
309
|
+
Object.assign(open, {
|
|
310
|
+
meta: { label: match[1] },
|
|
311
|
+
block: true,
|
|
312
|
+
map: [startLine, startLine]
|
|
313
|
+
});
|
|
314
|
+
const saved = {
|
|
315
|
+
bMark: state.bMarks[startLine],
|
|
316
|
+
tShift: state.tShift[startLine],
|
|
317
|
+
sCount: state.sCount[startLine],
|
|
318
|
+
parentType: state.parentType
|
|
319
|
+
};
|
|
320
|
+
let position = start + match[0].length;
|
|
321
|
+
const contentStart = position;
|
|
322
|
+
let offset = state.sCount[startLine] + match[0].length;
|
|
323
|
+
const initial = offset;
|
|
324
|
+
while (position < state.eMarks[startLine] && (state.src[position] === " " || state.src[position] === " ")) {
|
|
325
|
+
offset += state.src[position] === " " ? 4 - (offset + state.bsCount[startLine]) % 4 : 1;
|
|
326
|
+
position++;
|
|
327
|
+
}
|
|
328
|
+
state.tShift[startLine] = position - contentStart;
|
|
329
|
+
state.sCount[startLine] = offset - initial;
|
|
330
|
+
state.bMarks[startLine] = contentStart;
|
|
331
|
+
state.blkIndent += 4;
|
|
332
|
+
state.parentType = "footnote";
|
|
333
|
+
if (state.sCount[startLine] < state.blkIndent) state.sCount[startLine] += state.blkIndent;
|
|
334
|
+
state.md.block.tokenize(state, startLine, endLine);
|
|
335
|
+
state.parentType = saved.parentType;
|
|
336
|
+
state.blkIndent -= 4;
|
|
337
|
+
state.tShift[startLine] = saved.tShift;
|
|
338
|
+
state.sCount[startLine] = saved.sCount;
|
|
339
|
+
state.bMarks[startLine] = saved.bMark;
|
|
340
|
+
open.map[1] = state.line;
|
|
341
|
+
state.push("looma_footnote_close", "li", -1).block = true;
|
|
342
|
+
return true;
|
|
343
|
+
}
|
|
344
|
+
function footnoteReference(state, silent) {
|
|
345
|
+
if (state.src.charCodeAt(state.pos) !== 91 || state.src.charCodeAt(state.pos + 1) !== 94) return false;
|
|
346
|
+
const match = FOOTNOTE_REFERENCE.exec(state.src.slice(state.pos, state.posMax));
|
|
347
|
+
if (!match || !state.env.footnotes?.has(match[1])) return false;
|
|
348
|
+
if (!silent) state.push("looma_footnote_ref", "sup", 0).meta = { label: match[1] };
|
|
349
|
+
state.pos += match[0].length;
|
|
350
|
+
return true;
|
|
351
|
+
}
|
|
352
|
+
function footnotes(state) {
|
|
353
|
+
const definitions = /* @__PURE__ */ new Map();
|
|
354
|
+
const body = [];
|
|
355
|
+
for (let index = 0; index < state.tokens.length; index++) {
|
|
356
|
+
const token = state.tokens[index];
|
|
357
|
+
if (token.type !== "looma_footnote_open") {
|
|
358
|
+
body.push(token);
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
let depth = 0, end = index;
|
|
362
|
+
for (; end < state.tokens.length; end++) if (state.tokens[end].type === "looma_footnote_open") depth++;
|
|
363
|
+
else if (state.tokens[end].type === "looma_footnote_close" && --depth === 0) break;
|
|
364
|
+
const label = token.meta.label;
|
|
365
|
+
if (!definitions.has(label)) definitions.set(label, state.tokens.slice(index, end + 1));
|
|
366
|
+
index = end;
|
|
367
|
+
}
|
|
368
|
+
if (!definitions.size) return;
|
|
369
|
+
const order = [];
|
|
370
|
+
const counts = /* @__PURE__ */ new Map();
|
|
371
|
+
const number = (tokens) => {
|
|
372
|
+
for (const token of tokens) for (const child of token.children ?? []) {
|
|
373
|
+
if (child.type !== "looma_footnote_ref") continue;
|
|
374
|
+
const label = child.meta.label;
|
|
375
|
+
if (!counts.has(label)) order.push(label);
|
|
376
|
+
const occurrence = (counts.get(label) ?? 0) + 1;
|
|
377
|
+
counts.set(label, occurrence);
|
|
378
|
+
child.meta = {
|
|
379
|
+
label,
|
|
380
|
+
number: order.indexOf(label) + 1,
|
|
381
|
+
occurrence
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
number(body);
|
|
386
|
+
const notes = [];
|
|
387
|
+
for (let index = 0; index < order.length; index++) {
|
|
388
|
+
const label = order[index];
|
|
389
|
+
const tokens = definitions.get(label);
|
|
390
|
+
number(tokens);
|
|
391
|
+
const backrefs = Object.assign(new state.Token("looma_footnote_backrefs", "", 0), { meta: {
|
|
392
|
+
label,
|
|
393
|
+
number: index + 1
|
|
394
|
+
} });
|
|
395
|
+
const last = tokens.length - 2;
|
|
396
|
+
if (tokens[last].type === "paragraph_close") tokens.splice(last, 0, backrefs);
|
|
397
|
+
else tokens.splice(last + 1, 0, new state.Token("paragraph_open", "p", 1), backrefs, new state.Token("paragraph_close", "p", -1));
|
|
398
|
+
notes.push(...tokens);
|
|
399
|
+
}
|
|
400
|
+
if (!notes.length) {
|
|
401
|
+
state.tokens = body;
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
for (const note of notes) if (note.type === "looma_footnote_open") note.meta.count = counts.get(note.meta.label);
|
|
405
|
+
state.tokens = [
|
|
406
|
+
...body,
|
|
407
|
+
Object.assign(new state.Token("looma_footnotes_open", "section", 1), { block: true }),
|
|
408
|
+
...notes,
|
|
409
|
+
Object.assign(new state.Token("looma_footnotes_close", "section", -1), { block: true })
|
|
410
|
+
];
|
|
411
|
+
}
|
|
299
412
|
function tableOfContents(tokens, format, depth, escape) {
|
|
300
413
|
const roots = [];
|
|
301
414
|
const ancestors = [];
|
|
@@ -321,7 +434,7 @@ function tableOfContents(tokens, format, depth, escape) {
|
|
|
321
434
|
return `<nav data-looma-toc="" data-format="${format}" data-depth="${depth}" aria-label="Table of contents"><strong>Table of contents</strong>${roots.length ? render(roots) : ""}</nav>\n`;
|
|
322
435
|
}
|
|
323
436
|
/**
|
|
324
|
-
* Adds Looma Markdown to a markdown-it instance: containers, alerts, task lists, the table of
|
|
437
|
+
* Adds Looma Markdown to a markdown-it instance: containers, alerts, task lists, footnotes, the table of
|
|
325
438
|
* contents, chips, mentions, brace attributes, heading anchors, code meta and front matter.
|
|
326
439
|
* @contract Renders the canonical HTML, which is the HTML LoomaEditor saves. It is not sanitized.
|
|
327
440
|
* Linkify and raw HTML are instance options; `createLoomaMarkdownIt` turns both on.
|
|
@@ -329,6 +442,7 @@ function tableOfContents(tokens, format, depth, escape) {
|
|
|
329
442
|
function loomaMarkdown(md, { codeNotation = "apply" } = {}) {
|
|
330
443
|
const escape = md.utils.escapeHtml;
|
|
331
444
|
md.block.ruler.before("table", "looma_frontmatter", frontmatter);
|
|
445
|
+
md.block.ruler.before("reference", "looma_footnote", footnoteDefinition, { alt: ["paragraph", "reference"] });
|
|
332
446
|
md.block.ruler.before("fence", "looma_container", container, { alt: [
|
|
333
447
|
"paragraph",
|
|
334
448
|
"reference",
|
|
@@ -342,9 +456,11 @@ function loomaMarkdown(md, { codeNotation = "apply" } = {}) {
|
|
|
342
456
|
"list"
|
|
343
457
|
] });
|
|
344
458
|
md.inline.ruler.before("link", "looma_directive", directive);
|
|
459
|
+
md.inline.ruler.before("link", "looma_footnote_ref", footnoteReference);
|
|
345
460
|
md.core.ruler.after("block", "looma_task_lists", taskLists);
|
|
346
461
|
md.core.ruler.before("inline", "looma_alerts", alerts);
|
|
347
462
|
md.core.ruler.before("text_join", "looma_attributes", attributes);
|
|
463
|
+
md.core.ruler.after("inline", "looma_footnotes", footnotes);
|
|
348
464
|
const rules = md.renderer.rules;
|
|
349
465
|
rules.looma_frontmatter = () => "";
|
|
350
466
|
rules.looma_container_open = (tokens, index) => {
|
|
@@ -366,6 +482,22 @@ function loomaMarkdown(md, { codeNotation = "apply" } = {}) {
|
|
|
366
482
|
};
|
|
367
483
|
rules.looma_tab_open = (tokens, index) => `<section aria-label="${escape(tokens[index].meta.label)}">\n`;
|
|
368
484
|
rules.looma_tab_close = () => "</section>\n";
|
|
485
|
+
rules.looma_footnote_ref = (tokens, index) => {
|
|
486
|
+
const { label, number, occurrence } = tokens[index].meta;
|
|
487
|
+
return `<sup><a href="#fn:${escape(label)}" id="fnref:${escape(label)}${occurrence > 1 ? `:${occurrence}` : ""}" data-footnote-ref="" aria-describedby="footnote:label">${number}</a></sup>`;
|
|
488
|
+
};
|
|
489
|
+
rules.looma_footnote_backrefs = (tokens, index) => {
|
|
490
|
+
const { label, number } = tokens[index].meta;
|
|
491
|
+
const count = tokens.find((token) => token.type === "looma_footnote_open" && token.meta.label === label)?.meta.count ?? 1;
|
|
492
|
+
return Array.from({ length: count }, (_, occurrence) => {
|
|
493
|
+
const suffix = occurrence ? `:${occurrence + 1}` : "";
|
|
494
|
+
return ` <a href="#fnref:${escape(label)}${suffix}" data-footnote-backref="" aria-label="Back to reference ${number}${occurrence ? `-${occurrence + 1}` : ""}">↩${occurrence ? `<sup>${occurrence + 1}</sup>` : ""}</a>`;
|
|
495
|
+
}).join("");
|
|
496
|
+
};
|
|
497
|
+
rules.looma_footnotes_open = () => "<section data-footnotes=\"\" class=\"footnotes\">\n<h2 id=\"footnote:label\" class=\"sr-only\">Footnotes</h2>\n<ol>\n";
|
|
498
|
+
rules.looma_footnotes_close = () => "</ol>\n</section>\n";
|
|
499
|
+
rules.looma_footnote_open = (tokens, index) => `<li id="fn:${escape(tokens[index].meta.label)}">\n`;
|
|
500
|
+
rules.looma_footnote_close = () => "</li>\n";
|
|
369
501
|
rules.looma_toc = (tokens, index) => tableOfContents(tokens, tokens[index].meta.format, tokens[index].meta.depth, escape);
|
|
370
502
|
rules.looma_chip = (tokens, index) => {
|
|
371
503
|
const { label, attributes } = tokens[index].meta;
|
package/package.json
CHANGED
package/styles/ui-dialog.css
CHANGED
|
@@ -111,6 +111,11 @@
|
|
|
111
111
|
padding: calc(var(--ui-space-3) + var(--ui-space-1) / 2) var(--ui-space-3) var(--ui-space-3) var(--ui-space-5);
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
/* Alerts have no close target to add height above the title. */
|
|
115
|
+
:scope:where([data-component~="ui-dialog"])[data-ui-dialog-state~="alert"] header {
|
|
116
|
+
padding: var(--ui-space-5) var(--ui-space-5) var(--ui-space-3);
|
|
117
|
+
}
|
|
118
|
+
|
|
114
119
|
/* :host-qualified so page heading rules that reach in cannot outrank the dialog chrome. */
|
|
115
120
|
:scope:where([data-component~="ui-dialog"]) .title {
|
|
116
121
|
flex: 1;
|
|
@@ -238,6 +243,8 @@
|
|
|
238
243
|
|
|
239
244
|
/* Own the layout wrapper; nested component roots are outside these scoped rules. */
|
|
240
245
|
|
|
246
|
+
/* Alerts have no close target to add height above the title. */
|
|
247
|
+
|
|
241
248
|
/* :host-qualified so page heading rules that reach in cannot outrank the dialog chrome. */
|
|
242
249
|
|
|
243
250
|
/* A CSS-drawn X. */
|
package/vue/UiDialog.vue
CHANGED
|
@@ -363,6 +363,8 @@ useComponentHost(() => import('../components/ui-dialog/ui-dialog.js'), {
|
|
|
363
363
|
|
|
364
364
|
/* Own the layout wrapper; nested component roots are outside these scoped rules. */
|
|
365
365
|
|
|
366
|
+
/* Alerts have no close target to add height above the title. */
|
|
367
|
+
|
|
366
368
|
/* :host-qualified so page heading rules that reach in cannot outrank the dialog chrome. */
|
|
367
369
|
|
|
368
370
|
/* A CSS-drawn X. */
|
|
@@ -460,6 +462,9 @@ useComponentHost(() => import('../components/ui-dialog/ui-dialog.js'), {
|
|
|
460
462
|
padding: calc(var(--ui-space-3) + var(--ui-space-1) / 2) var(--ui-space-3) var(--ui-space-3)
|
|
461
463
|
var(--ui-space-5);
|
|
462
464
|
}
|
|
465
|
+
:global(:scope[data-ui-dialog-state~="alert"] header:where([data-hn-ui-dialog])) {
|
|
466
|
+
padding: var(--ui-space-5) var(--ui-space-5) var(--ui-space-3);
|
|
467
|
+
}
|
|
463
468
|
:global(:scope .title:where([data-hn-ui-dialog])) {
|
|
464
469
|
flex: 1;
|
|
465
470
|
min-inline-size: 0;
|
package/vue/chunks/UiDialog.js
CHANGED
package/vue/components.css
CHANGED
|
@@ -3035,18 +3035,20 @@ dd:where([data-hn-ui-description-item]) {
|
|
|
3035
3035
|
|
|
3036
3036
|
/* Own the layout wrapper; nested component roots are outside these scoped rules. */
|
|
3037
3037
|
|
|
3038
|
+
/* Alerts have no close target to add height above the title. */
|
|
3039
|
+
|
|
3038
3040
|
/* :host-qualified so page heading rules that reach in cannot outrank the dialog chrome. */
|
|
3039
3041
|
|
|
3040
3042
|
/* A CSS-drawn X. */
|
|
3041
3043
|
|
|
3042
3044
|
/* A touch target, like Button on coarse pointers. */
|
|
3043
|
-
@keyframes ui-dialog-enter-
|
|
3045
|
+
@keyframes ui-dialog-enter-f26be724 {
|
|
3044
3046
|
from {
|
|
3045
3047
|
opacity: 0;
|
|
3046
3048
|
transform: scale(0.96);
|
|
3047
3049
|
}
|
|
3048
3050
|
}
|
|
3049
|
-
@keyframes ui-dialog-backdrop-enter-
|
|
3051
|
+
@keyframes ui-dialog-backdrop-enter-f26be724 {
|
|
3050
3052
|
from {
|
|
3051
3053
|
background-color: transparent;
|
|
3052
3054
|
}
|
|
@@ -3094,7 +3096,7 @@ from {
|
|
|
3094
3096
|
pointer-events: auto;
|
|
3095
3097
|
opacity: 1;
|
|
3096
3098
|
transform: scale(1);
|
|
3097
|
-
animation: ui-dialog-enter-
|
|
3099
|
+
animation: ui-dialog-enter-f26be724 var(--ui-dialog-duration, var(--ui-motion-base))
|
|
3098
3100
|
var(--ui-motion-ease);
|
|
3099
3101
|
}
|
|
3100
3102
|
:scope[data-ui-dialog-state~="size=sm"]:where([data-hn-ui-dialog]) {
|
|
@@ -3124,6 +3126,9 @@ header:where([data-hn-ui-dialog]) {
|
|
|
3124
3126
|
padding: calc(var(--ui-space-3) + var(--ui-space-1) / 2) var(--ui-space-3) var(--ui-space-3)
|
|
3125
3127
|
var(--ui-space-5);
|
|
3126
3128
|
}
|
|
3129
|
+
:scope[data-ui-dialog-state~="alert"] header:where([data-hn-ui-dialog]) {
|
|
3130
|
+
padding: var(--ui-space-5) var(--ui-space-5) var(--ui-space-3);
|
|
3131
|
+
}
|
|
3127
3132
|
:scope .title:where([data-hn-ui-dialog]) {
|
|
3128
3133
|
flex: 1;
|
|
3129
3134
|
min-inline-size: 0;
|
|
@@ -3212,7 +3217,7 @@ footer:empty:where([data-hn-ui-dialog]) {
|
|
|
3212
3217
|
}
|
|
3213
3218
|
:scope[open]:modal:where([data-hn-ui-dialog])::backdrop {
|
|
3214
3219
|
background-color: var(--ui-overlay-backdrop);
|
|
3215
|
-
animation: ui-dialog-backdrop-enter-
|
|
3220
|
+
animation: ui-dialog-backdrop-enter-f26be724 var(--_ui-dialog-duration, var(--ui-motion-base))
|
|
3216
3221
|
var(--ui-motion-ease);
|
|
3217
3222
|
}
|
|
3218
3223
|
:scope[open][data-ui-backdrop-hidden]:where([data-hn-ui-dialog])::backdrop {
|