Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .github/workflows/wiki.yml
Original file line number Diff line number Diff line change
Expand Up @@ -173,11 +173,24 @@ jobs:
echo "$GITHUB_WORKSPACE/tools" >> "$GITHUB_PATH"
zola --version

- name: Point base_url at the Pages URL
# The Pages project site is served under the /Wiki/ subpath, so this
# build (and only this one — the test job keeps the local root base)
# compiles with the full Pages URL. Zola 0.23 has no config
# env-var expansion, so the value is rewritten in the pipeline.
run: sed -i 's|^base_url = .*|base_url = "https://openperpetuum.github.io/Wiki"|' config.toml

- name: Build
# Zola 0.23 builds from the current directory (no -d flag); the
# default working directory is the repo root where config.toml lives.
run: zola build --force -o public

- name: Prefix links with the Pages path
# The source targets the domain root (base_url "/", so local and the
# test job share the output); Zola 0.23 does not rewrite
# site-absolute links, so prefix them with the /Wiki path here.
run: python3 tools/rewrite_site_base.py public https://openperpetuum.github.io/Wiki

- name: Upload artifact
uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # ratchet:actions/upload-pages-artifact@v3
with:
Expand Down
6 changes: 4 additions & 2 deletions config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
default_language = "en"
title = "Open Perpetuum Wiki"
description = "Player and developer reference for the Open Perpetuum server: what you can do in the game, entity stats, zone generation, and stat field semantics."
# GitHub Pages: set the repo Pages URL once the site is published, e.g.
# base_url = "https://<user>.github.io/<repo>/wiki"
# Local/test base: the site lives at the domain root. The CI DEPLOY build
# rewrites this to the GitHub Pages project URL (a subpath —
# openperpetuum.github.io/Wiki/), see .github/workflows/wiki.yml; Zola 0.23
# has no config env-var expansion, so the override happens in the pipeline.
base_url = "/"
generate_feeds = false
# Search is a lightweight client-side script (search.js + search_index.json
Expand Down
23 changes: 22 additions & 1 deletion static/map.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,19 @@
// opens pre-zoomed onto the cluster (initContentZoom, below).
var MIN = 1, MAX = 8, INITIAL_MAX = 3, MIN_VISIBLE = 0.25;

// Deploy base (body[data-root] = config.base_url): "/" locally, the full
// GitHub Pages URL on the project site (subpath /Wiki/). The static
// builder (plus the CI rewrite pass) prefixes HTML links with the path
// part, but JS-built URLs and fetched-SVG links keep the site-absolute
// form — withRoot() prefixes them the same way.
var ROOT = (document.body && document.body.getAttribute('data-root')) || '/';
var ROOT_PATH = ROOT.replace(/^https?:\/\/[^/]+/, '').replace(/\/+$/, ''); // "" | "/Wiki"
function withRoot(h) {
if (!h || h.charAt(0) !== '/') return h; // not site-absolute
if (ROOT_PATH && h.indexOf(ROOT_PATH + '/') === 0) return h; // already
return ROOT_PATH + h;
}

function limit(v, lo, hi) { return Math.min(hi, Math.max(lo, v)); }

// Overlap length of [0, len] with the content span centered at offset t.
Expand Down Expand Up @@ -249,6 +262,14 @@
wrap.setAttribute('data-zoom-init', '1');
wrap._zoomSvg = svg;

// The static builder rewrites HTML links but not SVG <image href>
// (the world-map node thumbnails) — prefix the deploy base for
// subpath deployments (no-op when the base is "/").
Array.prototype.forEach.call(svg.querySelectorAll('image'), function (im) {
var h = im.getAttribute('href') || im.getAttribute('xlink:href');
if (h) im.setAttribute('href', withRoot(h));
});

