| import { VARIANT_META, type VariantMeta } from './variant-meta'; |
|
|
| |
| |
| |
| |
| |
| export const WEB_DASHBOARD_VARIANTS = ['tech', 'finance', 'commodity', 'happy', 'energy'] as const; |
|
|
| export function variantDashboardFileName(variant: string): string { |
| return `dashboard-${variant}.html`; |
| } |
|
|
| |
| |
| |
| function escHtml(s: string): string { |
| return s |
| .replace(/&/g, '&') |
| .replace(/</g, '<') |
| .replace(/>/g, '>') |
| .replace(/"/g, '"') |
| .replace(/'/g, '''); |
| } |
|
|
| interface CountBounds { |
| min: number; |
| max: number; |
| } |
|
|
| |
| |
| |
| |
| |
| function replaceCounted( |
| html: string, |
| pattern: RegExp, |
| replacer: (...groups: string[]) => string, |
| bounds: CountBounds, |
| label: string, |
| ): string { |
| let count = 0; |
| const result = html.replace(pattern, (...args) => { |
| count += 1; |
| |
| const groups = args.slice(0, -2) as string[]; |
| return replacer(...groups); |
| }); |
| if (count < bounds.min || count > bounds.max) { |
| throw new Error( |
| `[variant-dashboard-html] anchor "${label}" matched ${count} time(s), expected ${bounds.min}..${bounds.max} — dist/dashboard.html markup drifted; update src/config/variant-dashboard-html.ts`, |
| ); |
| } |
| return result; |
| } |
|
|
| const ONE: CountBounds = { min: 1, max: 1 }; |
| const TWO: CountBounds = { min: 2, max: 2 }; |
|
|
| |
| |
| |
| |
| |
| |
| |
| export function renderVariantDashboardHtml(fullDashboardHtml: string, variant: string): string { |
| const meta: VariantMeta | undefined = VARIANT_META[variant]; |
| if (!meta || variant === 'full') { |
| throw new Error(`[variant-dashboard-html] unknown web dashboard variant "${variant}"`); |
| } |
| const origin = new URL(meta.url).origin; |
| const ogImage = `${origin}/favico/${variant}/og-image.png`; |
|
|
| let html = fullDashboardHtml; |
|
|
| |
| html = replaceCounted(html, /(<title>)[^<]*(<\/title>)/g, (_m, a, b) => `${a}${escHtml(meta.title)}${b}`, ONE, '<title>'); |
| html = replaceCounted(html, /(<meta name="title" content=")[^"]*(" \/>)/g, (_m, a, b) => `${a}${escHtml(meta.title)}${b}`, ONE, 'meta title'); |
| html = replaceCounted(html, /(<meta property="og:title" content=")[^"]*(" \/>)/g, (_m, a, b) => `${a}${escHtml(meta.title)}${b}`, ONE, 'og:title'); |
| html = replaceCounted(html, /(<meta name="twitter:title" content=")[^"]*(" \/>)/g, (_m, a, b) => `${a}${escHtml(meta.title)}${b}`, ONE, 'twitter:title'); |
|
|
| |
| html = replaceCounted(html, /(<meta name="description" content=")[^"]*(" \/>)/g, (_m, a, b) => `${a}${escHtml(meta.description)}${b}`, ONE, 'meta description'); |
| html = replaceCounted(html, /(<meta property="og:description" content=")[^"]*(" \/>)/g, (_m, a, b) => `${a}${escHtml(meta.description)}${b}`, ONE, 'og:description'); |
| html = replaceCounted(html, /(<meta name="twitter:description" content=")[^"]*(" \/>)/g, (_m, a, b) => `${a}${escHtml(meta.description)}${b}`, ONE, 'twitter:description'); |
| html = replaceCounted(html, /(<meta name="keywords" content=")[^"]*(" \/>)/g, (_m, a, b) => `${a}${escHtml(meta.keywords)}${b}`, ONE, 'meta keywords'); |
| html = replaceCounted(html, /(<meta name="subject" content=")[^"]*(" \/>)/g, (_m, a, b) => `${a}${escHtml(meta.subject)}${b}`, ONE, 'meta subject'); |
| html = replaceCounted(html, /(<meta name="classification" content=")[^"]*(" \/>)/g, (_m, a, b) => `${a}${escHtml(meta.classification)}${b}`, ONE, 'meta classification'); |
|
|
| |
| html = replaceCounted( |
| html, |
| /(<meta (?:property="og:site_name"|name="application-name") content=")[^"]*(" \/>)/g, |
| (_m, a, b) => `${a}${escHtml(meta.siteName)}${b}`, |
| TWO, |
| 'site name metas', |
| ); |
|
|
| |
| |
| html = replaceCounted(html, /(<link rel="canonical" href=")[^"]*(" \/>)/g, (_m, a, b) => `${a}${escHtml(meta.url)}${b}`, ONE, 'canonical'); |
| html = replaceCounted(html, /(<meta property="og:url" content=")[^"]*(" \/>)/g, (_m, a, b) => `${a}${escHtml(meta.url)}${b}`, ONE, 'og:url'); |
| html = replaceCounted(html, /(<meta name="twitter:url" content=")[^"]*(" \/>)/g, (_m, a, b) => `${a}${escHtml(meta.url)}${b}`, ONE, 'twitter:url'); |
|
|
| |
| |
| |
| html = replaceCounted( |
| html, |
| /(<link rel="alternate" hreflang="[^"]+" href=")https:\/\/www\.worldmonitor\.app\/dashboard((?:\?[^"]*)?" \/>)/g, |
| (_m, a, b) => `${a}${escHtml(meta.url)}${b}`, |
| TWO, |
| 'hreflang alternates', |
| ); |
|
|
| |
| |
| html = replaceCounted( |
| html, |
| /(<meta (?:property="og:image"|name="twitter:image") content=")[^"]*(" \/>)/g, |
| (_m, a, b) => `${a}${escHtml(ogImage)}${b}`, |
| TWO, |
| 'og/twitter image', |
| ); |
|
|
| |
| html = replaceCounted( |
| html, |
| /("@type": "WebApplication",\s*"name": )"[^"]*"/g, |
| (_m, a) => `${a}${JSON.stringify(meta.siteName)}`, |
| ONE, |
| 'WebApplication name', |
| ); |
| html = replaceCounted( |
| html, |
| /("@type": "WebApplication",[\s\S]{0,600}?"url": )"[^"]*"/g, |
| (_m, a) => `${a}${JSON.stringify(meta.url)}`, |
| ONE, |
| 'WebApplication url', |
| ); |
| html = replaceCounted(html, /("screenshot": )"[^"]*"/g, (_m, a) => `${a}${JSON.stringify(ogImage)}`, ONE, 'WebApplication screenshot'); |
| html = replaceCounted( |
| html, |
| /("featureList": )\[[\s\S]*?\]/g, |
| (_m, a) => `${a}${JSON.stringify(meta.features, null, 8).replace(/\n/g, '\n ')}`, |
| ONE, |
| 'WebApplication featureList', |
| ); |
|
|
| |
| html = replaceCounted(html, /(<h1 class="app-heading">)[^<]*(<\/h1>)/g, (_m, a, b) => `${a}${escHtml(meta.title)}${b}`, ONE, 'app-heading h1'); |
|
|
| return html; |
| } |
|
|