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
2 changes: 1 addition & 1 deletion workspace/aubade/src/artisan/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ function extract(token: Token): string {
return 'text' in token ? token.text : '';
}

const util = {
export const util = {
commit<T>(array: T[], item: T) {
if (item !== util.last(array)) array.push(item);
return item;
Expand Down
17 changes: 13 additions & 4 deletions workspace/aubade/src/artisan/example.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe } from 'vitest';
import { forge } from './index.js';
import { engrave, forge } from './index.js';

// `deny` disallow features, either:
// - outright, like indented code block and setext headings
Expand Down Expand Up @@ -767,6 +767,16 @@ describe('libretto', ({ concurrent: it }) => {
'<figure>\n<img src="img.png" alt="alt" />\n<figcaption>annotated <em>title</em> for caption</figcaption>\n</figure>',
],

'quotes#double/1': ['"hello"', '<p>“hello”</p>'],
'quotes#double/2': ['"a *b* c"', '<p>“a <em>b</em> c”</p>'],
'quotes#single/1': ["'hello'", '<p>‘hello’</p>'],
'quotes#single/2': ["'a 'b' c'", '<p>‘a ‘b’ c’</p>'],
'quotes#mixed/1': [`"a 'b' c"`, '<p>“a ‘b’ c”</p>'],
'quotes#mixed/2': [`'a "b" c'`, '<p>‘a “b” c’</p>'],
'quotes#mixed/3': [`'a "b' c"`, '<p>‘a “b’ c”</p>'],
'quotes#mixed/4': [`'"hello"'`, '<p>‘“hello”’</p>'],
'quotes#mixed/5': [`"'hello'"`, '<p>“‘hello’”</p>'],

'strike#single': ['~strike~', '<p>~strike~</p>'],
'strike#normal': ['~~strike~~', '<p><del>strike</del></p>'],
'strike#code/1': ['~~`a~~`', '<p>~~<code>a~~</code></p>'],
Expand Down Expand Up @@ -796,7 +806,7 @@ describe('libretto', ({ concurrent: it }) => {
'strike#newline/1': ['~~a\n~~', '<p>~~a\n~~</p>'],
'strike#newline/2': ['~~\na~~', '<p>~~\na~~</p>'],
'strike#newline/3': ['~~\na\n~~', '<p>~~\na\n~~</p>'],
'strike#between/1': ['a~~"b"~~', '<p>a~~&quot;b&quot;~~</p>'],
'strike#between/1': ['a~~"b"~~', '<p>a~~“b”~~</p>'],
'strike#between/2|todo': ['-~~~~;~~~~~~', '<p>-<del><del>;</del></del>~~</p>'],

'table#normal': [
Expand All @@ -809,7 +819,6 @@ describe('libretto', ({ concurrent: it }) => {
],
};

const mark = forge();
for (const test in suite) {
const [input, output] = suite[test];
const [, ...props] = test.split('|');
Expand All @@ -820,7 +829,7 @@ describe('libretto', ({ concurrent: it }) => {
todo: props.includes('todo'),
};
it(test, options, ({ expect }) => {
expect(mark(input).html()).toBe(output);
expect(engrave(input).html()).toBe(output);
});
}
});
11 changes: 8 additions & 3 deletions workspace/aubade/src/artisan/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Token } from './registry.js';
import { escape } from './utils.js';
import { escape, typographic } from './utils.js';
import { annotate, compose } from './engine.js';
import { base, standard } from './resolver.js';

Expand All @@ -23,8 +23,8 @@ export interface Options {
quotes?: 'original' | 'typewriter' | 'typographic';
}

export const engrave = forge({});
export function forge({ directive = {}, renderer = {} }: Options = {}) {
export const engrave = forge({ quotes: 'typographic' });
export function forge({ directive = {}, renderer = {}, quotes }: Options = {}) {
const resolver = {
...standard,
...renderer,
Expand Down Expand Up @@ -60,6 +60,11 @@ export function forge({ directive = {}, renderer = {} }: Options = {}) {

return (input: string) => {
let { children: stream } = compose(input);

if (quotes === 'typographic') {
stream = stream.map((t) => walk(t, { 'inline:text': typographic }));
}

return {
get tokens() {
return stream;
Expand Down
28 changes: 28 additions & 0 deletions workspace/aubade/src/artisan/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import type { Token } from './registry.js';
import { util } from './context.js';

export function decode(cpt: number): string {
if (cpt === 0x00 || cpt > 0x10ffff) return '\uFFFD'; // HTML5 replacement character
if (cpt >= 0xd800 && cpt <= 0xdfff) return '\uFFFD'; // surrogate pair range
Expand All @@ -13,3 +16,28 @@ export function escape(source: string) {
.replace(/&(?!(?:[a-zA-Z][a-zA-Z0-9]{1,31}|#[0-9]{1,7}|#x[0-9a-fA-F]{1,6});)/g, '&amp;')
.replace(/[<>"]/g, (s) => symbols[s as keyof typeof symbols]);
}

// should probably work on 'block:paragraph' instead of individual text nodes
export function typographic(token: Extract<Token, { type: 'inline:text' }>) {
let result = '';
for (let i = 0; i < token.text.length; i++) {
const char = token.text[i];
if (char !== "'" && char !== '"') {
result += char;
continue;
}

const prev = token.text[i - 1];
const next = token.text[i + 1];

const left = util.is['left-flanking'](prev || ' ', next || ' ');
const right = util.is['right-flanking'](prev || ' ', next || ' ');

const double = left ? '“' : right ? '”' : char;
const single = left ? '‘' : right ? '’' : char;

result += char === '"' ? double : single;
}
token.text = result;
return token;
}
Loading