diff --git a/skills/baoyu-markdown-to-html/scripts/vendor/baoyu-md/src/content.ts b/skills/baoyu-markdown-to-html/scripts/vendor/baoyu-md/src/content.ts index 6be2b51..0bc73c5 100644 --- a/skills/baoyu-markdown-to-html/scripts/vendor/baoyu-md/src/content.ts +++ b/skills/baoyu-markdown-to-html/scripts/vendor/baoyu-md/src/content.ts @@ -46,6 +46,45 @@ export function stripWrappingQuotes(value: string): string { return value.trim(); } +const HTML_ENTITIES: Record = { + amp: "&", + apos: "'", + gt: ">", + lt: "<", + nbsp: " ", + quot: '"', +}; + +function decodeHtmlCodePoint(codePoint: number, fallback: string): string { + if (!Number.isFinite(codePoint) || codePoint < 0 || codePoint > 0x10ffff) { + return fallback; + } + return String.fromCodePoint(codePoint); +} + +function decodeHtmlEntities(value: string): string { + return value.replace(/&(#x?[0-9a-f]+|[a-z]+);/gi, (entity, body: string) => { + const normalized = body.toLowerCase(); + if (normalized.startsWith("#x")) { + return decodeHtmlCodePoint(Number.parseInt(normalized.slice(2), 16), entity); + } + if (normalized.startsWith("#")) { + return decodeHtmlCodePoint(Number.parseInt(normalized.slice(1), 10), entity); + } + return HTML_ENTITIES[normalized] ?? entity; + }); +} + +export function cleanSummaryText(value: string): string { + return decodeHtmlEntities(stripWrappingQuotes(value)) + .replace(//gi, " ") + .replace(//gi, " ") + .replace(//gi, " ") + .replace(/<\/?[a-z][a-z0-9:-]*(?:\s+[^>]*)?>/gi, " ") + .replace(/\s+/g, " ") + .trim(); +} + export function toFrontmatterString(value: unknown): string | undefined { if (typeof value === "string") { return stripWrappingQuotes(value); @@ -94,10 +133,11 @@ export function extractSummaryFromBody(body: string, maxLen: number): string { .replace(/\*(.+?)\*/g, "$1") .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1") .replace(/`([^`]+)`/g, "$1"); + const summaryText = cleanSummaryText(cleanText); - if (cleanText.length > 20) { - if (cleanText.length <= maxLen) return cleanText; - return `${cleanText.slice(0, maxLen - 3)}...`; + if (summaryText.length > 20) { + if (summaryText.length <= maxLen) return summaryText; + return `${summaryText.slice(0, maxLen - 3)}...`; } } diff --git a/skills/baoyu-markdown-to-html/scripts/vendor/baoyu-md/src/html-builder.ts b/skills/baoyu-markdown-to-html/scripts/vendor/baoyu-md/src/html-builder.ts index d27e03a..6756a1e 100644 --- a/skills/baoyu-markdown-to-html/scripts/vendor/baoyu-md/src/html-builder.ts +++ b/skills/baoyu-markdown-to-html/scripts/vendor/baoyu-md/src/html-builder.ts @@ -45,19 +45,24 @@ export function loadCodeThemeCss(themeName: string): string { } export function buildHtmlDocument(meta: HtmlDocumentMeta, css: string, html: string, codeThemeCss?: string): string { + const escapeHtmlAttribute = (value: string) => value + .replace(/&/g, "&") + .replace(/"/g, """) + .replace(//g, ">"); const lines = [ "", "", "", ' ', ' ', - ` ${meta.title}`, + ` ${escapeHtmlAttribute(meta.title)}`, ]; if (meta.author) { - lines.push(` `); + lines.push(` `); } if (meta.description) { - lines.push(` `); + lines.push(` `); } lines.push(` `); if (codeThemeCss) { diff --git a/skills/baoyu-post-to-weibo/scripts/vendor/baoyu-md/src/content.ts b/skills/baoyu-post-to-weibo/scripts/vendor/baoyu-md/src/content.ts index 6be2b51..0bc73c5 100644 --- a/skills/baoyu-post-to-weibo/scripts/vendor/baoyu-md/src/content.ts +++ b/skills/baoyu-post-to-weibo/scripts/vendor/baoyu-md/src/content.ts @@ -46,6 +46,45 @@ export function stripWrappingQuotes(value: string): string { return value.trim(); } +const HTML_ENTITIES: Record = { + amp: "&", + apos: "'", + gt: ">", + lt: "<", + nbsp: " ", + quot: '"', +}; + +function decodeHtmlCodePoint(codePoint: number, fallback: string): string { + if (!Number.isFinite(codePoint) || codePoint < 0 || codePoint > 0x10ffff) { + return fallback; + } + return String.fromCodePoint(codePoint); +} + +function decodeHtmlEntities(value: string): string { + return value.replace(/&(#x?[0-9a-f]+|[a-z]+);/gi, (entity, body: string) => { + const normalized = body.toLowerCase(); + if (normalized.startsWith("#x")) { + return decodeHtmlCodePoint(Number.parseInt(normalized.slice(2), 16), entity); + } + if (normalized.startsWith("#")) { + return decodeHtmlCodePoint(Number.parseInt(normalized.slice(1), 10), entity); + } + return HTML_ENTITIES[normalized] ?? entity; + }); +} + +export function cleanSummaryText(value: string): string { + return decodeHtmlEntities(stripWrappingQuotes(value)) + .replace(//gi, " ") + .replace(//gi, " ") + .replace(//gi, " ") + .replace(/<\/?[a-z][a-z0-9:-]*(?:\s+[^>]*)?>/gi, " ") + .replace(/\s+/g, " ") + .trim(); +} + export function toFrontmatterString(value: unknown): string | undefined { if (typeof value === "string") { return stripWrappingQuotes(value); @@ -94,10 +133,11 @@ export function extractSummaryFromBody(body: string, maxLen: number): string { .replace(/\*(.+?)\*/g, "$1") .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1") .replace(/`([^`]+)`/g, "$1"); + const summaryText = cleanSummaryText(cleanText); - if (cleanText.length > 20) { - if (cleanText.length <= maxLen) return cleanText; - return `${cleanText.slice(0, maxLen - 3)}...`; + if (summaryText.length > 20) { + if (summaryText.length <= maxLen) return summaryText; + return `${summaryText.slice(0, maxLen - 3)}...`; } } diff --git a/skills/baoyu-post-to-weibo/scripts/vendor/baoyu-md/src/html-builder.ts b/skills/baoyu-post-to-weibo/scripts/vendor/baoyu-md/src/html-builder.ts index d27e03a..6756a1e 100644 --- a/skills/baoyu-post-to-weibo/scripts/vendor/baoyu-md/src/html-builder.ts +++ b/skills/baoyu-post-to-weibo/scripts/vendor/baoyu-md/src/html-builder.ts @@ -45,19 +45,24 @@ export function loadCodeThemeCss(themeName: string): string { } export function buildHtmlDocument(meta: HtmlDocumentMeta, css: string, html: string, codeThemeCss?: string): string { + const escapeHtmlAttribute = (value: string) => value + .replace(/&/g, "&") + .replace(/"/g, """) + .replace(//g, ">"); const lines = [ "", "", "", ' ', ' ', - ` ${meta.title}`, + ` ${escapeHtmlAttribute(meta.title)}`, ]; if (meta.author) { - lines.push(` `); + lines.push(` `); } if (meta.description) { - lines.push(` `); + lines.push(` `); } lines.push(` `); if (codeThemeCss) {