import { Lazy, resolve_lazy } from './lazy'; const attrs_pattern = /\s+\{\s*(?:[\.#:][^\s]+(?:\s*[\.#:][^\s]+)*)?\s*}$/; export interface ParsedAttributes { id?: string; classes: string[]; attrs: Record; html_attrs: string[]; text: string; } export function parse_attributes(text: string, fallback_id?: Lazy) { 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; }