markdown2html/src/attrs.ts
2023-05-06 15:21:31 -07:00

93 lines
2.0 KiB
TypeScript

import { Lazy, resolve_lazy } from './lazy';
const attrs_pattern = /\s+\{\s*(?:[\.#:][^\s]+(?:\s*[\.#:][^\s]+)*)?\s*}$/;
export interface ParsedAttributes {
id?: string;
classes: string[];
attrs: Record<string, string | string[]>;
html_attrs: string[];
text: string;
}
export function parse_attributes(text: string, fallback_id?: Lazy<string>) {
const attrs: ParsedAttributes = {
id: null,
classes: [ ],
attrs: { },
html_attrs: [ ],
text,
};
const attrs_match = attrs_pattern.exec(text);
if (attrs_match) {
attrs.text = text.slice(0, -attrs_match[0].length).trim();
const raw_attrs = attrs_match[0].trim().slice(1, -1).trim().split(/\s+/g);
for (const attr of raw_attrs) {
if (attr.startsWith('.')) {
attrs.classes.push(attr.slice(1));
}
else if (attr.startsWith('#')) {
attrs.id = attr.slice(1);
}
else if (attr.startsWith(':')) {
const eq_index = attr.indexOf('=');
if (eq_index === -1) {
const name = attr.slice(1);
attrs.attrs[name] = '';
}
const name = attr.slice(1, eq_index);
const value = attr.slice(eq_index + 1);
// Enable passing the same attribute more than once for lists, i.e.:
// {:rel=external :rel=nofollow}
// should render as:
// rel="external nofollow"
if (attrs.attrs[name]) {
if (! Array.isArray(attrs.attrs[name])) {
attrs.attrs[name] = [ attrs.attrs[name] as string ];
}
(attrs.attrs[name] as string[]).push(value);
}
else {
attrs.attrs[name] = value;
}
}
}
}
if (! attrs.id) {
attrs.id = resolve_lazy(fallback_id);
}
if (attrs.id) {
attrs.html_attrs.push(`id="${attrs.id}"`);
}
if (attrs.classes.length) {
attrs.html_attrs.push(`class="${attrs.classes.join(' ')}"`);
}
for (const [name, value] of Object.entries(attrs.attrs)) {
if (Array.isArray(value)) {
attrs.html_attrs.push(`${name}="${value.join(' ')}"`);
}
else {
attrs.html_attrs.push(value ? `${name}="${value}"` : name);
}
}
return attrs;
}