diff --git a/docs/Project.toml b/docs/Project.toml index dc5e8d2bc..f7b46562b 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -11,8 +11,10 @@ MPSKit = "bb1c41ca-d63c-52ed-829e-0820dda26502" MPSKitModels = "ca635005-6f8c-4cd1-b51d-8491250ef2ab" Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" Polynomials = "f27b6e38-b328-58d1-80ce-0feddd5e7a45" +SHA = "ea8e919c-243c-51af-8825-aaa63cd721ce" Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" TensorKit = "07d1fe3e-3e46-537d-9eac-e9e13d0d4cec" +TensorKitTensors = "41b62e7d-e9d1-4e23-942c-79a97adf954b" TensorOperations = "6aa20fa7-93e2-5fca-9bc0-fbd0db3c71a2" [sources] @@ -22,3 +24,4 @@ MPSKit = {path = ".."} Documenter = "1.11" DocumenterInterLinks = "1" DocumenterVitepress = "0.3" +TensorKitTensors = "0.2" diff --git a/docs/make.jl b/docs/make.jl index 5922e4ee3..70cfc778d 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -11,6 +11,25 @@ using Documenter using DocumenterVitepress using DocumenterCitations using DocumenterInterLinks +using SHA: sha256 + +# `src/.vitepress/config.mts` is vendored from the DocumenterVitepress template (see the +# header comment there) so that we can hook `themeConfig.search` and `markdown.config`, +# which `MarkdownVitepress` does not expose. Warn when upstream changes the template, so +# that our copy can be re-synced — or dropped, once the fixes land upstream. +let template = joinpath(pkgdir(DocumenterVitepress), "template", "src", ".vitepress", "config.mts") + # Templates the vendored copy is known to be a faithful superset of; v0.3.4 and v0.3.5 + # differ only by the (inert for us) NOINDEX marker. + vendored_from = ( + "ca5a958eb398b3219557633f017467cfa07f4882dc2dfe55b12d1f6c0e70d729", # v0.3.4 + "56289223983a3844417eae597f81da878f395792a529721e7873178c92d60721", # v0.3.5 + ) + actual = bytes2hex(sha256(read(template))) + actual in vendored_from || @warn """ + DocumenterVitepress' `config.mts` template has changed since `docs/src/.vitepress/config.mts` \ + was vendored from it. Re-sync the vendored copy (keeping the `MPSKit:` additions), or delete \ + it if upstream now ships them.""" template actual +end # examples example_dir = joinpath(@__DIR__, "src", "examples") diff --git a/docs/src/.vitepress/config.mts b/docs/src/.vitepress/config.mts new file mode 100644 index 000000000..1c23640a1 --- /dev/null +++ b/docs/src/.vitepress/config.mts @@ -0,0 +1,202 @@ +// ============================================================================ +// VENDORED FILE — keep in sync with DocumenterVitepress. +// +// This is a verbatim copy of the DocumenterVitepress **v0.3.5** template at +// `template/src/.vitepress/config.mts`, plus the MPSKit additions marked with +// `MPSKit:` below. DocumenterVitepress copies this file into the build as-is +// and then performs its `REPLACE_ME_DOCUMENTER_VITEPRESS*` substitutions on it +// (see `modify_config_file` in DocumenterVitepress/src/vitepress_config.jl), so +// the nav, sidebar, base, outDir, ... all keep working. +// +// We only vendor it because `themeConfig.search.options` and `markdown.config` +// are not reachable from `docs/make.jl`. Both additions below belong upstream +// in DocumenterVitepress — see `docs/UPSTREAM_DVP_NOTES.md`. Once upstream +// ships them, DELETE this file and the drift guard in `docs/make.jl`. +// +// `docs/make.jl` hashes the installed DocumenterVitepress template and warns if +// it has changed, which means this copy needs re-syncing. +// ============================================================================ + +import { defineConfig } from 'vitepress' +import { tabsMarkdownPlugin } from 'vitepress-plugin-tabs' +import { mathjaxPlugin } from './mathjax-plugin' +import { juliaReplTransformer } from './julia-repl-transformer' +import footnote from "markdown-it-footnote"; +import path from 'path' + +const mathjax = mathjaxPlugin() + +function getBaseRepository(base: string): string { + if (!base || base === '/') return '/'; + const parts = base.split('/').filter(Boolean); + return parts.length > 0 ? `/${parts[0]}/` : '/'; +} + +// MPSKit: --------------------------------------------------------------- +// DocumenterVitepress renders each docstring as a raw `
` block whose +// anchor lives in the ``: +// +// +// MPSKit.FiniteMPS +// +// VitePress' local search only splits a page into indexable sections at +// headings (`......`), so an `@autodocs` page is +// indexed as one single huge document: docstrings rank poorly against short +// manual sections and every hit links to the top of the page. Rewriting each +// summary into a heading *for indexing only* gives one search entry per +// docstring, titled with the binding and deep-linking to its anchor. +// See QuantumKitHub/MPSKit.jl#478. +// +// The replacement has to mirror the shape VitePress' own heading anchors have, +// because the indexer reads the section title from the text *before* the anchor +// (`headingContentRegex = /(.*?).*?<\/a>/i`) and drops any +// section whose title comes out empty. +const DOCSTRING_SUMMARY = + /(.*?)<\/span><\/a>.*?<\/summary>/g + +// MPSKit: --------------------------------------------------------------- +// Documenter rewrites every markdown heading inside a docstring into a +// bold-only paragraph before any writer sees it (`recursive_heading_to_bold!` +// in Documenter/src/expander_pipeline.jl), so `# Constructors` reaches +// VitePress as `

