const linkTagPattern = /\{%\s*link\s+([\s\S]*?)\s*%\}/g; const markdownLinkPattern = /^\[[^\]]*]\(([^)\s]+)(?:\s+["'][^"']*["'])?\)$/; function escapeHtml(value) { return String(value) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } function splitLinkArgs(value) { const parts = []; let current = ''; let bracketDepth = 0; let parenDepth = 0; for (const char of value) { if (char === '[') bracketDepth += 1; if (char === ']' && bracketDepth > 0) bracketDepth -= 1; if (char === '(') parenDepth += 1; if (char === ')' && parenDepth > 0) parenDepth -= 1; if (char === ',' && bracketDepth === 0 && parenDepth === 0 && parts.length < 2) { parts.push(current.trim()); current = ''; } else { current += char; } } parts.push(current.trim()); return parts; } function normalizeUrl(value) { const trimmed = value.trim(); const markdownLink = trimmed.match(markdownLinkPattern); return markdownLink ? markdownLink[1] : trimmed; } function getHostname(url) { try { return new URL(url).hostname.replace(/^www\./, ''); } catch { return ''; } } function renderLinkCard(rawValue) { const [title, source, rawUrl] = splitLinkArgs(rawValue); const url = normalizeUrl(rawUrl ?? ''); if (!title || !url) return `{% link ${rawValue} %}`; const host = getHostname(url); const label = source?.split('@')[0]?.trim() || host || 'Link'; const initial = label.slice(0, 1).toUpperCase(); return ` ${escapeHtml(title)} ${escapeHtml(label)}${host ? ` ยท ${escapeHtml(host)}` : ''} `; } function transformText(value) { let matched = false; const transformed = value.replace(linkTagPattern, (_, rawValue) => { matched = true; return renderLinkCard(rawValue); }); return matched ? transformed : value; } function inlineToSource(node) { if (node.type === 'text' || node.type === 'inlineCode') return node.value ?? ''; if (node.type === 'link') { const text = Array.isArray(node.children) ? node.children.map(inlineToSource).join('') : node.url; return `[${text}](${node.url})`; } if (Array.isArray(node.children)) return node.children.map(inlineToSource).join(''); return ''; } function visit(node) { if (!node || typeof node !== 'object') return; if (node.type === 'paragraph' && Array.isArray(node.children)) { const source = node.children.map(inlineToSource).join(''); const transformed = transformText(source); if (transformed !== source) { node.type = 'html'; node.value = transformed; delete node.children; return; } } if (node.type === 'text' && typeof node.value === 'string') { const transformed = transformText(node.value); if (transformed !== node.value) { node.type = 'html'; node.value = transformed; } } if (Array.isArray(node.children)) { for (const child of node.children) visit(child); } } export default function remarkHexoLinkCards() { return (tree) => { visit(tree); }; }