@mdfriday/foundry
Version:
The core engine of MDFriday. Convert Markdown and shortcodes into fully themed static sites – Hugo-style, powered by TypeScript.
347 lines • 12.8 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.MarkdownImpl = void 0;
const yaml = __importStar(require("js-yaml"));
const smol_toml_1 = require("smol-toml");
const parserresult_1 = require("../vo/parserresult");
const context_1 = require("../vo/context");
const parseinfo_1 = require("../vo/parseinfo");
const item_1 = require("../../../../pkg/md/parser/item");
const shortcode_1 = require("../vo/shortcode");
const content_1 = require("../vo/content");
const log_1 = require("../../../../pkg/log");
const log = (0, log_1.getDomainLogger)('markdown', { component: 'MarkdownImpl' });
/**
* MarkdownImpl implements the main Markdown interface
* This is the TypeScript equivalent of the Go entity.Markdown
*/
class MarkdownImpl {
constructor(renderer, highlighter) {
this.renderer = renderer;
this.highlighter = highlighter;
}
async render(rctx, dctx) {
const parseResult = await this.parse(rctx);
const renderResult = await this.renderToBytes(rctx, dctx, parseResult);
return new parserresult_1.ResultImpl(parseResult, renderResult);
}
// Highlighter interface methods
async highlight(code, lang, opts) {
return this.highlighter.highlight(code, lang, opts);
}
async highlightCodeBlock(ctx, opts) {
return this.highlighter.highlightCodeBlock(ctx, opts);
}
async renderCodeblock(cctx, w, ctx) {
return this.highlighter.renderCodeblock(cctx, w, ctx);
}
isDefaultCodeBlockRenderer() {
return this.highlighter.isDefaultCodeBlockRenderer();
}
async prepareRender(source) {
const parseResult = await this.parseContent(source);
const frontMatter = parseResult.frontMatter?.params;
const res = await this.renderer.parse(parseResult.content.pureContent());
return {
frontMatter: () => frontMatter || {},
toc: () => res.tableOfContents(),
render: async (options) => {
const htmlContent = await parseResult.content.renderedContentAsync(options.shortcodeRenderer);
// Use Content's methods with provided options
const summary = await parseResult.content.getRenderedSummary(options.maxSummaryLength);
const wordCount = parseResult.content.getWordCount();
const readingTime = parseResult.content.getReadingTime(options.wordsPerMinute);
const result = {
renderedContent: htmlContent,
wordCount,
readingTime
};
// Add optional properties only if they exist
if (frontMatter) {
result.frontMatter = frontMatter;
}
if (summary) {
result.summary = summary;
}
return result;
}
};
}
/**
* Parse and render content in one step - simplified interface for external use
* This method hides implementation details and provides a clean interface
*/
async parseAndRenderContent(source, options = {}) {
// Parse the content first
const parseResult = await this.parseContent(source);
// Extract front matter as simple Record<string, any>
const frontMatter = parseResult.frontMatter?.params;
// Get rendered content with shortcodes processed using Content's built-in method
let renderedContent;
if (options.shortcodeRenderer && parseResult.shortcodes.length > 0) {
// Use Content's async rendering method
renderedContent = await parseResult.content.renderedContentAsync(options.shortcodeRenderer);
}
else {
// No shortcodes or no renderer provided, use pure content
renderedContent = parseResult.content.pureContent();
}
// Apply markdown rendering to the content
const htmlContent = await this.renderer.render(renderedContent);
// Use Content's methods with provided options
const summary = parseResult.content.getSummary(options.maxSummaryLength);
const wordCount = parseResult.content.getWordCount();
const readingTime = parseResult.content.getReadingTime(options.wordsPerMinute);
const result = {
renderedContent: htmlContent,
wordCount,
readingTime
};
// Add optional properties only if they exist
if (frontMatter) {
result.frontMatter = frontMatter;
}
if (summary) {
result.summary = summary;
}
return result;
}
/**
* Parse markdown source with front matter and shortcodes
*/
async parseContent(source) {
let frontMatter;
const content = new content_1.Content(source, this.renderer);
const shortcodeParser = new shortcode_1.ShortcodeParser(source);
const shortcodes = [];
// Create handlers for different content types
const handlers = {
frontMatterHandler: () => {
return (item) => {
frontMatter = this.parseFrontMatter(item, source);
};
},
summaryHandler: () => {
return (item, iter) => {
let posBody = -1;
const walkFn = (walkItem) => {
if (posBody === -1 && !walkItem.IsDone()) {
posBody = walkItem.Pos();
}
if (walkItem.IsNonWhitespace(source)) {
content.setSummaryTruncated();
return false; // Done
}
return true;
};
iter.PeekWalk(walkFn);
content.setSummaryDivider();
content.addReplacement(content_1.INTERNAL_SUMMARY_DIVIDER_PRE, item);
};
},
shortcodeHandler: () => {
return (item, iter) => {
const shortcode = shortcodeParser.parseItem(item, iter);
shortcodes.push(shortcode);
content.addShortcode(shortcode);
};
},
bytesHandler: () => {
return (item) => {
content.addItem(item);
};
}
};
// Parse the content
const parseInfo = (0, parseinfo_1.createSourceParseInfo)(source, handlers);
await parseInfo.parse();
await parseInfo.handle();
return {
frontMatter,
content,
shortcodes,
summary: content.getSummary(),
wordCount: content.getWordCount(),
readingTime: content.getReadingTime(),
};
}
/**
* Parse front matter from item
*/
parseFrontMatter(item, source) {
const content = item.ValStr(source);
let format;
let params = {};
switch (item.Type) {
case item_1.ItemType.TypeFrontMatterYAML:
format = 'yaml';
params = this.parseYAML(content);
break;
case item_1.ItemType.TypeFrontMatterTOML:
format = 'toml';
params = this.parseTOML(content);
break;
case item_1.ItemType.TypeFrontMatterJSON:
format = 'json';
params = this.parseJSON(content);
break;
case item_1.ItemType.TypeFrontMatterORG:
format = 'org';
params = this.parseOrg(content);
break;
default:
format = 'yaml';
}
return { params, format };
}
/**
* Simple YAML parser (basic implementation)
*/
parseYAML(content) {
try {
const parsed = yaml.load(content);
// yaml.load can return various types, we need to ensure it's an object
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
return parsed;
}
// If it's not an object, return empty object
return {};
}
catch (error) {
return {};
}
}
/**
* Simple TOML parser using smol-toml library
*/
parseTOML(content) {
try {
const parsed = (0, smol_toml_1.parse)(content);
// parseToml returns the parsed object directly
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
return parsed;
}
// If it's not an object, return empty object
return {};
}
catch (error) {
return {};
}
}
/**
* Simple JSON parser
*/
parseJSON(content) {
try {
return JSON.parse(content);
}
catch (error) {
return {};
}
}
/**
* Simple Org mode parser (basic implementation)
*/
parseOrg(content) {
const params = {};
const lines = content.split('\n');
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed.startsWith('#+'))
continue;
const match = trimmed.match(/^#\+([^:]+):\s*(.*)$/);
if (match) {
const key = match[1].trim().toLowerCase();
const value = match[2].trim();
params[key] = this.parseValue(value);
}
}
return params;
}
/**
* Parse value (string, number, boolean, array)
*/
parseValue(value) {
// Remove quotes
if ((value.startsWith('"') && value.endsWith('"')) ||
(value.startsWith("'") && value.endsWith("'"))) {
return value.slice(1, -1);
}
// Boolean
if (value === 'true')
return true;
if (value === 'false')
return false;
// Number
if (/^-?\d+$/.test(value)) {
return parseInt(value, 10);
}
if (/^-?\d*\.\d+$/.test(value)) {
return parseFloat(value);
}
// Array (simple comma-separated)
if (value.startsWith('[') && value.endsWith(']')) {
const arrayContent = value.slice(1, -1).trim();
if (!arrayContent)
return [];
return arrayContent.split(',').map(item => this.parseValue(item.trim()));
}
return value;
}
/**
* Parse markdown source into tokens and create parsing result
*/
async parse(rctx) {
const decoder = new TextDecoder();
const source = decoder.decode(rctx.src);
return await this.renderer.parse(source);
}
/**
* Render the parsed result to bytes
*/
async renderToBytes(rctx, dctx, parseResult) {
const decoder = new TextDecoder();
const source = decoder.decode(rctx.src);
// Render using the provided renderer
const html = await this.renderer.render(source);
// Create buffer and write HTML
const buf = new context_1.BufWriter();
await buf.writeString(html);
return new parserresult_1.RenderingResultImpl(buf);
}
}
exports.MarkdownImpl = MarkdownImpl;
//# sourceMappingURL=markdown.js.map