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
16 changes: 16 additions & 0 deletions .changeset/add-layout-options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
"hyperbook": minor
"@hyperbook/markdown": minor
"@hyperbook/types": minor
---

Add page layout options with automatic iframe detection

- Added three layout options: default, wide, and standalone
- Wide layout provides full-width content with drawer-only navigation, ideal for tables, galleries, and code examples
- Standalone layout displays content only (no header, sidebar, footer) for clean iframe embedding
- Standalone mode can be activated via frontmatter (`layout: standalone`), URL parameter (`?standalone=true`), or automatic iframe detection
- Automatically hides TOC toggle and QR code buttons when in standalone mode
- Zero-configuration embedding: pages automatically switch to standalone mode when embedded in iframes
- Added comprehensive documentation in Advanced Features section with usage examples and demos
- All changes are backward compatible with existing pages
29 changes: 29 additions & 0 deletions packages/markdown/assets/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -274,9 +274,38 @@ var hyperbook = (function () {
initBookmarks(root);
}

// Check for standalone layout URL parameter or iframe context
function checkStandaloneMode() {
// Check if explicitly requested via URL parameter
const urlParams = new URLSearchParams(window.location.search);
const standaloneParam = urlParams.get('standalone') === 'true';

// Check if page is inside an iframe
const isInIframe = window.self !== window.top;

if (standaloneParam || isInIframe) {
const mainGrid = document.querySelector('.main-grid');
if (mainGrid && !mainGrid.classList.contains('layout-standalone')) {
mainGrid.classList.add('layout-standalone');
}

// Hide TOC and QR code buttons when in standalone mode
const tocToggle = document.getElementById('toc-toggle');
if (tocToggle) {
tocToggle.style.display = 'none';
}

const qrcodeOpen = document.getElementById('qrcode-open');
if (qrcodeOpen) {
qrcodeOpen.style.display = 'none';
}
}
}

// Initialize existing elements on document load
document.addEventListener("DOMContentLoaded", () => {
init(document);
checkStandaloneMode();
});

// Observe for new elements added to the DOM
Expand Down
59 changes: 59 additions & 0 deletions packages/markdown/assets/shell.css
Original file line number Diff line number Diff line change
Expand Up @@ -807,3 +807,62 @@ nav.toc li.level-3 {
flex-shrink: 0;
/* Prevent caption from shrinking */
}

/* Wide layout: full width content with drawer-only navigation */
.main-grid.layout-wide {
grid-template-columns: 1fr;
grid-template-areas:
"header"
"article";


--main-width: 100%;
}

.main-grid.layout-wide .sidebar {
display: none;
}

.main-grid.layout-wide .mobile-nav {
display: flex;
align-items: center;
justify-content: center;
}

.main-grid.layout-wide main {
padding: 20px 40px;
max-width: 100%;
}

/* Standalone layout: content only, no navigation or header (for iframe embedding) */
.main-grid.layout-standalone {
grid-template-columns: 1fr;
grid-template-rows: 1fr;
grid-template-areas: "article";

--main-width: 100%;

#toc-toggle {
top: inherit;
}

#qrcode-open {
top: 90px;
}
}

.main-grid.layout-standalone header,
.main-grid.layout-standalone .sidebar {
display: none;
}

.main-grid.layout-standalone main {
padding: 20px 40px;
max-width: 100%;
}

