]> BookStack Code Mirror - bookstack/blobdiff - resources/js/components/shelf-sort.js
Merge branch 'feature/mail-verify-peer' into development
[bookstack] / resources / js / components / shelf-sort.js
index 07526716a932da86ca34bbec46f8e8cbfa4bf7b3..01ca11a333f10289f1fd185dbce66bc3a704316b 100644 (file)
@@ -1,6 +1,31 @@
-import Sortable from "sortablejs";
+import Sortable from 'sortablejs';
+import {Component} from './component';
 
-class ShelfSort {
+/**
+ * @type {Object<string, function(HTMLElement, HTMLElement, HTMLElement)>}
+ */
+const itemActions = {
+    move_up(item) {
+        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) {
+        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) {
+        shelfBooksList.appendChild(item);
+    },
+};
+
+export class ShelfSort extends Component {
 
     setup() {
         this.elem = this.$el;
@@ -8,6 +33,9 @@ class ShelfSort {
         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();
@@ -15,10 +43,11 @@ class ShelfSort {
 
     initSortable() {
         const scrollBoxes = this.elem.querySelectorAll('.scroll-box');
-        for (let scrollBox of scrollBoxes) {
+        for (const scrollBox of scrollBoxes) {
             new Sortable(scrollBox, {
                 group: 'shelf-books',
                 ghostClass: 'primary-background-light',
+                handle: '.handle',
                 animation: 150,
                 onSort: this.onChange.bind(this),
             });
@@ -27,27 +56,32 @@ class ShelfSort {
 
     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.bookSearchInput.addEventListener('input', () => {
             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);
+            }
+        });
     }
 
     /**
      * @param {String} filterVal
      */
     filterBooksByName(filterVal) {
-
         // Set height on first search, if not already set, to prevent the distraction
         // of the list height jumping around
         if (!this.allBookList.style.height) {
-            this.allBookList.style.height = this.allBookList.getBoundingClientRect().height + 'px';
+            this.allBookList.style.height = `${this.allBookList.getBoundingClientRect().height}px`;
         }
 
         const books = this.allBookList.children;
@@ -60,15 +94,16 @@ class ShelfSort {
     }
 
     /**
-     * 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;
+
+        const actionFunction = itemActions[action];
+        actionFunction(sortItem, this.shelfBookList, this.allBookList);
+
         this.onChange();
     }
 
@@ -77,6 +112,27 @@ class ShelfSort {
         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();
 
-export default ShelfSort;
\ No newline at end of file
+            if (reverse) {
+                return bProp.localeCompare(aProp);
+            }
+
+            return aProp.localeCompare(bProp);
+        });
+
+        for (const book of books) {
+            this.shelfBookList.append(book);
+        }
+
+        this.lastSort = (this.lastSort === sortProperty) ? null : sortProperty;
+        this.onChange();
+    }
+
+}