1 import Sortable, {MultiDrag} from "sortablejs";
2 import {Component} from "./component";
3 import {htmlToDom} from "../services/dom";
6 const sortOperations = {
8 const aName = a.getAttribute('data-name').trim().toLowerCase();
9 const bName = b.getAttribute('data-name').trim().toLowerCase();
10 return aName.localeCompare(bName);
12 created: function(a, b) {
13 const aTime = Number(a.getAttribute('data-created'));
14 const bTime = Number(b.getAttribute('data-created'));
17 updated: function(a, b) {
18 const aTime = Number(a.getAttribute('data-updated'));
19 const bTime = Number(b.getAttribute('data-updated'));
22 chaptersFirst: function(a, b) {
23 const aType = a.getAttribute('data-type');
24 const bType = b.getAttribute('data-type');
25 if (aType === bType) {
28 return (aType === 'chapter' ? -1 : 1);
30 chaptersLast: function(a, b) {
31 const aType = a.getAttribute('data-type');
32 const bType = b.getAttribute('data-type');
33 if (aType === bType) {
36 return (aType === 'chapter' ? 1 : -1);
40 export class BookSort extends Component {
43 this.container = this.$el;
44 this.sortContainer = this.$refs.sortContainer;
45 this.input = this.$refs.input;
47 Sortable.mount(new MultiDrag());
49 const initialSortBox = this.container.querySelector('.sort-box');
50 this.setupBookSortable(initialSortBox);
51 this.setupSortPresets();
53 window.$events.listen('entity-select-confirm', this.bookSelect.bind(this));
57 * Setup the handlers for the preset sort type buttons.
62 const reversibleTypes = ['name', 'created', 'updated'];
64 this.sortContainer.addEventListener('click', event => {
65 const sortButton = event.target.closest('.sort-box-options [data-sort]');
66 if (!sortButton) return;
68 event.preventDefault();
69 const sortLists = sortButton.closest('.sort-box').querySelectorAll('ul');
70 const sort = sortButton.getAttribute('data-sort');
72 reverse = (lastSort === sort) ? !reverse : false;
73 let sortFunction = sortOperations[sort];
74 if (reverse && reversibleTypes.includes(sort)) {
75 sortFunction = function(a, b) {
76 return 0 - sortOperations[sort](a, b)
80 for (let list of sortLists) {
81 const directItems = Array.from(list.children).filter(child => child.matches('li'));
82 directItems.sort(sortFunction).forEach(sortedItem => {
83 list.appendChild(sortedItem);
88 this.updateMapInput();
93 * Handle book selection from the entity selector.
94 * @param {Object} entityInfo
96 bookSelect(entityInfo) {
97 const alreadyAdded = this.container.querySelector(`[data-type="book"][data-id="${entityInfo.id}"]`) !== null;
98 if (alreadyAdded) return;
100 const entitySortItemUrl = entityInfo.link + '/sort-item';
101 window.$http.get(entitySortItemUrl).then(resp => {
102 const newBookContainer = htmlToDom(resp.data);
103 this.sortContainer.append(newBookContainer);
104 this.setupBookSortable(newBookContainer);
109 * Set up the given book container element to have sortable items.
110 * @param {Element} bookContainer
112 setupBookSortable(bookContainer) {
113 const sortElems = [bookContainer.querySelector('.sort-list')];
114 sortElems.push(...bookContainer.querySelectorAll('.entity-list-item + ul'));
116 const bookGroupConfig = {
118 pull: ['book', 'chapter'],
119 put: ['book', 'chapter'],
122 const chapterGroupConfig = {
124 pull: ['book', 'chapter'],
125 put: function(toList, fromList, draggedElem) {
126 return draggedElem.getAttribute('data-type') === 'page';
130 for (const sortElem of sortElems) {
131 Sortable.create(sortElem, {
132 group: sortElem.classList.contains('sort-list') ? bookGroupConfig : chapterGroupConfig,
134 fallbackOnBody: true,
136 onSort: this.updateMapInput.bind(this),
137 dragClass: 'bg-white',
138 ghostClass: 'primary-background-light',
140 multiDragKey: 'Control',
141 selectedClass: 'sortable-selected',
147 * Update the input with our sort data.
150 const pageMap = this.buildEntityMap();
151 this.input.value = JSON.stringify(pageMap);
155 * Build up a mapping of entities with their ordering and nesting.
159 const entityMap = [];
160 const lists = this.container.querySelectorAll('.sort-list');
162 for (let list of lists) {
163 const bookId = list.closest('[data-type="book"]').getAttribute('data-id');
164 const directChildren = Array.from(list.children)
165 .filter(elem => elem.matches('[data-type="page"], [data-type="chapter"]'));
166 for (let i = 0; i < directChildren.length; i++) {
167 this.addBookChildToMap(directChildren[i], i, bookId, entityMap);
175 * Parse a sort item and add it to a data-map array.
176 * Parses sub0items if existing also.
177 * @param {Element} childElem
178 * @param {Number} index
179 * @param {Number} bookId
180 * @param {Array} entityMap
182 addBookChildToMap(childElem, index, bookId, entityMap) {
183 const type = childElem.getAttribute('data-type');
184 const parentChapter = false;
185 const childId = childElem.getAttribute('data-id');
190 parentChapter: parentChapter,
195 const subPages = childElem.querySelectorAll('[data-type="page"]');
196 for (let i = 0; i < subPages.length; i++) {
198 id: subPages[i].getAttribute('data-id'),
200 parentChapter: childId,