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
21 changes: 10 additions & 11 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
name: PR Preview

on:
pull_request:
types:
- opened
- reopened
- synchronize
- closed
pull_request_target:
types:
- opened
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
39 changes: 25 additions & 14 deletions embedding.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,41 @@ Use the following guidance when loading the Unraid documentation inside an ifram

- `theme=<light|dark>` — Forces the initial Docs theme. The value is persisted for the iframe session so reloads stay consistent.
- `entry=<path>` — 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") {
Expand All @@ -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();
}
```
Expand All @@ -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
Expand Down
20 changes: 3 additions & 17 deletions src/css/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
45 changes: 1 addition & 44 deletions src/theme/Layout/index.tsx
Original file line number Diff line number Diff line change
@@ -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',
}
: {};

Expand Down
3 changes: 1 addition & 2 deletions src/utils/iframeConstants.ts
Original file line number Diff line number Diff line change
@@ -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"]);

Expand All @@ -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;
Expand Down