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
3 changes: 2 additions & 1 deletion .doxygen/Doxyfile
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ HTML_EXTRA_STYLESHEET = .doxygen/doxygen-awesome/doxygen-awesome.css \
.doxygen/custom-layout-fixes.css \
.doxygen/lootlocker-theme.css
HTML_EXTRA_FILES = .doxygen/images/lootlocker-logo.png \
.doxygen/nav-customization.js
.doxygen/nav-customization.js \
.doxygen/version-picker.js

#---------------------------------------------------------------------------
# Disable unused outputs
Expand Down
1 change: 1 addition & 0 deletions .doxygen/header.html
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@
}
}
</script>
<script type="text/javascript" src="$relpath^version-picker.js" defer="defer"></script>

<!--BEGIN FULL_SIDEBAR-->
<div id="side-nav" class="ui-resizable side-nav-resizable"><!-- do not remove this div, it is closed by doxygen! -->
Expand Down
34 changes: 32 additions & 2 deletions .doxygen/lootlocker-theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,9 @@ html.dark-mode {
margin-right: 4px;
}

/* ── Version badge ── */
#ll-topnav-version {
/* ── Version badge + version picker ── */
#ll-topnav-version,
#ll-version-picker {
font-size: 11px;
font-weight: 500;
color: var(--ll-nav-muted);
Expand All @@ -177,6 +178,35 @@ html.dark-mode {
margin-right: 2px;
}

#ll-version-picker {
appearance: none;
-webkit-appearance: none;
background: transparent;
border: 1px solid color-mix(in srgb, var(--ll-nav-muted) 40%, transparent);
border-radius: 4px;
padding: 1px 18px 1px 6px;
cursor: pointer;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23888'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 5px center;
background-size: 8px 5px;
}

#ll-version-picker:hover {
border-color: var(--ll-nav-muted);
color: var(--ll-nav-fg);
}

#ll-version-picker:focus {
outline: 2px solid var(--ll-accent, #7c5cbf);
outline-offset: 1px;
}

