chore: sync baoyu-md vendor to baoyu-markdown-to-html and baoyu-post-to-weibo

This commit is contained in:
Jim Liu 宝玉 2026-04-12 20:18:05 -05:00
parent 434d4857da
commit bfdd64bd4e
4 changed files with 102 additions and 12 deletions

View File

@ -46,6 +46,45 @@ export function stripWrappingQuotes(value: string): string {
return value.trim(); return value.trim();
} }
const HTML_ENTITIES: Record<string, string> = {
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(/<script\b[\s\S]*?<\/script>/gi, " ")
.replace(/<style\b[\s\S]*?<\/style>/gi, " ")
.replace(/<br\s*\/?>/gi, " ")
.replace(/<\/?[a-z][a-z0-9:-]*(?:\s+[^>]*)?>/gi, " ")
.replace(/\s+/g, " ")
.trim();
}
export function toFrontmatterString(value: unknown): string | undefined { export function toFrontmatterString(value: unknown): string | undefined {
if (typeof value === "string") { if (typeof value === "string") {
return stripWrappingQuotes(value); return stripWrappingQuotes(value);
@ -94,10 +133,11 @@ export function extractSummaryFromBody(body: string, maxLen: number): string {
.replace(/\*(.+?)\*/g, "$1") .replace(/\*(.+?)\*/g, "$1")
.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1") .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
.replace(/`([^`]+)`/g, "$1"); .replace(/`([^`]+)`/g, "$1");
const summaryText = cleanSummaryText(cleanText);
if (cleanText.length > 20) { if (summaryText.length > 20) {
if (cleanText.length <= maxLen) return cleanText; if (summaryText.length <= maxLen) return summaryText;
return `${cleanText.slice(0, maxLen - 3)}...`; return `${summaryText.slice(0, maxLen - 3)}...`;
} }
} }

View File

@ -45,19 +45,24 @@ export function loadCodeThemeCss(themeName: string): string {
} }
export function buildHtmlDocument(meta: HtmlDocumentMeta, css: string, html: string, codeThemeCss?: string): string { export function buildHtmlDocument(meta: HtmlDocumentMeta, css: string, html: string, codeThemeCss?: string): string {
const escapeHtmlAttribute = (value: string) => value
.replace(/&/g, "&amp;")
.replace(/"/g, "&quot;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
const lines = [ const lines = [
"<!doctype html>", "<!doctype html>",
"<html>", "<html>",
"<head>", "<head>",
' <meta charset="utf-8" />', ' <meta charset="utf-8" />',
' <meta name="viewport" content="width=device-width, initial-scale=1" />', ' <meta name="viewport" content="width=device-width, initial-scale=1" />',
` <title>${meta.title}</title>`, ` <title>${escapeHtmlAttribute(meta.title)}</title>`,
]; ];
if (meta.author) { if (meta.author) {
lines.push(` <meta name="author" content="${meta.author}" />`); lines.push(` <meta name="author" content="${escapeHtmlAttribute(meta.author)}" />`);
} }
if (meta.description) { if (meta.description) {
lines.push(` <meta name="description" content="${meta.description}" />`); lines.push(` <meta name="description" content="${escapeHtmlAttribute(meta.description)}" />`);
} }
lines.push(` <style>${css}</style>`); lines.push(` <style>${css}</style>`);
if (codeThemeCss) { if (codeThemeCss) {

View File

@ -46,6 +46,45 @@ export function stripWrappingQuotes(value: string): string {
return value.trim(); return value.trim();
} }
const HTML_ENTITIES: Record<string, string> = {
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(/<script\b[\s\S]*?<\/script>/gi, " ")
.replace(/<style\b[\s\S]*?<\/style>/gi, " ")
.replace(/<br\s*\/?>/gi, " ")
.replace(/<\/?[a-z][a-z0-9:-]*(?:\s+[^>]*)?>/gi, " ")
.replace(/\s+/g, " ")
.trim();
}
export function toFrontmatterString(value: unknown): string | undefined { export function toFrontmatterString(value: unknown): string | undefined {
if (typeof value === "string") { if (typeof value === "string") {
return stripWrappingQuotes(value); return stripWrappingQuotes(value);
@ -94,10 +133,11 @@ export function extractSummaryFromBody(body: string, maxLen: number): string {
.replace(/\*(.+?)\*/g, "$1") .replace(/\*(.+?)\*/g, "$1")
.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1") .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
.replace(/`([^`]+)`/g, "$1"); .replace(/`([^`]+)`/g, "$1");
const summaryText = cleanSummaryText(cleanText);
if (cleanText.length > 20) { if (summaryText.length > 20) {
if (cleanText.length <= maxLen) return cleanText; if (summaryText.length <= maxLen) return summaryText;
return `${cleanText.slice(0, maxLen - 3)}...`; return `${summaryText.slice(0, maxLen - 3)}...`;
} }
} }

View File

@ -45,19 +45,24 @@ export function loadCodeThemeCss(themeName: string): string {
} }
export function buildHtmlDocument(meta: HtmlDocumentMeta, css: string, html: string, codeThemeCss?: string): string { export function buildHtmlDocument(meta: HtmlDocumentMeta, css: string, html: string, codeThemeCss?: string): string {
const escapeHtmlAttribute = (value: string) => value
.replace(/&/g, "&amp;")
.replace(/"/g, "&quot;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
const lines = [ const lines = [
"<!doctype html>", "<!doctype html>",
"<html>", "<html>",
"<head>", "<head>",
' <meta charset="utf-8" />', ' <meta charset="utf-8" />',
' <meta name="viewport" content="width=device-width, initial-scale=1" />', ' <meta name="viewport" content="width=device-width, initial-scale=1" />',
` <title>${meta.title}</title>`, ` <title>${escapeHtmlAttribute(meta.title)}</title>`,
]; ];
if (meta.author) { if (meta.author) {
lines.push(` <meta name="author" content="${meta.author}" />`); lines.push(` <meta name="author" content="${escapeHtmlAttribute(meta.author)}" />`);
} }
if (meta.description) { if (meta.description) {
lines.push(` <meta name="description" content="${meta.description}" />`); lines.push(` <meta name="description" content="${escapeHtmlAttribute(meta.description)}" />`);
} }
lines.push(` <style>${css}</style>`); lines.push(` <style>${css}</style>`);
if (codeThemeCss) { if (codeThemeCss) {