import { useEffect, useMemo, useState, type CSSProperties, type ReactNode } from "react"import ReactMarkdown, { type Components } from "react-markdown"import remarkGfm from "remark-gfm"import { ArrowUpRight, BookOpen, Bot, ChevronLeft, ChevronRight, Check, Copy, FlaskConical, GitBranch, Layers, Moon, PackageCheck, Rocket, Search, ShieldCheck, Star, Sun, Terminal,} from "lucide-react"import { useTheme } from "./theme" const mono = `"JetBrains Mono", "SF Mono", ui-monospace, monospace` const DOCS_SERIF = `"Computer Modern Concrete", "Concrete Roman", Georgia, "Times New Roman", serif` type DocsConfig = { name?: string navigation: { tabs: Array<{ tab: string groups: Array<{ group: string pages: Array<string | { group: string; pages: string[] }> }> }> global?: { anchors?: Array<{ anchor: string href: string }> } } navbar?: { links?: Array<{ label: string href: string }> primary?: { label: string href: string } }} type DocsPage = { path: string title: string description: string icon: ReactNode body: string headings: string[]} type SearchResult = Pick<DocsPage, "path" | "title" | "description" | "icon"> & { section: SectionKey sectionLabel: string} type MintlifyCard = { title: string href: string icon?: string body: string horizontal?: boolean} type SectionKey = "openscience" type Section = { key: SectionKey label: string short: string tagline: string lead: boolean} const SECTIONS: Section[] = [ { key: "openscience", label: "OpenScience", short: "OpenScience", tagline: "Open-source AI workbench", lead: false },] const SECTION_KEYS = SECTIONS.map((section) => section.key) // All MDX content and per-section configs, loaded at build time. Adding a page is// just dropping an .mdx file into the right content/<section>/ folder.const RAW_PAGES = import.meta.glob("./content/**/*.{mdx,md}", { query: "?raw", import: "default", eager: true,}) as Record<string, string> const RAW_CONFIGS = import.meta.glob("./content/**/docs.json", { import: "default", eager: true,}) as Record<string, DocsConfig> const ICONS: Record<string, ReactNode> = { index: <GitBranch size={17} strokeWidth={1.8} />, quickstart: <Rocket size={17} strokeWidth={1.8} />, workspace: <Layers size={17} strokeWidth={1.8} />, agents: <Bot size={17} strokeWidth={1.8} />, models: <PackageCheck size={17} strokeWidth={1.8} />, skills: <BookOpen size={17} strokeWidth={1.8} />, sessions: <Terminal size={17} strokeWidth={1.8} />, atlas: <GitBranch size={17} strokeWidth={1.8} />, commands: <Terminal size={17} strokeWidth={1.8} />, security: <ShieldCheck size={17} strokeWidth={1.8} />,} const SECTION_FALLBACK_ICON: Record<SectionKey, ReactNode> = { openscience: <FlaskConical size={17} strokeWidth={1.8} />,} const FRONTMATTER_RE = /^---\n([\s\S]*?)\n---\n?/ function parseFrontmatter(source: string): { title: string; description: string; body: string } { const match = source.match(FRONTMATTER_RE) if (!match) return { title: "Untitled", description: "", body: source } const frontmatter = match[1] const body = source.slice(match[0].length) const field = (name: string) => { const line = frontmatter.split("\n").find((entry) => entry.trim().startsWith(`${name}:`)) return line ? line .split(":") .slice(1) .join(":") .trim() .replace(/^["']|["']$/g, "") : "" } return { title: field("title") || "Untitled", description: field("description"), body, }} function extractHeadings(markdown: string): string[] { return markdown .split("\n") .filter((line) => line.startsWith("## ")) .map((line) => line.replace(/^##\s+/, "").trim()) .slice(0, 10)} function flattenPages(items: Array<string | { group: string; pages: string[] }>): string[] { return items.flatMap((item) => (typeof item === "string" ? [item] : item.pages))} function iconForPath(path: string, section: SectionKey): ReactNode { const leaf = path.split("/").pop() ?? path return ICONS[path] ?? ICONS[leaf] ?? SECTION_FALLBACK_ICON[section]} function buildSectionPages(section: SectionKey): Record<string, DocsPage> { const prefix = `./content/${section}/` const pages: Record<string, DocsPage> = {} for (const [key, source] of Object.entries(RAW_PAGES)) { if (!key.startsWith(prefix)) continue const path = key.slice(prefix.length).replace(/\.(mdx|md)$/, "") const parsed = parseFrontmatter(source) pages[path] = { path, title: parsed.title, description: parsed.description, icon: iconForPath(path, section), body: parsed.body, headings: extractHeadings(parsed.body), } } return pages} const SECTION_DOC_PAGES: Record<SectionKey, Record<string, DocsPage>> = { openscience: buildSectionPages("openscience"),} const SECTION_CONFIGS: Record<SectionKey, DocsConfig> = { openscience: RAW_CONFIGS["./content/openscience/docs.json"],} function pageExists(section: SectionKey, path: string): boolean { return Boolean(SECTION_DOC_PAGES[section]?.[path])} // Retired first URL segments that redirect into the openscience scheme.const SECTION_ALIASES: Record<string, SectionKey> = { "agent-cli": "openscience",} // Old CLI page names that moved during the OpenScience rebuild.const PAGE_ALIASES: Record<string, string> = { "first-session": "sessions", "sub-agents": "agents", "web-ui": "workspace", credentials: "atlas",} // Redirects from the oldest single-segment URLs to the #/<section>/<page> scheme.const LEGACY_REDIRECTS: Record<string, { section: SectionKey; path: string }> = { "cli:index": { section: "openscience", path: "index" }, "cli:installation": { section: "openscience", path: "quickstart" }, "cli:quickstart": { section: "openscience", path: "quickstart" }, "cli:first-session": { section: "openscience", path: "sessions" }, "cli:sessions": { section: "openscience", path: "sessions" }, "cli:models": { section: "openscience", path: "models" }, "cli:codex": { section: "openscience", path: "models" }, "cli:sub-agents": { section: "openscience", path: "agents" }, "cli:skills": { section: "openscience", path: "skills" }, "cli:cli-runtime": { section: "openscience", path: "commands" }, "cli:connect": { section: "openscience", path: "atlas" }, "cli:credentials": { section: "openscience", path: "atlas" }, "cli:security": { section: "openscience", path: "security" }, "cli:feature-map": { section: "openscience", path: "commands" }, "cli:commands": { section: "openscience", path: "commands" }, "cli:web-ui": { section: "openscience", path: "workspace" }, "cli:server-mode": { section: "openscience", path: "workspace" },} type Route = { section: SectionKey; path: string } function defaultRoute(): Route { return { section: "openscience", path: "index" }} function routeFromHash(): Route { if (typeof window === "undefined") return defaultRoute() const raw = decodeURIComponent(window.location.hash.replace(/^#\/?/, "")).replace(/\/$/, "") if (!raw) return defaultRoute() const segments = raw.split("/") const maybeSection = segments[0] as SectionKey if (SECTION_KEYS.includes(maybeSection)) { const path = segments.slice(1).join("/") || "index" if (pageExists(maybeSection, path)) return { section: maybeSection, path } return { section: maybeSection, path: "index" } } // Retired section names redirect into the openscience scheme. const aliasSection = SECTION_ALIASES[segments[0]] if (aliasSection) { const rawPath = segments.slice(1).join("/") || "index" const path = PAGE_ALIASES[rawPath] ?? rawPath if (pageExists(aliasSection, path)) return { section: aliasSection, path } return { section: aliasSection, path: "index" } } // Legacy single-segment URL from the old CLI docs. const redirect = LEGACY_REDIRECTS[`cli:${raw}`] if (redirect && pageExists(redirect.section, redirect.path)) return redirect return defaultRoute()} function pageHref(section: SectionKey, path: string): string { return `#/${section}/${path}`} // Module-level pointers updated on each render so the markdown renderer (which// can't take props through react-markdown) can resolve links and card icons.let CURRENT_SECTION: SectionKey = "openscience" function resolveHref(href: string | undefined): string | undefined { if (!href) return href if (href.startsWith("http") || href.startsWith("#") || href.startsWith("mailto:")) return href if (href.startsWith("/")) { const clean = href.slice(1).replace(/\/$/, "") if (!clean) return pageHref(CURRENT_SECTION, "index") const segments = clean.split("/") const maybeSection = segments[0] as SectionKey if (SECTION_KEYS.includes(maybeSection)) { const path = segments.slice(1).join("/") || "index" if (pageExists(maybeSection, path)) return pageHref(maybeSection, path) } if (pageExists(CURRENT_SECTION, clean)) return pageHref(CURRENT_SECTION, clean) } return href} function sectionForHref(href: string): SectionKey { const clean = href.replace(/^#\/?/, "").replace(/\/$/, "") const segments = clean.split("/") const maybeSection = segments[0] as SectionKey return SECTION_KEYS.includes(maybeSection) ? maybeSection : CURRENT_SECTION} function parseMdxAttrs(attrs: string): Record<string, string | boolean> { const parsed: Record<string, string | boolean> = {} for (const match of attrs.matchAll(/([\w-]+)(?:=(?:"([^"]*)"|'([^']*)'|\{([^}]*)\}))?/g)) { const key = match[1] if (!key) continue parsed[key] = match[2] ?? match[3] ?? match[4] ?? true } return parsed} function dedent(source: string): string { const lines = source.replace(/\t/g, " ").split("\n") let min = Infinity for (const line of lines) { if (line.trim() === "") continue const leading = line.match(/^( *)/) if (leading) min = Math.min(min, leading[1].length) } if (!Number.isFinite(min) || min === 0) return source return lines.map((line) => (line.length >= min ? line.slice(min) : line)).join("\n")} function parseCards(source: string): MintlifyCard[] { return Array.from(source.matchAll(/<Card\b([^>]*)>\s*([\s\S]*?)\s*<\/Card>/g)).map((match) => { const attrs = parseMdxAttrs(match[1] ?? "") return { title: String(attrs.title ?? "Untitled"), href: String(attrs.href ?? "#"), icon: attrs.icon ? String(attrs.icon) : undefined, horizontal: Boolean(attrs.horizontal), body: dedent(match[2] ?? "").trim(), } })} function iconForCard(card: MintlifyCard): ReactNode { const resolved = resolveHref(card.href) if (resolved && resolved.startsWith("#/")) { const section = sectionForHref(resolved) const path = resolved.replace(/^#\/?/, "").replace(/\/$/, "").split("/").slice(1).join("/") const page = SECTION_DOC_PAGES[section]?.[path] if (page) return page.icon } return <BookOpen size={17} strokeWidth={1.8} />} const OPENSCIENCE_REPO = "synthetic-sciences/openscience" function formatStars(count: number): string { return count >= 1000 ? `${(count / 1000).toFixed(1).replace(/\.0$/, "")}k` : String(count)} function GitHubStars() { const [stars, setStars] = useState<number | null>(null) useEffect(() => { let cancelled = false const cacheKey = `docs-gh-stars:${OPENSCIENCE_REPO}` try { const cached = JSON.parse(window.localStorage.getItem(cacheKey) ?? "null") as { stars: number; at: number } | null if (cached && Date.now() - cached.at < 60 * 60 * 1000) { setStars(cached.stars) return } } catch { /* ignore */ } fetch(`https://api.github.com/repos/${OPENSCIENCE_REPO}`) .then((response) => (response.ok ? response.json() : null)) .then((data: { stargazers_count?: number } | null) => { const count = data?.stargazers_count if (typeof count !== "number" || cancelled) return setStars(count) try { window.localStorage.setItem(cacheKey, JSON.stringify({ stars: count, at: Date.now() })) } catch { /* ignore */ } }) .catch(() => undefined) return () => { cancelled = true } }, []) return ( <div className="docs-ghstars"> <a className="docs-ghstars-primary" href={`https://github.com/${OPENSCIENCE_REPO}`} target="_blank" rel="noreferrer" > <Star size={13} strokeWidth={1.8} /> <span>Star on GitHub</span> {stars !== null ? <em>{formatStars(stars)}</em> : null} </a> <a href={`https://github.com/${OPENSCIENCE_REPO}/blob/main/LICENSE`} target="_blank" rel="noreferrer"> Apache-2.0 </a> <a href="https://www.npmjs.com/package/@synsci/openscience" target="_blank" rel="noreferrer"> npm · @synsci/openscience </a> </div> )} function CopyButton({ text }: { text: string }) { const [copied, setCopied] = useState(false) return ( <button type="button" className="docs-copy" onClick={() => { void navigator.clipboard.writeText(text) setCopied(true) window.setTimeout(() => setCopied(false), 1200) }} aria-label="copy code" title="copy code" > {copied ? <Check size={13} strokeWidth={1.8} /> : <Copy size={13} strokeWidth={1.8} />} <span>{copied ? "copied" : "copy"}</span> </button> )} const markdownComponents: Components = { h2({ children }) { const id = String(children) .toLowerCase() .replace(/[^a-z0-9]+/g, "-") .replace(/^-|-$/g, "") return <h2 id={id}>{children}</h2> }, a({ href, children }) { const external = href?.startsWith("http") const internalDocsHref = href ? resolveHref(href) : undefined return ( <a href={internalDocsHref} target={external ? "_blank" : undefined} rel={external ? "noreferrer" : undefined}> {children} {external ? <ArrowUpRight size={12} strokeWidth={1.8} /> : null} </a> ) }, pre({ children }) { const text = extractCodeText(children) return ( <div className="docs-code-wrap"> <CopyButton text={text} /> <pre>{children}</pre> </div> ) }, code({ className, children }) { const isBlock = className?.startsWith("language-") return <code className={isBlock ? className : "docs-inline-code"}>{children}</code> }, table({ children }) { return ( <div className="docs-table-wrap"> <table>{children}</table> </div> ) }, blockquote({ children }) { return <blockquote className="docs-callout">{children}</blockquote> },} function extractCodeText(node: ReactNode): string { if (typeof node === "string") return node if (Array.isArray(node)) return node.map(extractCodeText).join("") if (node && typeof node === "object" && "props" in node) { const props = (node as { props?: { children?: ReactNode } }).props return extractCodeText(props?.children ?? "") } return ""} function MarkdownChunk({ children }: { children: string }) { if (!children.trim()) return null return ( <ReactMarkdown remarkPlugins={[remarkGfm]} components={markdownComponents}> {children} </ReactMarkdown> )} function MintlifyCardView({ card }: { card: MintlifyCard }) { const external = card.href.startsWith("http") return ( <a className={card.horizontal ? "docs-card docs-card-horizontal" : "docs-card"} href={resolveHref(card.href)} target={external ? "_blank" : undefined} rel={external ? "noreferrer" : undefined} > <span className="docs-card-icon">{iconForCard(card)}</span> <span className="docs-card-copy"> <strong>{card.title}</strong> <small>{card.body}</small> </span> <ArrowUpRight size={14} strokeWidth={1.8} />Lines 1–500 of 1779. Load more
import { useEffect, useMemo, useState, type CSSProperties, type ReactNode } from "react" import ReactMarkdown, { type Components } from "react-markdown" import remarkGfm from "remark-gfm" import { ArrowUpRight, BookOpen, Bot, ChevronLeft, ChevronRight, Check, Copy, FlaskConical, GitBranch, Layers, Moon, PackageCheck, Rocket, Search, ShieldCheck, Star, Sun, Terminal, } from "lucide-react" import { useTheme } from "./theme" const mono = `"JetBrains Mono", "SF Mono", ui-monospace, monospace` const DOCS_SERIF = `"Computer Modern Concrete", "Concrete Roman", Georgia, "Times New Roman", serif` type DocsConfig = { name?: string navigation: { tabs: Array<{ tab: string groups: Array<{ group: string pages: Array<string | { group: string; pages: string[] }> }> }> global?: { anchors?: Array<{ anchor: string href: string }> } } navbar?: { links?: Array<{ label: string href: string }> primary?: { label: string href: string } } } type DocsPage = { path: string title: string description: string icon: ReactNode body: string headings: string[] } type SearchResult = Pick<DocsPage, "path" | "title" | "description" | "icon"> & { section: SectionKey sectionLabel: string } type MintlifyCard = { title: string href: string icon?: string body: string horizontal?: boolean } type SectionKey = "openscience" type Section = { key: SectionKey label: string short: string tagline: string lead: boolean } const SECTIONS: Section[] = [ { key: "openscience", label: "OpenScience", short: "OpenScience", tagline: "Open-source AI workbench", lead: false }, ] const SECTION_KEYS = SECTIONS.map((section) => section.key) // All MDX content and per-section configs, loaded at build time. Adding a page is // just dropping an .mdx file into the right content/<section>/ folder. const RAW_PAGES = import.meta.glob("./content/**/*.{mdx,md}", { query: "?raw", import: "default", eager: true, }) as Record<string, string> const RAW_CONFIGS = import.meta.glob("./content/**/docs.json", { import: "default", eager: true, }) as Record<string, DocsConfig> const ICONS: Record<string, ReactNode> = { index: <GitBranch size={17} strokeWidth={1.8} />, quickstart: <Rocket size={17} strokeWidth={1.8} />, workspace: <Layers size={17} strokeWidth={1.8} />, agents: <Bot size={17} strokeWidth={1.8} />, models: <PackageCheck size={17} strokeWidth={1.8} />, skills: <BookOpen size={17} strokeWidth={1.8} />, sessions: <Terminal size={17} strokeWidth={1.8} />, atlas: <GitBranch size={17} strokeWidth={1.8} />, commands: <Terminal size={17} strokeWidth={1.8} />, security: <ShieldCheck size={17} strokeWidth={1.8} />, } const SECTION_FALLBACK_ICON: Record<SectionKey, ReactNode> = { openscience: <FlaskConical size={17} strokeWidth={1.8} />, } const FRONTMATTER_RE = /^---\n([\s\S]*?)\n---\n?/ function parseFrontmatter(source: string): { title: string; description: string; body: string } { const match = source.match(FRONTMATTER_RE) if (!match) return { title: "Untitled", description: "", body: source } const frontmatter = match[1] const body = source.slice(match[0].length) const field = (name: string) => { const line = frontmatter.split("\n").find((entry) => entry.trim().startsWith(`${name}:`)) return line ? line .split(":") .slice(1) .join(":") .trim() .replace(/^["']|["']$/g, "") : "" } return { title: field("title") || "Untitled", description: field("description"), body, } } function extractHeadings(markdown: string): string[] { return markdown .split("\n") .filter((line) => line.startsWith("## ")) .map((line) => line.replace(/^##\s+/, "").trim()) .slice(0, 10) } function flattenPages(items: Array<string | { group: string; pages: string[] }>): string[] { return items.flatMap((item) => (typeof item === "string" ? [item] : item.pages)) } function iconForPath(path: string, section: SectionKey): ReactNode { const leaf = path.split("/").pop() ?? path return ICONS[path] ?? ICONS[leaf] ?? SECTION_FALLBACK_ICON[section] } function buildSectionPages(section: SectionKey): Record<string, DocsPage> { const prefix = `./content/${section}/` const pages: Record<string, DocsPage> = {} for (const [key, source] of Object.entries(RAW_PAGES)) { if (!key.startsWith(prefix)) continue const path = key.slice(prefix.length).replace(/\.(mdx|md)$/, "") const parsed = parseFrontmatter(source) pages[path] = { path, title: parsed.title, description: parsed.description, icon: iconForPath(path, section), body: parsed.body, headings: extractHeadings(parsed.body), } } return pages } const SECTION_DOC_PAGES: Record<SectionKey, Record<string, DocsPage>> = { openscience: buildSectionPages("openscience"), } const SECTION_CONFIGS: Record<SectionKey, DocsConfig> = { openscience: RAW_CONFIGS["./content/openscience/docs.json"], } function pageExists(section: SectionKey, path: string): boolean { return Boolean(SECTION_DOC_PAGES[section]?.[path]) } // Retired first URL segments that redirect into the openscience scheme. const SECTION_ALIASES: Record<string, SectionKey> = { "agent-cli": "openscience", } // Old CLI page names that moved during the OpenScience rebuild. const PAGE_ALIASES: Record<string, string> = { "first-session": "sessions", "sub-agents": "agents", "web-ui": "workspace", credentials: "atlas", } // Redirects from the oldest single-segment URLs to the #/<section>/<page> scheme. const LEGACY_REDIRECTS: Record<string, { section: SectionKey; path: string }> = { "cli:index": { section: "openscience", path: "index" }, "cli:installation": { section: "openscience", path: "quickstart" }, "cli:quickstart": { section: "openscience", path: "quickstart" }, "cli:first-session": { section: "openscience", path: "sessions" }, "cli:sessions": { section: "openscience", path: "sessions" }, "cli:models": { section: "openscience", path: "models" }, "cli:codex": { section: "openscience", path: "models" }, "cli:sub-agents": { section: "openscience", path: "agents" }, "cli:skills": { section: "openscience", path: "skills" }, "cli:cli-runtime": { section: "openscience", path: "commands" }, "cli:connect": { section: "openscience", path: "atlas" }, "cli:credentials": { section: "openscience", path: "atlas" }, "cli:security": { section: "openscience", path: "security" }, "cli:feature-map": { section: "openscience", path: "commands" }, "cli:commands": { section: "openscience", path: "commands" }, "cli:web-ui": { section: "openscience", path: "workspace" }, "cli:server-mode": { section: "openscience", path: "workspace" }, } type Route = { section: SectionKey; path: string } function defaultRoute(): Route { return { section: "openscience", path: "index" } } function routeFromHash(): Route { if (typeof window === "undefined") return defaultRoute() const raw = decodeURIComponent(window.location.hash.replace(/^#\/?/, "")).replace(/\/$/, "") if (!raw) return defaultRoute() const segments = raw.split("/") const maybeSection = segments[0] as SectionKey if (SECTION_KEYS.includes(maybeSection)) { const path = segments.slice(1).join("/") || "index" if (pageExists(maybeSection, path)) return { section: maybeSection, path } return { section: maybeSection, path: "index" } } // Retired section names redirect into the openscience scheme. const aliasSection = SECTION_ALIASES[segments[0]] if (aliasSection) { const rawPath = segments.slice(1).join("/") || "index" const path = PAGE_ALIASES[rawPath] ?? rawPath if (pageExists(aliasSection, path)) return { section: aliasSection, path } return { section: aliasSection, path: "index" } } // Legacy single-segment URL from the old CLI docs. const redirect = LEGACY_REDIRECTS[`cli:${raw}`] if (redirect && pageExists(redirect.section, redirect.path)) return redirect return defaultRoute() } function pageHref(section: SectionKey, path: string): string { return `#/${section}/${path}` } // Module-level pointers updated on each render so the markdown renderer (which // can't take props through react-markdown) can resolve links and card icons. let CURRENT_SECTION: SectionKey = "openscience" function resolveHref(href: string | undefined): string | undefined { if (!href) return href if (href.startsWith("http") || href.startsWith("#") || href.startsWith("mailto:")) return href if (href.startsWith("/")) { const clean = href.slice(1).replace(/\/$/, "") if (!clean) return pageHref(CURRENT_SECTION, "index") const segments = clean.split("/") const maybeSection = segments[0] as SectionKey if (SECTION_KEYS.includes(maybeSection)) { const path = segments.slice(1).join("/") || "index" if (pageExists(maybeSection, path)) return pageHref(maybeSection, path) } if (pageExists(CURRENT_SECTION, clean)) return pageHref(CURRENT_SECTION, clean) } return href } function sectionForHref(href: string): SectionKey { const clean = href.replace(/^#\/?/, "").replace(/\/$/, "") const segments = clean.split("/") const maybeSection = segments[0] as SectionKey return SECTION_KEYS.includes(maybeSection) ? maybeSection : CURRENT_SECTION } function parseMdxAttrs(attrs: string): Record<string, string | boolean> { const parsed: Record<string, string | boolean> = {} for (const match of attrs.matchAll(/([\w-]+)(?:=(?:"([^"]*)"|'([^']*)'|\{([^}]*)\}))?/g)) { const key = match[1] if (!key) continue parsed[key] = match[2] ?? match[3] ?? match[4] ?? true } return parsed } function dedent(source: string): string { const lines = source.replace(/\t/g, " ").split("\n") let min = Infinity for (const line of lines) { if (line.trim() === "") continue const leading = line.match(/^( *)/) if (leading) min = Math.min(min, leading[1].length) } if (!Number.isFinite(min) || min === 0) return source return lines.map((line) => (line.length >= min ? line.slice(min) : line)).join("\n") } function parseCards(source: string): MintlifyCard[] { return Array.from(source.matchAll(/<Card\b([^>]*)>\s*([\s\S]*?)\s*<\/Card>/g)).map((match) => { const attrs = parseMdxAttrs(match[1] ?? "") return { title: String(attrs.title ?? "Untitled"), href: String(attrs.href ?? "#"), icon: attrs.icon ? String(attrs.icon) : undefined, horizontal: Boolean(attrs.horizontal), body: dedent(match[2] ?? "").trim(), } }) } function iconForCard(card: MintlifyCard): ReactNode { const resolved = resolveHref(card.href) if (resolved && resolved.startsWith("#/")) { const section = sectionForHref(resolved) const path = resolved.replace(/^#\/?/, "").replace(/\/$/, "").split("/").slice(1).join("/") const page = SECTION_DOC_PAGES[section]?.[path] if (page) return page.icon } return <BookOpen size={17} strokeWidth={1.8} /> } const OPENSCIENCE_REPO = "synthetic-sciences/openscience" function formatStars(count: number): string { return count >= 1000 ? `${(count / 1000).toFixed(1).replace(/\.0$/, "")}k` : String(count) } function GitHubStars() { const [stars, setStars] = useState<number | null>(null) useEffect(() => { let cancelled = false const cacheKey = `docs-gh-stars:${OPENSCIENCE_REPO}` try { const cached = JSON.parse(window.localStorage.getItem(cacheKey) ?? "null") as { stars: number; at: number } | null if (cached && Date.now() - cached.at < 60 * 60 * 1000) { setStars(cached.stars) return } } catch { /* ignore */ } fetch(`https://api.github.com/repos/${OPENSCIENCE_REPO}`) .then((response) => (response.ok ? response.json() : null)) .then((data: { stargazers_count?: number } | null) => { const count = data?.stargazers_count if (typeof count !== "number" || cancelled) return setStars(count) try { window.localStorage.setItem(cacheKey, JSON.stringify({ stars: count, at: Date.now() })) } catch { /* ignore */ } }) .catch(() => undefined) return () => { cancelled = true } }, []) return ( <div className="docs-ghstars"> <a className="docs-ghstars-primary" href={`https://github.com/${OPENSCIENCE_REPO}`} target="_blank" rel="noreferrer" > <Star size={13} strokeWidth={1.8} /> <span>Star on GitHub</span> {stars !== null ? <em>{formatStars(stars)}</em> : null} </a> <a href={`https://github.com/${OPENSCIENCE_REPO}/blob/main/LICENSE`} target="_blank" rel="noreferrer"> Apache-2.0 </a> <a href="https://www.npmjs.com/package/@synsci/openscience" target="_blank" rel="noreferrer"> npm · @synsci/openscience </a> </div> ) } function CopyButton({ text }: { text: string }) { const [copied, setCopied] = useState(false) return ( <button type="button" className="docs-copy" onClick={() => { void navigator.clipboard.writeText(text) setCopied(true) window.setTimeout(() => setCopied(false), 1200) }} aria-label="copy code" title="copy code" > {copied ? <Check size={13} strokeWidth={1.8} /> : <Copy size={13} strokeWidth={1.8} />} <span>{copied ? "copied" : "copy"}</span> </button> ) } const markdownComponents: Components = { h2({ children }) { const id = String(children) .toLowerCase() .replace(/[^a-z0-9]+/g, "-") .replace(/^-|-$/g, "") return <h2 id={id}>{children}</h2> }, a({ href, children }) { const external = href?.startsWith("http") const internalDocsHref = href ? resolveHref(href) : undefined return ( <a href={internalDocsHref} target={external ? "_blank" : undefined} rel={external ? "noreferrer" : undefined}> {children} {external ? <ArrowUpRight size={12} strokeWidth={1.8} /> : null} </a> ) }, pre({ children }) { const text = extractCodeText(children) return ( <div className="docs-code-wrap"> <CopyButton text={text} /> <pre>{children}</pre> </div> ) }, code({ className, children }) { const isBlock = className?.startsWith("language-") return <code className={isBlock ? className : "docs-inline-code"}>{children}</code> }, table({ children }) { return ( <div className="docs-table-wrap"> <table>{children}</table> </div> ) }, blockquote({ children }) { return <blockquote className="docs-callout">{children}</blockquote> }, } function extractCodeText(node: ReactNode): string { if (typeof node === "string") return node if (Array.isArray(node)) return node.map(extractCodeText).join("") if (node && typeof node === "object" && "props" in node) { const props = (node as { props?: { children?: ReactNode } }).props return extractCodeText(props?.children ?? "") } return "" } function MarkdownChunk({ children }: { children: string }) { if (!children.trim()) return null return ( <ReactMarkdown remarkPlugins={[remarkGfm]} components={markdownComponents}> {children} </ReactMarkdown> ) } function MintlifyCardView({ card }: { card: MintlifyCard }) { const external = card.href.startsWith("http") return ( <a className={card.horizontal ? "docs-card docs-card-horizontal" : "docs-card"} href={resolveHref(card.href)} target={external ? "_blank" : undefined} rel={external ? "noreferrer" : undefined} > <span className="docs-card-icon">{iconForCard(card)}</span> <span className="docs-card-copy"> <strong>{card.title}</strong> <small>{card.body}</small> </span> <ArrowUpRight size={14} strokeWidth={1.8} />