Constructors

` with nothing to style. +// Tag exactly those paragraphs so `theme/custom.css` can render them as +// section headings again. See QuantumKitHub/MPSKit.jl#477. +// +// Scoped to docstring `
` blocks, and requires the paragraph to consist +// of nothing but one ``, so that a paragraph merely *starting* with +// bold text (e.g. "**Not every algorithm ...** — see the table below") is left +// alone. +function docstringHeadings(md) { + md.core.ruler.push('mpskit_docstring_headings', (state) => { + const tokens = state.tokens + let depth = 0 + for (let i = 0; i < tokens.length; i++) { + const token = tokens[i] + + if (token.type === 'html_block') { + if (/]*\bclass=['"][^'"]*\bjldocstring\b/.test(token.content)) depth++ + else if (depth > 0 && /<\/details>/.test(token.content)) depth-- + continue + } + + if (depth === 0 || token.type !== 'paragraph_open') continue + const inline = tokens[i + 1] + if (!inline || inline.type !== 'inline' || !inline.children) continue + + // markdown-it emits empty `text` tokens around inline markup. + const children = inline.children.filter( + (c) => !(c.type === 'text' && c.content === '') + ) + if (children.length < 2) continue + if (children[0].type !== 'strong_open') continue + if (children[children.length - 1].type !== 'strong_close') continue + + token.attrJoin('class', 'jldocstring-heading') + } + }) +} +// ----------------------------------------------------------------------- + +const baseTemp = { + base: 'REPLACE_ME_DOCUMENTER_VITEPRESS',// TODO: replace this in makedocs! +} + +const navTemp = { + nav: 'REPLACE_ME_DOCUMENTER_VITEPRESS', +} + +const nav = [ + ...navTemp.nav, + { + component: 'VersionPicker' + } +] + +// https://vitepress.dev/reference/site-config +export default defineConfig({ + base: 'REPLACE_ME_DOCUMENTER_VITEPRESS',// TODO: replace this in makedocs! + title: 'REPLACE_ME_DOCUMENTER_VITEPRESS', + description: 'REPLACE_ME_DOCUMENTER_VITEPRESS', + lastUpdated: true, + cleanUrls: true, + outDir: 'REPLACE_ME_DOCUMENTER_VITEPRESS', // This is required for MarkdownVitepress to work correctly... + head: [ + ['link', { rel: 'icon', href: 'REPLACE_ME_DOCUMENTER_VITEPRESS_FAVICON' }], + ['script', {src: `${getBaseRepository(baseTemp.base)}versions.js`}], + // ['script', {src: '/versions.js'], for custom domains, I guess if deploy_url is available. + ['script', {src: `${baseTemp.base}siteinfo.js`}], + // REPLACE_ME_DOCUMENTER_VITEPRESS_NOINDEX + ], + + markdown: { + codeTransformers: [juliaReplTransformer()], + config(md) { + md.use(tabsMarkdownPlugin); + md.use(footnote); + mathjax.markdownConfig(md); + md.use(docstringHeadings); // MPSKit: see above + }, + theme: { + light: "github-light", + dark: "github-dark" + }, + }, + vite: { + plugins: [ + mathjax.vitePlugin, + ], + define: { + __DEPLOY_ABSPATH__: JSON.stringify('REPLACE_ME_DOCUMENTER_VITEPRESS_DEPLOY_ABSPATH'), + }, + resolve: { + alias: { + '@': path.resolve(__dirname, '../components') + } + }, + optimizeDeps: { + exclude: [ + '@nolebase/vitepress-plugin-enhanced-readabilities/client', + 'vitepress', + '@nolebase/ui', + ], + }, + ssr: { + noExternal: [ + // If there are other packages that need to be processed by Vite, you can add them here. + '@nolebase/vitepress-plugin-enhanced-readabilities', + '@nolebase/ui', + ], + }, + }, + themeConfig: { + outline: 'deep', + logo: 'REPLACE_ME_DOCUMENTER_VITEPRESS', + search: { + provider: 'local', + options: { + detailedView: true, + // MPSKit: index every docstring as its own section — see above. + _render(src, env, md) { + const html = md.render(src, env) + if (env.frontmatter?.search === false) return '' + return html.replace( + DOCSTRING_SUMMARY, + (_match, id, href, name) => + `

