| --- |
| 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'; |
| |
| 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'; |
| |
| 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 ?? []; |
| |
| 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)); |
|
|
| |
| 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}`; |
| |
| |
| |
| |
| 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> |
| |
| |
| <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> |
| |
| 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> |
| |
| 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> |
| |
| 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> |
|
|
|
|
|
|