UNPKG

@mdfriday/foundry

Version:

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

365 lines 11.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Content = exports.INTERNAL_SUMMARY_DIVIDER_PRE = exports.INTERNAL_SUMMARY_DIVIDER_BASE = void 0; const type_1 = require("../../../../pkg/media/type"); /** * Summary divider constants */ exports.INTERNAL_SUMMARY_DIVIDER_BASE = 'HUGOMORE42'; exports.INTERNAL_SUMMARY_DIVIDER_PRE = new TextEncoder().encode(`\n\n${exports.INTERNAL_SUMMARY_DIVIDER_BASE}\n\n`); /** * Content class - manages parsed content with shortcodes and replacements */ class Content { constructor(source, renderer) { this.hasSummaryDivider = false; this.summaryTruncated = false; this.items = []; this.rawSource = source; this.renderer = renderer; } /** * Check if content is empty */ isEmpty() { return !this.rawSource || this.rawSource.length === 0; } /** * Set summary divider flag */ setSummaryDivider() { this.hasSummaryDivider = true; } /** * Check if content has summary divider */ getHasSummaryDivider() { return this.hasSummaryDivider; } /** * Set summary truncated flag */ setSummaryTruncated() { this.summaryTruncated = true; } /** * Check if summary is truncated */ getTruncated() { return this.summaryTruncated; } /** * Add replacement item */ addReplacement(val, source) { const replacement = { val, source }; this.items.push(replacement); } /** * Add shortcode */ addShortcode(shortcode) { this.items.push(shortcode); } /** * Add parsed item */ addItem(item) { this.items.push(item); } /** * Add multiple items */ addItems(...items) { this.items.push(...items); } /** * Get raw content as string */ rawContent() { const decoder = new TextDecoder(); return decoder.decode(this.rawSource); } /** * Get pure content (with replacements and shortcodes processed) */ pureContent() { const content = []; const decoder = new TextDecoder(); const encoder = new TextEncoder(); for (const item of this.items) { if (this.isItem(item)) { const itemContent = this.rawSource.slice(item.Pos(), item.Pos() + item.Val(this.rawSource).length); content.push(itemContent); } else if (this.isContentReplacement(item)) { content.push(item.val); } else if (this.isShortcode(item)) { content.push(encoder.encode(item.placeholder)); } } // Combine all content const totalLength = content.reduce((sum, chunk) => sum + chunk.length, 0); const result = new Uint8Array(totalLength); let offset = 0; for (const chunk of content) { result.set(chunk, offset); offset += chunk.length; } return decoder.decode(result); } pureContentWithoutPlaceholder() { const content = []; const decoder = new TextDecoder(); for (const item of this.items) { if (this.isItem(item)) { const itemContent = this.rawSource.slice(item.Pos(), item.Pos() + item.Val(this.rawSource).length); content.push(itemContent); } else if (this.isContentReplacement(item)) { content.push(item.val); } else if (this.isShortcode(item)) { } } // Combine all content const totalLength = content.reduce((sum, chunk) => sum + chunk.length, 0); const result = new Uint8Array(totalLength); let offset = 0; for (const chunk of content) { result.set(chunk, offset); offset += chunk.length; } return decoder.decode(result); } /** * Get content with shortcodes rendered */ renderedContent(shortcodeRenderer) { let content = this.pureContent(); if (shortcodeRenderer) { for (const item of this.items) { if (this.isShortcode(item)) { const rendered = shortcodeRenderer(item); content = content.replace(item.placeholder, rendered); } } } return content; } /** * Get content with shortcodes rendered (async version) */ async renderedContentAsync(shortcodeRenderer) { let content = await this.renderer.render(this.pureContent()); if (shortcodeRenderer) { for (const item of this.items) { if (this.isShortcode(item)) { try { const rendered = await shortcodeRenderer(item); content = content.replace(item.placeholder, rendered); } catch (error) { // Keep placeholder if rendering fails } } } } return content; } /** * Get all items */ getItems() { return [...this.items]; } /** * Get shortcodes only */ getShortcodes() { return this.items.filter(this.isShortcode); } /** * Get regular items only */ getTextItems() { return this.items.filter(this.isItem); } /** * Get content replacements only */ getReplacements() { return this.items.filter(this.isContentReplacement); } /** * Check if summary is empty */ isSummaryEmpty() { const summary = this.getSummary(); return !summary || summary.trim() === ''; } /** * Extract summary automatically when summary is empty */ extractSummary(input, mediaType) { const inputStr = new TextDecoder().decode(input); const res = this.extractSummaryFromHTML(mediaType, inputStr, 70, this.containsCJK(inputStr)); const sum = res.summaryLowHigh.high > res.summaryLowHigh.low ? inputStr.substring(res.summaryLowHigh.low, res.summaryLowHigh.high).trim() : ''; if (sum) { this.summaryTruncated = res.truncated; return { summary: sum, truncated: res.truncated }; } const ts = this.trimShortHTML(input); this.summaryTruncated = ts.length < input.length; return { summary: new TextDecoder().decode(input), truncated: this.summaryTruncated }; } /** * Extract summary from HTML content */ extractSummaryFromHTML(mediaType, input, numWords, isCJK) { const result = { source: input, summaryLowHigh: { low: 0, high: input.length }, truncated: false }; if (numWords <= 0) { return result; } let count = 0; const words = input.split(/\s+/); for (let i = 0; i < words.length && count < numWords; i++) { const word = words[i].trim(); if (word.length === 0) continue; if (this.isProbablyHTMLToken(word)) continue; if (isCJK) { const cleanWord = this.stripHTML(word); const runeCount = [...cleanWord].length; count += cleanWord.length === cleanWord.replace(/[^\u0000-\u007F]/g, '').length ? 1 : runeCount; } else { count += 1; } if (count >= numWords) { // Find the end position of this word in the original string const wordsUpToHere = words.slice(0, i + 1).join(' '); const endPos = input.indexOf(wordsUpToHere) + wordsUpToHere.length; result.summaryLowHigh = { low: 0, high: Math.min(endPos, input.length) }; result.truncated = true; break; } } return result; } /** * Check if string contains CJK characters */ containsCJK(s) { return /[\u4e00-\u9fff\u3400-\u4dbf\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/.test(s); } /** * Check if a word is probably an HTML token */ isProbablyHTMLToken(s) { return s === '>' || /^<\/?[A-Za-z]+>?$/.test(s) || /^[A-Za-z]+=["']/.test(s); } /** * Strip HTML tags from string */ stripHTML(s) { return s.replace(/<[^>]*>/g, '').replace(/\s+/g, ' ').trim(); } /** * Trim short HTML content (remove single p tags if they wrap the entire content) */ trimShortHTML(input) { const decoder = new TextDecoder(); const encoder = new TextEncoder(); let content = decoder.decode(input); const openingTag = '<p>'; const closingTag = '</p>'; // Count occurrences of opening tags const openTagCount = (content.match(/<p>/g) || []).length; if (openTagCount === 1) { content = content.trim(); if (content.startsWith(openingTag) && content.endsWith(closingTag)) { content = content.slice(openingTag.length, -closingTag.length).trim(); } } return encoder.encode(content); } /** * Get content summary (up to summary divider) */ getSummary(maxLength) { const content = this.pureContent(); if (this.hasSummaryDivider) { const dividerIndex = content.indexOf(exports.INTERNAL_SUMMARY_DIVIDER_BASE); if (dividerIndex !== -1) { return content.substring(0, dividerIndex).trim(); } } return ''; } async getRenderedSummary(maxLength) { let rawSummary = this.getSummary(maxLength); // If summary is empty, extract it automatically if (this.isSummaryEmpty()) { const pureContent = this.pureContentWithoutPlaceholder(); const encoder = new TextEncoder(); const contentBytes = encoder.encode(pureContent); // Use a default MediaType for HTML content const defaultMediaType = new type_1.MediaType({ type: 'text/html', mainType: 'text', subType: 'html', delimiter: '.', firstSuffix: { suffix: 'html', fullSuffix: '.html' }, mimeSuffix: '', suffixesCSV: 'html' }); const extracted = this.extractSummary(contentBytes, defaultMediaType); rawSummary = extracted.summary; if (extracted.truncated) { rawSummary += '...'; } } if (maxLength && rawSummary.length > maxLength) { rawSummary = rawSummary.substring(0, maxLength).trim() + '...'; } return await this.renderer.render(rawSummary); } /** * Get word count */ getWordCount() { const content = this.pureContent(); const words = content.trim().split(/\s+/).filter(word => word.length > 0); return words.length; } /** * Get reading time estimate (words per minute) */ getReadingTime(wordsPerMinute = 200) { const wordCount = this.getWordCount(); return Math.ceil(wordCount / wordsPerMinute); } // Type guards isItem(item) { return 'Type' in item && 'firstByte' in item && 'low' in item && 'high' in item; } isShortcode(item) { return 'name' in item && 'placeholder' in item && 'params' in item; } isContentReplacement(item) { return 'val' in item && 'source' in item; } } exports.Content = Content; //# sourceMappingURL=content.js.map