# Theme System

# Anzeige der Revisionen in einem PDF Export

<p class="callout success">getestet mit Version **24.02**</p>

## Anforderung

Um die Revisionen und ggfs. den Changelog in einem PDF Export ganz zum Schluss anzeigen lassen zu können sind einige Anpassungen nötig.

Zuerst habe ich dafür einen zusätzlichen Link in das Export-Menü eingebaut um einmal eine Version ohne Revisionen und eine mit Revisionen exportieren zu können.

## betroffene Dateien

<p class="callout info">Dateien müssen sich in der entsprechenden Struktur unterhalb des Themes befinden.  
Ordner = *kursiv*  
Dateien = **fett**</p>

- *entities*
    - **export-menu.blade.php**
- *exports*
    - *parts*
        - **revisions-index-row-compact.blade.php**
    - **page.blade.php**

## Inhalte der Dateien

<details id="bkmrk-export-menu.blade.ph"><summary>export-menu.blade.php</summary>

Hier wurde ein weiterer Link in **Zeile 6** eingefügt, der mittels GET den Wert `history=true` übergibt. Diese GET Variable kann dann später abgefragt werden.

```html
[...]
    <ul refs="dropdown@menu" class="wide dropdown-menu" role="menu">
        <li><a href="{{ $entity->getUrl('/export/html') }}" target="_blank" class="label-item"><span>{{ trans('entities.export_html') }}</span><span>.html</span></a></li>
        <li><a href="{{ $entity->getUrl('/export/pdf') }}" target="_blank" class="label-item"><span>{{ trans('entities.export_pdf') }}</span><span>.pdf</span></a></li>
        <li><a href="{{ $entity->getUrl('/export/pdf?qr=true') }}" target="_blank" class="label-item"><span>{{ trans('entities.export_pdf') }} + QR</span><span>.pdf</span></a></li>
        <li><a href="{{ $entity->getUrl('/export/pdf?history=true') }}" target="_blank" class="label-item"><span>{{ trans('entities.export_pdf') }} + History</span><span>.pdf</span></a></li>
        <li><a href="{{ $entity->getUrl('/export/plaintext') }}" target="_blank" class="label-item"><span>{{ trans('entities.export_text') }}</span><span>.txt</span></a></li>
        <li><a href="{{ $entity->getUrl('/export/markdown') }}" target="_blank" class="label-item"><span>{{ trans('entities.export_md') }}</span><span>.md</span></a></li>
    </ul>
[...]
```

</details><details id="bkmrk-revisions-index-row-"><summary>revisions-index-row-compact.blade.php</summary>

Ich habe in dem Ordner eine weitere Datei angelegt und mit folgendem Inhalt gefüllt:

```html
<tr>
    <td>{{ $revision->created_at->isoFormat('D MMMM Y') }}</td>
    <td>{{ $revision->revision_number == 0 ? '' : $revision->revision_number }}</td>
    <td>@if($revision->createdBy) {{ $revision->createdBy->name }} @else {{ trans('common.deleted_user') }} @endif</td>
    <td>{{ $revision->summary }}</td>
</tr>
```

</details><details id="bkmrk-page.blade.php-in-di"><summary>page.blade.php</summary>

In dieser Datei habe ich an das Ende der Seite folgenden Code eingefügt und die alte Meta Ansicht deaktiviert. Der geänderte Code beginnt in **Zeile 7** (in diesem Codeschnipsel).

```html
[...]
        <div style="clear:left;"></div>

        {!! $page->renderedHTML ?? $page->html !!}
    </div>

    @if(request()->query('history'))
        <hr>    
        <h2>Dokumentenhistorie</h2>
        <table>
            <tr>
                <th>Datum</th>
                <th>Version</th>
                <th>Autor</th>
                <th>Anmerkungen</th>
            </tr>
            @if(count($page->revisions) > 0)
                @foreach($page->revisions as $index => $revision)
                    @include('exports.parts.revisions-index-row-compact', ['revision' => $revision, 'current' => $page->revision_count === $revision->revision_number])
                @endforeach
            @else
                <p>{{ trans('entities.pages_revisions_none') }}</p>
            @endif
        </table>
    @endif

    <!-- <hr>

    <div class="text-muted text-small">
        @include('exports.parts.meta', ['entity' => $page])
    </div> -->
@endsection

```

