1 import {Sortable, MultiDrag} from "sortablejs";
4 const sortOperations = {
6 const aName = a.getAttribute('data-name').trim().toLowerCase();
7 const bName = b.getAttribute('data-name').trim().toLowerCase();
8 return aName.localeCompare(bName);
10 created: function(a, b) {
11 const aTime = Number(a.getAttribute('data-created'));
12 const bTime = Number(b.getAttribute('data-created'));
15 updated: function(a, b) {
16 const aTime = Number(a.getAttribute('data-updated'));
17 const bTime = Number(b.getAttribute('data-updated'));
20 chaptersFirst: function(a, b) {
21 const aType = a.getAttribute('data-type');
22 const bType = b.getAttribute('data-type');
23 if (aType === bType) {
26 return (aType === 'chapter' ? -1 : 1);
28 chaptersLast: function(a, b) {
29 const aType = a.getAttribute('data-type');
30 const bType = b.getAttribute('data-type');
31 if (aType === bType) {
34 return (aType === 'chapter' ? 1 : -1);
42 this.sortContainer = elem.querySelector('[book-sort-boxes]');
43 this.input = elem.querySelector('[book-sort-input]');
45 const initialSortBox = elem.querySelector('.sort-box');
46 Sortable.mount(new MultiDrag());
47 this.setupBookSortable(initialSortBox);
48 this.setupSortPresets();
50 window.$events.listen('entity-select-confirm', this.bookSelect.bind(this));
54 * Setup the handlers for the preset sort type buttons.
59 const reversibleTypes = ['name', 'created', 'updated'];
61 this.sortContainer.addEventListener('click', event => {
62 const sortButton = event.target.closest('.sort-box-options [data-sort]');
63 if (!sortButton) return;
65 event.preventDefault();
66 const sortLists = sortButton.closest('.sort-box').querySelectorAll('ul');
67 const sort = sortButton.getAttribute('data-sort');
69 reverse = (lastSort === sort) ? !reverse : false;
70 let sortFunction = sortOperations[sort];
71 if (reverse && reversibleTypes.includes(sort)) {
72 sortFunction = function(a, b) {
73 return 0 - sortOperations[sort](a, b)
77 for (let list of sortLists) {
78 const directItems = Array.from(list.children).filter(child => child.matches('li'));
79 directItems.sort(sortFunction).forEach(sortedItem => {
80 list.appendChild(sortedItem);
85 this.updateMapInput();
90 * Handle book selection from the entity selector.
91 * @param {Object} entityInfo
93 bookSelect(entityInfo) {
94 const alreadyAdded = this.elem.querySelector(`[data-type="book"][data-id="${entityInfo.id}"]`) !== null;
95 if (alreadyAdded) return;
97 const entitySortItemUrl = entityInfo.link + '/sort-item';
98 window.$http.get(entitySortItemUrl).then(resp => {
99 const wrap = document.createElement('div');
100 wrap.innerHTML = resp.data;
101 const newBookContainer = wrap.children[0];
102 this.sortContainer.append(newBookContainer);
103 this.setupBookSortable(newBookContainer);
108 * Setup the given book container element to have sortable items.
109 * @param {Element} bookContainer
111 setupBookSortable(bookContainer) {
112 const sortElems = [bookContainer.querySelector('.sort-list')];
113 sortElems.push(...bookContainer.querySelectorAll('.entity-list-item + ul'));
115 const bookGroupConfig = {
117 pull: ['book', 'chapter'],
118 put: ['book', 'chapter'],
121 const chapterGroupConfig = {
123 pull: ['book', 'chapter'],
124 put: function(toList, fromList, draggedElem) {
125 return draggedElem.getAttribute('data-type') === 'page';
129 for (let sortElem of sortElems) {
130 new Sortable(sortElem, {
131 group: sortElem.classList.contains('sort-list') ? bookGroupConfig : chapterGroupConfig,
133 fallbackOnBody: true,
135 onSort: this.updateMapInput.bind(this),
136 dragClass: 'bg-white',
137 ghostClass: 'primary-background-light',
139 multiDragKey: 'CTRL',
140 selectedClass: 'sortable-selected',
146 * Update the input with our sort data.
149 const pageMap = this.buildEntityMap();
150 this.input.value = JSON.stringify(pageMap);
154 * Build up a mapping of entities with their ordering and nesting.
158 const entityMap = [];
159 const lists = this.elem.querySelectorAll('.sort-list');
161 for (let list of lists) {
162 const bookId = list.closest('[data-type="book"]').getAttribute('data-id');
163 const directChildren = Array.from(list.children)
164 .filter(elem => elem.matches('[data-type="page"], [data-type="chapter"]'));
165 for (let i = 0; i < directChildren.length; i++) {
166 this.addBookChildToMap(directChildren[i], i, bookId, entityMap);
174 * Parse a sort item and add it to a data-map array.
175 * Parses sub0items if existing also.
176 * @param {Element} childElem
177 * @param {Number} index
178 * @param {Number} bookId
179 * @param {Array} entityMap
181 addBookChildToMap(childElem, index, bookId, entityMap) {
182 const type = childElem.getAttribute('data-type');
183 const parentChapter = false;
184 const childId = childElem.getAttribute('data-id');
189 parentChapter: parentChapter,
194 const subPages = childElem.querySelectorAll('[data-type="page"]');
195 for (let i = 0; i < subPages.length; i++) {
197 id: subPages[i].getAttribute('data-id'),
199 parentChapter: childId,
208 export default BookSort;