aubade
Version:
filesystem-based content processor
117 lines (116 loc) • 3.89 kB
JavaScript
// only parse http[s]:// and mails for safety
export function autolink({ cursor }) {
let text = '';
if (cursor.eat('<')) {
text = cursor.locate(/(?=>)/);
if (!text || /\s/.test(text))
return null;
cursor.eat('>'); // eat closing `>`
}
else {
text = cursor.locate(/\s|$/);
}
let href = '';
if (/^https?:\/\//.test(text)) {
href = text;
}
else if (/^mailto:/.test(text)) {
href = text;
}
else if (/^[\w.+-]+@[\w-]+\.[\w.-]+$/.test(text)) {
href = `mailto:${text}`;
}
else {
return null;
}
return { type: 'inline:autolink', text, attr: { href } };
}
// code span backticks have higher precedence than any other inline constructs
// except HTML tags and auto-links. https://spec.commonmark.org/0.31.2/#code-spans
export function code({ cursor }) {
if (!cursor.eat('`'))
return null;
let code = '';
let char = '';
const n = 1 + cursor.locate(/[^`]/).length;
while (!cursor.eat('`'.repeat(n)) && (char = cursor.read(1))) {
code += char;
}
if (!char)
return null;
if (code[0] === ' ' && code[0] === code[code.length - 1]) {
code = code.slice(1, -1); // trim the single space
}
return { type: 'inline:code', text: code };
}
export function comment({ cursor }) {
if (!cursor.eat('<!--'))
return null;
const comment = cursor.locate(/-->|$/);
cursor.eat('-->');
return { type: 'inline:comment', text: comment };
}
export function escape({ cursor, stack }) {
if (!cursor.eat('\\'))
return null;
let next = cursor.read(1);
if (!/[\\`*{}\[\]()#+\-!.<>:"'?=|~^&$%,@;]/.test(next)) {
next = '\\' + next; // escape character is not a valid inline token
}
const last = stack.peek();
if (last?.type === 'inline:text') {
last.text += next;
return last;
}
return stack.push({ type: 'inline:text', text: next });
}
export function image({ cursor }) {
if (!cursor.eat('!['))
return null;
const alt = cursor.locate(/]/);
if (!cursor.eat(']('))
return null;
cursor.trim(); // eat whitespace between opening `(` and link
const src = cursor.locate(/\s|\)/);
cursor.trim(); // eat whitespace between link and optionally title
const title = (cursor.eat('"') && cursor.locate(/"/)) || '';
cursor.eat('"'), cursor.trim(); // eat the closing quote and whitespace
// includes backticks that invalidates "](" pattern
const invalid = alt.includes('`') && src.includes('`');
if (invalid || !cursor.eat(')'))
return null; // closing `)` is required
return {
type: 'inline:image',
attr: { src, alt, title: title.trim() },
};
}
export function link({ cursor, annotate }) {
if (!cursor.eat('['))
return null;
const name = cursor.locate(/]/).replace(/\n/g, ' ');
if (!cursor.eat(']('))
return null;
cursor.trim(); // eat whitespace between opening `(` and link
const href = cursor.locate(/\s|\)/);
cursor.trim(); // eat whitespace between link and optionally title
const title = (cursor.eat('"') && cursor.locate(/"/)) || '';
cursor.trim(); // eat whitespace between optionally title and closing `)`
// includes backticks that invalidates "](" pattern
const invalid = name.includes('`') && href.includes('`');
if (invalid || !cursor.eat(')'))
return null; // closing `)` is required
return {
type: 'inline:link',
attr: { href, title: title.trim() },
children: annotate(name),
};
}
export function text({ cursor, stack }) {
const char = cursor.read(1);
const last = stack.peek();
if (last?.type === 'inline:text') {
last.text += char;
return last;
}
return stack.push({ type: 'inline:text', text: char });
}