Der Codeabschnitt kann auch an jeder anderen Stelle hinterlegt werden, für mich hat es aber am Ende des Dokuments den meisten Sinn gemacht.

</details>Ich habe bei den Anpassungen auf Übersetzungen verzichtet, das würde sich aber problemlos ändern lassen.

## Screenshots

[![image.png](https://bookstack.jelinek-rz.de/uploads/images/gallery/2024-04/scaled-1680-/nsfimage.png)](https://bookstack.jelinek-rz.de/uploads/images/gallery/2024-04/nsfimage.png)

# URL mit Mausklick kopieren

<p class="callout success">getestet mit Version **24.02**</p>

## Anforderung

Wenn man Bookstack als PWA nutzt ist es nur umständlich möglich, die URL zu kopieren wenn man diese jemandem schicken möchte.

## betroffene Dateien

<p class="callout info">Dateien müssen sich in der entsprechenden Struktur unterhalb des Themes befinden.  
Ordner = *kursiv*  
Dateien = **fett**</p>

- *entities*
    - **share-link.blade.php**
- *shelves*
    - **show.blade.php**
- *books*
    - **show.blade.php**
- *chapters*
    - **show.blade.php**
- *page*
    - **show.blade.php**

## Inhalte der Dateien

<details id="bkmrk-export-menu.blade.ph"><summary>share-link.blade.php</summary>

```html
<button type="button"
        id="share-link-button"
        data-success-text="Link copied to clipboard!"
        class="icon-list-item text-link">
    <span>@icon('share')</span>
    <span>{{ trans('common.share') }}</span>
</button>
<script nonce="{{ $cspNonce }}">
    (async function() {
        const shareButton = document.getElementById('share-link-button');
        shareButton.addEventListener('click', event => {
           copyTextToClipboard(window.location.href);
           window.$events.success(shareButton.dataset.successText);
        });

        async function copyTextToClipboard(text) {
            if (window.isSecureContext && navigator.clipboard) {
                await navigator.clipboard.writeText(text);
                return;
            }

            // Backup option where we can't use the navigator.clipboard API
            const tempInput = document.createElement('textarea');
            tempInput.style = 'position: absolute; left: -1000px; top: -1000px;';
            tempInput.value = text;
            document.body.appendChild(tempInput);
            tempInput.select();
            document.execCommand('copy');
            document.body.removeChild(tempInput);
        }
    })()
</script>
```

</details><details id="bkmrk-revisions-index-row-"><summary>show.blade.php (gleich für jede Datei)</summary>

Hier wurde die Zeile 10 hinzugefügt (Zeilennummer nur für diesen Ausschnitt)

```html
            @if($watchOptions->canWatch() && !$watchOptions->isWatching())
                @include('entities.watch-action', ['entity' => $page])
            @endif
            @if(user()->hasAppAccess())
                @include('entities.favourite-action', ['entity' => $page])
            @endif
            @if(userCan('content-export'))
                @include('entities.export-menu', ['entity' => $page])
            @endif
            @include('entities.share-link', ['entity' => $page])
        </div>

    </div>
@stop
```

</details>## Screenshots

[![image.png](https://bookstack.jelinek-rz.de/uploads/images/gallery/2024-04/scaled-1680-/fUFimage.png)](https://bookstack.jelinek-rz.de/uploads/images/gallery/2024-04/fUFimage.png)

# tabellarische Darstellung der Tags in einem PDF Export

<p class="callout success">getestet mit Version **24.02**</p>

## Anforderung

Für diverse Zertifizierungsdokument wird ein "Dokumentenheader" benötigt. Da ich die Informationen in Tags versteckt habe lasse ich diese einfach als Tabelle bei einem PDF Export darstellen.

## betroffene Dateien

<p class="callout info">Dateien müssen sich in der entsprechenden Struktur unterhalb des Themes befinden.  
Ordner = *kursiv*  
Dateien = **fett**</p>

- *exports*
    - *parts*
        - **tag-export-table.blade.php**
    - **page.blade.php**

## Inhalte der Dateien

<details id="bkmrk-export-menu.blade.ph"><summary>tag-export-table.blade.php</summary>

```html
<tr>
    <th>{{ $tag->name }}</th>
    @if($tag->value)<td>{{$tag->value}}</td>@else<td>&nbsp;</td>@endif
</tr>
```

</details><details id="bkmrk-revisions-index-row-"><summary>page.blade.php</summary>

An der gewünschten Stelle muss folgender Code eingefügt werden:

```php
@if($page->tags->count() > 0)
    <h2>Dokumenteninformationen</h2>
    <table>
        @foreach($page->tags as $tag)
            @include('exports.parts.tag-export-table', ['tag' => $tag])
        @endforeach
    </table>
    </div>
    <hr>
@endif
```

</details>## Screenshots

[![image.png](https://bookstack.jelinek-rz.de/uploads/images/gallery/2024-04/scaled-1680-/d7Limage.png)](https://bookstack.jelinek-rz.de/uploads/images/gallery/2024-04/d7Limage.png)

# PDF Export mit QR-Code

<p class="callout success">getestet mit Version **24.02**</p>

## Anforderung

Ich wollte gerne die Möglichkeit haben, exportierte Daten einfach wieder in der digitalen Welt zu finden. Was ist dazu besser geeignet als ein **QR-Code**.

## betroffene Dateien

<p class="callout info">Dateien müssen sich in der entsprechenden Struktur unterhalb des Themes befinden.  
Ordner = *kursiv*  
Dateien = **fett**</p>

- *layouts*
    - *parts*
        - **export-body-start.blade.php**
- *entities*
    - **export-menu.blade.php**

## Inhalte der Dateien

<details id="bkmrk-export-body-start.bl"><summary>export-body-start.blade.php</summary>

Vor dem ersten `div`-Container muss hier folgendes eingetragen werden:

```
@inject('totp', 'BookStack\Access\Mfa\TotpService')

@php
$qrCode = $totp->generateQrCodeSvg($page->getUrl());
$imgStr = 'data:image/svg+xml;base64,' . base64_encode($qrCode);
@endphp
```

Im Anschluss kann an einer beliebigen Stelle das Bild an die PDF übergeben werden.  
Dazu muss folgender Abschnitt hinzugefügt werden:

```php
@if(request()->query('qr'))
    <div style="float: right;">
        <img width="50" src="{{ $imgStr }}" alt="{{ $page->getUrl() }}">
    </div>
@endif
```

</details><details id="bkmrk-revisions-index-row-"><summary>export-menu.blade.php</summary>

Hier muss nun einfach nach der Zeile gesucht werden mit dem Inhalt `/export/pdf`.

Danach die Zeile kopieren und den String ?qr=true anhängen an die selbe Stelle.

Es sollte dann wie folgt aussehen:

```html
<ul refs="dropdown@menu" class="wide dropdown-menu" role="menu">
      <li><a href="{{ $entity->getUrl('/export/html') }}" target="_blank" class="label-item"><span>{{ trans('entities.export_html') }}</span><span>.html</span></a></li>
      <li><a href="{{ $entity->getUrl('/export/pdf') }}" target="_blank" class="label-item"><span>{{ trans('entities.export_pdf') }}</span><span>.pdf</span></a></li>
      <li><a href="{{ $entity->getUrl('/export/pdf?qr=true') }}" target="_blank" class="label-item"><span>{{ trans('entities.export_pdf') }} + QR</span><span>.pdf</span></a></li>
      <li><a href="{{ $entity->getUrl('/export/plaintext') }}" target="_blank" class="label-item"><span>{{ trans('entities.export_text') }}</span><span>.txt</span></a></li>
      <li><a href="{{ $entity->getUrl('/export/markdown') }}" target="_blank" class="label-item"><span>{{ trans('entities.export_md') }}</span><span>.md</span></a></li>
  </ul>
```

In diesem Ausschnitt ist in Zeile 4 der neue Export Link hinzugefügt.

</details>## Screenshots

[![image.png](https://bookstack.jelinek-rz.de/uploads/images/gallery/2024-06/scaled-1680-/image.png)](https://bookstack.jelinek-rz.de/uploads/images/gallery/2024-06/image.png)

# PDF Export anpassen mit Header und Footer

<p class="callout success">getestet mit Version **25.07**</p>

## Anforderung

Die Standard-PDF Seite ist nicht wirklich ansprechend, also werden hier Header und Footer sowie Seitenzahlen eingefügt.

## betroffene Dateien

<p class="callout info">Dateien müssen sich in der entsprechenden Struktur unterhalb des Themes befinden.  
Ordner = *kursiv*  
Dateien = **fett**</p>

- *layouts*
    - *parts*
        - **export-body-start.blade.php**

## Inhalte der Dateien

<details id="bkmrk-export-body-start.bl"><summary>export-body-start.blade.php</summary>

Die Datei muss am Ende wie folgt aussehen:

```html
@if ($format === 'pdf')
<style media="print">
    .print-header-footer {
        position: fixed;
        width: 100%;
    }
    .print-footer {
        position: fixed;
        bottom: -40px;
        width: 100%;
    }
    .print-header-footer-inner {
        max-width: 840px;
        margin: 0 auto;
        color: #666;
    }
    .print-page-number:after {
        content: "Seite "counter(page);
    }
    @page {
        margin-top: 100px;
        margin-bottom: 80px;
    }
</style>

<div class="print-header-footer" style="top: -60px;">
    <div class="print-header-footer-inner">
        <div style="float: left; opacity: 0.8;">
            <img height="50" src="data:image/png;base64,{{ base64_encode(file_get_contents(theme_path('images/logo.png'))) }}">
        </div>
    </div>
    <div style="clear:both;"></div>
    <hr style="color: #ccc;">
</div>

<div class="print-header-footer" style="bottom: -40px;">
    <div class="print-header-footer-inner">
        <div style="float: left; opacity: 0.8; font-size: 8pt; text-align: left">
            &copy; [Footer-Text links]
        </div>
        <div style="float: right; opacity: 0.8; font-size: 8pt; text-align: right">
            <div class="print-page-number" style="opacity: 1"></div>
        </div>
    </div>
    <div style="clear:both;"></div>
</div>
@endif
```

</details>

# News-Seite / schwarzes Brett

<p class="callout success">getestet mit Version 24.12</p>

## Anforderung

Zum Abbilden der Funktion eines schwarzen Bretts bzw. einer News-Seite um aktuelle Infos anzuzeigen.

## betroffene Dateien

<p class="callout info">Dateien müssen sich in der entsprechenden Struktur unterhalb des Themes befinden.  
Ordner = *kursiv*  
Dateien = **fett**</p>

- *home*
    - **specific-page.blade.php**

## Inhalte der Dateien

<details id="bkmrk-export-menu.blade.ph"><summary>specific-page.blade.php</summary>

Innerhalb der `section('left')` oder `section('right')`, je nach Präferenz, muss folgender `<div>`-Block eingefügt werden:

```html
<div class="card mb-xl">
    @php
    $newsBookId = 34;
    $newsItems = \BookStack\Entities\Models\Page::visible()
        ->where('book_id', $newsBookId)
        ->orderBy('created_at', 'desc')
        ->take(7)
        ->get();
    @endphp
    <h3 class="card-title" style="font-weight: bold; font-size: 15pt;">{{ trans('common.actualnews') }}</h3>
    <div class="px-m">
        @include('entities.list', [
            'entities' => $newsItems,
            'style' => 'compact',
        ])
    </div>
    <a href="{{ url('/books/firmen-aushange-schwarzes-brett')  }}" class="card-footer-link">{{ trans('common.view_all') }}</a>
</div>
```

die ID für das Buch welches hier genutzt werden soll lässt sich einfach herausfinden, in dem ein Buch öffnet und dann die Entwicklertools startet (`F12`). Hier sucht man dann nach folgendem Begriff: `option:entity-search:entity-id`  
Direkt dahinter steht die ID des Buches, welches dann in der Anpassung hinterlegt werden muss.

</details>## Screenshots

[![image.png](https://bookstack.jelinek-rz.de/uploads/images/gallery/2025-01/scaled-1680-/image.png)](https://bookstack.jelinek-rz.de/uploads/images/gallery/2025-01/image.png)

# Regale zu denen ein Buch gehört anzeigen

<p class="callout success">getestet mit Version **25.02**</p>

## Anforderung

Ich wollte sehen in welchen Regalen ein Buch steht, da ein Buch in mehreren Regalen stehen kann und weil die Brotkrumen Navigation das Regal nicht anzeigt wenn man nicht das Buch z.B. über die Suche öffnet.

## betroffene Dateien

<p class="callout info">Dateien müssen sich in der entsprechenden Struktur unterhalb des Themes befinden.  
Ordner = *kursiv*  
Dateien = **fett**</p>

- *books*
    - **show.blade.oph**

## Inhalte der Dateien

<details id="bkmrk-export-menu.blade.ph"><summary>show.blade.php</summary>

Unter `@section('left')` einfach an der gewünschten Stelle den folgenden Code einfügen

```html
[...]
    @if(count($bookParentShelves) > 0)
        <div class="actions mb-xl">
            <h5>{{ trans('entities.shelves') }}</h5>
            @include('entities.list', ['entities' => $bookParentShelves, 'style' => 'compact'])
        </div>
    @endif
[...]
```

</details>## Screenshots

[![image.png](https://bookstack.jelinek-rz.de/uploads/images/gallery/2025-03/scaled-1680-/image.png)](https://bookstack.jelinek-rz.de/uploads/images/gallery/2025-03/image.png)

# Anzeige von Buch, Kapitel und Seitentitel im Footer

<p class="callout success">getestet mit Version **25.12.8**</p>

## Anforderung

Zur besseren Nachverfolgung von Exports an welcher Stelle die exportierte Seite im BookStack zu finden sind.

## betroffene Dateien

<p class="callout info">Dateien müssen sich in der entsprechenden Struktur unterhalb des Themes befinden.  
Ordner = *kursiv*  
Dateien = **fett**</p>

- *layouts*
    - *parts*
        - **export-body-start.blade.php**

## Inhalte der Dateien

<details id="bkmrk-export-menu.blade.ph"><summary>export-body-start.blade.php</summary>

Hier wird folgender Bereich benötigt wenn nicht schon vorhanden bzw. muss im den Teil ergänzt werden wenn schon vorhanden:

```html
[...]
<div style="float: left; opacity: 0.8; font-size: 8pt; text-align: center">
    <!-- Anzeige von Buch, Kapitel und Seitentitel im Footer -->
    @if(isset($page))
        {{-- Buch & optional Kapitel & Seite --}}
        {{ trans('entities.book') }}: {{ $page->book->name ?? '' }}
        @if(isset($page->chapter))
            – {{ trans('entities.chapter') }}: {{ $page->chapter->name }}
        @endif
        <br>
        {{ trans('entities.pages_title') }}: {{ $page->name }}
    @elseif(isset($chapter))
        {{-- Export eines Kapitels --}}
        {{ trans('entities.book') }}: {{ $chapter->book->name ?? '' }}<br>
        {{ trans('entities.chapter') }}: {{ $chapter->name }}
    @elseif(isset($book))
        {{-- Export eines ganzen Buchs --}}
        {{ trans('entities.book') }}: {{ $book->name }}
    @endif
</div>
[...]
```

</details>## Screenshots

[![image.png](https://bookstack.jelinek-rz.de/uploads/images/gallery/2026-03/scaled-1680-/image.png)](https://bookstack.jelinek-rz.de/uploads/images/gallery/2026-03/image.png)