From e700abebb267694496c3959a4930faae510d46a7 Mon Sep 17 00:00:00 2001 From: Kenny Van de Maele Date: Sun, 25 Oct 2026 10:33:08 +0100 Subject: [PATCH] docs: the website and its docs describe each page to search engines Search engines and link previews had little to go on: the landing page's title was just "OpenReliant", without StarLancer in it, every docs page shared one description, the landing and contribute pages gave no canonical address, and no one pointed search engines at a sitemap. Each page now has its own title and description, a canonical address and Open Graph tags, and the landing page says what OpenReliant is in schema.org's terms. The docs' sitemap dates each page by its last commit, where it gave every page the build's date, and a sitemap of the website's own pages joins it. robots.txt at the root of openreliant.github.io names both. --- .github/workflows/pages.yml | 9 +++- website/contribute/index.html | 10 +++++ website/docs-theme/main.html | 21 +++++++-- website/docs-theme/sitemap.xml | 15 +++++++ website/docs_hooks.py | 78 ++++++++++++++++++++++++++++++++-- website/index.html | 43 +++++++++++++++++-- website/sitemap.xml | 7 +++ 7 files changed, 173 insertions(+), 10 deletions(-) create mode 100644 website/docs-theme/sitemap.xml create mode 100644 website/sitemap.xml diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 56ccf964..cc23e74a 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -1,7 +1,9 @@ # Publishes the website, website/, on GitHub Pages: what OpenReliant is, the latest release's # downloads, the roadmap and the mods, and the documentation under docs/ at /docs/. The page reads # the release and the milestones from GitHub as it opens, so it needs publishing again only when -# it or the documentation changes. +# it or the documentation changes. Search engines find the pages through two sitemaps, the +# website's own (website/sitemap.xml) and the docs' (docs/sitemap.xml), which robots.txt at the +# root of openreliant.github.io names. name: Pages on: @@ -26,7 +28,12 @@ jobs: name: github-pages url: ${{ steps.deploy.outputs.page_url }} steps: + # The whole history, without the old files' contents, for the day each page of the docs last + # changed (website/docs_hooks.py). - uses: actions/checkout@v7 + with: + fetch-depth: 0 + filter: blob:none # The Predator's wire frame from the docs, drawn in the page's orange. - run: sed 's/#7d8590/#f08a2c/g' docs/images/predator-wireframe.svg > website/predator.svg # OpenReliant's icon (assets/icon) as the favicon of the site, its docs and the mods' site. diff --git a/website/contribute/index.html b/website/contribute/index.html index 6f1adafe..3abc71c2 100644 --- a/website/contribute/index.html +++ b/website/contribute/index.html @@ -5,6 +5,16 @@ Contribute · OpenReliant + + + + + + + + + + diff --git a/website/docs-theme/main.html b/website/docs-theme/main.html index 079d3adf..ebace76d 100644 --- a/website/docs-theme/main.html +++ b/website/docs-theme/main.html @@ -16,9 +16,24 @@ -{% if page.title and not page.is_homepage %}{{ page.title }} · {% endif %}OpenReliant docs - -{%- if page.canonical_url %}{% endif %} +{#- The page's title and description, for the browser's tab and for search engines: a page's own + description is the start of its first paragraph (docs_hooks.py). #} +{%- set title = page.title ~ " · OpenReliant docs" if page.title and not page.is_homepage else "OpenReliant docs: install, play and mod StarLancer" %} +{%- set description = page.meta.description or config.site_description %} +{{ title|e }} + +{%- if page.canonical_url %} + + +{%- endif %} +{#- What a link to the page shows in chats and on social sites. #} + + + + + + + diff --git a/website/docs-theme/sitemap.xml b/website/docs-theme/sitemap.xml new file mode 100644 index 00000000..1d640466 --- /dev/null +++ b/website/docs-theme/sitemap.xml @@ -0,0 +1,15 @@ +{#- The docs' sitemap for search engines: each page's address, and the day git last changed its + file, where git says (docs_hooks.py). -#} + + +{%- for file in pages %} + {%- if not file.page.is_link and file.page.canonical_url %} + + {{ file.page.canonical_url|e }} + {%- if file.page.update_date %} + {{ file.page.update_date }} + {%- endif %} + + {%- endif %} +{%- endfor %} + diff --git a/website/docs_hooks.py b/website/docs_hooks.py index e2c02db5..0148dfbc 100644 --- a/website/docs_hooks.py +++ b/website/docs_hooks.py @@ -1,12 +1,36 @@ -"""MkDocs hook for the website's docs: a link from a page under docs/ to a file outside it, such -as a source file, goes to that file on GitHub, and one past the repository's root, such as -../../milestones from the README, to that page of the repository on GitHub.""" +"""MkDocs hooks for the website's docs. +- A link from a page under docs/ to a file outside it, such as a source file, goes to that file on + GitHub, and one past the repository's root, such as ../../milestones from the README, to that + page of the repository on GitHub. +- Each page but the first gets a description for search engines and link previews: the start of + its text, as plain text (main.html). +- Each page's date in the sitemap is the day git last changed its file (sitemap.xml), so search + engines see which pages changed. Without git's whole history, the sitemap gives no dates. +""" + +import html +import os import posixpath import re +import subprocess REPOSITORY = "https://github.com/OpenReliant/openreliant" LINK = re.compile(r"(\]\()([^)\s]+)((?:\s+\"[^\"]*\")?\))") +# A paragraph or an item of a list, and a tag within one. +TEXT = re.compile(r"<(p|li)(?:\s[^>]*)?>(.*?)", re.S) +TAG = re.compile(r"<[^>]+>") + +# The longest description, about what a search result shows, and the shortest that is cut at the +# end of a sentence rather than at a word. A description cut at a word ends in an ellipsis. +LONGEST_DESCRIPTION = 160 +SHORTEST_DESCRIPTION = 80 +ELLIPSIS = "..." + + +def on_pre_page(page, **_): + page.update_date = last_change(page.file.abs_src_path) + return page def on_page_markdown(markdown, page, **_): @@ -27,3 +51,51 @@ def rewrite(match): return match.group(1) + url + (f"#{anchor}" if anchor else "") + match.group(3) return LINK.sub(rewrite, markdown) + + +def on_page_content(content, page, **_): + if not page.is_homepage and "description" not in page.meta: + text = "" + for match in TEXT.finditer(content): + text = " ".join((text + " " + html.unescape(TAG.sub("", match.group(2)))).split()) + # A text that leads into a list goes on with its items. + if len(text) >= SHORTEST_DESCRIPTION and not text.endswith(":"): + break + if text: + page.meta["description"] = shortened(text) + return content + + +def shortened(text): + """`text` as a description: whole where it fits, else cut after its last sentence that fits, + or at a word.""" + if len(text) <= LONGEST_DESCRIPTION: + return text + end = text.rfind(". ", 0, LONGEST_DESCRIPTION) + 1 + if end >= SHORTEST_DESCRIPTION: + return text[:end] + fits = text[: LONGEST_DESCRIPTION - len(ELLIPSIS)] + return fits[: fits.rfind(" ")].rstrip(",;:") + ELLIPSIS + + +def git(*arguments): + """What git prints for `arguments`, run in the website's folder; None where it fails.""" + try: + return subprocess.run( + ["git", *arguments], cwd=os.path.dirname(os.path.abspath(__file__)), + capture_output=True, text=True, check=True, + ).stdout.strip() + except (OSError, subprocess.CalledProcessError): + return None + + +# Whether git has the repository's whole history. A shallow clone takes every file as changed in +# its one commit. +WHOLE_HISTORY = git("rev-parse", "--is-shallow-repository") == "false" + + +def last_change(path): + """The day git last changed the file at `path`, such as 2026-10-24; None where git can't say.""" + if not WHOLE_HISTORY: + return None + return git("log", "-1", "--format=%cs", "--", path) or None diff --git a/website/index.html b/website/index.html index cdc1e2ab..b87f056b 100644 --- a/website/index.html +++ b/website/index.html @@ -3,8 +3,45 @@ -OpenReliant - +OpenReliant: play StarLancer on Windows, macOS and Linux + + + + + + + + + + + + + @@ -38,7 +75,7 @@

OpenReliant

Mods - Wireframe of the Predator light fighter, from its model + Wireframe of the Predator light fighter, from its model
diff --git a/website/sitemap.xml b/website/sitemap.xml new file mode 100644 index 00000000..c01754d3 --- /dev/null +++ b/website/sitemap.xml @@ -0,0 +1,7 @@ + + + + https://openreliant.github.io/openreliant/ + https://openreliant.github.io/openreliant/contribute/ +