mirror of
https://github.com/dergigi/boris.git
synced 2025-12-17 06:34:24 +01:00
- Change OG HTML redirect to use ?_spa=1 query param instead of redirecting to / - Simplify vercel.json rewrites: serve SPA when _spa=1, otherwise serve OG HTML - Remove brittle user-agent detection patterns - Add cleanup effect to strip _spa param from URL after SPA loads - Fixes refresh redirect regression while maintaining OG preview support
81 lines
3.3 KiB
TypeScript
81 lines
3.3 KiB
TypeScript
import type { ArticleMetadata } from './ogStore.js'
|
||
|
||
export function escapeHtml(text: string): string {
|
||
return text
|
||
.replace(/&/g, '&')
|
||
.replace(/</g, '<')
|
||
.replace(/>/g, '>')
|
||
.replace(/"/g, '"')
|
||
.replace(/'/g, ''')
|
||
}
|
||
|
||
export function generateHtml(naddr: string, meta: ArticleMetadata | null): string {
|
||
const baseUrl = 'https://read.withboris.com'
|
||
const articleUrl = `${baseUrl}/a/${naddr}`
|
||
|
||
const title = meta?.title || 'Boris – Read, Highlight, Explore'
|
||
const description = meta?.summary || 'Your reading list for the Nostr world. A minimal nostr client for bookmark management with highlights.'
|
||
const image = meta?.image?.startsWith('http') ? meta.image : `${baseUrl}${meta?.image || '/boris-social-1200.png'}`
|
||
const author = meta?.author || 'Boris'
|
||
const imageAlt = meta?.imageAlt || title
|
||
|
||
// Generate article:tag meta tags
|
||
const articleTags = meta?.tags && meta.tags.length > 0
|
||
? meta.tags.map((tag) => ` <meta property="article:tag" content="${escapeHtml(tag)}" />`).join('\n')
|
||
: ''
|
||
|
||
return `<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
||
<meta name="theme-color" content="#0f172a" />
|
||
<link rel="manifest" href="/manifest.webmanifest" />
|
||
<title>${escapeHtml(title)}</title>
|
||
<meta name="description" content="${escapeHtml(description)}" />
|
||
<link rel="canonical" href="${articleUrl}" />
|
||
|
||
<!-- Open Graph / Social Media -->
|
||
<meta property="og:type" content="article" />
|
||
<meta property="og:url" content="${articleUrl}" />
|
||
<meta property="og:title" content="${escapeHtml(title)}" />
|
||
<meta property="og:description" content="${escapeHtml(description)}" />
|
||
<meta property="og:image" content="${escapeHtml(image)}" />
|
||
<meta property="og:image:alt" content="${escapeHtml(imageAlt)}" />
|
||
<meta property="og:site_name" content="Boris" />
|
||
${meta?.published ? ` <meta property="article:published_time" content="${new Date(meta.published * 1000).toISOString()}" />` : ''}
|
||
<meta property="article:author" content="${escapeHtml(author)}" />
|
||
${articleTags}
|
||
|
||
<!-- Twitter Card -->
|
||
<meta name="twitter:card" content="summary_large_image" />
|
||
<meta name="twitter:url" content="${articleUrl}" />
|
||
<meta name="twitter:title" content="${escapeHtml(title)}" />
|
||
<meta name="twitter:description" content="${escapeHtml(description)}" />
|
||
<meta name="twitter:image" content="${escapeHtml(image)}" />
|
||
</head>
|
||
<body>
|
||
<noscript>
|
||
<p>Redirecting to <a href="/a/${naddr}">Boris</a>...</p>
|
||
</noscript>
|
||
<script>
|
||
(function(){
|
||
try {
|
||
var p = '/a/${naddr}';
|
||
if (window.location.pathname !== p) {
|
||
history.replaceState(null, '', p);
|
||
}
|
||
var sep = window.location.search ? '&' : '?';
|
||
window.location.replace(p + sep + '_spa=1');
|
||
} catch (e) {}
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>`
|
||
}
|
||
|