Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 64 additions & 55 deletions workspace/aubade/src/artisan/example.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ describe('spec', ({ concurrent: it }) => {
' - foo\n - bar\n\t - baz',
'<ul>\n<li>foo\n<ul>\n<li>bar\n<ul>\n<li>baz</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>',
],
'010|plus': ['#\tFoo', '<h1 id="foo" data-text="Foo">Foo</h1>'],
'010|plus': ['#\tFoo', '<h1 id="foo">Foo</h1>'],
'011': ['*\t*\t*\t', '<hr />'],
'012': [
'\\!\\"\\#\\$\\%\\&\\\'\\(\\)\\*\\+\\,\\-\\.\\/\\:\\;\\<\\=\\>\\?\\@\\[\\\\\\]\\^\\_\\`\\{\\|\\}\\~',
Expand Down Expand Up @@ -119,66 +119,53 @@ describe('spec', ({ concurrent: it }) => {
'062|plus': [
'# foo\n## foo\n### foo\n#### foo\n##### foo\n###### foo',
[
'<h1 id="foo" data-text="foo">foo</h1>',
`<h2 id="${Array(2).fill('foo').join('-')}" data-text="foo">foo</h2>`,
`<h3 id="${Array(3).fill('foo').join('-')}" data-text="foo">foo</h3>`,
`<h4 id="${Array(4).fill('foo').join('-')}" data-text="foo">foo</h4>`,
`<h5 id="${Array(5).fill('foo').join('-')}" data-text="foo">foo</h5>`,
`<h6 id="${Array(6).fill('foo').join('-')}" data-text="foo">foo</h6>`,
'<h1 id="foo">foo</h1>',
`<h2 id="${Array(2).fill('foo').join('-')}">foo</h2>`,
`<h3 id="${Array(3).fill('foo').join('-')}">foo</h3>`,
`<h4 id="${Array(4).fill('foo').join('-')}">foo</h4>`,
`<h5 id="${Array(5).fill('foo').join('-')}">foo</h5>`,
`<h6 id="${Array(6).fill('foo').join('-')}">foo</h6>`,
].join('\n'),
],
'063': ['####### foo', '<p>####### foo</p>'],
'064': ['#5 bolt\n\n#hashtag', '<p>#5 bolt</p>\n<p>#hashtag</p>'],
'065': ['\\## foo', '<p>## foo</p>'],
'066|plus': [
'# foo *bar* \\*baz\\*',
'<h1 id="foo-bar-baz" data-text="foo bar *baz*">foo <em>bar</em> *baz*</h1>',
],
'067|plus': [
'# foo ',
'<h1 id="foo" data-text="foo">foo</h1>',
],
'066|plus': ['# foo *bar* \\*baz\\*', '<h1 id="foo-bar-baz">foo <em>bar</em> *baz*</h1>'],
'067|plus': ['# foo ', '<h1 id="foo">foo</h1>'],
'068|plus': [
' ### foo\n ## foo\n # foo',
[
'<h3 id="foo" data-text="foo">foo</h3>',
'<h2 id="foo-1" data-text="foo">foo</h2>',
'<h1 id="foo-2" data-text="foo">foo</h1>',
].join('\n'),
['<h3 id="foo">foo</h3>', '<h2 id="foo-1">foo</h2>', '<h1 id="foo-2">foo</h1>'].join('\n'),
],
'069|deny': [' # foo', '<h1 id="foo" data-text="foo">foo</h1>'],
'070|deny': ['foo\n # bar', '<p>foo</p>\n<h1 id="bar" data-text="bar">bar</h1>'],
'069|deny': [' # foo', '<h1 id="foo">foo</h1>'],
'070|deny': ['foo\n # bar', '<p>foo</p>\n<h1 id="bar">bar</h1>'],
'071|deny': [
'## foo ##\n ### bar ###',
[
'<h2 id="foo" data-text="foo ##">foo ##</h2>',
'<h3 id="foo-bar" data-text="bar ###">bar ###</h3>',
].join('\n'),
['<h2 id="foo">foo ##</h2>', '<h3 id="foo-bar">bar ###</h3>'].join('\n'),
],
'072|deny': [
'# foo ##################################\n##### foo ##',
[
'<h1 id="foo" data-text="foo ##################################">foo ##################################</h1>',
'<h5 id="foo-foo" data-text="foo ##">foo ##</h5>',
'<h1 id="foo">foo ##################################</h1>',
'<h5 id="foo-foo">foo ##</h5>',
].join('\n'),
],
'073|deny': ['### foo ### ', '<h3 id="foo" data-text="foo ###">foo ###</h3>'],
'074|plus': ['### foo ### b', '<h3 id="foo-b" data-text="foo ### b">foo ### b</h3>'],
'075|plus': ['# foo#', '<h1 id="foo" data-text="foo#">foo#</h1>'],
'073|deny': ['### foo ### ', '<h3 id="foo">foo ###</h3>'],
'074|plus': ['### foo ### b', '<h3 id="foo-b">foo ### b</h3>'],
'075|plus': ['# foo#', '<h1 id="foo">foo#</h1>'],
'076|deny': [
'### foo \\###\n## foo #\\##\n# foo \\#',
[
'<h3 id="foo" data-text="foo ###">foo ###</h3>',
'<h2 id="foo-1" data-text="foo ###">foo ###</h2>',
'<h1 id="foo-2" data-text="foo #">foo #</h1>',
'<h3 id="foo">foo ###</h3>',
'<h2 id="foo-1">foo ###</h2>',
'<h1 id="foo-2">foo #</h1>',
].join('\n'),
],
'077|deny': ['****\n## foo\n****', '<hr />\n<h2 id="foo" data-text="foo">foo</h2>\n<hr />'],
'077|deny': ['****\n## foo\n****', '<hr />\n<h2 id="foo">foo</h2>\n<hr />'],
'078|plus': [
'Foo bar\n# baz\nBar foo',
'<p>Foo bar</p>\n<h1 id="baz" data-text="baz">baz</h1>\n<p>Bar foo</p>',
'<p>Foo bar</p>\n<h1 id="baz">baz</h1>\n<p>Bar foo</p>',
],
'079|deny': ['## \n#\n### ###', '<p>##\n#</p>\n<h3 data-text="###">###</h3>'],
'079|deny': ['## \n#\n### ###', '<p>##\n#</p>\n<h3>###</h3>'],
'080|deny': [
'Foo *bar*\n=========\n\nFoo *bar*\n---------',
'<p>Foo <em>bar</em>\n=========</p>\n<p>Foo <em>bar</em></p>\n<hr />',
Expand Down Expand Up @@ -249,7 +236,7 @@ describe('spec', ({ concurrent: it }) => {
'140': ['foo\n```\nbar\n```\nbaz', '<p>foo</p>\n<pre><code>bar\n</code></pre>\n<p>baz</p>'],
'141|mod': [
'foo\n---\n```\nbar\n```\n# baz',
'<p>foo</p>\n<hr />\n<pre><code>bar\n</code></pre>\n<h1 id="baz" data-text="baz">baz</h1>',
'<p>foo</p>\n<hr />\n<pre><code>bar\n</code></pre>\n<h1 id="baz">baz</h1>',
],
'142|mod': [
'```ruby\ndef foo(x)\n return 3\nend\n```',
Expand Down Expand Up @@ -281,29 +268,26 @@ describe('spec', ({ concurrent: it }) => {
'224': [' aaa\nbbb', '<p>aaa\nbbb</p>'],
'225|deny': [' aaa\nbbb', '<p>aaa\nbbb</p>'],
'226': ['aaa \nbbb ', '<p>aaa<br />\nbbb</p>'],
'227|plus': [
' \n\naaa\n \n\n# aaa\n\n ',
'<p>aaa</p>\n<h1 id="aaa" data-text="aaa">aaa</h1>',
],
'227|plus': [' \n\naaa\n \n\n# aaa\n\n ', '<p>aaa</p>\n<h1 id="aaa">aaa</h1>'],
'228|plus': [
'> # Foo\n> bar\n> baz',
'<blockquote>\n<h1 id="foo" data-text="Foo">Foo</h1>\n<p>bar\nbaz</p>\n</blockquote>',
'<blockquote>\n<h1 id="foo">Foo</h1>\n<p>bar\nbaz</p>\n</blockquote>',
],
'229|plus': [
'># Foo\n>bar\n> baz',
'<blockquote>\n<h1 id="foo" data-text="Foo">Foo</h1>\n<p>bar\nbaz</p>\n</blockquote>',
'<blockquote>\n<h1 id="foo">Foo</h1>\n<p>bar\nbaz</p>\n</blockquote>',
],
'230': [
' > # Foo\n > bar\n > baz',
'<blockquote>\n<h1 id="foo" data-text="Foo">Foo</h1>\n<p>bar\nbaz</p>\n</blockquote>',
'<blockquote>\n<h1 id="foo">Foo</h1>\n<p>bar\nbaz</p>\n</blockquote>',
],
'231|deny': [
' > # Foo\n > bar\n > baz',
'<blockquote>\n<h1 id="foo" data-text="Foo">Foo</h1>\n<p>bar\nbaz</p>\n</blockquote>',
'<blockquote>\n<h1 id="foo">Foo</h1>\n<p>bar\nbaz</p>\n</blockquote>',
],
'232|deny': [
'> # Foo\n> bar\nbaz',
'<blockquote>\n<h1 id="foo" data-text="Foo">Foo</h1>\n<p>bar</p>\n</blockquote>\n<p>baz</p>',
'<blockquote>\n<h1 id="foo">Foo</h1>\n<p>bar</p>\n</blockquote>\n<p>baz</p>',
],
'233|deny': [
'> bar\nbaz\n> foo',
Expand Down Expand Up @@ -615,10 +599,6 @@ describe('spec', ({ concurrent: it }) => {
],
'495|todo': ['[link](\\(foo\\))', '<p><a href="(foo)">link</a></p>'],
// @TODO: 496-571 [links]
'572|plus': [
'![foo](/url "title")',
'<figure>\n<img src="/url" alt="foo" />\n<figcaption>title</figcaption>\n</figure>',
],
'572|mod': ['a ![foo](/url "title")', '<p>a <img src="/url" alt="foo" title="title" /></p>'],
'573|skip': [
'![foo *bar*]\n\n[foo *bar*]: train.jpg "train & tracks"',
Expand All @@ -642,8 +622,8 @@ describe('spec', ({ concurrent: it }) => {
'643|skip': ['<a href="foo\\\nbar">', '<a href="foo\\\nbar">'],
'644': ['foo\\', '<p>foo\\</p>'],
'645': ['foo ', '<p>foo</p>'],
'646|plus': ['### foo\\', '<h3 id="foo" data-text="foo\\">foo\\</h3>'],
'647|plus': ['### foo ', '<h3 id="foo" data-text="foo">foo</h3>'],
'646|plus': ['### foo\\', '<h3 id="foo">foo\\</h3>'],
'647|plus': ['### foo ', '<h3 id="foo">foo</h3>'],
'648': ['foo\nbaz', '<p>foo\nbaz</p>'],
'649': ['foo \n baz', '<p>foo\nbaz</p>'],
'650': ["hello $.;'there", "<p>hello $.;'there</p>"],
Expand Down Expand Up @@ -683,7 +663,9 @@ describe('libretto', ({ concurrent: it }) => {
'@youtube{id=7TovqLDCosk caption="hitoribocchi tokyo"}',
[
'<figure>',
'<div data-aubade="youtube">',
'<iframe src="https://www.youtube-nocookie.com/embed/7TovqLDCosk" title="YouTube video player" loading="lazy" frameborder="0" allowfullscreen allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin"></iframe>',
'</div>',
'<figcaption>hitoribocchi tokyo</figcaption>',
'</figure>',
].join('\n'),
Expand All @@ -693,15 +675,19 @@ describe('libretto', ({ concurrent: it }) => {
[
'<details>',
'<summary>hitoribocchi tokyo</summary>',
'<div data-aubade="youtube">',
'<iframe src="https://www.youtube-nocookie.com/embed/7TovqLDCosk" title="YouTube video player" loading="lazy" frameborder="0" allowfullscreen allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin"></iframe>',
'</div>',
'</details>',
].join('\n'),
],
'directive#youtube/newlines': [
['@youtube{', ' id=7TovqLDCosk', ' caption="hitoribocchi tokyo"', '}'].join('\n'),
[
'<figure>',
'<div data-aubade="youtube">',
'<iframe src="https://www.youtube-nocookie.com/embed/7TovqLDCosk" title="YouTube video player" loading="lazy" frameborder="0" allowfullscreen allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin"></iframe>',
'</div>',
'<figcaption>hitoribocchi tokyo</figcaption>',
'</figure>',
].join('\n'),
Expand All @@ -710,15 +696,19 @@ describe('libretto', ({ concurrent: it }) => {
'@youtube{id=7TovqLDCosk}',
[
'<figure>',
'<div data-aubade="youtube">',
'<iframe src="https://www.youtube-nocookie.com/embed/7TovqLDCosk" title="YouTube video player" loading="lazy" frameborder="0" allowfullscreen allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin"></iframe>',
'</div>',
'</figure>',
].join('\n'),
],
'directive#youtube/series': [
'@youtube{series="PLZRRxQcaEjA4qyEuYfAMCazlL0vQDkIj2" caption="Mind Field: Season 1"}',
[
'<figure>',
'<div data-aubade="youtube">',
'<iframe src="https://www.youtube-nocookie.com/embed/videoseries?list=PLZRRxQcaEjA4qyEuYfAMCazlL0vQDkIj2" title="YouTube video player" loading="lazy" frameborder="0" allowfullscreen allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin"></iframe>',
'</div>',
'<figcaption>Mind Field: Season 1</figcaption>',
'</figure>',
].join('\n'),
Expand All @@ -728,10 +718,12 @@ describe('libretto', ({ concurrent: it }) => {
'@video{src="./video.mp4" caption="local video"}',
[
'<figure>',
'<div data-aubade="video">',
'<video controls preload="metadata">',
'<source src="./video.mp4" type="video/mp4" />',
'Your browser does not support HTML5 video.',
'</video>',
'</div>',
'<figcaption>local video</figcaption>',
'</figure>',
].join('\n'),
Expand All @@ -741,32 +733,49 @@ describe('libretto', ({ concurrent: it }) => {
[
'<details>',
'<summary>local video</summary>',
'<div data-aubade="video">',
'<video controls preload="metadata">',
'<source src="./video.mp4" type="video/mp4" />',
'Your browser does not support HTML5 video.',
'</video>',
'</div>',
'</details>',
].join('\n'),
],
'directive#video/no-caption': [
'@video{src="./video.mp4"}',
[
'<figure>',
'<div data-aubade="video">',
'<video controls preload="metadata">',
'<source src="./video.mp4" type="video/mp4" />',
'Your browser does not support HTML5 video.',
'</video>',
'</div>',
'</figure>',
].join('\n'),
],

'image#plain': [
'![unannotated *alt* text](img.png)',
'<figure>\n<img src="img.png" alt="unannotated *alt* text" />\n</figure>',
[
'<figure>',
'<div data-aubade="image">',
'<img src="img.png" alt="unannotated *alt* text" />',
'</div>',
'</figure>',
].join('\n'),
],
'image#caption': [
'![alt](img.png "annotated *title* for caption")',
'<figure>\n<img src="img.png" alt="alt" />\n<figcaption>annotated <em>title</em> for caption</figcaption>\n</figure>',
[
'<figure>',
'<div data-aubade="image">',
'<img src="img.png" alt="alt" />',
'</div>',
'<figcaption>annotated <em>title</em> for caption</figcaption>',
'</figure>',
].join('\n'),
],

'quotes#escaped': ['\\"a b\\"', '<p>&quot;a b&quot;</p>'],
Expand Down
29 changes: 13 additions & 16 deletions workspace/aubade/src/artisan/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,8 +313,8 @@ export function figure(context: Context): null | {

export function heading({ annotate, extract, cursor, stack, util }: Context): null | {
type: 'block:heading';
meta: { level: number };
attr: { id: string; 'data-text': string };
meta: { level: number; text: string };
attr: { id: string };
children: Annotation[];
} {
cursor.trim(); // trim leading whitespace
Expand All @@ -325,32 +325,29 @@ export function heading({ annotate, extract, cursor, stack, util }: Context): nu
if (!title.length) return null;

const children = annotate(title);
const attr = {
id: title
.toLowerCase()
.replace(/[\s\][!"#$%&'()*+,./:;<=>?@\\^_`{|}~-]+/g, '-')
.replace(/^-+|-+$|(?<=-)-+/g, ''),
'data-text': children.map(extract).join(''),
};
let id = title
.toLowerCase()
.replace(/[\s\][!"#$%&'()*+,./:;<=>?@\\^_`{|}~-]+/g, '-')
.replace(/^-+|-+$|(?<=-)-+/g, '');

for (let i = stack['block:heading'].length - 1; i >= 0; i--) {
const { attr: parent, meta } = stack['block:heading'][i];
if (meta.level >= level) continue;
attr.id = `${parent.id}-${attr.id}`;
break;
if (meta.level < level) {
id = `${parent.id}-${id}`;
break;
}
}

let suffix = 0;
for (const h of stack['block:heading']) {
const check = suffix ? `${attr.id}-${suffix}` : attr.id;
const check = suffix ? `${id}-${suffix}` : id;
if (h.attr.id === check) suffix++;
}
attr.id = suffix ? `${attr.id}-${suffix}` : attr.id;

return util.commit(stack['block:heading'], {
type: 'block:heading',
meta: { level },
attr,
meta: { level, text: children.map(extract).join('') },
attr: { id: suffix ? `${id}-${suffix}` : id },
children,
});
}
Expand Down
20 changes: 12 additions & 8 deletions workspace/aubade/src/artisan/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ export const base = {
return print(
data.disclosure ? '<details>' : '<figure>',
data.disclosure && `<summary>${text}</summary>`,
'<div data-aubade="youtube">',
`<iframe src="${src}" ${attributes.join(' ')}></iframe>`,
'</div>',
!data.disclosure && data.caption && `<figcaption>${text}</figcaption>`,
data.disclosure ? '</details>' : '</figure>',
);
Expand All @@ -26,14 +28,15 @@ export const base = {
const src = sanitize(data.src || '');
const type = sanitize(data.type || 'video/mp4').toLowerCase();
const text = annotate(data.caption || 'video').map(render);
const fallback = sanitize(data.fallback || 'Your browser does not support HTML5 video.');
return print(
data.disclosure ? '<details>' : '<figure>',
data.disclosure && `<summary>${text}</summary>`,
'<div data-aubade="video">',
'<video controls preload="metadata">',
`<source src="${src}" type="${type}" />`,
fallback,
sanitize(data.fallback || 'Your browser does not support HTML5 video.'),
'</video>',
'</div>',
!data.disclosure && data.caption && `<figcaption>${text}</figcaption>`,
data.disclosure ? '</details>' : '</figure>',
);
Expand All @@ -54,11 +57,11 @@ export const standard = {
'block:break': () => `<hr />`,
'block:heading'({ token, render, sanitize }) {
const tag = `h${token.meta.level}`;
const attributes = Object.entries(token.attr).flatMap(([k, v]) =>
v.length ? `${k}="${sanitize(v)}"` : [],
);
const attributes = Object.entries(token.attr)
.flatMap(([k, v]) => (v.length ? `${k}="${sanitize(v)}"` : []))
.join(' ');
const children = token.children.map(render).join('');
return `<${tag} ${attributes.join(' ')}>${children}</${tag}>`;
return `<${tag}${attributes ? ' ' + attributes : ''}>${children}</${tag}>`;
},
'block:code'({ token, sanitize }) {
const { 'data-language': lang } = token.attr;
Expand Down Expand Up @@ -102,10 +105,11 @@ export const standard = {
return `<table>\n${thead}${tbody}\n</table>`;
},
'block:image'({ token, render, sanitize }) {
const img = `<img src="${sanitize(token.attr.src)}" alt="${sanitize(token.attr.alt)}" />`;
const title = token.children.map(render).join('');
const caption = title ? `\n<figcaption>${title}</figcaption>` : '';
return `<figure>\n${img}${caption}\n</figure>`;
const img = `<img src="${sanitize(token.attr.src)}" alt="${sanitize(token.attr.alt)}" />`;
const body = `<div data-aubade="image">\n${img}\n</div>`;
return `<figure>\n${body}${caption}\n</figure>`;
},
'block:paragraph'({ token, render }) {
return `<p>${token.children.map(render).join('')}</p>`;
Expand Down
Loading
Loading