.main-grid.layout-standalone .jump-container,
.main-grid.layout-standalone .meta,
.main-grid.layout-standalone #custom-links-footer {
display: none;
}
10 changes: 9 additions & 1 deletion packages/markdown/src/rehypeShell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -981,12 +981,20 @@ const makeCustomLinksFooter = (ctx: HyperbookContext) => {
export default (ctx: HyperbookContext) => () => {
return (tree: Root, file: VFile) => {
const originalChildren = tree.children as ElementContent[];
const layout = ctx.navigation.current?.layout || "default";
let mainGridClass = "main-grid";
if (layout === "wide") {
mainGridClass = "main-grid layout-wide";
} else if (layout === "standalone") {
mainGridClass = "main-grid layout-standalone";
}

tree.children = [
{
type: "element",
tagName: "div",
properties: {
class: "main-grid",
class: mainGridClass,
},
children: [
...makeHeaderElements(ctx),
Expand Down
64 changes: 64 additions & 0 deletions packages/markdown/tests/rehypeShell.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,68 @@ describe("rehypeShell", () => {
expect(value).toContain("__I_AM_HERE");
expect(value).not.toContain("__I_AM_NOT_HERE");
});

it("should apply default layout class when no layout is specified", () => {
const defaultCtx: HyperbookContext = {
...ctx,
navigation: {
...ctx.navigation,
current: {
...ctx.navigation.current!,
layout: undefined,
},
},
};
const value = toHtml("", defaultCtx).value;
expect(value).toContain('class="main-grid"');
expect(value).not.toContain("layout-wide");
expect(value).not.toContain("layout-standalone");
});

it("should apply wide layout class when layout is set to wide", () => {
const wideCtx: HyperbookContext = {
...ctx,
navigation: {
...ctx.navigation,
current: {
...ctx.navigation.current!,
layout: "wide",
},
},
};
const value = toHtml("", wideCtx).value;
expect(value).toContain('class="main-grid layout-wide"');
});

it("should apply default layout class when layout is explicitly set to default", () => {
const defaultExplicitCtx: HyperbookContext = {
...ctx,
navigation: {
...ctx.navigation,
current: {
...ctx.navigation.current!,
layout: "default",
},
},
};
const value = toHtml("", defaultExplicitCtx).value;
expect(value).toContain('class="main-grid"');
expect(value).not.toContain("layout-wide");
expect(value).not.toContain("layout-standalone");
});

it("should apply standalone layout class when layout is set to standalone", () => {
const standaloneCtx: HyperbookContext = {
...ctx,
navigation: {
...ctx.navigation,
current: {
...ctx.navigation.current!,
layout: "standalone",
},
},
};
const value = toHtml("", standaloneCtx).value;
expect(value).toContain('class="main-grid layout-standalone"');
});
});
3 changes: 3 additions & 0 deletions packages/types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ export type Glossary = Record<string, Term[]>;

export type Language = "de" | "en" | "fr" | "es" | "it" | "pt" | "nl";

export type Layout = "default" | "wide" | "standalone";

export type HyperbookPageFrontmatter = {
name: string;
permaid?: string;
Expand All @@ -48,6 +50,7 @@ export type HyperbookPageFrontmatter = {
toc?: boolean;
next?: string;
prev?: string;
layout?: Layout;
};

export type HyperbookSectionFrontmatter = HyperbookPageFrontmatter & {
Expand Down
13 changes: 7 additions & 6 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
packages:
# all packages in subdirs of packages/ and components/
- "packages/*"
- "templates/*"
- "platforms/*"
# exclude packages that are inside test directories
- "!**/test/**"
- packages/*
- templates/*
- platforms/*
- '!**/test/**'

onlyBuiltDependencies:
- sharp
142 changes: 142 additions & 0 deletions website/de/book/advanced/layouts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
---
name: Layouts
index: 8
lang: de
---

# Seiten-Layouts

Hyperbook bietet drei Layout-Optionen für unterschiedliche Anforderungen an die Inhaltsdarstellung. Sie können ein Layout wählen, indem Sie die `layout` Eigenschaft im Frontmatter Ihrer Seite hinzufügen.

## Verfügbare Layouts

### Standard-Layout

Das Standardlayout mit sichtbarer Seitenleiste auf Desktop-Bildschirmen. Dies ist das Standardlayout, wenn kein Layout angegeben wird.

**Merkmale:**
- Seitenleisten-Navigation auf Desktop immer sichtbar
- Inhaltsbereich mit optimaler Lesebreite
- Responsives Design, das auf Mobilgeräten zur Drawer-Navigation wechselt

**Verwendung:**
```md
---
name: Meine Seite
layout: default
---
```

Oder lassen Sie die `layout` Eigenschaft einfach weg.

**Am besten für:** Standard-Dokumentationsseiten, Tutorials und Artikel.

---

### Wide Layout

Inhalt in voller Breite mit Navigation immer im Drawer-Modus, bietet maximalen horizontalen Platz.

**Merkmale:**
- Inhalt erstreckt sich über volle Breite mit Padding
- Seitenleiste auf allen Bildschirmgrößen versteckt
- Navigation über Hamburger-Menü zugänglich
- Ideal für Inhalte, die horizontalen Platz benötigen

**Verwendung:**
```md
---
name: Meine Breite Seite
layout: wide
---
```

**Am besten für:**
- Datentabellen mit vielen Spalten
- Lange Code-Beispiele
- Bildergalerien
- Interaktive eingebettete Inhalte
- Präsentations-Seiten

**[Wide Layout Demo ansehen →](/de/advanced/wide-layout-demo)**

---

### Standalone Layout

Nur-Inhalts-Anzeige mit allen versteckten Navigations- und UI-Elementen, perfekt für iframe-Einbettung.

**Merkmale:**
- Keine Header-, Seitenleisten- oder Footer-Elemente
- Saubere, ablenkungsfreie Inhalte
- Kann über Frontmatter, URL-Parameter oder automatisch in iframes aktiviert werden
- Entwickelt für die Einbettung in externe Seiten
- Versteckt automatisch TOC- und QR-Code-Buttons

**Verwendungsmethode 1: Frontmatter**
```md
---
name: Meine Standalone-Seite
layout: standalone
---
```

**Verwendungsmethode 2: URL-Parameter** (funktioniert auf jeder Seite)
```html
<iframe src="https://ihr-hyperbook.com/beliebige-seite?standalone=true"></iframe>
```

**Verwendungsmethode 3: Automatische Erkennung** (iframe-Einbettung)

Wenn eine Hyperbook-Seite in einem iframe eingebettet wird, wechselt sie automatisch in den Standalone-Modus - keine Konfiguration erforderlich! Dies ermöglicht eine nahtlose Einbettung ohne URL-Parameter oder Frontmatter-Änderungen.

```html
<!-- Betten Sie einfach eine beliebige Seite ein - Standalone-Modus wird automatisch aktiviert -->
<iframe src="https://ihr-hyperbook.com/beliebige-seite"></iframe>
```

Die automatische Erkennung sorgt für saubere, ablenkungsfreie Inhalte bei iframe-Einbettungen und behält gleichzeitig die volle Funktionalität bei, wenn Seiten direkt aufgerufen werden.

**Am besten für:**
- Lernmanagementsystem (LMS) Integration
- Einbettung in Dokumentationsportale
- Mobile App Webviews
- Widget-Integration
- Präsentationen

**[Standalone Layout Demo ansehen →](/de/advanced/standalone-layout-demo)**

---

## Konfiguration

Die Layout-Eigenschaft ist optional im Frontmatter Ihrer Seite:

```md
---
name: Seitentitel
layout: wide # oder 'default', 'standalone'
---

# Ihr Inhalt hier
```

## Layout-Vergleich

| Merkmal | Standard | Wide | Standalone |
|---------|---------|------|------------|
| Seitenleisten-Sichtbarkeit | Sichtbar auf Desktop | Immer versteckt | Immer versteckt |
| Inhaltsbreite | Begrenzt für Lesbarkeit | Volle Breite | Volle Breite |
| Navigationszugriff | Seitenleiste / Drawer | Nur Drawer | Keine (versteckt) |
| Header | Sichtbar | Sichtbar | Versteckt |
| Footer | Sichtbar | Sichtbar | Versteckt |
| Bester Anwendungsfall | Dokumentation | Tabellen, Galerien | iframe-Einbettung |

---

## Tipps

- **Standard-Layout**: Verwenden Sie es für die meisten Dokumentationsseiten, um eine konsistente Navigation zu gewährleisten
- **Wide Layout**: Wechseln Sie zu wide, wenn Inhalte horizontalen Platz benötigen (Tabellen, Code, Galerien)
- **Standalone Layout**: Verwenden Sie den URL-Parameter (`?standalone=true`) für flexible iframe-Einbettung ohne Änderung der Seitenquelle
- Sie können verschiedene Layouts über Seiten im selben Hyperbook-Projekt mischen
Loading