| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
| |
| function escapeHtmlText(s) { |
| return String(s) |
| .replace(/&/g, '&') |
| .replace(/</g, '<') |
| .replace(/>/g, '>') |
| .replace(/"/g, '"'); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| 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; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| 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}>`); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| 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[^>]*>[^<]*<\/title>/i, `<title data-i18n>${escapeHtmlText(dt)}</title>`); |
|
|
| 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 textBlock = |
| `<div class="nav-landing-card-text">` + |
| `<span class="nav-landing-card-title" data-i18n>${escapeHtmlText(navMeta.title)}</span>` + |
| `<span class="nav-landing-card-subtitle" data-i18n>${escapeHtmlText(navMeta.subtitle)}</span>` + |
| `</div>`; |
| const shot = |
| navKey === 'causalFlow' |
| ? null |
| : `<div class="nav-landing-card-shot" aria-hidden="true"></div>`; |
| const badge = |
| navKey === 'causalFlow' |
| ? `<span class="nav-landing-card-badge" title="Go to demo on RedNote: xhslink.com" data-i18n="text,title">500K+ plays on RedNote</span>` |
| : ''; |
|
|
| if (navKey === 'causalFlow') { |
| const re = new RegExp( |
| `(<div\\b[^>]*\\bdata-nav-page=["']?causalFlow["']?[^>]*>)([\\s\\S]*?)(<\\/div>)`, |
| 'i' |
| ); |
| const m = html.match(re); |
| if (!m) { |
| throw new Error('injectPageMeta: missing <div data-nav-page="causalFlow"> in home HTML'); |
| } |
| const href = escapeHtmlText(navMeta.href || 'causal_flow.html'); |
| const slideLink = (slide, content) => |
| `<a class="nav-landing-card-link" data-demo-slide="${slide}" href="${href}" target="_blank" rel="noopener">${content}</a>`; |
| const carouselShot = |
| `<div class="nav-landing-card-shot nav-landing-card-shot--carousel" aria-hidden="true">` + |
| `<div class="nav-landing-card-carousel-viewport">` + |
| `<div class="nav-landing-card-slide" data-slide="flow">${slideLink('flow', '<video muted loop playsinline preload="metadata"></video>')}</div>` + |
| `<div class="nav-landing-card-slide" data-slide="spiral">${slideLink('spiral', '<video muted loop playsinline preload="none"></video>')}</div>` + |
| `<div class="nav-landing-card-slide" data-slide="cot">${slideLink('cot', '<video muted loop playsinline preload="none"></video>')}</div>` + |
| `</div>` + |
| `<button type="button" class="nav-landing-card-carousel-arrow nav-landing-card-carousel-arrow--prev" aria-label="Previous preview">‹</button>` + |
| `<button type="button" class="nav-landing-card-carousel-arrow nav-landing-card-carousel-arrow--next" aria-label="Next preview">›</button>` + |
| `<div class="nav-landing-card-carousel-dots">` + |
| `<button type="button" class="nav-landing-card-carousel-dot is-active" aria-label="Preview 1"></button>` + |
| `<button type="button" class="nav-landing-card-carousel-dot" aria-label="Preview 2"></button>` + |
| `<button type="button" class="nav-landing-card-carousel-dot" aria-label="Preview 3"></button>` + |
| `</div></div>`; |
| const inner = |
| badge + |
| `<a class="nav-landing-card-link" data-demo-slide="flow" href="${href}" target="_blank" rel="noopener" title="${escapeHtmlText(navTitle)}">` + |
| textBlock + |
| `</a>` + |
| carouselShot; |
| html = html.replace(re, `${m[1]}${inner}${m[3]}`); |
| continue; |
| } |
|
|
| const re = new RegExp( |
| `(<a\\b[^>]*\\bdata-nav-page=["']?${navKey}["']?[^>]*>)([\\s\\S]*?)(<\\/a>)`, |
| 'i' |
| ); |
| const m = html.match(re); |
| if (!m) { |
| throw new Error(`injectPageMeta: missing <a data-nav-page="${navKey}"> 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 inner = badge + textBlock + shot; |
| html = html.replace(re, `${openTag}${inner}${m[3]}`); |
| } |
| } |
|
|
| return html; |
| } |
|
|
| module.exports = { injectPageMeta, escapeHtmlText, documentTitleEn }; |
|
|