// Wheel = zoom (no key needed). The listener is non-passive so the
// page scroll is suppressed while the pointer is over the map.
wrap.addEventListener('wheel', function (e) {
Expand Down Expand Up @@ -503,7 +524,7 @@
var cur = null, closeTimer = 0, inPop = false;

// mirror of the generator's DisplayName (zone_asi_a_real -> "ASI A Real")
function hrefOf(n) { return '/zones/' + n.toLowerCase().replace(/_/g, '-') + '/'; }
function hrefOf(n) { return withRoot('/zones/' + n.toLowerCase().replace(/_/g, '-') + '/'); }
// data-dests: ;-separated "display name|zone name" pairs — the
// display name is the same string the column label shows (so the
// selector entries read like the map), the zone name drives the line
Expand Down
32 changes: 24 additions & 8 deletions static/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,30 @@ function wikiSearchInit() {
});
}

// Relative path from the current page back to the site root (works under a
// subpath deployment too). The index lists site-absolute paths ('/features/x/'),
// so the root is derived from where the index itself was fetched from.
function relToRoot() {
// Site root, from the deploy base (body[data-root] = config.base_url):
// "/" locally, the full GitHub Pages URL on the project site. Only the
// PATH part matters — the site always lives on the CURRENT origin under
// that path — so both the index fetch and the result links are built
// origin-relative (a full-URL fetch would leave the origin).
var baseRoot = (document.body && document.body.getAttribute('data-root')) || '';
function rootInfo() {
if (baseRoot) {
var p = baseRoot.replace(/^https?:\/\/[^/]+/, '').replace(/\/+$/, ''); // "" | "/Wiki"
return { fetch: p + '/search_index.json', path: p || '/' };
}
// no data-root (older build): climb out of the current path
var parts = location.pathname.replace(/\/+$/, '').split('/').filter(Boolean);
return parts.map(function () { return '..'; }).concat('search_index.json').join('/');
return {
fetch: parts.map(function () { return '..'; }).concat('search_index.json').join('/'),
path: new URL(parts.map(function () { return '..'; }).join('/') + '/', location.href).pathname
};
}

function loadIndex() {
if (pages) return Promise.resolve(pages);
rootPath = new URL(relToRoot(), location.href).pathname.replace(/search_index\.json$/, '');
return fetch(relToRoot(), { cache: 'force-cache' })
var ri = rootInfo();
rootPath = ri.path;
return fetch(ri.fetch, { cache: 'force-cache' })
.then(function (r) {
if (!r.ok) throw new Error('HTTP ' + r.status);
return r.json();
Expand All @@ -38,7 +50,11 @@ function wikiSearchInit() {
pages = data.map(function (p) {
var t = p.t || '';
var d = p.d || '';
var u = rootPath + (p.u || '/').replace(/^\//, '');
// p.u is site-absolute ("/content/x/"); rootPath is "" for
// "/" (no change) or the base path ("/Wiki") to prepend.
var u = p.u || '/';
if (rootPath && rootPath !== '/' && u.indexOf(rootPath + '/') !== 0)
u = rootPath + (u.charAt(0) === '/' ? u : '/' + u);
return {
t: t, d: d, u: u,
tl: t.toLowerCase(), dl: (d + ' ' + u).toLowerCase()
Expand Down
22 changes: 21 additions & 1 deletion static/zone-map.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,18 @@
}
}

// Deploy base (body[data-root]): the SVG files carry SITE-ABSOLUTE
// links and image hrefs (written by the generator; fetched as plain
// files, so nothing rewrites them) — prefix with the base's PATH part
// for subpath deploys (no-op when the base is "/").
var ROOT = (document.body && document.body.getAttribute('data-root')) || '/';
var ROOT_PATH = ROOT.replace(/^https?:\/\/[^/]+/, '').replace(/\/+$/, ''); // "" | "/Wiki"
function withRoot(h) {
if (!h || h.charAt(0) !== '/') return h;
if (ROOT_PATH && h.indexOf(ROOT_PATH + '/') === 0) return h;
return ROOT_PATH + h;
}

function upgrade(img) {
var src = img.getAttribute('src');
fetch(src, { credentials: 'same-origin' }).then(function (r) {
Expand All @@ -63,6 +75,14 @@
svg.classList.add('zonemap');
svg.removeAttribute('width');
svg.removeAttribute('height');
// teleport links + terrain <image>s: prefix the deploy base
Array.prototype.forEach.call(svg.querySelectorAll('a[href]'), function (a) {
a.setAttribute('href', withRoot(a.getAttribute('href')));
});
Array.prototype.forEach.call(svg.querySelectorAll('image'), function (im) {
var h = im.getAttribute('href') || im.getAttribute('xlink:href');
if (h) im.setAttribute('href', withRoot(h));
});

var wrap = document.createElement('div');
wrap.className = 'zonemap-wrap zonetp-wrap';
Expand Down Expand Up @@ -112,7 +132,7 @@
}

function scan() {
var imgs = document.querySelectorAll('img[src^="/zonemaps/"]');
var imgs = document.querySelectorAll('img[src*="/zonemaps/"]');
for (var i = 0; i < imgs.length; i++) {
if (imgs[i].getAttribute('data-zonetp-init')) continue;
imgs[i].setAttribute('data-zonetp-init', '1');
Expand Down
41 changes: 26 additions & 15 deletions templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
<script src="/zone-map.js"></script>
<script src="/mermaid.min.js" defer></script>
</head>
<body{% if page and page.extra | get(key="family", default=null) %} data-zone-family="{{ page.extra | get(key="family", default=null) }}"{% endif %}>
<body data-root="{{ config.base_url }}"{% if page and page.extra | get(key="family", default=null) %} data-zone-family="{{ page.extra | get(key="family", default=null) }}"{% endif %}>
<svg width="0" height="0" style="position:absolute" aria-hidden="true">
<defs>
<symbol id="mi-start" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h13M13 6l6 6-6 6"/></symbol>
Expand Down Expand Up @@ -638,6 +638,18 @@
// bottom menu.
(function () {
var path = document.location.pathname;
// Deploy base (config.base_url, e.g. "/" locally or the full GitHub
// Pages URL). Zola rewrites every menu href to carry it, so menu
// matching compares against the pathname part and strips the origin.
var ROOT = document.body.getAttribute("data-root") || "/";
// the base's path part as a prefix: "" at the domain root, "/Wiki"
// under the Pages subpath ("" — not "/" — so concatenating
// rootPath + "/zones/..." never produces a double slash)
var rootPath = ROOT.replace(/^https?:\/\/[^/]+/, "").replace(/\/+$/, "");
function stripOrigin(h) {
var o = location.origin;
return h && h.indexOf(o) === 0 ? (h.slice(o.length) || "/") : h;
}
function norm(p) { return p.length > 1 && p[p.length - 1] === "/" ? p : p + "/"; }
// Left menu selection: same-page anchors (e.g. /zones/map/#training)
// use an exact full-href match (path + hash), so the selected anchor
Expand All @@ -653,8 +665,8 @@
// (Alpha / Beta / Gamma) instead of the plain "/zones/" prefix,
// and open Gamma's tier sub-list so the entry is reachable.
var fam = document.body.getAttribute("data-zone-family");
if (fam && path.indexOf("/zones/zone-") === 0) {
var famLink = document.querySelector('.sidenav a[href="' + "/zones/" + fam + "/" + '"]');
if (fam && path.indexOf(rootPath + "/zones/zone-") === 0) {
var famLink = document.querySelector('.sidenav a[href="' + rootPath + "/zones/" + fam + "/" + '"]');
if (famLink) {
var sub = famLink.closest("li.nav-has-sub");
if (sub && sub.classList.contains("collapsed")) {
Expand All @@ -667,7 +679,7 @@
// a gamma zone page highlights its TIER (T1–T4), not the
// Gamma head — the number ranges must stay in sync with
// ZonesMapPage.GammaTier (the nav labels carry them too).
var gm = /^\/zones\/zone-gamma-z(\d+)\/?$/.exec(path);
var gm = new RegExp("^" + rootPath + "/zones/zone-gamma-z(\\d+)/?$").exec(path);
if (gm) {
var n = parseInt(gm[1], 10);
if (n >= 106 && n <= 140) {
Expand All @@ -680,13 +692,13 @@
}
if (!best && hash.length > 1) {
document.querySelectorAll(".sidenav a[href]").forEach(function (a) {
if (a.getAttribute("href") === path + hash) best = a;
if (stripOrigin(a.getAttribute("href")) === path + hash) best = a;
});
}
if (!best) {
var bestLen = -1;
document.querySelectorAll(".sidenav a[href]").forEach(function (a) {
var href = a.getAttribute("href");
var href = stripOrigin(a.getAttribute("href"));
if (!href || href === "/") return;
var h = href[href.length - 1] === "/" ? href : href + "/";
var hit = h === norm(path) || (path.startsWith(h) && path.length > h.length);
Expand Down Expand Up @@ -731,15 +743,14 @@
target.scrollIntoView();
}
}
// bottom menu: section
var sections = ["/features/", "/content/", "/zones/"];
var here = null;
for (var i = 0; i < sections.length; i++)
if (path.startsWith(sections[i])) { here = sections[i]; break; }
if (!here && path !== "/") return;
var link = document.querySelector('.footnav a[href="' + (here || "/") + '"]');
if (!link) return;
link.classList.add("active");
// bottom menu: section (match on the pathname relative to the
// deploy base; the links carry data-section, so no href guessing)
var rel = path.indexOf(rootPath) === 0 ? path.slice(rootPath.length) : path;
var sec = (rel.match(/^\/(features|content|zones)\//) || [])[1];
var foot = sec
? document.querySelector('.footnav a[data-section="' + sec + '"]')
: (rel === "/" || rel === "" ? document.querySelector('.footnav a:not([data-section])') : null);
if (foot) foot.classList.add("active");
})();
// Left-menu scroll: restore the last position after navigation and make
// sure the highlighted entry is visible (a reset scroll must not hide the
Expand Down
60 changes: 60 additions & 0 deletions tools/rewrite_site_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/usr/bin/env python3
"""Prefix site-absolute links with the deploy base path (CI deploy step).

The site is written against the domain root (base_url = "/") so local
development and the test job see the same output. GitHub Pages serves a
PROJECT site under a subpath (openperpetuum.github.io/Wiki/), and Zola 0.23
does NOT rewrite site-absolute links ("/style.css", "/zones/...") — only
the sitemap's canonical URLs pick up base_url. This pass prefixes every
href="/..." and src="/..." in the built HTML/SVG with the base's path
part ("/Wiki") so the same build works under the subpath.

Usage: python3 tools/rewrite_site_base.py <site_dir> <base_url>
python3 tools/rewrite_site_base.py public https://openperpetuum.github.io/Wiki

Idempotent: values already carrying the path part are left alone. Protocol
links ("//cdn..."), data: URIs, hashes and relative links never match.
"""
import os
import re
import sys
from urllib.parse import urlparse

# href="..." / src="..." whose value is site-absolute ("/..." but not "//...")
ATTR = re.compile(r'((?:href|src)\s*=\s*")(/(?!/)[^"]*)(")')


def main() -> None:
if len(sys.argv) != 3:
sys.exit(__doc__)
site, base = sys.argv[1], sys.argv[2]
path = urlparse(base).path.rstrip("/")
if not path:
print(f"base {base} has no path part; nothing to rewrite")
return

def rep(m: "re.Match[str]") -> str:
# m.group(2) starts with "/" and (by the regex) not "//"
v = m.group(2)
if v.startswith(path + "/") or v == path:
return m.group(0) # already prefixed
return m.group(1) + path + v + m.group(3)

count = 0
for root, _dirs, files in os.walk(site):
for f in files:
if not (f.endswith(".html") or f.endswith(".svg")):
continue
p = os.path.join(root, f)
with open(p, encoding="utf-8") as fh:
text = fh.read()
new = ATTR.sub(rep, text)
if new != text:
count += 1
with open(p, "w", encoding="utf-8") as fh:
fh.write(new)
print(f"prefixed links in {count} files with base path {path}/")


if __name__ == "__main__":
main()
Loading