UNPKG

@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
"use strict"; 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