UNPKG

@mdfriday/foundry

Version:

The core engine of MDFriday. Convert Markdown and shortcodes into fully themed static sites – Hugo-style, powered by TypeScript.

301 lines 11.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SHORTCODE_PLACEHOLDER_PREFIX = exports.TOC_SHORTCODE_PLACEHOLDER = exports.ShortcodeParser = exports.ShortcodeImpl = void 0; exports.createShortcodePlaceholder = createShortcodePlaceholder; exports.hasShortcodePlaceholder = hasShortcodePlaceholder; exports.replaceShortcodePlaceholders = replaceShortcodePlaceholders; const log_1 = require("../../../../pkg/log"); // Create a domain-specific logger for markdown operations const log = (0, log_1.getDomainLogger)('markdown', { component: 'vo/shortcode' }); class ShortcodeImpl { constructor(ordinal = 0, name = '', params = null, pos = 0, length = 0, inline = false, closed = false) { this.ordinal = ordinal; this.name = name; this.params = params; this.pos = pos; this.length = length; this.rawContent = ''; this.inline = inline; this.closed = closed; this.doMarkup = false; this.isClosing = false; this.placeholder = ''; this.inner = []; } needsInner() { // This would need template service integration // For now, assume all shortcodes might need inner content return !this.inline; } } exports.ShortcodeImpl = ShortcodeImpl; /** * Simple shortcode parser */ class ShortcodeParser { constructor(source, pid = Date.now()) { this.shortcodes = []; this.nameSet = new Set(); this.ordinal = 0; this.openShortcodes = new Map(); this.paramElements = 0; this.source = source; this.pid = pid; } /** * Parse shortcode from iterator */ parseItem(item, iter) { const shortcode = this.extractShortcode(this.ordinal, 0, iter); if (!shortcode) { throw new Error("Failed to extract shortcode"); } shortcode.pos = item.Pos() + item.ValStr(this.source).length; shortcode.length = iter.Current().Pos() + iter.Current().ValStr(this.source).length - shortcode.pos; const rawContent = this.source.slice(shortcode.pos, shortcode.pos + shortcode.length); shortcode.rawContent = new TextDecoder().decode(rawContent); if (shortcode.name) { this.nameSet.add(shortcode.name); } shortcode.params ?? (shortcode.params = []); shortcode.placeholder = createShortcodePlaceholder('s', this.pid, this.ordinal); this.ordinal++; this.shortcodes.push(shortcode); return shortcode; } /** * Extract shortcode from iterator (following Go version logic exactly) */ extractShortcode(ordinal, level, pt) { const sc = new ShortcodeImpl(ordinal); // Back up one to identify any indentation if (pt.Pos() > 0) { pt.Backup(); const item = pt.Next(); if (item.IsIndentation()) { sc.indentation = item.ValStr(this.source); } } let cnt = 0; let nestedOrdinal = 0; const nextLevel = level + 1; let closed = false; const errorPrefix = "failed to extract shortcode"; let paramElements = 0; while (true) { const currItem = pt.Next(); if (currItem.IsLeftShortcodeDelim()) { const next = pt.Peek(); if (next.IsRightShortcodeDelim()) { // no name: {{< >}} or {{% %}} throw new Error("shortcode has no name"); } if (next.IsShortcodeClose()) { continue; } if (cnt > 0) { // nested shortcode; append it to inner content pt.Backup(); const nested = this.extractShortcode(nestedOrdinal, nextLevel, pt); nestedOrdinal++; if (nested && nested.name) { this.nameSet.add(nested.name); if (!Array.isArray(sc.inner)) { sc.inner = []; } sc.inner.push(nested); } } else { sc.doMarkup = currItem.IsShortcodeMarkupDelimiter(); } cnt++; } else if (currItem.IsRightShortcodeDelim()) { if (!sc.inline) { if (!sc.needsInner()) { this.openShortcodes.set(sc.name, false); return sc; } } } else if (currItem.IsShortcodeClose()) { closed = true; const next = pt.Peek(); if (!sc.inline && !sc.needsInner()) { if (next.IsError()) { continue; } throw new Error(`${errorPrefix}: shortcode "${sc.name}" does not evaluate .Inner or .InnerDeindent, yet a closing tag was provided`); } if (next.IsRightShortcodeDelim()) { pt.Consume(1); } else { sc.isClosing = true; pt.Consume(2); } if (!sc.inline) { this.openShortcodes.set(sc.name, false); } return sc; } else if (currItem.IsText()) { if (!Array.isArray(sc.inner)) { sc.inner = []; } const text = currItem.ValStr(this.source); sc.inner.push(text); } else if (currItem.IsShortcodeName() || currItem.IsInlineShortcodeName()) { sc.name = currItem.ValStr(this.source).trim(); sc.inline = currItem.IsInlineShortcodeName(); if (this.openShortcodes.has(sc.name) && this.openShortcodes.get(sc.name)) { throw new Error(`shortcode ${sc.name} nested in itself`); } if (!sc.inline) { this.openShortcodes.set(sc.name, true); } // Check for inline shortcode nesting if (sc.inline) { const b = this.source.slice(pt.Pos() + 3); const end = indexNonWhiteSpace(b, '/'); if (end !== this.source.length - 1) { const remainingText = new TextDecoder().decode(b.slice(end + 1)); if (end === -1 || !remainingText.startsWith(sc.name + " ")) { throw new Error("inline shortcodes do not support nesting"); } } } } else if (currItem.IsShortcodeParam()) { if (!pt.IsValueNext()) { log.warn(`${errorPrefix}: shortcode "${sc.name}" has a parameter without a value`); continue; } // At this point we know we have a value if (pt.Peek().IsShortcodeParamVal()) { // Named params if (sc.params === null || sc.params === undefined) { const params = {}; const paramName = currItem.ValStr(this.source); pt.Next(); // consume the value token params[paramName] = pt.Current().ValTyped(this.source); sc.params = params; } else { if (Array.isArray(sc.params)) { throw new Error(`${errorPrefix}: invalid state: invalid param type Array for shortcode "${sc.name}", expected a map`); } else { const paramName = currItem.ValStr(this.source); pt.Next(); // consume the value token sc.params[paramName] = pt.Current().ValTyped(this.source); } } } else { // Positional params if (sc.params === null || sc.params === undefined) { const params = []; params.push(currItem.ValTyped(this.source)); sc.params = params; } else { if (!Array.isArray(sc.params)) { throw new Error(`${errorPrefix}: invalid state: invalid param type Object for shortcode "${sc.name}", expected an array`); } else { sc.params.push(currItem.ValTyped(this.source)); } } } } else if (currItem.IsShortcodeParamVal()) { // TODO, check if this is necessary if (paramElements === 0) { paramElements = 1; } if (Array.isArray(sc.params)) { sc.params.push(currItem.ValTyped(this.source)); } else if (sc.params === null || sc.params === undefined) { const params = []; params.push(currItem.ValTyped(this.source)); sc.params = params; } } else if (currItem.IsDone()) { if (!currItem.IsError()) { if (!closed && sc.needsInner()) { throw new Error(`${errorPrefix}: shortcode "${sc.name}" must be closed or self-closed`); } } pt.Backup(); break; } } if (!sc.inline) { this.openShortcodes.set(sc.name, false); } return sc; } /** * Get all parsed shortcodes */ getShortcodes() { return this.shortcodes; } /** * Get shortcode names */ getNames() { return Array.from(this.nameSet); } } exports.ShortcodeParser = ShortcodeParser; /** * Constants for shortcode placeholders */ exports.TOC_SHORTCODE_PLACEHOLDER = createShortcodePlaceholder('TOC', 0, 0); exports.SHORTCODE_PLACEHOLDER_PREFIX = 'HAHAHUGOSHORTCODE'; /** * Create shortcode placeholder */ function createShortcodePlaceholder(id, sid, ordinal) { return `${exports.SHORTCODE_PLACEHOLDER_PREFIX}${id}${sid}${ordinal}HBHB`; } /** * Check if string contains shortcode placeholder */ function hasShortcodePlaceholder(content) { return content.includes(exports.SHORTCODE_PLACEHOLDER_PREFIX); } /** * Replace shortcode placeholders in content */ function replaceShortcodePlaceholders(content, shortcodes, renderer) { let result = content; for (const shortcode of shortcodes) { const rendered = renderer(shortcode); result = result.replace(shortcode.placeholder, rendered); } return result; } // Helper function to find first non-whitespace character function indexNonWhiteSpace(source, char) { const charCode = char.charCodeAt(0); for (let i = 0; i < source.length; i++) { if (!isSpace(source[i])) { if (source[i] === charCode) { return i; } } } return -1; } // Helper function to check if a byte is a space function isSpace(b) { return b === 0x20 || b === 0x09 || b === 0x0D || b === 0x0A; } //# sourceMappingURL=shortcode.js.map