BookStack icon indicating copy to clipboard operation
BookStack copied to clipboard

Redirect opening a Book to the first page instead of the page/section index if it only contains one page?

Open aekramer opened this issue 1 year ago • 7 comments

Describe the feature you'd like

I use BookStack as a wiki implementation, and often only have a single page contained within a book.

It would be nice to offer the ability for the user to toggle if clicking on a book should automatically redirect the visitor to the first page, instead of showing an additional page index that is basically just a second click to get to where they wanted to go since the index is otherwise empty.

Additionally, perhaps it would be a nice feature to offer a "page redirect" automatically, so when opening a book the author can select the first page that should be displayed rather than the page index (if there are multiple pages)

Describe the benefits this would bring to existing BookStack users

More clarity and efficient browsing, I often get requests/questions from users why they have to go through 2 "layers" when accessing articles instead of being able to view the main page when clicking the book right away.

Can the goal of this request already be achieved via other means?

Not that I am aware of.

Have you searched for an existing open/closed issue?

  • [X] I have searched for existing issues and none cover my fundamental request

How long have you been using BookStack?

1 to 5 years

Additional context

Example page of my bookstack integration, where I would like this to apply: https://wiki.runerealm.org/books/cape-of-accomplishment-perks

aekramer avatar Oct 09 '24 07:10 aekramer

i was thinking of the same thing. Due to the fact that the editor of the shelve and the editor of the book are not that feature rich, the first item which makes sense is a page. But when creating a book with only one page it would be great to skip the book between, to shortcut the clicking

vmario89 avatar Oct 13 '24 21:10 vmario89

Relevant hacky optional JS based solution shared in this thread: https://www.reddit.com/r/BookStack/comments/14u65y3/book_with_only_one_page_automatic_opening/

ssddanbrown avatar Oct 13 '24 21:10 ssddanbrown

indeed the problem with that code will be, that you cannot edit the book between until you deactivate the code again. This makes it unmaintainable regarding to the role based access. You do not want to make editors to admins which can change the site code

i think the problem should be resolved more generic: it would be great if we can put pages to a shelf without putting that page into a book first. In reality we have some unsorted document paper stacks too. I have shelves at my home where i put paper and later i am going to sort them into a folder, when i have time

vmario89 avatar Oct 14 '24 10:10 vmario89

meanwhile: added some delay to make it possible to do at least some changes clicking fast enough

<!-- Bücher mit nur einer Seite: Seite direkt anzeigen -->
<script type="module">
    const isBookPage = document.querySelector('.book-content') !== null;
    if (isBookPage) {
        const contents = document.querySelectorAll('.book-contents .entity-list-item');
        if (contents.length === 1 && contents[0].classList.contains('page')) {
            const delay = ms => new Promise(res => setTimeout(res, ms));
            await delay(3000);
            window.location = contents[0].href;
        }
    }
</script>

vmario89 avatar Oct 14 '24 10:10 vmario89

btw. it would be also cool to skip the book, if the shelve only contains one book, and if the book contains only one page, to open that page, so kind of double redirect. but skipping might also skip information like images or description ... hm

vmario89 avatar Oct 14 '24 10:10 vmario89

I have this same use case. I need to make many, many single-page books, and don't want them to have any index or navigation. I wonder if this could be implemented as a special kind of book?

prohtex avatar Jun 01 '25 07:06 prohtex

Here's a script with a spinner and cancel button:

<!-- Auto-redirect single-page books with responsive layout and proper spinner -->
<script type="module">
  (async () => {
    const isBookPage = document.querySelector('.book-content') !== null;
    if (!isBookPage) return;

    const contents = document.querySelectorAll('.book-contents .entity-list-item');
    if (!(contents.length === 1 && contents[0].classList.contains('page'))) return;

    // Spinner animation
    const style = document.createElement('style');
    style.textContent = `
      @keyframes spin {
        0% { transform: rotate(0deg); }
        100% { transform: rotate(360deg); }
      }

      .redirect-overlay {
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        background: #fff;
        border: 1px solid #ccc;
        box-shadow: 0 2px 12px rgba(0,0,0,0.25);
        padding: 16px 20px;
        z-index: 10000;
        font-family: sans-serif;
        border-radius: 8px;
        text-align: center;
        display: flex;
        align-items: center;
        justify-content: center;
        gap: 12px;
        flex-direction: row;
      }

      @media (max-width: 600px) {
        .redirect-overlay {
          flex-direction: column;
        }
      }

      .redirect-spinner {
        width: 20px;
        height: 20px;
        min-width: 20px;
        min-height: 20px;
        border: 3px solid #ccc;
        border-top: 3px solid #333;
        border-radius: 50%;
        animation: spin 1s linear infinite;
      }

      .redirect-message {
        font-size: 14px;
      }

      .redirect-cancel {
        background: #eee;
        border: 1px solid #aaa;
        padding: 4px 10px;
        border-radius: 4px;
        cursor: pointer;
        font-size: 14px;
      }
    `;
    document.head.appendChild(style);

    // Elements
    const overlay = document.createElement('div');
    overlay.className = 'redirect-overlay';

    const spinner = document.createElement('div');
    spinner.className = 'redirect-spinner';

    const message = document.createElement('div');
    message.className = 'redirect-message';

    const cancelBtn = document.createElement('button');
    cancelBtn.textContent = 'Cancel';
    cancelBtn.className = 'redirect-cancel';

    overlay.appendChild(spinner);
    overlay.appendChild(message);
    overlay.appendChild(cancelBtn);
    document.body.appendChild(overlay);

    // Cancel logic
    let canceled = false;
    cancelBtn.addEventListener('click', () => {
      canceled = true;
      overlay.remove();
    });

    // Countdown
    let remaining = 3;
    message.textContent = `Single-page book, redirecting in ${remaining}...`;
    while (remaining > 0) {
      await new Promise(res => setTimeout(res, 1000));
      if (canceled) return;
      remaining--;
      message.textContent = `Single-page book, redirecting in ${remaining}...`;
    }

    if (!canceled) {
      window.location.href = contents[0].href;
    }
  })();
</script>

prohtex avatar Jun 14 '25 09:06 prohtex