/**
* 构建时根据 content/page-meta.json 向 HTML 写入英文标题、副标题、浏览器标题与导航锚文本,
* 便于爬虫与不执行 JS 的环境读取。页内可见文案带 data-i18n,运行时由 initI18n 按英文 key 翻译;
* 浏览器标题在构建时由本脚本写入:主标题与副标题与 page-meta 一致;副标题若以 “-” 开头(工具页 tagline)则与标题之间只加一个空格;否则中间加 “ - ”(如首页主副标题)。
* 中文环境由
在 initI18n 翻译整串 key。
*/
/**
* @param {string} s
* @returns {string}
*/
function escapeHtmlText(s) {
return String(s)
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"');
}
/**
* 浏览器标题与链接 title:title 与 subtitle 拼接(仅 trim 首尾空白)。
* 副标题已以 “-” 开头时不额外插入横线,避免 “Title - - tagline”;否则插入 “ - ”。
*
* @param {{ title: string, subtitle: string }} meta
*/
function documentTitleEn(meta) {
const title = String(meta.title ?? '').trim();
const subtitle = String(meta.subtitle ?? '').trim();
if (!title) return subtitle;
if (!subtitle) return title;
const joiner = subtitle.startsWith('-') ? ' ' : ' - ';
return title + joiner + subtitle;
}
/**
* 将带 `data-page-*` 的成对标签内容替换为纯文本(英文,已转义)。
* @param {string} html
* @param {string} attrToken 如 data-page-title
* @param {string} text
*/
function injectDataPageBlock(html, attrToken, text) {
const esc = escapeHtmlText(text);
const re = new RegExp(
`<([a-z][a-z0-9]*)([^>]*\\b${attrToken}\\b[^>]*)>([\\s\\S]*?)<\\/\\1>`,
'gi'
);
return html.replace(re, (_m, tag, attrs) => `<${tag}${attrs}>${esc}${tag}>`);
}
/**
* @param {string} html
* @param {string} pageKey
* @param {{ pages: Record, navPageKeys: string[] }} doc
* @returns {string}
*/
function injectPageMeta(html, pageKey, doc) {
const meta = doc.pages[pageKey];
if (!meta) {
throw new Error(`injectPageMeta: unknown pageKey "${pageKey}"`);
}
const dt = documentTitleEn(meta);
html = html.replace(/]*>[^<]*<\/title>/i, `${escapeHtmlText(dt)}`);
html = injectDataPageBlock(html, 'data-page-title', meta.title);
html = injectDataPageBlock(html, 'data-page-subtitle', meta.subtitle);
const heartlineElRe = /<([a-z][a-z0-9]*)([^>]*\bdata-page-heartline\b[^>]*)>([\s\S]*?)<\/\1>/gi;
if (meta.heartline) {
html = html.replace(heartlineElRe, (_m, tag, attrs) => `<${tag}${attrs}>${escapeHtmlText(meta.heartline)}${tag}>`);
} else {
html = html.replace(heartlineElRe, '');
}
const formulaElRe = /<([a-z][a-z0-9]*)([^>]*\bdata-page-formula\b[^>]*)>([\s\S]*?)<\/\1>/gi;
if (meta.formula) {
html = html.replace(formulaElRe, (_m, tag, attrs) => `<${tag}${attrs}>${escapeHtmlText(meta.formula)}${tag}>`);
} else {
html = html.replace(formulaElRe, '');
}
if (pageKey === 'home' && Array.isArray(doc.navPageKeys)) {
for (const navKey of doc.navPageKeys) {
const navMeta = doc.pages[navKey];
if (!navMeta) {
throw new Error(`injectPageMeta: navPageKeys references missing page "${navKey}"`);
}
const navTitle = documentTitleEn(navMeta);
const re = new RegExp(
`(]*\\bdata-nav-page=["']?${navKey}["']?[^>]*>)([\\s\\S]*?)(<\\/a>)`,
'i'
);
const m = html.match(re);
if (!m) {
throw new Error(`injectPageMeta: missing in home HTML`);
}
let openTag = m[1];
if (navMeta.href) {
if (/\bhref\s*=/.test(openTag)) {
openTag = openTag.replace(
/\bhref\s*=\s*("[^"]*"|'[^']*')/i,
`href="${escapeHtmlText(navMeta.href)}"`
);
} else {
openTag = openTag.replace(/>$/, ` href="${escapeHtmlText(navMeta.href)}">`);
}
}
if (/\btitle\s*=/.test(openTag)) {
openTag = openTag.replace(/\btitle\s*=\s*("[^"]*"|'[^']*')/i, `title="${escapeHtmlText(navTitle)}"`);
} else {
openTag = openTag.replace(/>$/, ` title="${escapeHtmlText(navTitle)}">`);
}
const shot =
navKey === 'genAttribute'
? ``
: ``;
const inner =
`` +
`${escapeHtmlText(navMeta.title)}` +
`${escapeHtmlText(navMeta.subtitle)}` +
`
` +
shot;
html = html.replace(re, `${openTag}${inner}${m[3]}`);
}
}
return html;
}
module.exports = { injectPageMeta, escapeHtmlText, documentTitleEn };