1 import Sortable 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 this.setupBookSortable(initialSortBox);
47 this.setupSortPresets();
49 window.$events.listen('entity-select-confirm', this.bookSelect.bind(this));
53 * Setup the handlers for the preset sort type buttons.
58 const reversibleTypes = ['name', 'created', 'updated'];
60 this.sortContainer.addEventListener('click', event => {
61 const sortButton = event.target.closest('.sort-box-options [data-sort]');
62 if (!sortButton) return;
64 event.preventDefault();
65 const sortLists = sortButton.closest('.sort-box').querySelectorAll('ul');
66 const sort = sortButton.getAttribute('data-sort');
68 reverse = (lastSort === sort) ? !reverse : false;
69 let sortFunction = sortOperations[sort];
70 if (reverse && reversibleTypes.includes(sort)) {
71 sortFunction = function(a, b) {
72 return 0 - sortOperations[sort](a, b)
76 for (let list of sortLists) {
77 const directItems = Array.from(list.children).filter(child => child.matches('li'));
78 directItems.sort(sortFunction).forEach(sortedItem => {
79 list.appendChild(sortedItem);
84 this.updateMapInput();
89 * Handle book selection from the entity selector.
90 * @param {Object} entityInfo
92 bookSelect(entityInfo) {
93 const alreadyAdded = this.elem.querySelector(`[data-type="book"][data-id="${entityInfo.id}"]`) !== null;
94 if (alreadyAdded) return;
96 const entitySortItemUrl = entityInfo.link + '/sort-item';
97 window.$http.get(entitySortItemUrl).then(resp => {
98 const wrap = document.createElement('div');
99 wrap.innerHTML = resp.data;
100 const newBookContainer = wrap.children[0];
101 this.sortContainer.append(newBookContainer);
102 this.setupBookSortable(newBookContainer);
107 * Setup the given book container element to have sortable items.
108 * @param {Element} bookContainer
110 setupBookSortable(bookContainer) {
111 const sortElems = [bookContainer.querySelector('.sort-list')];
112 sortElems.push(...bookContainer.querySelectorAll('.entity-list-item + ul'));
114 const bookGroupConfig = {
116 pull: ['book', 'chapter'],
117 put: ['book', 'chapter'],
120 const chapterGroupConfig = {
122 pull: ['book', 'chapter'],
123 put: function(toList, fromList, draggedElem) {
124 return draggedElem.getAttribute('data-type') === 'page';
128 for (let sortElem of sortElems) {
129 new Sortable(sortElem, {
130 group: sortElem.classList.contains('sort-list') ? bookGroupConfig : chapterGroupConfig,
132 fallbackOnBody: true,
134 onSort: this.updateMapInput.bind(this),
135 dragClass: 'bg-white',
136 ghostClass: 'primary-background-light',
142 * Update the input with our sort data.
145 const pageMap = this.buildEntityMap();
146 this.input.value = JSON.stringify(pageMap);
150 * Build up a mapping of entities with their ordering and nesting.
154 const entityMap = [];
155 const lists = this.elem.querySelectorAll('.sort-list');
157 for (let list of lists) {
158 const bookId = list.closest('[data-type="book"]').getAttribute('data-id');
159 const directChildren = Array.from(list.children)
160 .filter(elem => elem.matches('[data-type="page"], [data-type="chapter"]'));
161 for (let i = 0; i < directChildren.length; i++) {
162 this.addBookChildToMap(directChildren[i], i, bookId, entityMap);
170 * Parse a sort item and add it to a data-map array.
171 * Parses sub0items if existing also.
172 * @param {Element} childElem
173 * @param {Number} index
174 * @param {Number} bookId
175 * @param {Array} entityMap
177 addBookChildToMap(childElem, index, bookId, entityMap) {
178 const type = childElem.getAttribute('data-type');
179 const parentChapter = false;
180 const childId = childElem.getAttribute('data-id');
185 parentChapter: parentChapter,
190 const subPages = childElem.querySelectorAll('[data-type="page"]');
191 for (let i = 0; i < subPages.length; i++) {
193 id: subPages[i].getAttribute('data-id'),
195 parentChapter: childId,
204 export default BookSort;