diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 797acd3c29..236694d47a 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -1,12 +1,6 @@ name: PR Preview on: - pull_request: - types: - - opened - - reopened - - synchronize - - closed pull_request_target: types: - opened @@ -15,12 +9,12 @@ on: - closed concurrency: - group: pr-preview-${{ github.event.number }}-${{ github.event_name }} + group: pr-preview-${{ github.event.number }} cancel-in-progress: true jobs: lint: - if: ${{ github.event_name == 'pull_request' && github.event.action != 'closed' }} + if: ${{ github.event.action != 'closed' }} runs-on: ubuntu-latest permissions: contents: write @@ -29,7 +23,9 @@ jobs: uses: actions/checkout@v5 with: fetch-depth: 0 - ref: ${{ github.head_ref }} + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.ref }} + persist-credentials: false - name: Setup Node.js uses: actions/setup-node@v5 @@ -70,11 +66,14 @@ jobs: - name: Push lint fixes if: steps.lint_changes.outputs.has_changes == 'true' && github.repository == github.event.pull_request.head.repo.full_name + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - git push origin HEAD:${{ github.head_ref }} || echo "Unable to push lint fixes (likely due to branch permissions)." + git remote set-url origin https://x-access-token:${GITHUB_TOKEN}@github.com/${{ github.repository }}.git + git push origin HEAD:${{ github.event.pull_request.head.ref }} || echo "Unable to push lint fixes (likely due to branch permissions)." deploy-preview: - if: ${{ github.event_name == 'pull_request_target' }} + if: ${{ github.event.action != 'closed' }} runs-on: ubuntu-latest permissions: contents: write diff --git a/embedding.md b/embedding.md index 7b575c315c..c29999fe2d 100644 --- a/embedding.md +++ b/embedding.md @@ -10,26 +10,41 @@ Use the following guidance when loading the Unraid documentation inside an ifram - `theme=` — Forces the initial Docs theme. The value is persisted for the iframe session so reloads stay consistent. - `entry=` — Marks the logical entry point for the iframe session. Supply an absolute docs path (e.g. `/unraid-os/...`) or a full docs URL; the embedded UI shows a floating back icon that returns visitors to this path and hides itself while you remain on it. Defaults to the first loaded URL if omitted. -- `sidebar=1` — Re-enables the documentation sidebar and table of contents, which are hidden by default in embedded mode. ## Session Storage Keys The iframe experience uses `window.sessionStorage` to remember state while a browser tab stays open. Host applications normally do not need to interact with these keys, but they are listed here for completeness. -| Key | Purpose | -| ------------------------- | --------------------------------------------------------------- | -| `unraidDocsIframe` | Tracks whether the current session originated inside an iframe. | -| `unraidDocsTheme` | Stores the last used Docs theme so reloads stay consistent. | -| `unraidDocsIframeEntry` | Holds the iframe entry path for the fallback back button. | -| `unraidDocsIframeSidebar` | Marks whether the sidebar was explicitly enabled. | +| Key | Purpose | +| ----------------------- | --------------------------------------------------------------- | +| `unraidDocsIframe` | Tracks whether the current session originated inside an iframe. | +| `unraidDocsTheme` | Stores the last used Docs theme so reloads stay consistent. | +| `unraidDocsIframeEntry` | Holds the iframe entry path for the fallback back button. | A host can clear these keys to reset the embedded state before opening a new iframe session. ## Example URL Builders ```js -function buildDocsUrl(path, { theme, entry, sidebar } = {}) { - const url = new URL(path, "https://docs.unraid.net"); +function prefixLocale(path, locale) { + const cleanLocale = (locale || "").toLowerCase(); + if (!cleanLocale || cleanLocale === "en") { + return path; + } + + const trimmed = path.replace(/^\/+/, ""); + const segments = trimmed.split("/").filter(Boolean); + + if (segments[0]?.toLowerCase() === cleanLocale) { + return `/${segments.join("/")}`; + } + + return `/${cleanLocale}/${segments.join("/")}`; +} + +function buildDocsUrl(path, { theme, entry, locale } = {}) { + const localizedPath = prefixLocale(path, locale); + const url = new URL(localizedPath, "https://docs.unraid.net"); url.searchParams.set("embed", "1"); if (theme === "light" || theme === "dark") { @@ -40,10 +55,6 @@ function buildDocsUrl(path, { theme, entry, sidebar } = {}) { url.searchParams.set("entry", entry); } - if (sidebar) { - url.searchParams.set("sidebar", "1"); - } - return url.toString(); } ``` @@ -52,7 +63,7 @@ function buildDocsUrl(path, { theme, entry, sidebar } = {}) { 1. Decide which route should serve as the iframe entry point and supply it via `entry` when loading the iframe. 2. Pass the current host theme if you want the Docs theme to match immediately. -3. Toggle `sidebar=1` only when the host layout can accommodate the wider viewport required for the sidebar. +3. Prefix the docs path with the desired locale segment (for example `/es/...`) if you want to start in a translated version. The iframe experience reads the language from the pathname, not from a query parameter. 4. When tearing down an iframe session, optionally clear the session-storage keys to remove residual state before launching a new session in the same tab. ## Messaging API diff --git a/src/css/custom.css b/src/css/custom.css index 9f5d6778cb..5273f20142 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -218,30 +218,16 @@ details:target { Iframe Styles - Preserved =========================== */ -div[data-iframe="true"] .navbar, div[data-iframe="true"] header, -div[data-iframe="true"] footer, -div[data-iframe="true"] nav.pagination-nav, -div[data-iframe="true"] div[role="complementary"], -div[data-iframe="true"] aside { +div[data-iframe="true"] .navbar__items:not(.navbar__items--right), +div[data-iframe="true"] .navbar__brand { display: none !important; } -div[data-iframe="true"]:not([data-iframe-sidebar="visible"]) .theme-doc-sidebar-container, -div[data-iframe="true"]:not([data-iframe-sidebar="visible"]) .theme-doc-toc-desktop, -div[data-iframe="true"]:not([data-iframe-sidebar="visible"]) .theme-doc-toc-mobile { +div[data-iframe="true"] nav.pagination-nav { display: none !important; } -div[data-iframe="true"]:not([data-iframe-sidebar="visible"]) main[class^="docMainContainer_"] { - max-width: 100% !important; - width: 100% !important; -} - -div[data-iframe="true"]:not([data-iframe-sidebar="visible"]) main[class^="docMainContainer_"] > div { - padding-top: 1rem !important; -} - div[data-iframe="true"] .iframe-back-button { position: fixed; right: 1.5rem; diff --git a/src/theme/Layout/index.tsx b/src/theme/Layout/index.tsx index 7b2befca91..81c7f48840 100644 --- a/src/theme/Layout/index.tsx +++ b/src/theme/Layout/index.tsx @@ -1,58 +1,15 @@ -import React, { useEffect, useState } from "react"; +import React from "react"; import Layout from "@theme-original/Layout"; -import { useLocation } from "@docusaurus/router"; import { ThemeSync } from "./ThemeSync"; import { IframeNavigation } from "./IframeNavigation"; import { useIframe } from "../../hooks/useIframe"; -import { - SIDEBAR_QUERY_PARAM, - SIDEBAR_STORAGE_KEY, - clearSessionValue, - parseBooleanFlag, - readSessionValue, - writeSessionValue, -} from "../../utils/iframeConstants"; export default function LayoutWrapper(props) { const isInIframeState = useIframe(); - const location = useLocation(); - const [isSidebarVisible, setIsSidebarVisible] = useState(false); - - useEffect(() => { - if (!isInIframeState || typeof window === "undefined") { - setIsSidebarVisible(false); - clearSessionValue(SIDEBAR_STORAGE_KEY); - return; - } - - const params = new URLSearchParams(location?.search || window.location.search); - const hasSidebarParam = params.has(SIDEBAR_QUERY_PARAM); - const sidebarFromQuery = parseBooleanFlag(params.get(SIDEBAR_QUERY_PARAM)); - - if (hasSidebarParam) { - if (sidebarFromQuery) { - setIsSidebarVisible(true); - writeSessionValue(SIDEBAR_STORAGE_KEY, "true"); - } else { - setIsSidebarVisible(false); - clearSessionValue(SIDEBAR_STORAGE_KEY); - } - return; - } - - const sidebarFromStorage = parseBooleanFlag(readSessionValue(SIDEBAR_STORAGE_KEY)); - if (sidebarFromStorage) { - setIsSidebarVisible(true); - return; - } - - setIsSidebarVisible(false); - }, [isInIframeState, location]); const dataAttributes = isInIframeState ? { 'data-iframe': 'true', - 'data-iframe-sidebar': isSidebarVisible ? 'visible' : 'hidden', } : {}; diff --git a/src/utils/iframeConstants.ts b/src/utils/iframeConstants.ts index 665729271d..7b90c44979 100644 --- a/src/utils/iframeConstants.ts +++ b/src/utils/iframeConstants.ts @@ -1,12 +1,10 @@ export const IFRAME_QUERY_PARAM = "embed"; export const THEME_QUERY_PARAM = "theme"; export const ENTRY_QUERY_PARAM = "entry"; -export const SIDEBAR_QUERY_PARAM = "sidebar"; export const IFRAME_STORAGE_KEY = "unraidDocsIframe"; export const THEME_STORAGE_KEY = "unraidDocsTheme"; export const ENTRY_STORAGE_KEY = "unraidDocsIframeEntry"; -export const SIDEBAR_STORAGE_KEY = "unraidDocsIframeSidebar"; const BOOLEAN_TRUE_VALUES = new Set(["1", "true", "yes"]); @@ -20,6 +18,7 @@ export function parseBooleanFlag(value: string | null): boolean { return BOOLEAN_TRUE_VALUES.has(value.toLowerCase()); } + export function normalizeTheme(theme: string | null): SupportedTheme | null { if (!theme) { return null;