Skip to content

Commit d5deb4d

Browse files
Extract site script injection action
Move long inline PowerShell from Build-Site into a dedicated local action with src/inject-site-scripts.ps1, and switch to folder-based site injector scripts under .github/scripts/site-injectors/. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent 528fed2 commit d5deb4d

4 files changed

Lines changed: 154 additions & 10 deletions

File tree

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
name: Inject-SiteScripts
2+
description: Inject shared JavaScript snippets into generated site HTML files.
3+
4+
inputs:
5+
SitePath:
6+
description: Path to the generated site output directory.
7+
required: true
8+
WorkflowPath:
9+
description: Path to the checked out workflow repository root.
10+
required: false
11+
default: _wf
12+
13+
runs:
14+
using: composite
15+
steps:
16+
- name: Inject site scripts
17+
shell: pwsh
18+
run: |
19+
& "${{ github.action_path }}/src/inject-site-scripts.ps1" `
20+
-SitePath "${{ inputs.SitePath }}" `
21+
-WorkflowPath "${{ inputs.WorkflowPath }}"
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
param(
2+
[Parameter(Mandatory)]
3+
[string]$SitePath,
4+
5+
[Parameter(Mandatory)]
6+
[string]$WorkflowPath
7+
)
8+
9+
$resolvedSitePath = Resolve-Path -Path $SitePath -ErrorAction Stop | Select-Object -ExpandProperty Path
10+
$injectorsPath = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath "$WorkflowPath/.github/scripts/site-injectors"
11+
12+
if (-not (Test-Path -Path $injectorsPath)) {
13+
throw "Expected site injector folder at $injectorsPath but it was not found."
14+
}
15+
16+
$injectorScripts = Get-ChildItem -Path $injectorsPath -File -Filter '*.js' | Sort-Object -Property Name
17+
if (-not $injectorScripts) {
18+
Write-Host "No site injector scripts found under $injectorsPath."
19+
exit 0
20+
}
21+
22+
Get-ChildItem -Path $resolvedSitePath -Filter '*.html' -Recurse | ForEach-Object {
23+
$html = Get-Content -Path $_.FullName -Raw
24+
$modified = $false
25+
26+
foreach ($injectorScript in $injectorScripts) {
27+
$marker = "data-psmodule-site-injector=""$($injectorScript.Name)"""
28+
if ($html -match [Regex]::Escape($marker)) {
29+
continue
30+
}
31+
32+
$scriptContent = Get-Content -Path $injectorScript.FullName -Raw
33+
$injectedScript = "<script $marker>$scriptContent</script>"
34+
$html = $html -replace '</body>', "$injectedScript`n</body>"
35+
$modified = $true
36+
}
37+
38+
if ($modified) {
39+
Set-Content -Path $_.FullName -Value $html -NoNewline
40+
}
41+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
(() => {
2+
const storageKey = "zensical-nav-state-v1";
3+
let lastSignature = "";
4+
5+
const getToggles = () =>
6+
Array.from(document.querySelectorAll("input.md-nav__toggle.md-toggle[id]"));
7+
8+
const getPrimaryList = () =>
9+
document.querySelector("nav.md-nav--primary > ul.md-nav__list");
10+
11+
const applyDefaultTopLevelState = (toggles) => {
12+
const primaryList = getPrimaryList();
13+
if (!primaryList) {
14+
return;
15+
}
16+
17+
const topLevelToggles = Array.from(
18+
primaryList.querySelectorAll(
19+
":scope > li > input.md-nav__toggle.md-toggle[id]"
20+
)
21+
);
22+
const topLevelIds = new Set(topLevelToggles.map((toggle) => toggle.id));
23+
24+
for (const toggle of toggles) {
25+
toggle.checked = topLevelIds.has(toggle.id);
26+
}
27+
};
28+
29+
const restoreState = (toggles) => {
30+
const raw = localStorage.getItem(storageKey);
31+
if (!raw) {
32+
applyDefaultTopLevelState(toggles);
33+
return;
34+
}
35+
36+
try {
37+
const state = JSON.parse(raw);
38+
for (const toggle of toggles) {
39+
if (Object.prototype.hasOwnProperty.call(state, toggle.id)) {
40+
toggle.checked = !!state[toggle.id];
41+
}
42+
}
43+
} catch {
44+
applyDefaultTopLevelState(toggles);
45+
}
46+
};
47+
48+
const persistState = (toggles) => {
49+
const state = {};
50+
for (const toggle of toggles) {
51+
state[toggle.id] = !!toggle.checked;
52+
}
53+
localStorage.setItem(storageKey, JSON.stringify(state));
54+
};
55+
56+
const initialize = () => {
57+
const toggles = getToggles();
58+
if (toggles.length === 0) {
59+
return;
60+
}
61+
62+
const signature = toggles.map((toggle) => toggle.id).join("|");
63+
if (signature === lastSignature) {
64+
return;
65+
}
66+
67+
lastSignature = signature;
68+
restoreState(toggles);
69+
for (const toggle of toggles) {
70+
if (toggle.dataset.navStateBound === "true") {
71+
continue;
72+
}
73+
74+
toggle.dataset.navStateBound = "true";
75+
toggle.addEventListener("change", () => persistState(getToggles()));
76+
}
77+
};
78+
79+
if (document.readyState === "loading") {
80+
document.addEventListener("DOMContentLoaded", initialize, { once: true });
81+
} else {
82+
initialize();
83+
}
84+
85+
setInterval(initialize, 500);
86+
})();

.github/workflows/Build-Site.yml

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -179,18 +179,14 @@ jobs:
179179
if (-not (Test-Path -Path '../../_site')) {
180180
throw "Expected Zensical output at ../../_site but it was not created."
181181
}
182-
183-
$navStateScript = '(()=>{const k="zensical-nav-state-v1";let s="";const g=()=>Array.from(document.querySelectorAll("input.md-nav__toggle.md-toggle[id]"));const p=()=>document.querySelector("nav.md-nav--primary > ul.md-nav__list");const d=t=>{const l=p();if(!l)return;const n=new Set(Array.from(l.querySelectorAll(":scope > li > input.md-nav__toggle.md-toggle[id]")).map(x=>x.id));for(const x of t){x.checked=n.has(x.id)}};const r=t=>{const raw=localStorage.getItem(k);if(!raw){d(t);return;}try{const m=JSON.parse(raw);for(const x of t){if(Object.prototype.hasOwnProperty.call(m,x.id))x.checked=!!m[x.id];}}catch{d(t)}};const w=t=>{const m={};for(const x of t){m[x.id]=!!x.checked;}localStorage.setItem(k,JSON.stringify(m));};const i=()=>{const t=g();if(t.length===0)return;const sig=t.map(x=>x.id).join("|");if(sig===s)return;s=sig;r(t);for(const x of t){if(x.dataset.navStateBound==="true")continue;x.dataset.navStateBound="true";x.addEventListener("change",()=>w(g()));}};if(document.readyState==="loading"){document.addEventListener("DOMContentLoaded",i,{once:true});}else{i();}setInterval(i,500);})();'
184-
185-
Get-ChildItem -Path '../../_site' -Filter '*.html' -Recurse | ForEach-Object {
186-
$html = Get-Content -Path $_.FullName -Raw
187-
if ($html -notmatch 'zensical-nav-state-v1') {
188-
$html = $html -replace '</body>', "<script>$navStateScript</script>`n</body>"
189-
Set-Content -Path $_.FullName -Value $html -NoNewline
190-
}
191-
}
192182
}
193183
184+
- name: Inject shared site scripts
185+
uses: ./_wf/.github/actions/Inject-SiteScripts
186+
with:
187+
SitePath: ${{ fromJson(inputs.Settings).WorkingDirectory }}/_site
188+
WorkflowPath: _wf
189+
194190
- uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
195191
with:
196192
name: github-pages

0 commit comments

Comments
 (0)