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/ +