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