Inject nav-state script centrally #1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build-Site | ||
| on: | ||
| workflow_call: | ||
| inputs: | ||
| Settings: | ||
| type: string | ||
| description: The complete settings object. | ||
| required: true | ||
| permissions: | ||
| contents: read # to checkout the repo | ||
| jobs: | ||
| Build-Site: | ||
| name: Build-Site | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Checkout Code | ||
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | ||
| with: | ||
| persist-credentials: false | ||
| fetch-depth: 0 | ||
| - name: Checkout Process-PSModule | ||
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | ||
| with: | ||
| repository: ${{ job.workflow_repository }} | ||
| ref: ${{ job.workflow_sha }} | ||
| path: _wf | ||
| persist-credentials: false | ||
| - name: Install-PSModule | ||
| uses: ./_wf/.github/actions/Install-PSModule | ||
| - name: Download docs artifact | ||
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | ||
| with: | ||
| name: docs | ||
| path: ${{ fromJson(inputs.Settings).WorkingDirectory }}/outputs/docs | ||
| - name: Install Zensical | ||
| shell: pwsh | ||
| run: | | ||
| pip install zensical | ||
| - name: Structure site | ||
| uses: PSModule/GitHub-Script@8083ec1f733f00357ee4d0db0c6056686e483bc0 # v1.9.0 | ||
| with: | ||
| Debug: ${{ fromJson(inputs.Settings).Debug }} | ||
| Prerelease: ${{ fromJson(inputs.Settings).Prerelease }} | ||
| Verbose: ${{ fromJson(inputs.Settings).Verbose }} | ||
| Version: ${{ fromJson(inputs.Settings).Version }} | ||
| WorkingDirectory: ${{ fromJson(inputs.Settings).WorkingDirectory }} | ||
| Script: | | ||
| LogGroup "Get folder structure" { | ||
| $functionDocsFolder = New-Item -Path "outputs/site/docs/Functions" -ItemType Directory -Force | ||
| Copy-Item -Path "outputs/docs/*" -Destination "outputs/site/docs/Functions" -Recurse -Force | ||
| $moduleName = [string]::IsNullOrEmpty('${{ fromJson(inputs.Settings).Name }}') ? $env:GITHUB_REPOSITORY_NAME : '${{ fromJson(inputs.Settings).Name }}' | ||
| $ModuleSourcePath = Resolve-Path 'src' | Select-Object -ExpandProperty Path | ||
| $SiteOutputPath = Resolve-Path 'outputs/site' | Select-Object -ExpandProperty Path | ||
| Write-Host "Function Docs Folder: $functionDocsFolder" | ||
| Write-Host "Module Name: $moduleName" | ||
| Write-Host "Module Source Path: $ModuleSourcePath" | ||
| Write-Host "Site Output Path: $SiteOutputPath" | ||
| } | ||
| LogGroup "Get folder structure" { | ||
| Get-ChildItem -Recurse | Select-Object -ExpandProperty FullName | Sort-Object | Format-List | ||
| } | ||
| Get-ChildItem -Path $functionDocsFolder -Recurse -Force -Include '*.md' | ForEach-Object { | ||
| $fileName = $_.Name | ||
| LogGroup " - $fileName" { | ||
| Show-FileContent -Path $_ | ||
| } | ||
| } | ||
| LogGroup 'Build docs - Process about topics' { | ||
| $aboutDocsFolderPath = Join-Path -Path $SiteOutputPath -ChildPath 'docs/About' | ||
| $aboutDocsFolder = New-Item -Path $aboutDocsFolderPath -ItemType Directory -Force | ||
| $aboutSourceFolder = Get-ChildItem -Path $ModuleSourcePath -Directory | Where-Object { $_.Name -eq 'en-US' } | ||
| Get-ChildItem -Path $aboutSourceFolder -Filter *.txt | Copy-Item -Destination $aboutDocsFolder -Force -Verbose -PassThru | | ||
| Rename-Item -NewName { $_.Name -replace '.txt', '.md' } | ||
| Write-Host "About Docs Folder: $aboutDocsFolder" | ||
| Write-Host "About Source Folder: $aboutSourceFolder" | ||
| } | ||
| LogGroup 'Build docs - Copy icon to assets' { | ||
| $assetsFolderPath = Join-Path -Path $SiteOutputPath -ChildPath 'docs/Assets' | ||
| $assetsFolder = New-Item -Path $assetsFolderPath -ItemType Directory -Force | ||
| $rootPath = Split-Path -Path $ModuleSourcePath -Parent | ||
| $iconPath = Resolve-Path 'icon\icon.png' | Select-Object -ExpandProperty Path | ||
| Copy-Item -Path $iconPath -Destination $assetsFolder -Force -Verbose | ||
| Write-Host "Assets Folder: $assetsFolder" | ||
| Write-Host "Icon Path: $iconPath" | ||
| } | ||
| LogGroup 'Build docs - Copy readme.md' { | ||
| $readmePath = Resolve-Path 'README.md' | Select-Object -ExpandProperty Path | ||
| $readmeTargetPath = Join-Path -Path $SiteOutputPath -ChildPath 'docs/README.md' | ||
| Copy-Item -Path $readmePath -Destination $readmeTargetPath -Force -Verbose | ||
| Write-Host "Readme Path: $readmePath" | ||
| Write-Host "Readme Target Path: $readmeTargetPath" | ||
| } | ||
| LogGroup 'Build docs - Create docs config' { | ||
| $rootPath = Split-Path -Path $ModuleSourcePath -Parent | ||
| $possiblePaths = @( | ||
| '.github/zensical.toml', | ||
| 'docs/zensical.toml', | ||
| 'zensical.toml' | ||
| ) | ||
| $docsConfigSourcePath = $null | ||
| foreach ($path in $possiblePaths) { | ||
| $candidatePath = Join-Path -Path $rootPath -ChildPath $path | ||
| if (Test-Path -Path $candidatePath) { | ||
| $docsConfigSourcePath = $candidatePath | ||
| break | ||
| } | ||
| } | ||
| if (-not $docsConfigSourcePath) { | ||
| throw "Documentation config file not found. Expected one of: $($possiblePaths -join ', ')" | ||
| } | ||
| $docsConfigFileName = [System.IO.Path]::GetFileName($docsConfigSourcePath) | ||
| $docsConfigTargetPath = Join-Path -Path $SiteOutputPath -ChildPath $docsConfigFileName | ||
| Write-Host "Docs Config Source: $docsConfigSourcePath" | ||
| Write-Host "Docs Config Target: $docsConfigTargetPath" | ||
| $docsConfigContent = Get-Content -Path $docsConfigSourcePath -Raw | ||
| $docsConfigContent = $docsConfigContent.Replace('-{{ REPO_NAME }}-', $ModuleName) | ||
| $docsConfigContent = $docsConfigContent.Replace('-{{ REPO_OWNER }}-', $env:GITHUB_REPOSITORY_OWNER) | ||
| if ($docsConfigFileName -eq 'zensical.toml') { | ||
| if ($docsConfigContent -match '(?m)^\s*site_dir\s*=') { | ||
| $docsConfigContent = $docsConfigContent -replace '(?m)^\s*site_dir\s*=.*$', 'site_dir = "_site"' | ||
| } else { | ||
| $docsConfigContent = $docsConfigContent -replace '(?m)^\[project\]\s*$', "[project]`nsite_dir = ""_site""" | ||
| } | ||
| } | ||
| $docsConfigContent | Set-Content -Path $docsConfigTargetPath -Force | ||
| Write-Host "Build Config Type: $docsConfigFileName" | ||
| Show-FileContent -Path $docsConfigTargetPath | ||
| } | ||
| - name: Build documentation site with Zensical | ||
| working-directory: ${{ fromJson(inputs.Settings).WorkingDirectory }}/outputs/site | ||
| shell: pwsh | ||
| run: | | ||
| if (-not (Test-Path -Path 'zensical.toml')) { | ||
| throw "No documentation config file found in outputs/site. Expected zensical.toml." | ||
| } | ||
| $configFile = 'zensical.toml' | ||
| LogGroup 'Build docs - Zensical config content' { | ||
| Write-Host "Using config: $configFile" | ||
| Show-FileContent -Path $configFile | ||
| } | ||
| LogGroup 'Build docs - Zensical build' { | ||
| zensical build --config-file $configFile | ||
| } | ||
| LogGroup 'Build docs - verify output' { | ||
| if (Test-Path -Path '_site') { | ||
| if (Test-Path -Path '../../_site') { | ||
| Remove-Item -Path '../../_site' -Recurse -Force | ||
| } | ||
| Move-Item -Path '_site' -Destination '../../_site' -Force | ||
| } | ||
| if (-not (Test-Path -Path '../../_site')) { | ||
| throw "Expected Zensical output at ../../_site but it was not created." | ||
| } | ||
| $navStateScript = @' | ||
| (() => { | ||
| const storageKey = "zensical-nav-state-v1"; | ||
| let lastSignature = ""; | ||
| const getToggles = () => | ||
| Array.from(document.querySelectorAll("input.md-nav__toggle.md-toggle[id]")); | ||
| const getPrimaryList = () => | ||
| document.querySelector("nav.md-nav--primary > ul.md-nav__list"); | ||
| const applyDefaultTopLevelState = (toggles) => { | ||
| const primaryList = getPrimaryList(); | ||
| if (!primaryList) { | ||
| return; | ||
| } | ||
| const topLevelToggles = Array.from( | ||
| primaryList.querySelectorAll( | ||
| ":scope > li > input.md-nav__toggle.md-toggle[id]" | ||
| ) | ||
| ); | ||
| const topLevelIds = new Set(topLevelToggles.map((toggle) => toggle.id)); | ||
| for (const toggle of toggles) { | ||
| toggle.checked = topLevelIds.has(toggle.id); | ||
| } | ||
| }; | ||
| const restoreState = (toggles) => { | ||
| const raw = localStorage.getItem(storageKey); | ||
| if (!raw) { | ||
| applyDefaultTopLevelState(toggles); | ||
| return; | ||
| } | ||
| try { | ||
| const state = JSON.parse(raw); | ||
| for (const toggle of toggles) { | ||
| if (Object.prototype.hasOwnProperty.call(state, toggle.id)) { | ||
| toggle.checked = !!state[toggle.id]; | ||
| } | ||
| } | ||
| } catch { | ||
| applyDefaultTopLevelState(toggles); | ||
| } | ||
| }; | ||
| const persistState = (toggles) => { | ||
| const state = {}; | ||
| for (const toggle of toggles) { | ||
| state[toggle.id] = !!toggle.checked; | ||
| } | ||
| localStorage.setItem(storageKey, JSON.stringify(state)); | ||
| }; | ||
| const initialize = () => { | ||
| const toggles = getToggles(); | ||
| if (toggles.length === 0) { | ||
| return; | ||
| } | ||
| const signature = toggles.map((toggle) => toggle.id).join("|"); | ||
| if (signature === lastSignature) { | ||
| return; | ||
| } | ||
| lastSignature = signature; | ||
| restoreState(toggles); | ||
| for (const toggle of toggles) { | ||
| if (toggle.dataset.navStateBound === "true") { | ||
| continue; | ||
| } | ||
| toggle.dataset.navStateBound = "true"; | ||
| toggle.addEventListener("change", () => persistState(getToggles())); | ||
| } | ||
| }; | ||
| if (document.readyState === "loading") { | ||
| document.addEventListener("DOMContentLoaded", initialize, { once: true }); | ||
| } else { | ||
| initialize(); | ||
| } | ||
| setInterval(initialize, 500); | ||
| })(); | ||
| '@ | ||
| Get-ChildItem -Path '../../_site' -Filter '*.html' -Recurse | ForEach-Object { | ||
| $html = Get-Content -Path $_.FullName -Raw | ||
| if ($html -notmatch 'zensical-nav-state-v1') { | ||
| $html = $html -replace '</body>', "<script>$navStateScript</script>`n</body>" | ||
| Set-Content -Path $_.FullName -Value $html -NoNewline | ||
| } | ||
| } | ||
| } | ||
| - uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 | ||
| with: | ||
| name: github-pages | ||
| path: ${{ fromJson(inputs.Settings).WorkingDirectory }}/_site | ||
| retention-days: 1 | ||