]> BookStack Code Mirror - bookstack/blob - resources/js/components/shelf-sort.js
Merge pull request #4049 from BookStackApp/shelf_book_sort_updates
[bookstack] / resources / js / components / shelf-sort.js
1 import Sortable from "sortablejs";
2 import {Component} from "./component";
3
4 /**
5  * @type {Object<string, function(HTMLElement, HTMLElement, HTMLElement)>}
6  */
7 const itemActions = {
8     move_up(item, shelfBooksList, allBooksList) {
9         const list = item.parentNode;
10         const index = Array.from(list.children).indexOf(item);
11         const newIndex = Math.max(index - 1, 0);
12         list.insertBefore(item, list.children[newIndex] || null);
13     },
14     move_down(item, shelfBooksList, allBooksList) {
15         const list = item.parentNode;
16         const index = Array.from(list.children).indexOf(item);
17         const newIndex = Math.min(index + 2, list.children.length);
18         list.insertBefore(item, list.children[newIndex] || null);
19     },
20     remove(item, shelfBooksList, allBooksList) {
21         allBooksList.appendChild(item);
22     },
23     add(item, shelfBooksList, allBooksList) {
24         shelfBooksList.appendChild(item);
25     },
26 };
27
28 export class ShelfSort extends Component {
29
30     setup() {
31         this.elem = this.$el;
32         this.input = this.$refs.input;
33         this.shelfBookList = this.$refs.shelfBookList;
34         this.allBookList = this.$refs.allBookList;
35         this.bookSearchInput = this.$refs.bookSearch;
36         this.sortButtonContainer = this.$refs.sortButtonContainer;
37
38         this.lastSort = null;
39
40         this.initSortable();
41         this.setupListeners();
42     }
43
44     initSortable() {
45         const scrollBoxes = this.elem.querySelectorAll('.scroll-box');
46         for (const scrollBox of scrollBoxes) {
47             new Sortable(scrollBox, {
48                 group: 'shelf-books',
49                 ghostClass: 'primary-background-light',
50                 handle: '.handle',
51                 animation: 150,
52                 onSort: this.onChange.bind(this),
53             });
54         }
55     }
56
57     setupListeners() {
58         this.elem.addEventListener('click', event => {
59             const sortItemAction = event.target.closest('.scroll-box-item button[data-action]');
60             if (sortItemAction) {
61                 this.sortItemActionClick(sortItemAction);
62             }
63         });
64
65         this.bookSearchInput.addEventListener('input', event => {
66             this.filterBooksByName(this.bookSearchInput.value);
67         });
68
69         this.sortButtonContainer.addEventListener('click' , event => {
70             const button = event.target.closest('button[data-sort]');
71             if (button) {
72                 this.sortShelfBooks(button.dataset.sort);
73             }
74         });
75     }
76
77     /**
78      * @param {String} filterVal
79      */
80     filterBooksByName(filterVal) {
81
82         // Set height on first search, if not already set, to prevent the distraction
83         // of the list height jumping around
84         if (!this.allBookList.style.height) {
85             this.allBookList.style.height = this.allBookList.getBoundingClientRect().height + 'px';
86         }
87
88         const books = this.allBookList.children;
89         const lowerFilter = filterVal.trim().toLowerCase();
90
91         for (const bookEl of books) {
92             const show = !filterVal || bookEl.textContent.toLowerCase().includes(lowerFilter);
93             bookEl.style.display = show ? null : 'none';
94         }
95     }
96
97     /**
98      * Called when a sort item action button is clicked.
99      * @param {HTMLElement} sortItemAction
100      */
101     sortItemActionClick(sortItemAction) {
102         const sortItem = sortItemAction.closest('.scroll-box-item');
103         const action = sortItemAction.dataset.action;
104
105         const actionFunction = itemActions[action];
106         actionFunction(sortItem, this.shelfBookList, this.allBookList);
107
108         this.onChange();
109     }
110
111     onChange() {
112         const shelfBookElems = Array.from(this.shelfBookList.querySelectorAll('[data-id]'));
113         this.input.value = shelfBookElems.map(elem => elem.getAttribute('data-id')).join(',');
114     }
115
116     sortShelfBooks(sortProperty) {
117         const books = Array.from(this.shelfBookList.children);
118         const reverse = sortProperty === this.lastSort;
119
120         books.sort((bookA, bookB) => {
121             const aProp = bookA.dataset[sortProperty].toLowerCase();
122             const bProp = bookB.dataset[sortProperty].toLowerCase();
123
124             if (reverse) {
125                 return aProp < bProp ? (aProp === bProp ? 0 : 1) : -1;
126             }
127
128             return aProp < bProp ? (aProp === bProp ? 0 : -1) : 1;
129         });
130
131         for (const book of books) {
132             this.shelfBookList.append(book);
133         }
134
135         this.lastSort = (this.lastSort === sortProperty) ? null : sortProperty;
136         this.onChange();
137     }
138
139 }