#ll-version-picker option {
background: var(--ll-nav-bg, #1e1e2e);
color: var(--ll-nav-fg, #e0e0e0);
}

/* ── GitHub icon link ── */
#ll-topnav-github {
display: inline-flex;
Expand Down
115 changes: 115 additions & 0 deletions .doxygen/version-picker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/**
* version-picker.js — LootLocker Unreal SDK
*
* Replaces the static #ll-topnav-version span with a <select> dropdown
* that lets users navigate between published doc versions.
*
* The list of available versions is fetched from versions.json at the root
* of the published site (one level above the current version directory).
* Falls back gracefully to the static badge when versions.json is unavailable
* (e.g. local Doxygen builds).
*
* Expected versions.json shape:
* [
* { "version": "latest", "path": "latest/" },
* { "version": "v2.5.0", "path": "v2.5.0/" }
* ]
*
* URL resolution:
* The script detects the current version directory by looking at the last
* path segment of the page URL and resolves versions.json relative to the
* site root (two levels up from the current page, one for the HTML file
* and one for the version directory).
*/
(function () {
'use strict';

/** Normalize pathname: treat a trailing slash as /index.html so that
* directory-style URLs (e.g. /latest/) are handled correctly. */
function normalizePath(pathname) {
return pathname.endsWith('/') ? pathname + 'index.html' : pathname;
}

/** Fetch versions.json by trying candidate site roots 2 then 3 path segments
* above the current page. This covers both the common Doxygen layout
* (/<repo>/<version>/page.html) and the search subdirectory
* (/<repo>/<version>/search/page.html).
* Resolves with { root, versions } on success. */
function fetchVersions() {
var loc = window.location;
var parts = normalizePath(loc.pathname).split('/');
var tried = Promise.reject(new Error('no candidates'));
for (var strip = 2; strip <= 3; strip++) {
(function (s) {
if (parts.length > s) {
var rootPath = parts.slice(0, parts.length - s).join('/') + '/';
var root = loc.protocol + '//' + loc.host + rootPath;
tried = tried.catch(function () {
return fetch(root + 'versions.json', { cache: 'no-cache' })
.then(function (r) {
if (!r.ok) throw new Error('versions.json not found at ' + root);
return r.json().then(function (v) { return { root: root, versions: v }; });
});
});
}
})(strip);
}
return tried;
}

function buildPicker(result) {
var badge = document.getElementById('ll-topnav-version');
if (!badge) return;

var loc = window.location;
var root = result.root;
var versions = result.versions;

// Derive the version dir and page path relative to it from the current URL
var rootPath = new URL(root).pathname; // e.g. '/unreal-sdk/'
var relative = normalizePath(loc.pathname).slice(rootPath.length); // e.g. 'latest/foo.html'
var slash = relative.indexOf('/');
var currentVersionPath = slash !== -1 ? relative.slice(0, slash + 1) : 'latest/';
var page = slash !== -1 ? relative.slice(slash + 1) : 'index.html';
var suffix = loc.search + loc.hash; // preserve query string and anchor

var select = document.createElement('select');
select.id = 'll-version-picker';
select.setAttribute('aria-label', 'Select documentation version');
select.title = 'Switch version';

versions.forEach(function (entry) {
var opt = document.createElement('option');
opt.value = root + entry.path + page + suffix;
opt.textContent = entry.version;
if (entry.path === currentVersionPath) {
opt.selected = true;
}
select.appendChild(opt);
});

select.addEventListener('change', function () {
window.location.href = select.value;
});
Comment thread
kirre-bylund marked this conversation as resolved.

badge.parentNode.replaceChild(select, badge);
}

function init() {
fetchVersions()
.then(function (result) {
if (Array.isArray(result.versions) && result.versions.length > 0) {
buildPicker(result);
}
})
.catch(function () {
// Graceful fallback — keep the static badge as-is
});
}

if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
}());
129 changes: 129 additions & 0 deletions .github/workflows/generate-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,132 @@ jobs:
run: gh release upload --clobber ${{ github.ref_name }} docs-${{ github.ref_name }}.zip
env:
GH_TOKEN: ${{ github.token }}

deploy-pages:
name: Deploy docs to GitHub Pages
runs-on: ubuntu-latest
needs: generate-docs
# Deploy on release or manual dispatch only
if: github.event_name == 'release' || github.event_name == 'workflow_dispatch'
permissions:
Comment thread
kirre-bylund marked this conversation as resolved.
contents: write

steps:
- name: Download docs artifact
uses: actions/download-artifact@v4
with:
name: unreal-sdk-docs-${{ needs.generate-docs.outputs.sdk-version }}
path: site/

- name: Checkout gh-pages branch
uses: actions/checkout@v4
with:
ref: gh-pages
path: gh-pages-branch
continue-on-error: true # first run — branch may not exist yet

- name: Determine deploy target(s)
id: targets
run: |
if [ "${{ github.event_name }}" = "release" ]; then
echo "target_dir=${{ github.ref_name }}" >> "$GITHUB_OUTPUT"
echo "deploy_latest=true" >> "$GITHUB_OUTPUT"
echo "is_release=true" >> "$GITHUB_OUTPUT"
else
BRANCH="${{ github.ref_name }}"
if [ "$BRANCH" = "main" ]; then
echo "target_dir=latest" >> "$GITHUB_OUTPUT"
else
SAFE_BRANCH="branch-$(echo "$BRANCH" | tr '/' '-')"
echo "target_dir=$SAFE_BRANCH" >> "$GITHUB_OUTPUT"
fi
echo "deploy_latest=false" >> "$GITHUB_OUTPUT"
echo "is_release=false" >> "$GITHUB_OUTPUT"
fi
Comment thread
kirre-bylund marked this conversation as resolved.

- name: Build versions.json
run: |
python3 - << 'PYEOF'
import json, os, re

existing_path = "gh-pages-branch/versions.json"
versions = []
if os.path.exists(existing_path):
with open(existing_path) as f:
versions = json.load(f)

target_dir = os.environ.get("TARGET_DIR", "")
is_release = os.environ.get("IS_RELEASE", "false") == "true"

# Update or add the new entry
paths = {v["path"] for v in versions}
if target_dir and target_dir + "/" not in paths:
versions.append({"version": target_dir, "path": target_dir + "/"})

def semver_key(v):
m = re.match(r"v?(\d+)\.(\d+)\.(\d+)", v["version"])
return tuple(int(x) for x in m.groups()) if m else (0, 0, 0)

if is_release:
# latest always first, release versions desc, branch previews last
release_vs = [v for v in versions if v["version"] != "latest" and not v["version"].startswith("branch-")]
branch_vs = [v for v in versions if v["version"].startswith("branch-")]
release_vs.sort(key=semver_key, reverse=True)
versions = [{"version": "latest", "path": "latest/"}] + release_vs + branch_vs
else:
# Dispatch: always include latest entry; branch previews at end
latest_entry = next((v for v in versions if v["version"] == "latest"), {"version": "latest", "path": "latest/"})
release_vs = [v for v in versions if v["version"] != "latest" and not v["version"].startswith("branch-")]
branch_vs = [v for v in versions if v["version"].startswith("branch-")]
release_vs.sort(key=semver_key, reverse=True)
versions = [latest_entry] + release_vs + branch_vs

os.makedirs("site-root", exist_ok=True)
with open("site-root/versions.json", "w") as f:
json.dump(versions, f, indent=2)
print("versions.json:", json.dumps(versions, indent=2))
PYEOF
env:
TARGET_DIR: ${{ steps.targets.outputs.target_dir }}
IS_RELEASE: ${{ steps.targets.outputs.is_release }}

- name: Build root redirect page
run: |
cat > site-root/index.html << 'EOF'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta http-equiv="refresh" content="0;url=latest/"/>
<title>LootLocker Unreal SDK — Reference Docs</title>
Comment thread
kirre-bylund marked this conversation as resolved.
</head>
<body>
<p>Redirecting to <a href="latest/">latest documentation</a>…</p>
</body>
</html>
EOF

- name: Deploy to target directory
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./site
destination_dir: ${{ steps.targets.outputs.target_dir }}
keep_files: true

- name: Deploy to latest (release only)
if: steps.targets.outputs.deploy_latest == 'true'
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./site
destination_dir: latest
keep_files: true

- name: Deploy site root (versions.json + index.html)
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./site-root
destination_dir: .
keep_files: true
Loading