]> BookStack Code Mirror - bookstack/blobdiff - resources/js/components/shelf-sort.js
Comments: Added HTML filter on load, tinymce elem filtering
[bookstack] / resources / js / components / shelf-sort.js
index 38e8ae8d3e2a40c2acde17725a448f9d880943cd..01ca11a333f10289f1fd185dbce66bc3a704316b 100644 (file)
@@ -1,11 +1,41 @@
-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);
+    },
+};
 
-    constructor(elem) {
-        this.elem = elem;
-        this.input = document.getElementById('books-input');
-        this.shelfBooksList = elem.querySelector('[shelf-sort-assigned-books]');
+export class ShelfSort extends Component {
+
+    setup() {
+        this.elem = this.$el;
+        this.input = this.$refs.input;
+        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();
@@ -13,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),
             });
@@ -25,32 +56,83 @@ class ShelfSort {
 
     setupListeners() {
         this.elem.addEventListener('click', event => {
-            const sortItem = event.target.closest('.scroll-box-item:not(.instruction)');
-            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', () => {
+            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);
             }
         });
     }
 
     /**
-     * Called when a sort item is clicked.
-     * @param {Element} sortItem
+     * @param {String} filterVal
      */
-    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);
+    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`;
         }
+
+        const books = this.allBookList.children;
+        const lowerFilter = filterVal.trim().toLowerCase();
+
+        for (const bookEl of books) {
+            const show = !filterVal || bookEl.textContent.toLowerCase().includes(lowerFilter);
+            bookEl.style.display = show ? null : 'none';
+        }
+    }
+
+    /**
+     * Called when a sort item action button is clicked.
+     * @param {HTMLElement} sortItemAction
+     */
+    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();
     }
 
     onChange() {
-        const shelfBookElems = Array.from(this.shelfBooksList.querySelectorAll('[data-id]'));
+        const shelfBookElems = Array.from(this.shelfBookList.querySelectorAll('[data-id]'));
         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 bProp.localeCompare(aProp);
+            }
+
+            return aProp.localeCompare(bProp);
+        });
+
+        for (const book of books) {
+            this.shelfBookList.append(book);
+        }
 
-export default ShelfSort;
\ No newline at end of file
+        this.lastSort = (this.lastSort === sortProperty) ? null : sortProperty;
+        this.onChange();
+    }
+
+}