1 import Sortable 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 const initialSortBox = this.container.querySelector('.sort-box');
48 this.setupBookSortable(initialSortBox);
49 this.setupSortPresets();
51 window.$events.listen('entity-select-confirm', this.bookSelect.bind(this));
55 * Setup the handlers for the preset sort type buttons.
60 const reversibleTypes = ['name', 'created', 'updated'];
62 this.sortContainer.addEventListener('click', event => {
63 const sortButton = event.target.closest('.sort-box-options [data-sort]');
64 if (!sortButton) return;
66 event.preventDefault();
67 const sortLists = sortButton.closest('.sort-box').querySelectorAll('ul');
68 const sort = sortButton.getAttribute('data-sort');
70 reverse = (lastSort === sort) ? !reverse : false;
71 let sortFunction = sortOperations[sort];
72 if (reverse && reversibleTypes.includes(sort)) {
73 sortFunction = function(a, b) {
74 return 0 - sortOperations[sort](a, b)
78 for (let list of sortLists) {
79 const directItems = Array.from(list.children).filter(child => child.matches('li'));
80 directItems.sort(sortFunction).forEach(sortedItem => {
81 list.appendChild(sortedItem);
86 this.updateMapInput();
91 * Handle book selection from the entity selector.
92 * @param {Object} entityInfo
94 bookSelect(entityInfo) {
95 const alreadyAdded = this.container.querySelector(`[data-type="book"][data-id="${entityInfo.id}"]`) !== null;
96 if (alreadyAdded) return;
98 const entitySortItemUrl = entityInfo.link + '/sort-item';
99 window.$http.get(entitySortItemUrl).then(resp => {
100 const newBookContainer = htmlToDom(resp.data);
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',
138 multiDragKey: 'CTRL',
139 selectedClass: 'sortable-selected',
145 * Update the input with our sort data.
148 const pageMap = this.buildEntityMap();
149 this.input.value = JSON.stringify(pageMap);
153 * Build up a mapping of entities with their ordering and nesting.
157 const entityMap = [];
158 const lists = this.container.querySelectorAll('.sort-list');
160 for (let list of lists) {
161 const bookId = list.closest('[data-type="book"]').getAttribute('data-id');
162 const directChildren = Array.from(list.children)
163 .filter(elem => elem.matches('[data-type="page"], [data-type="chapter"]'));
164 for (let i = 0; i < directChildren.length; i++) {
165 this.addBookChildToMap(directChildren[i], i, bookId, entityMap);
173 * Parse a sort item and add it to a data-map array.
174 * Parses sub0items if existing also.
175 * @param {Element} childElem
176 * @param {Number} index
177 * @param {Number} bookId
178 * @param {Array} entityMap
180 addBookChildToMap(childElem, index, bookId, entityMap) {
181 const type = childElem.getAttribute('data-type');
182 const parentChapter = false;
183 const childId = childElem.getAttribute('data-id');
188 parentChapter: parentChapter,
193 const subPages = childElem.querySelectorAll('[data-type="page"]');
194 for (let i = 0; i < subPages.length; i++) {
196 id: subPages[i].getAttribute('data-id'),
198 parentChapter: childId,