@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
JavaScript
"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