diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 464ae9c..0a64b34 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -16,5 +16,15 @@ RUN HUGO_VERSION="$(cat /tmp/.hugo-version)" \ && apt-get install /tmp/hugo.deb -y \ && rm /tmp/hugo.deb /tmp/.hugo-version +# Install Node.js (needed to `npm ci` the FontAwesome/Bootstrap build-time +# deps in themes/pydata). Version is pinned in .node-version at the repo +# root — the same file CI's actions/setup-node reads — so the devcontainer +# and GitHub Actions never drift apart. +COPY .node-version /tmp/.node-version +RUN NODE_VERSION="$(cat /tmp/.node-version)" \ + && curl -fsSL "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz" -o /tmp/node.tar.xz \ + && tar -xJf /tmp/node.tar.xz --strip-components=1 -C /usr/local \ + && rm /tmp/node.tar.xz /tmp/.node-version + # Help avoid "unsupported locale setting" in Sphinx RUN echo "export LC_ALL=C.UTF-8" > ~/.bashrc diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index abe7bc6..c59e473 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -17,17 +17,18 @@ }, // Features to add to the dev container. More info: https://containers.dev/features. - // "features": { - // node / npm is required for markdownlint-cli2 - // By default, installs lts version - // "ghcr.io/devcontainers/features/node:1": {} - // }, + // Node.js itself is installed directly in the Dockerfile (pinned via the + // root .node-version file), not via this feature — avoids two divergent + // install paths. + // "features": {}, // Use 'forwardPorts' to make a list of ports inside the container available locally. // "forwardPorts": [], - // Use 'postCreateCommand' to run commands after the container is created. - // "postCreateCommand": "" + // Install the theme's build-time npm deps (FontAwesome/Bootstrap) after + // the workspace is mounted — a Dockerfile RUN step would get shadowed by + // the workspace bind mount, since node_modules isn't part of the repo. + "postCreateCommand": "cd themes/pydata && npm ci", // There is also a postStartCommand that executes every time the container starts. // The parameters behave exactly like postCreateCommand, but the commands execute on start rather than create. diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 27fcccc..8974130 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -49,6 +49,15 @@ jobs: uses: actions/checkout@v4 - name: Install Hugo CLI uses: ./.github/actions/setup-hugo + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.node-version' + cache: 'npm' + cache-dependency-path: themes/pydata/package-lock.json + - name: Install npm dependencies + working-directory: themes/pydata + run: npm ci - name: Setup Pages id: pages uses: actions/configure-pages@v5 diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index 3a4aa3f..497db6d 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -27,6 +27,15 @@ jobs: uses: actions/checkout@v4 - name: Install Hugo CLI uses: ./.github/actions/setup-hugo + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.node-version' + cache: 'npm' + cache-dependency-path: themes/pydata/package-lock.json + - name: Install npm dependencies + working-directory: themes/pydata + run: npm ci - name: Build with Hugo env: HUGO_CACHEDIR: ${{ runner.temp }}/hugo_cache diff --git a/.gitignore b/.gitignore index dfb1e1c..b2cf406 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,5 @@ static/images/.DS_Store .env.* *.pem *.key +sandbox/ +node_modules/ diff --git a/.node-version b/.node-version new file mode 100644 index 0000000..ba33190 --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +20.19.2 diff --git a/README.md b/README.md index 15e6049..cd6eb0c 100644 --- a/README.md +++ b/README.md @@ -8,12 +8,17 @@ This repository is for the main static site of https://saltproject.io, built wit - The exact version pinned for this project is tracked in the root `.hugo-version` file. The devcontainer and CI both install that exact version automatically (see `.devcontainer/Dockerfile` and `.github/actions/setup-hugo`), so it only needs to be updated in one place. - Must be the `extended` version, and at least the minimum declared in the vendored `themes/pydata` theme's `hugo.toml` (`module.hugoVersion.min`). - This was built with the `extended` version of Hugo, which is required. +- Node.js — version pinned in the root `.node-version` file, same single-source-of-truth pattern as `.hugo-version`. Needed to install the theme's FontAwesome/Bootstrap build-time dependencies (see [FontAwesome and Bootstrap](#fontawesome-and-bootstrap-npm-at-build-time)). - Git - Python 3.14+ (only needed to run `scripts/validate-tags.py` locally) ### Build a local preview ```bash +# Install the theme's build-time npm dependencies (FontAwesome/Bootstrap) — +# only needs to be re-run when themes/pydata/package.json changes. +cd themes/pydata && npm ci && cd - + # Serves for viewing changes locally # Dynamically loads updates when changes happen in repo hugo serve @@ -195,6 +200,14 @@ The `scripts` folder holds standalone helper scripts used in local development a - `.github/workflows/gh-pages.yml` — builds the site on every push, and deploys it to GitHub Pages when the push is a tag matching `v**` (see [Push the current `main` branch to the live site](#push-the-current-main-branch-to-the-live-site)). Deploy-time (`pages`/`id-token` write) permissions are scoped to just the `deploy` job; it never triggers on `pull_request`. - `.github/actions/setup-hugo` — local composite action shared by both workflows above to install the Hugo CLI. Defaults to the version pinned in the root `.hugo-version` file — the same file the devcontainer's `Dockerfile` reads — so the required Hugo version only needs to be maintained in that one place. +### FontAwesome and Bootstrap (npm, at build time) + +`themes/pydata` doesn't vendor FontAwesome or Bootstrap as committed files. `themes/pydata/package.json` pins the exact versions (FontAwesome's JS+SVG icon kit, Bootstrap's JS bundle); Hugo's `[[module.mounts]]` (in `themes/pydata/hugo.toml`) mounts the relevant prebuilt files straight out of `node_modules` into the asset pipeline, and `themes/pydata/layouts/_partials/head/{fontawesome,bootstrap-js}.html` load them through Hugo Pipes (fingerprinted in production builds). This means: + +- You need to run `npm ci` inside `themes/pydata` before `hugo build`/`hugo server` will work — the devcontainer does this automatically via `postCreateCommand`; CI does it via an `actions/setup-node` + `npm ci` step in both workflows. +- Their license text isn't duplicated anywhere in this repo — it ships inside the respective (gitignored) npm packages under `themes/pydata/node_modules/.../LICENSE*`. +- The Node.js version used for this is pinned in the root `.node-version` file, following the same single-source-of-truth pattern as `.hugo-version`. + ## Credits The `themes/pydata` theme vendored in this repository is a Hugo port of the [PyData Sphinx Theme](https://github.com/pydata/pydata-sphinx-theme) ([docs](https://pydata-sphinx-theme.readthedocs.io/en/stable/)), originally built for Sphinx documentation sites. Credit to the PyData Sphinx Theme authors and contributors for the design and functionality this port is based on. diff --git a/hugo.toml b/hugo.toml index cc75298..406caad 100644 --- a/hugo.toml +++ b/hugo.toml @@ -51,6 +51,14 @@ theme = 'pydata' image_dark = 'images/SaltProject_altlogo_teal.png' alt_text = 'Salt Project - Home' +# --------------------------------------------------------------------------- +# External links — any link pointing off-site opens in a new tab, except +# base URLs listed under `exceptions` (matched by prefix). +# --------------------------------------------------------------------------- +[params.external_links] + new_tab = true + exceptions = ['https://docs.saltproject.io/'] + [params.landing] title = "Welcome to the Salt Project" description = "Salt is an open-source automation framework for remote execution, configuration management, and infrastructure orchestration — used by teams to manage thousands of systems from a single control plane." @@ -60,31 +68,37 @@ theme = 'pydata' title = "Install Salt" description = "Step-by-step instructions for every platform and operating system." url = "https://docs.saltproject.io/salt/install-guide/en/latest/" + icon = "fa-solid fa-download" [[params.landing.nav_boxes]] title = "User Guide" description = "Learn Salt's core concepts: states, grains, pillars, and more." url = "https://docs.saltproject.io/salt/user-guide/en/latest/" + icon = "fa-solid fa-book" [[params.landing.nav_boxes]] title = "Reference Docs" description = "Full module and API reference for Salt." url = "https://docs.saltproject.io/en/latest/contents.html" + icon = "fa-solid fa-code" [[params.landing.nav_boxes]] title = "Blog" description = "Release announcements, community updates, and Salt news." url = "/blog/" + icon = "fa-solid fa-newspaper" [[params.landing.nav_boxes]] title = "Discord Community" description = "Join the Salt Project Discord, attend events, and connect with contributors." url = "https://discord.gg/J7b7EscrAs" + icon = "fa-brands fa-discord" [[params.landing.nav_boxes]] title = "GitHub" description = "Browse the source, open issues, and contribute to Salt." url = "https://github.com/saltstack/salt" + icon = "fa-brands fa-github" [[params.icon_links]] name = 'Discord' diff --git a/layouts/community/event-calendar.html b/layouts/community/event-calendar.html index 20dac6c..bf94481 100644 --- a/layouts/community/event-calendar.html +++ b/layouts/community/event-calendar.html @@ -27,9 +27,10 @@

