From f71d7edc12ecd594e3fcb6b3b7c87fed87897f34 Mon Sep 17 00:00:00 2001 From: GeiserX <9169332+GeiserX@users.noreply.github.com> Date: Fri, 6 Nov 2026 13:30:36 +0100 Subject: [PATCH] build(docs): move the site to Zensical Material for MkDocs reaches end of life on 2027-05-05 and its maintainers now build Zensical, so the docs site moves there before that date. Zensical builds the same six pages from the same Markdown, adds instant navigation, a "Copy as Markdown" button and an llms.txt index, and drops MkDocs' nav and absolute-link validation without a warning; a post-build site check in the Docs workflow puts those checks back. --- .github/dependabot.yml | 8 +- .github/scripts/site-check.py | 233 ++++++++++++++++++++++++++++++++++ .github/workflows/docs.yml | 61 +++++---- .gitignore | 3 +- docs/development.md | 7 +- docs/requirements-docs.txt | 2 +- docs/stylesheets/theme.css | 12 +- mkdocs.yml | 143 --------------------- zensical.toml | 104 +++++++++++++++ 9 files changed, 395 insertions(+), 178 deletions(-) create mode 100644 .github/scripts/site-check.py delete mode 100644 mkdocs.yml create mode 100644 zensical.toml diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 85a74ba..382ced0 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -32,13 +32,13 @@ updates: applies-to: security-updates patterns: - '*' - # The docs toolchain is a separate install: docs.yml pip-installs - # docs/requirements-docs.txt to run MkDocs. + # The docs site's one pin (zensical): docs.yml pip-installs + # docs/requirements-docs.txt. Monthly: the strict build and site-check in + # the Docs workflow prove each bump. - package-ecosystem: pip directory: /docs schedule: - interval: weekly - day: monday + interval: monthly commit-message: prefix: docs labels: diff --git a/.github/scripts/site-check.py b/.github/scripts/site-check.py new file mode 100644 index 0000000..31a8df5 --- /dev/null +++ b/.github/scripts/site-check.py @@ -0,0 +1,233 @@ +# Copied from the new-repo templates into every docs repo. Repos run different ruff +# settings, so their formatter leaves this file as it is: lint it, do not reformat it. +# fmt: off +"""Post-build check of a Zensical site: the rules `zensical build --strict` does not enforce. + +usage: python3 -I .github/scripts/site-check.py [zensical.toml] + +Run from the repository root (the workflow does): the config is read from ./zensical.toml +unless its path is given. No config file is an error, never a skipped pass. + +Exit 1 on any of these, each printed as one ERROR line: + - a link or image whose target is not in the built site: a nav entry whose page does not + exist, a missing image, any other dangling href or src (Zensical validates Markdown page + links and anchors; it does not validate images, nav entries or raw HTML links). A redirect + stub's own links (canonical and body) are checked the same way; + - an href ending in .md: a correct page link is rewritten to a directory URL, so a .md left + in the HTML is a raw-HTML or unprocessed link, and it opens the llmstxt Markdown copy; + - a root-absolute link (/getting-started/) outside the site's own path prefix: it breaks + under https://geiserx.github.io// (MkDocs' `absolute_links: warn`, which Zensical + ignores); + - a page that no navigation entry links to: a stray file under docs/ published at a public + URL that nothing reaches (MkDocs' `nav.omitted_files: warn`, which Zensical ignores); + - a .py, .pyc or __pycache__ entry in the site: the awesome lists keep their Markdown + extension under docs/hooks/, and Zensical publishes every file not in exclude_docs; + - an awesome list's count token left in a page (``, `n:categorias`, + `n:retirados`): the Markdown extension that fills them in did not run. Only those three + names: CashPilot writes other `` comments of its own in its pages; + - a raw , <textarea>, <xmp>, <plaintext>, <noembed> or <noframes> tag inside a page's + article: a browser's HTML parser treats everything after it as text, so the rest of the + page disappears (Markdown passes `<title>` written in prose through as a real tag; + Telegram-Archive shipped that way); + - a "Copy as Markdown" button (data-md-copy-url) whose Markdown copy does not exist: the + llmstxt plugin writes the copies, and only for pages its `sections` globs match. +And, from zensical.toml (Zensical drops what it does not know without a word; under MkDocs +strict an unknown plugin aborted the build): + - a plugin name Zensical 0.1.0 does not know (`redirect` for `redirects`, `llmstxtt`): the + plugin's whole output silently vanishes otherwise; + - a theme feature name the Zensical UI 0.0.41 does not know (the list below is what its + templates and bundle test for; `search.suggest` and `search.share` are not in it); + - `theme.variant` other than "modern" or "classic" (anything else is classic, silently), + and a "modern" config whose index.html does not load the modern stylesheet; + - a `redirect_maps` entry whose stub was not written; + - a `[project.plugins.llmstxt]` table with no site/llms.txt, or `content.action.copy` in + the features with a page that carries no copy button. +Stdlib only (tomllib needs Python 3.11+), so it runs under `python -I` on any runner. +""" + +import re +import sys +import tomllib +from pathlib import Path +from urllib.parse import unquote, urlparse + +SWALLOW = re.compile(r"<(title|textarea|xmp|plaintext|noembed|noframes)\b", re.IGNORECASE) +TOKEN = re.compile(r"<!--\s*n:(?:proyectos|categorias|retirados)\s*-->") +ARTICLE = re.compile(r"<article\b.*?</article>", re.DOTALL) +ATTR = re.compile(r'\s(?:href|src)="([^"]*)"') +COPY = re.compile(r'\sdata-md-copy-url="([^"]*)"') +NAV = re.compile(r'<a\s+href="([^"]*)"\s+class="md-(?:nav|tabs)__link') +STUB = "<title>Redirecting..." + +# Keys of _PLUGIN_UNSUPPORTED_OPTIONS in zensical/config.py (0.1.0, same set as 0.0.74): every other name is +# dropped by `_convert_plugins` without a message. `material/` prefixes and `social/`, +# `privacy/`, `optimize/` instance names canonicalise the same way Zensical does. +KNOWN_PLUGINS = { + "api-autonav", "autorefs", "awesome-nav", "blog", "callouts", "exclude", "gh-admonitions", + "glightbox", "include-markdown", "literate-nav", "llmstxt", "macros", "markdown-exec", + "markdownextradata", "meta", "mike", "minify", "mkdocs-audio", "mkdocs-autoapi", + "mkdocs-nav-weight", "mkdocs-video", "mkdocstrings", "offline", "optimize", "privacy", + "redirects", "rss", "search", "social", "table-reader", "tags", +} +# Feature names the shipped UI tests for (zensical_ui 0.0.41, read from its templates and +# bundle.*.min.js). Regenerate after a bump with: +# grep -rhoE '"[a-z]+(\.[a-z]+)+" in features' /zensical_ui/templates | sort -u +# grep -oE '"(navigation|content|search|toc|header|announce)\.[a-z.]+"' .../bundle.*.min.js | sort -u +KNOWN_FEATURES = { + "announce.dismiss", "content.action.copy", "content.action.edit", "content.action.view", + "content.code.annotate", "content.code.copy", "content.code.select", + "content.footnote.tooltips", "content.lazy", "content.tabs.link", "content.tooltips", + "header.autohide", "navigation.expand", "navigation.footer", "navigation.indexes", + "navigation.instant", "navigation.instant.prefetch", "navigation.instant.preview", + "navigation.instant.progress", "navigation.path", "navigation.prune", "navigation.sections", + "navigation.tabs", "navigation.tabs.sticky", "navigation.top", "navigation.tracking", + "search.highlight", "toc.follow", "toc.integrate", +} + + +def body_urls(body: str) -> set[str]: + return set(ATTR.findall(body)) + + +def canonical_plugin(name: str) -> str: + name = name.removeprefix("material/") + return name.split("/", 1)[0] if name.startswith(("social/", "privacy/", "optimize/")) else name + + +def read_config(path: Path, errors: set[str]) -> dict: + if not path.is_file(): + errors.add(f"{path}: config file not found (run from the repository root, or pass its path)") + return {} + try: + project = tomllib.loads(path.read_text(encoding="utf-8")).get("project") + except (tomllib.TOMLDecodeError, UnicodeDecodeError) as e: + errors.add(f"{path}: not valid TOML: {e}") + return {} + # Every config check reads [project]: without it they would all pass by skipping. + if not isinstance(project, dict) or not project: + errors.add(f"{path}: no [project] table, so no config check can run") + return {} + return project + + +def main(site_dir: str, config_path: str) -> int: + site = Path(site_dir).resolve() + index = site / "index.html" + if not index.is_file(): + print(f"ERROR {site_dir}: no index.html (did the build run?)") + return 1 + m = re.search(r' in the page body hides everything after it in a browser") + for tok in TOKEN.findall(art): + errors.add(f"{rel}: build token {tok} was not filled in (extension not loaded?)") + # holds from the + # llmstxt plugin, a legitimate .md href; the .md rule looks at the body only. + body = text.split("", 1)[-1] + for url in ATTR.findall(text): + t = resolve(page, url) + if t == "ABS": + errors.add(f"{rel}: absolute link {url} (outside {prefix})") + elif t is not None and not t.is_relative_to(site): + # ../LICENSE from the top page: the file exists in the checkout, not on Pages. + errors.add(f"{rel}: {url} leaves the built site (resolves to {t})") + elif t is not None and not t.exists(): + shown = t.relative_to(site) if t.is_relative_to(site) else t + errors.add(f"{rel}: {url} -> missing {shown}") + elif t is not None and unquote(urlparse(url).path).endswith(".md") and url in body_urls(body): + errors.add(f"{rel}: {url} links a Markdown source, not a page URL (raw HTML link?)") + copies = COPY.findall(text) + for url in copies: + t = resolve(page, url) + if isinstance(t, Path) and not t.is_file(): + errors.add(f"{rel}: Copy as Markdown points at {url}, which was not written (llmstxt sections?)") + if rel.name == "index.html" and not stub and not copies \ + and "content.action.copy" in ((cfg.get("theme") or {}).get("features") or []): + errors.add(f"{rel}: content.action.copy is on but the page has no Copy as Markdown button") + for url in NAV.findall(text): + t = resolve(page, url) + if isinstance(t, Path): + nav_targets.add(t) + + for page in pages: + if page.resolve() not in nav_targets: + errors.add(f"{page.relative_to(site)}: page is in no navigation entry (stray page)") + + if cfg: + plugins = cfg.get("plugins") or {} + for name in plugins: + if canonical_plugin(name) not in KNOWN_PLUGINS: + errors.add(f"zensical.toml: plugin {name!r} is unknown to Zensical and silently ignored") + theme = cfg.get("theme") or {} + for f in theme.get("features") or []: + if f not in KNOWN_FEATURES: + errors.add(f"zensical.toml: feature {f!r} is unknown to the Zensical UI and silently ignored") + variant = theme.get("variant", "classic") + if variant not in ("modern", "classic"): + errors.add(f"zensical.toml: theme.variant {variant!r} is not modern or classic (Zensical falls back to classic silently)") + elif variant == "modern" and "assets/stylesheets/modern/" not in index.read_text(errors="replace"): + errors.add("index.html does not load the modern stylesheet although theme.variant is modern") + # The offline plugin turns directory URLs off (zensical/config.py, + # _apply_offline_plugin), so a redirect stub becomes old.html there. + offline = plugins.get("offline", False) + offline_on = "offline" in plugins and (offline or {}).get("enabled", True) is not False + directory_urls = cfg.get("use_directory_urls", True) and not offline_on + for src in (plugins.get("redirects") or {}).get("redirect_maps") or {}: + s = src.removesuffix(".md") + # A README page is its folder's index: sub/README.md -> sub/index.html in every mode. + if s == "README" or s.endswith("/README"): + s = s[: -len("README")] + "index" + if not directory_urls or s == "index" or s.endswith("/index"): + stub = site / (s + ".html") + else: + stub = site / s / "index.html" + if not stub.is_file() or STUB not in stub.read_text(errors="replace"): + errors.add(f"redirect_maps {src!r}: no redirect stub at {stub.relative_to(site)}") + if "llmstxt" in plugins and not (site / "llms.txt").is_file(): + errors.add("llmstxt plugin is configured but site/llms.txt was not written") + + for e in sorted(errors): + print("ERROR", e) + print(f"site-check: {len(pages)} pages, {len(errors)} errors, prefix {prefix}") + return 1 if errors else 0 + + +if __name__ == "__main__": + if len(sys.argv) not in (2, 3): + sys.exit("usage: site-check.py [zensical.toml]") + sys.exit(main(sys.argv[1], sys.argv[2] if len(sys.argv) == 3 else "zensical.toml")) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 1fe049b..70f1ef6 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -2,31 +2,31 @@ name: Docs # Copy to .github/workflows/docs.yml. Before the first run, set the repository's # Pages source to "GitHub Actions": Settings -> Pages -> Source, or -# `gh api -X POST repos/GeiserX//pages -f build_type=workflow` for a repo -# with no Pages yet. For a repo already publishing from a branch the API is a -# PUT, and its docs list `source.branch` and `source.path` as required, so the -# bare `-f build_type=workflow` PUT may 422: try it on CashPilot first and -# record which form worked. A deploy while the source is still a branch is -# expected to fail; that is the test that the switch happened. +# `gh api -X PUT repos/GeiserX//pages -f build_type=workflow` (HTTP 204; +# the API keeps reporting the old source branch afterwards, which is cosmetic). +# A repo with no Pages at all uses `-X POST` with the same field. A repo +# already on the artifact flow needs no change. # # Never for a repo whose gh-pages branch holds a Helm chart index (index.yaml, -# artifacthub-repo.yml): Pumperly, LynxPrompt, genieacs-container, -# duplicacy-container. Those use docs-pages-helm.yml instead. +# artifacthub-repo.yml): Pumperly, LynxPrompt, genieacs-container. Those use +# docs-pages-helm.yml instead. # -# Pull requests that touch the docs run the strict build, so a broken link, a -# missing page or an unreachable page fails the check instead of reaching the -# site. A push to main deploys, and only after the same build has passed. +# Pull requests that touch the docs run the strict build and the site check, +# so a broken link, a missing page or an unreachable page fails the check +# instead of reaching the site. A push to main deploys, and only after the +# same build has passed. # -# The site is published as a Pages artifact, never with `mkdocs gh-deploy`: -# no gh-pages branch, no `contents: write`, and one artifact is the whole site. +# The site is published as a Pages artifact: no gh-pages branch, no +# `contents: write`, and one artifact is the whole site. on: push: branches: - main paths: - 'docs/**' - - 'mkdocs.yml' + - 'zensical.toml' - '.github/workflows/docs.yml' + - '.github/scripts/site-check.py' # Add every file a page includes with pymdownx.snippets (for example # docker-compose.yml, .env.example): a change to it must rebuild the # site, and a rename must fail the build. @@ -36,9 +36,11 @@ on: pull_request: workflow_dispatch: -# One run at a time per ref, in push order, never cancelled. Locking only the -# deploy job is not enough: an older push whose build finishes later would -# deploy last and publish a stale site. Pull requests get their own group. +# One run at a time per ref, never cancelled. GitHub keeps one running and one +# pending run per group and a newer push replaces the pending one, so the last +# deploy is the newest push; it does not promise strict push order beyond that. +# Locking only the deploy job is not enough: an older push whose build finishes +# later could deploy last and publish a stale site. Pull requests get their own group. concurrency: group: pages-${{ github.ref }} cancel-in-progress: false @@ -55,32 +57,45 @@ jobs: steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - # This job never pushes; a persisted token is readable by pip and every MkDocs plugin. + # This job never pushes; a persisted token is readable by pip and by the build. persist-credentials: false - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0 with: python-version: '3.13' + # pip's download cache only. Zensical's own build cache (.cache/) is + # never kept between runs: its docs advise against it on CI, and + # --clean below starts from nothing anyway. cache: pip cache-dependency-path: docs/requirements-docs.txt - name: Install dependencies run: pip install -r docs/requirements-docs.txt - # mkdocs.yml sets `strict: true`; the flag here keeps the build strict - # even if someone removes that line. + # zensical.toml sets `strict = true`; the flag here keeps the build strict + # even if someone removes that line. `python -m zensical`, never the bare + # `zensical` command: it puts the repo root on sys.path, which the awesome + # lists' Markdown extension needs, and every repo builds the same way. - name: Build - run: mkdocs build --strict + run: python -m zensical build --clean --strict + # What the strict build does not check: a page in no nav entry, a nav + # entry for a missing file, a missing image, a /rooted link, a raw + # in a page. Each one was proven to turn this step red (validate-zensical.sh). + - name: Check the built site + run: python -I .github/scripts/site-check.py site + + # Push to main only: workflow_dispatch can be run on any branch, and the + # push filter above does not apply to it. Same line as docs-pages-helm.yml. - name: Upload the site - if: github.event_name != 'pull_request' + if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main' uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 with: path: site deploy: # Runs on push to main only, after the build job has passed. - if: github.event_name != 'pull_request' + if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main' needs: build runs-on: ubuntu-latest permissions: diff --git a/.gitignore b/.gitignore index b2435eb..b735460 100644 --- a/.gitignore +++ b/.gitignore @@ -7,8 +7,9 @@ output/ *.epub .DS_Store -# MkDocs build output +# Docs build output (Zensical writes site/ and its build cache .cache/) site/ +.cache/ # Beads: bead text lives in the Dolt tracker only, never in git .beads/issues.jsonl diff --git a/docs/development.md b/docs/development.md index 534affb..996ae95 100644 --- a/docs/development.md +++ b/docs/development.md @@ -37,11 +37,12 @@ Every pull request runs `test` and `docker-build` from `ci.yml`, and the strict ## Docs -The site is built by MkDocs Material from `docs/` and `mkdocs.yml`: +The site is built by [Zensical](https://zensical.org/) from `docs/` and `zensical.toml`: ```bash pip install -r docs/requirements-docs.txt -mkdocs build --strict +python -m zensical build --clean --strict +python -I .github/scripts/site-check.py site ``` -The same strict build runs on every pull request, and a push to `main` that changes the docs deploys the site. How to send a change: [CONTRIBUTING.md](https://github.com/GeiserX/ePubLangMerger/blob/main/CONTRIBUTING.md). +The second command checks what the strict build does not: a page left out of the nav, a nav entry for a missing file, a missing image and a link rooted at `/`. The same two commands run on every pull request, and a push to `main` that changes the docs deploys the site. How to send a change: [CONTRIBUTING.md](https://github.com/GeiserX/ePubLangMerger/blob/main/CONTRIBUTING.md). diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index 48b1d43..f01d4c2 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1 +1 @@ -mkdocs-material==9.8.0 +zensical==0.1.0 diff --git a/docs/stylesheets/theme.css b/docs/stylesheets/theme.css index aa78d27..d8e7eee 100644 --- a/docs/stylesheets/theme.css +++ b/docs/stylesheets/theme.css @@ -3,8 +3,8 @@ * banner.svg weaves coral strips (#ff7a59, shaded to #b8472f) through cyan * strips (#4cc9f0, shaded to #2a86a8), one colour per language, and sets the * name in white serif on a #15131f plate. The header and the dark page use - * the plate; links are cyan and turn coral on hover. mkdocs.yml sets - * `primary: custom` and `accent: custom`, so Material paints nothing until + * the plate; links are cyan and turn coral on hover. zensical.toml sets + * `primary: custom` and `accent: custom`, so the theme paints nothing until * these variables exist. */ :root { @@ -50,12 +50,18 @@ * .md-header__topic and the page title in a second one, and swaps them on * scroll. Styling both would change every page title too. */ .md-header__topic:first-child .md-ellipsis { - color: #ffffff; font-family: "Palatino Linotype", Palatino, "Book Antiqua", "URW Palladio L", P052, Georgia, serif; font-weight: 700; letter-spacing: 0; } +/* That colour only on the dark scheme: Zensical's modern header takes the page + background, so on the light scheme it is white and a light name would vanish. + There the name keeps the default text colour. */ +[data-md-color-scheme="slate"] .md-header__topic:first-child .md-ellipsis { + color: #ffffff; +} + /* Keep the mark and the name close, so they read as one unit. */ .md-header__button.md-logo { padding-right: 0.2rem; diff --git a/mkdocs.yml b/mkdocs.yml deleted file mode 100644 index 4a60497..0000000 --- a/mkdocs.yml +++ /dev/null @@ -1,143 +0,0 @@ -site_name: ePubLangMerger -site_url: https://geiserx.github.io/ePubLangMerger/ -site_description: Merge two ePub files of the same book in different languages into one bilingual ebook for parallel reading. -repo_url: https://github.com/GeiserX/ePubLangMerger -repo_name: GeiserX/ePubLangMerger -edit_uri: edit/main/docs/ - -# A warning is a broken link, a missing page or a page nobody can reach. All of -# them fail the build, locally and in CI, so nobody has to remember --strict. -strict: true - -theme: - name: material - language: en - logo: images/logo.svg - favicon: images/icon.svg - # Dark first: the first palette entry is the default, and the banner is dark. - palette: - - scheme: slate - primary: custom - accent: custom - toggle: - icon: material/brightness-4 - name: Switch to light mode - - scheme: default - primary: custom - accent: custom - toggle: - icon: material/brightness-7 - name: Switch to dark mode - features: - - navigation.tabs - - navigation.sections - - navigation.expand - - navigation.top - - content.code.copy - - content.action.edit - - search.highlight - - search.suggest - - toc.follow - icon: - repo: fontawesome/brands/github - -# MkDocs reports most link problems as `info`, which --strict ignores. Every -# line here promotes one of them to a warning so the build fails on it: -# nav.omitted_files a Markdown file under docs/ that is in no nav entry and -# not in exclude_docs (a page nobody can reach, published -# at a public URL anyway); -# nav.not_found a nav entry whose file does not exist; -# links.not_found a relative link to a page or file that does not exist; -# links.anchors a link to a heading that does not exist; -# links.absolute_links a link written as /path/ (breaks under the -# /ePubLangMerger/ prefix of the site); -# links.unrecognized_links a relative link that leaves docs/ (../README.md). -# Repo files are linked by absolute GitHub URL instead. -validation: - nav: - omitted_files: warn - not_found: warn - absolute_links: warn - links: - not_found: warn - anchors: warn - absolute_links: warn - unrecognized_links: warn - -plugins: - - search - # No page has been renamed, so there is nothing to redirect. When one is, - # add `mkdocs-redirects==1.2.2` to docs/requirements-docs.txt and uncomment: - # - redirects: - # redirect_maps: - # old-name.md: new-name.md - -markdown_extensions: - - admonition - - pymdownx.details - - pymdownx.superfences: - custom_fences: - - name: mermaid - class: mermaid - format: !!python/name:pymdownx.superfences.fence_code_format - - pymdownx.tabbed: - alternate_style: true - - pymdownx.highlight: - anchor_linenums: true - - pymdownx.inlinehilite - # base_path includes the repository root, so a page can include a file that - # ships (docker-compose.yml) word for word with `--8<-- "docker-compose.yml"`. - # check_paths makes a missing include fail the build instead of rendering an - # empty block. No page includes a file today; the day one does, add that file - # to the push paths of docs.yml. - - pymdownx.snippets: - base_path: - - . - - docs - check_paths: true - - pymdownx.mark - - attr_list - - md_in_html - # Renders :material-xxx: shortcodes as icons. The grid cards on index.md use - # them; without this they print as literal text. - - pymdownx.emoji: - emoji_index: !!python/name:material.extensions.emoji.twemoji - emoji_generator: !!python/name:material.extensions.emoji.to_svg - - tables - - toc: - permalink: true - -extra: - social: - - icon: fontawesome/brands/github - link: https://github.com/GeiserX/ePubLangMerger - - icon: fontawesome/brands/docker - link: https://hub.docker.com/r/drumsergio/epublangmerger - -extra_css: - - stylesheets/theme.css - -# Files under docs/ that are not pages. MkDocs publishes every Markdown file in -# docs/ whether or not it is in the nav, so anything that is not a page must be -# listed here. A page is a lowercase kebab-case .md file; anything with a -# capital letter (ROADMAP.md, CHANGELOG.md) is a repo-facing note, and design/, -# research/ and notes/ hold material read on GitHub. This repo has no note -# under docs/ today; the block keeps the next one off the site. -exclude_docs: | - [A-Z]*.md - design/ - research/ - notes/ - requirements-docs.txt - -# The nav title must equal the page's H1. -nav: - - Home: index.md - - Get started: - - Getting started: getting-started.md - - Usage: usage.md - - Reference: - - How it works: how-it-works.md - - Troubleshooting: troubleshooting.md - - Development: - - Development: development.md diff --git a/zensical.toml b/zensical.toml new file mode 100644 index 0000000..6357873 --- /dev/null +++ b/zensical.toml @@ -0,0 +1,104 @@ +# Generated from mkdocs.yml by mkdocs-to-zensical.py; the why of every block is in +# new-repo/templates/zensical.toml. Edit this file, not mkdocs.yml, which is gone. + +[project] +site_name = "ePubLangMerger" +site_url = "https://geiserx.github.io/ePubLangMerger/" +site_description = "Merge two ePub files of the same book in different languages into one bilingual ebook for parallel reading." +repo_url = "https://github.com/GeiserX/ePubLangMerger" +repo_name = "GeiserX/ePubLangMerger" +edit_uri = "edit/main/docs/" +strict = true +extra_css = ["stylesheets/theme.css"] +exclude_docs = """ +[A-Z]*.md +design/ +research/ +notes/ +requirements-docs.txt +""" +nav = [ + { Home = "index.md" }, + { "Get started" = [ + { "Getting started" = "getting-started.md" }, + { Usage = "usage.md" }, + ] }, + { Reference = [ + { "How it works" = "how-it-works.md" }, + { Troubleshooting = "troubleshooting.md" }, + ] }, + { Development = [ + { Development = "development.md" }, + ] }, +] + +[project.validation] +invalid_links = true +invalid_link_anchors = true + +[project.theme] +language = "en" +variant = "modern" +logo = "images/logo.svg" +favicon = "images/icon.svg" +font = false +features = [ + "navigation.tabs", + "navigation.sections", + "navigation.expand", + "navigation.top", + "content.code.copy", + "content.action.edit", + "search.highlight", + "toc.follow", + "navigation.instant", + "navigation.instant.progress", + "content.action.copy", +] +icon.repo = "fontawesome/brands/github" + +[[project.theme.palette]] +scheme = "slate" +primary = "custom" +accent = "custom" +toggle.icon = "lucide/sun" +toggle.name = "Switch to light mode" + +[[project.theme.palette]] +scheme = "default" +primary = "custom" +accent = "custom" +toggle.icon = "lucide/moon" +toggle.name = "Switch to dark mode" + +[[project.extra.social]] +icon = "fontawesome/brands/github" +link = "https://github.com/GeiserX/ePubLangMerger" + +[[project.extra.social]] +icon = "fontawesome/brands/docker" +link = "https://hub.docker.com/r/drumsergio/epublangmerger" + +[project.plugins.search] + +[project.plugins.llmstxt.sections] +Documentation = ["*.md", "*/*.md"] + +[project.markdown_extensions] +admonition = {} +"pymdownx.details" = {} +"pymdownx.superfences".custom_fences = [ + { name = "mermaid", class = "mermaid", format = "pymdownx.superfences.fence_code_format" }, +] +"pymdownx.tabbed".alternate_style = true +"pymdownx.highlight".anchor_linenums = true +"pymdownx.inlinehilite" = {} +"pymdownx.snippets".base_path = [".", "docs"] +"pymdownx.snippets".check_paths = true +"pymdownx.mark" = {} +attr_list = {} +md_in_html = {} +"pymdownx.emoji".emoji_index = "zensical.extensions.emoji.twemoji" +"pymdownx.emoji".emoji_generator = "zensical.extensions.emoji.to_svg" +tables = {} +toc.permalink = true