${name}

` + ) + } + } + }, + nav, + sidebar: 'REPLACE_ME_DOCUMENTER_VITEPRESS', + sidebarDrawer: 'REPLACE_ME_DOCUMENTER_VITEPRESS_SIDEBAR_DRAWER', + editLink: 'REPLACE_ME_DOCUMENTER_VITEPRESS', + socialLinks: [ + { icon: 'github', link: 'REPLACE_ME_DOCUMENTER_VITEPRESS' } + ], + footer: { + message: 'Made with DocumenterVitepress.jl
', + copyright: `© Copyright ${new Date().getUTCFullYear()}.` + } + } +}) diff --git a/docs/src/.vitepress/theme/custom.css b/docs/src/.vitepress/theme/custom.css index bcc238a95..2d7a52802 100644 --- a/docs/src/.vitepress/theme/custom.css +++ b/docs/src/.vitepress/theme/custom.css @@ -1,3 +1,45 @@ +/* ===== Bold text on Linux ===== + VitePress' reset sets `font-synthesis: style` on , permitting a synthesised + oblique but not a synthesised weight. That is fine wherever the resolved font has + a real bold face — as `-apple-system` and `Segoe UI` do — but the + DocumenterVitepress font stack names three families it never loads ("Barlow", + "Inter var experimental", "Inter var"), and on Linux fontconfig satisfies those + *and* the platform names after them with a single regular face. `font-weight: 700` + then computes correctly and still renders at regular weight, which is why bold + docstring headings, admonition titles and inline read as plain text on + Linux while being fine on macOS and Windows (QuantumKitHub/MPSKit.jl#477). + + Allowing weight synthesis fixes that without touching the font stack, so nothing + changes on platforms that already resolve a real bold face. Proposed upstream; drop + this once DocumenterVitepress ships it. Note that the deeper problem is separate: + VitePress self-hosts Inter with a real 100–900 weight axis, but the stack above + names the family names it used before v4 renamed the face to plain "Inter" + (vuejs/vitepress#3694), so that font is downloaded and never used. Naming it would + give real bold faces instead of synthesised ones, but would also change body text + on every platform — a look-and-feel decision for upstream, not a bug fix. */ +body { + font-synthesis: weight style; +} + +/* ===== Docstring section headings ===== + Documenter rewrites markdown headings inside docstrings into bold-only + paragraphs (`recursive_heading_to_bold!`), so there is no to style. The + markdown-it rule in `.vitepress/config.mts` tags exactly those paragraphs; + give them a heading-like treatment — bold, plus a light rule beneath. */ +.jldocstring.custom-block p.jldocstring-heading { + margin: 1.5rem 0 0.6rem; + padding-bottom: 0.25rem; + font-size: 1em; + font-weight: 700; + line-height: 1.4; + color: var(--vp-c-text-1); + border-bottom: 1px solid var(--vp-c-divider); +} + +.jldocstring.custom-block p.jldocstring-heading strong { + font-weight: inherit; +} + /* ===== MPSKit branding ===== */ /* Accent color matching the (green) MPSKit logo in light mode. Dark mode already uses the green palette defined by the DVP template. */ @@ -9,6 +51,67 @@ --vp-c-brand-light: #389826; } +/* ===== Admonitions ===== + Documenter draws `!!! note` / `!!! warning` as a bordered box with a bold, + category-coloured, icon-marked header. DocumenterVitepress maps them onto + VitePress custom blocks, whose title is neither coloured nor icon-marked, and + whose border is transparent — so the blocks stop reading as callouts. Worse, + DVP maps every `note` onto the `tip` container and then restyles `tip` to a + flat grey box in dark mode, so the docs' notes lose their colour entirely. + + Restore a Documenter-like treatment: accent-coloured bold title with an icon, + a coloured left rule, and body text at full contrast. `:not(.jldocstring)` + keeps this off docstrings, which also carry the `custom-block` class. */ +.vp-doc .custom-block:not(.jldocstring) { + --mpskit-admonition-accent: var(--vp-c-default-1); + padding: 14px 16px; + border-left-width: 4px; + border-left-color: var(--mpskit-admonition-accent); + color: var(--vp-c-text-1); +} + +.custom-block:not(.jldocstring) .custom-block-title { + display: flex; + align-items: center; + gap: 0.5em; + margin-bottom: 8px; + font-weight: 700; + color: var(--mpskit-admonition-accent); +} + +.custom-block:not(.jldocstring) .custom-block-title::before { + content: ""; + flex: none; + width: 1.15em; + height: 1.15em; + background-color: currentColor; + /* fa-circle-exclamation, the icon Documenter puts on every admonition */ + --mpskit-admonition-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20Zm-1 5h2v7h-2Zm0 9h2v2h-2Z'/%3E%3C/svg%3E"); + -webkit-mask: var(--mpskit-admonition-icon) center / contain no-repeat; + mask: var(--mpskit-admonition-icon) center / contain no-repeat; +} + +/* `note` and `tip` are indistinguishable by the time the markdown reaches us — + DVP maps both onto the `tip` container — so they share the tip palette. The + background is restated because the DVP template overrides it to a flat grey in + dark mode, which is what drained the colour out of every `!!! note`. */ +.vp-doc .custom-block.tip:not(.jldocstring) { + --mpskit-admonition-accent: var(--vp-c-tip-1); + background-color: var(--vp-c-tip-soft); +} +.vp-doc .custom-block.warning:not(.jldocstring) { + --mpskit-admonition-accent: var(--vp-c-warning-1); +} +.vp-doc .custom-block.danger:not(.jldocstring) { + --mpskit-admonition-accent: var(--vp-c-danger-1); +} +.vp-doc .custom-block.caution:not(.jldocstring) { + --mpskit-admonition-accent: var(--vp-c-caution-1); +} +.vp-doc .custom-block.important:not(.jldocstring) { + --mpskit-admonition-accent: var(--vp-c-important-1); +} + /* ===== Color-invertible diagrams ===== The manual embeds line-drawing diagrams (black on transparent) via `@raw html` . Legible on a light background diff --git a/docs/src/assets/favicon.ico b/docs/src/public/favicon.ico similarity index 100% rename from docs/src/assets/favicon.ico rename to docs/src/public/favicon.ico diff --git a/docs/src/assets/logo.svg b/docs/src/public/logo.svg similarity index 100% rename from docs/src/assets/logo.svg rename to docs/src/public/logo.svg