{{ .Title }}

document.getElementById("calendar_container").src = pref + encodeURIComponent(timezone.name().replace(" ", "")); + {{- $icsURL := "https://calendar.google.com/calendar/ical/salt.project%40broadcom.com/public/basic.ics" }}

Download the ics: - + Salt Project Community Calendar ics

diff --git a/layouts/community/working-groups.html b/layouts/community/working-groups.html index 109a030..4b0b65f 100644 --- a/layouts/community/working-groups.html +++ b/layouts/community/working-groups.html @@ -19,10 +19,11 @@

{{ .Title }}

{{ range site.Params.working_groups }} + {{ $captainURL := printf "https://github.com/%s" .captain_handle }} {{ .title }} {{ .description }} - {{ .captain }} + {{ .captain }} {{ .discord_channel }} Community calendar diff --git a/layouts/index.html b/layouts/index.html index 4e00a41..ccf0801 100644 --- a/layouts/index.html +++ b/layouts/index.html @@ -22,7 +22,12 @@ {{- if $navBoxes -}} {{- $body = printf "%s
" $body -}} {{- range $navBoxes -}} - {{- $body = printf "%s

%s

%s

" $body .title .description .url .title -}} + {{- $iconHTML := "" -}} + {{- with .icon -}} + {{- $iconHTML = printf "" . -}} + {{- end -}} + {{- $linkAttrs := partial "link-target-attrs.html" .url -}} + {{- $body = printf "%s
%s

%s

%s

" $body $iconHTML .title .description .url $linkAttrs .title -}} {{- end -}} {{- $body = printf "%s
" $body -}} {{- end -}} diff --git a/themes/pydata/.gitignore b/themes/pydata/.gitignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/themes/pydata/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/themes/pydata/assets/css/components/footer.css b/themes/pydata/assets/css/components/footer.css deleted file mode 100644 index abe2b5a..0000000 --- a/themes/pydata/assets/css/components/footer.css +++ /dev/null @@ -1,4 +0,0 @@ -footer { - border-top: 1px solid #222; - margin-top: 1rem; -} diff --git a/themes/pydata/assets/css/components/header.css b/themes/pydata/assets/css/components/header.css deleted file mode 100644 index 8efea1e..0000000 --- a/themes/pydata/assets/css/components/header.css +++ /dev/null @@ -1,4 +0,0 @@ -header { - border-bottom: 1px solid #222; - margin-bottom: 1rem; -} diff --git a/themes/pydata/assets/css/main.css b/themes/pydata/assets/css/main.css deleted file mode 100644 index 6c0b660..0000000 --- a/themes/pydata/assets/css/main.css +++ /dev/null @@ -1,15 +0,0 @@ -@import "components/header.css"; -@import "components/footer.css"; - -body { - color: #222; - font-family: sans-serif; - line-height: 1.5; - margin: 1rem; - max-width: 768px; -} - -a { - color: #00e; - text-decoration: none; -} diff --git a/themes/pydata/assets/js/main.js b/themes/pydata/assets/js/main.js deleted file mode 100644 index e2aac52..0000000 --- a/themes/pydata/assets/js/main.js +++ /dev/null @@ -1 +0,0 @@ -console.log('This site was generated by Hugo.'); diff --git a/themes/pydata/hugo.toml b/themes/pydata/hugo.toml index 45e66e7..49fe3f1 100644 --- a/themes/pydata/hugo.toml +++ b/themes/pydata/hugo.toml @@ -7,6 +7,41 @@ title = 'My New Hugo Project' extended = true min = '0.162.1' + # Declaring any [[module.mounts]] disables Hugo's implicit default mounts + # for this module, so the standard component dirs must be re-declared + # explicitly alongside the new npm-sourced vendor mounts below. + [[module.mounts]] + source = "content" + target = "content" + [[module.mounts]] + source = "static" + target = "static" + [[module.mounts]] + source = "layouts" + target = "layouts" + [[module.mounts]] + source = "data" + target = "data" + [[module.mounts]] + source = "assets" + target = "assets" + [[module.mounts]] + source = "i18n" + target = "i18n" + [[module.mounts]] + source = "archetypes" + target = "archetypes" + + # Build-time-only npm deps (FontAwesome JS+SVG icon kit, Bootstrap JS + # bundle). Requires `npm install` in themes/pydata before + # `hugo build`/`hugo server`. See themes/pydata/package.json. + [[module.mounts]] + source = "node_modules/@fortawesome/fontawesome-free/js/all.min.js" + target = "assets/vendor/fontawesome/all.min.js" + [[module.mounts]] + source = "node_modules/bootstrap/dist/js/bootstrap.bundle.min.js" + target = "assets/vendor/bootstrap/bootstrap.bundle.min.js" + # --------------------------------------------------------------------------- # Default menus (overridable by site hugo.toml) # --------------------------------------------------------------------------- diff --git a/themes/pydata/layouts/_default/_markup/render-link.html b/themes/pydata/layouts/_default/_markup/render-link.html new file mode 100644 index 0000000..c2cece6 --- /dev/null +++ b/themes/pydata/layouts/_default/_markup/render-link.html @@ -0,0 +1,7 @@ +{{- /* + Markdown link render hook — applies the same external-link/new-tab rules + (see link-target-attrs.html) to every [text](url) link written in content, + site-wide, with no per-post markup needed. +*/ -}} +{{- $attrs := partial "link-target-attrs.html" .Destination | safeHTMLAttr -}} +{{ .Text }} diff --git a/themes/pydata/layouts/_partials/head.html b/themes/pydata/layouts/_partials/head.html index 9794d80..fec9885 100644 --- a/themes/pydata/layouts/_partials/head.html +++ b/themes/pydata/layouts/_partials/head.html @@ -28,7 +28,7 @@ {{- end }} - +{{ partial "head/fontawesome.html" . }} {{- if not site.Params.disable_search }} + {{- else }} + {{- with . | fingerprint }} + + {{- end }} + {{- end }} +{{- end }} diff --git a/themes/pydata/layouts/_partials/head/css.html b/themes/pydata/layouts/_partials/head/css.html deleted file mode 100644 index 8897866..0000000 --- a/themes/pydata/layouts/_partials/head/css.html +++ /dev/null @@ -1,15 +0,0 @@ -{{- with resources.Get "css/main.css" }} - {{- $opts := dict - "minify" (cond hugo.IsDevelopment false true) - "sourceMap" (cond hugo.IsDevelopment "linked" "none") - }} - {{- with . | css.Build $opts }} - {{- if hugo.IsDevelopment }} - - {{- else }} - {{- with . | fingerprint }} - - {{- end }} - {{- end }} - {{- end }} -{{- end }} diff --git a/themes/pydata/layouts/_partials/head/fontawesome.html b/themes/pydata/layouts/_partials/head/fontawesome.html new file mode 100644 index 0000000..78a56bf --- /dev/null +++ b/themes/pydata/layouts/_partials/head/fontawesome.html @@ -0,0 +1,15 @@ +{{- /* + FontAwesome's JS+SVG icon kit, mounted from node_modules via + [[module.mounts]] in hugo.toml (see themes/pydata/package.json). It's + already a prebuilt minified bundle, so no js.Build step is needed — just + fingerprint it in production. +*/ -}} +{{- with resources.Get "vendor/fontawesome/all.min.js" }} + {{- if hugo.IsDevelopment }} + + {{- else }} + {{- with . | fingerprint }} + + {{- end }} + {{- end }} +{{- end }} diff --git a/themes/pydata/layouts/_partials/head/js.html b/themes/pydata/layouts/_partials/head/js.html deleted file mode 100644 index 0210efa..0000000 --- a/themes/pydata/layouts/_partials/head/js.html +++ /dev/null @@ -1,15 +0,0 @@ -{{- with resources.Get "js/main.js" }} - {{- $opts := dict - "minify" (cond hugo.IsDevelopment false true) - "sourceMap" (cond hugo.IsDevelopment "linked" "none") - }} - {{- with . | js.Build $opts }} - {{- if hugo.IsDevelopment }} - - {{- else }} - {{- with . | fingerprint }} - - {{- end }} - {{- end }} - {{- end }} -{{- end }} diff --git a/themes/pydata/layouts/_partials/header-links.html b/themes/pydata/layouts/_partials/header-links.html index 51c1dab..2782ed1 100644 --- a/themes/pydata/layouts/_partials/header-links.html +++ b/themes/pydata/layouts/_partials/header-links.html @@ -19,7 +19,7 @@ diff --git a/themes/pydata/layouts/_partials/link-target-attrs.html b/themes/pydata/layouts/_partials/link-target-attrs.html new file mode 100644 index 0000000..171b7aa --- /dev/null +++ b/themes/pydata/layouts/_partials/link-target-attrs.html @@ -0,0 +1,31 @@ +{{- /* + Given a URL (context .), returns ` target="_blank" rel="noopener noreferrer"` + if the URL is external and should open in a new tab, or an empty string + otherwise. A URL is treated as "internal" (no new tab) if it's not + http(s), points back at this site's own baseURL, or matches one of the + configured exceptions (matched by prefix). + + Controlled via site.Params.external_links in hugo.toml: + [params.external_links] + new_tab = true + exceptions = ['https://docs.saltproject.io/'] + + @example: {{ partial "link-target-attrs.html" .url }} +*/ -}} +{{- $url := . -}} +{{- $cfg := site.Params.external_links | default dict -}} +{{- $attrs := "" -}} +{{- if $cfg.new_tab | default true -}} + {{- if or (hasPrefix $url "http://") (hasPrefix $url "https://") -}} + {{- $isInternal := hasPrefix $url site.BaseURL -}} + {{- range $cfg.exceptions | default (slice) -}} + {{- if hasPrefix $url . -}} + {{- $isInternal = true -}} + {{- end -}} + {{- end -}} + {{- if not $isInternal -}} + {{- $attrs = " target=\"_blank\" rel=\"noopener noreferrer\"" -}} + {{- end -}} + {{- end -}} +{{- end -}} +{{- $attrs -}} diff --git a/themes/pydata/layouts/_partials/navbar-icon-links.html b/themes/pydata/layouts/_partials/navbar-icon-links.html index 6c1b4c0..4b4720c 100644 --- a/themes/pydata/layouts/_partials/navbar-icon-links.html +++ b/themes/pydata/layouts/_partials/navbar-icon-links.html @@ -4,22 +4,22 @@ {{- $iconLinks := site.Params.icon_links | default (slice) -}} {{- /* Scripts at end of body */ -}} - +{{ partial "head/bootstrap-js.html" . }}