]> BookStack Code Mirror - bookstack/commitdiff
Added shelf book item sort action functionality
authorDan Brown <redacted>
Fri, 17 Feb 2023 15:53:24 +0000 (15:53 +0000)
committerDan Brown <redacted>
Fri, 17 Feb 2023 15:53:24 +0000 (15:53 +0000)
Adds JS logic, and dropdown action list, for quick-sorting the book
shelf list in addition to handling the book item action buttons.

resources/js/components/shelf-sort.js
resources/sass/_components.scss
resources/views/shelves/parts/form.blade.php

index d10470bd79a4ea7e19774bcb5eedc3c6e533ffad..e4aefc5918bb41d0bcfb6be918263692afa62b19 100644 (file)
@@ -1,6 +1,30 @@
 import Sortable from "sortablejs";
 import {Component} from "./component";
 
+/**
+ * @type {Object<string, function(HTMLElement, HTMLElement, HTMLElement)>}
+ */
+const itemActions = {
+    move_up(item, shelfBooksList, allBooksList) {
+        const list = item.parentNode;
+        const index = Array.from(list.children).indexOf(item);
+        const newIndex = Math.max(index - 1, 0);
+        list.insertBefore(item, list.children[newIndex] || null);
+    },
+    move_down(item, shelfBooksList, allBooksList) {
+        const list = item.parentNode;
+        const index = Array.from(list.children).indexOf(item);
+        const newIndex = Math.min(index + 2, list.children.length);
+        list.insertBefore(item, list.children[newIndex] || null);
+    },
+    remove(item, shelfBooksList, allBooksList) {
+        allBooksList.appendChild(item);
+    },
+    add(item, shelfBooksList, allBooksList) {
+        shelfBooksList.appendChild(item);
+    },
+};
+
 export class ShelfSort extends Component {
 
     setup() {
@@ -9,6 +33,9 @@ export class ShelfSort extends Component {
         this.shelfBookList = this.$refs.shelfBookList;
         this.allBookList = this.$refs.allBookList;
         this.bookSearchInput = this.$refs.bookSearch;
+        this.sortButtonContainer = this.$refs.sortButtonContainer;
+
+        this.lastSort = null;
 
         this.initSortable();
         this.setupListeners();
@@ -29,16 +56,22 @@ export class ShelfSort extends Component {
 
     setupListeners() {
         this.elem.addEventListener('click', event => {
-            const sortItem = event.target.closest('.scroll-box-item');
-            if (sortItem) {
-                event.preventDefault();
-                this.sortItemClick(sortItem);
+            const sortItemAction = event.target.closest('.scroll-box-item button[data-action]');
+            if (sortItemAction) {
+                this.sortItemActionClick(sortItemAction);
             }
         });
 
         this.bookSearchInput.addEventListener('input', event => {
             this.filterBooksByName(this.bookSearchInput.value);
         });
+
+        this.sortButtonContainer.addEventListener('click' , event => {
+            const button = event.target.closest('button[data-sort]');
+            if (button) {
+                this.sortShelfBooks(button.dataset.sort);
+            }
+        });
     }
 
     /**
@@ -62,15 +95,16 @@ export class ShelfSort extends Component {
     }
 
     /**
-     * Called when a sort item is clicked.
-     * @param {Element} sortItem
+     * Called when a sort item action button is clicked.
+     * @param {HTMLElement} sortItemAction
      */
-    sortItemClick(sortItem) {
-        const lists = this.elem.querySelectorAll('.scroll-box');
-        const newList = Array.from(lists).filter(list => sortItem.parentElement !== list);
-        if (newList.length > 0) {
-            newList[0].appendChild(sortItem);
-        }
+    sortItemActionClick(sortItemAction) {
+        const sortItem = sortItemAction.closest('.scroll-box-item');
+        const action = sortItemAction.dataset.action;
+
+        const actionFunction = itemActions[action];
+        actionFunction(sortItem, this.shelfBookList, this.allBookList);
+
         this.onChange();
     }
 
@@ -79,4 +113,27 @@ export class ShelfSort extends Component {
         this.input.value = shelfBookElems.map(elem => elem.getAttribute('data-id')).join(',');
     }
 
+    sortShelfBooks(sortProperty) {
+        const books = Array.from(this.shelfBookList.children);
+        const reverse = sortProperty === this.lastSort;
+
+        books.sort((bookA, bookB) => {
+            const aProp = bookA.dataset[sortProperty].toLowerCase();
+            const bProp = bookB.dataset[sortProperty].toLowerCase();
+
+            if (reverse) {
+                return aProp < bProp ? (aProp === bProp ? 0 : 1) : -1;
+            }
+
+            return aProp < bProp ? (aProp === bProp ? 0 : -1) : 1;
+        });
+
+        for (const book of books) {
+            this.shelfBookList.append(book);
+        }
+
+        this.lastSort = (this.lastSort === sortProperty) ? null : sortProperty;
+        this.onChange();
+    }
+
 }
\ No newline at end of file
index ac0d913aaf6d23500b6e41740c177b0d636b5908..c86c04d33721b1d272d3290f06dad10f5572fdb3 100644 (file)
@@ -1057,7 +1057,7 @@ $btt-size: 40px;
   list-style: none;
   padding: 0;
   margin: 0;
-  max-height: 250px;
+  max-height: 280px;
   overflow-y: scroll;
   border: 1px solid;
   @include lightDark(border-color, #DDD, #000);
@@ -1104,7 +1104,6 @@ $btt-size: 40px;
 
 input.scroll-box-search, .scroll-box-header-item {
   font-size: 0.8rem;
-  padding: $-xs $-m;
   border: 1px solid;
   @include lightDark(border-color, #DDD, #000);
   @include lightDark(background-color, #FFF, #222);
@@ -1125,6 +1124,9 @@ input.scroll-box-search, .scroll-box-header-item {
 .scroll-box[refs="shelf-sort@shelf-book-list"] [data-action="add"] {
   display: none;
 }
-.scroll-box[refs="shelf-sort@all-book-list"] [data-action="remove"] {
+.scroll-box[refs="shelf-sort@all-book-list"] [data-action="remove"],
+.scroll-box[refs="shelf-sort@all-book-list"] [data-action="move_up"],
+.scroll-box[refs="shelf-sort@all-book-list"] [data-action="move_down"],
+{
   display: none;
 }
\ No newline at end of file
index 02cea64ffd4c8af129a07dd9161688970f75fa94..4598cbacc751c97af7bb0c86426dbc44b21e30cc 100644 (file)
         <label for="books">{{ trans('entities.shelves_books') }}</label>
         <input refs="shelf-sort@input" type="hidden" name="books"
                value="{{ isset($shelf) ? $shelf->visibleBooks->implode('id', ',') : '' }}">
-        <div class="scroll-box-header-item">{{ trans('entities.shelves_drag_books') }}</div>
+        <div class="scroll-box-header-item flex-container-row items-center py-xs">
+            <span class="px-m py-xs">{{ trans('entities.shelves_drag_books') }}</span>
+            <div class="dropdown-container ml-auto" component="dropdown">
+                <button refs="dropdown@toggle"
+                        type="button"
+                        title="{{ trans('common.more') }}"
+                        class="icon-button px-xs py-xxs mx-xs text-bigger"
+                        aria-haspopup="true"
+                        aria-expanded="false">
+                    @icon('more')
+                </button>
+                <div refs="dropdown@menu shelf-sort@sort-button-container" class="dropdown-menu" role="menu">
+                    <button type="button" class="text-item" data-sort="name">{{ trans('entities.books_sort_name') }}</button>
+                    <button type="button" class="text-item" data-sort="created">{{ trans('entities.books_sort_created') }}</button>
+                    <button type="button" class="text-item" data-sort="updated">{{ trans('entities.books_sort_updated') }}</button>
+                </div>
+            </div>
+        </div>
         <ul refs="shelf-sort@shelf-book-list" class="scroll-box">
             @foreach (($shelf->visibleBooks ?? []) as $book)
                 @include('shelves.parts.shelf-sort-book-item', ['book' => $book])