thibaud frere
update
c1d1666
raw
history blame
6.78 kB
---
import * as ArticleMod from '../content/article.mdx';
import Hero from '../components/Hero.astro';
import Footer from '../components/Footer.astro';
import ThemeToggle from '../components/ThemeToggle.astro';
import Seo from '../components/Seo.astro';
import TableOfContent from '../components/TableOfContent.astro';
// Default OG image served from public/
const ogDefaultUrl = '/thumb.jpg';
import 'katex/dist/katex.min.css';
import '../styles/global.css';
const articleFM = (ArticleMod as any).frontmatter ?? {};
const Article = (ArticleMod as any).default;
const docTitle = articleFM?.title ?? 'Untitled article';
// Allow explicit line breaks in the title via "\n" or YAML newlines
const docTitleHtml = (articleFM?.title ?? 'Untitled article')
.replace(/\\n/g, '<br/>')
.replace(/\n/g, '<br/>');
const subtitle = articleFM?.subtitle ?? '';
const description = articleFM?.description ?? '';
const authors = articleFM?.authors ?? [];
const published = articleFM?.published ?? undefined;
const tags = articleFM?.tags ?? [];
// Prefer ogImage from frontmatter if provided
const fmOg = articleFM?.ogImage as string | undefined;
const imageAbs: string = fmOg && fmOg.startsWith('http')
? fmOg
: (Astro.site ? new URL((fmOg ?? ogDefaultUrl), Astro.site).toString() : (fmOg ?? ogDefaultUrl));
// ---- Build citation text & BibTeX from frontmatter ----
const rawTitle = articleFM?.title ?? 'Untitled article';
const titleFlat = String(rawTitle)
.replace(/\\n/g, ' ')
.replace(/\n/g, ' ')
.replace(/\s+/g, ' ')
.trim();
const extractYear = (val: string | undefined): number | undefined => {
if (!val) return undefined;
const d = new Date(val);
if (!Number.isNaN(d.getTime())) return d.getFullYear();
const m = String(val).match(/(19|20)\d{2}/);
return m ? Number(m[0]) : undefined;
};
const year = extractYear(published);
const citationAuthorsText = authors.join(', ');
const citationText = `${citationAuthorsText}${year ? ` (${year})` : ''}. "${titleFlat}".`;
const authorsBib = authors.join(' and ');
const keyAuthor = (authors[0] || 'article').split(/\s+/).slice(-1)[0].toLowerCase();
const keyTitle = titleFlat.toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_|_$/g, '').slice(0, 24);
const bibKey = `${keyAuthor}${year ?? ''}_${keyTitle}`;
const bibtex = `@misc{${bibKey},\n title={${titleFlat}},\n author={${authorsBib}},\n ${year ? `year={${year}}` : ''}\n}`;
// Determine if TOC auto-collapse is enabled via (priority):
// 1) article frontmatter: tableOfContentAutoCollapse (fallback: tocAutoCollapse)
// 2) public env flag: PUBLIC_TOC_AUTO_COLLAPSE
// Falls back to 'false' when not defined (string or boolean)
const __env = (import.meta as any)?.env || {};
const envCollapse = (
String((__env.PUBLIC_TABLE_OF_CONTENT_AUTO_COLLAPSE ?? __env.PUBLIC_TOC_AUTO_COLLAPSE ?? 'false')).toLowerCase() === 'true'
|| (__env.PUBLIC_TABLE_OF_CONTENT_AUTO_COLLAPSE === true)
|| (__env.PUBLIC_TOC_AUTO_COLLAPSE === true)
);
const tableOfContentAutoCollapse = Boolean(
(articleFM as any)?.tableOfContentAutoCollapse ?? (articleFM as any)?.tocAutoCollapse ?? envCollapse
);
---
<html lang="en" data-theme="light" data-toc-auto-collapse={tableOfContentAutoCollapse ? '1' : '0'}>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Seo title={docTitle} description={description} authors={authors} published={published} tags={tags} image={imageAbs} />
<script is:inline>
(() => {
try {
const saved = localStorage.getItem('theme');
const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
const theme = saved || (prefersDark ? 'dark' : 'light');
document.documentElement.setAttribute('data-theme', theme);
} catch {}
})();
</script>
<script is:inline src="/scripts/color-palettes.js"></script>
<!-- TO MANAGE PROPERLY -->
<script src="https://cdn.plot.ly/plotly-3.0.0.min.js" charset="utf-8"></script>
<script src="https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/medium-zoom@1.1.0/dist/medium-zoom.min.js"></script>
</head>
<body>
<ThemeToggle />
<Hero title={docTitleHtml} titleRaw={docTitle} description={subtitle} authors={articleFM?.authors} affiliation={articleFM?.affiliation} published={articleFM?.published} />
<section class="content-grid">
<TableOfContent tableOfContentAutoCollapse={tableOfContentAutoCollapse} />
<main>
<Article />
<style is:inline>
/* Inline tweak for details blocks used in MDX */
details { background: var(--code-bg) !important; border: 1px solid var(--border-color) !important; border-radius: 6px; margin: 1em 0; padding: .5em .75em; }
</style>
</main>
</section>
<Footer citationText={citationText} bibtex={bibtex} />
<script>
// Open external links in a new tab; keep internal anchors in-page
const setExternalTargets = () => {
const isExternal = (href) => {
try { const u = new URL(href, location.href); return u.origin !== location.origin; } catch { return false; }
};
document.querySelectorAll('a[href]').forEach(a => {
const href = a.getAttribute('href');
if (!href) return;
if (isExternal(href)) {
a.setAttribute('target', '_blank');
a.setAttribute('rel', 'noopener noreferrer');
} else {
a.removeAttribute('target');
}
});
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', setExternalTargets, { once: true });
} else { setExternalTargets(); }
</script>
<script>
// Delegate copy clicks for code blocks injected by rehypeCodeCopyAndLabel
document.addEventListener('click', async (e) => {
const target = e.target instanceof Element ? e.target : null;
const btn = target ? target.closest('.code-copy') : null;
if (!btn) return;
const card = btn.closest('.code-card');
const pre = card && card.querySelector('pre');
if (!pre) return;
const text = pre.textContent || '';
try {
await navigator.clipboard.writeText(text.trim());
const old = btn.innerHTML;
btn.innerHTML = '<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M9 16.2l-3.5-3.5-1.4 1.4L9 19 20.3 7.7l-1.4-1.4z"/></svg>';
setTimeout(() => (btn.innerHTML = old), 1200);
} catch {
btn.textContent = 'Error';
setTimeout(() => (btn.textContent = 'Copy'), 1200);
}
});
</script>
</body>
</html>