mirror of
				https://github.com/BookStackApp/BookStack.git
				synced 2025-11-03 02:13:16 +03:00 
			
		
		
		
	Had to change way sortable is imported due to changes, Still seemed to have functioning multi-select.
		
			
				
	
	
		
			207 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			207 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
import Sortable from "sortablejs";
 | 
						|
 | 
						|
// Auto sort control
 | 
						|
const sortOperations = {
 | 
						|
    name: function(a, b) {
 | 
						|
        const aName = a.getAttribute('data-name').trim().toLowerCase();
 | 
						|
        const bName = b.getAttribute('data-name').trim().toLowerCase();
 | 
						|
        return aName.localeCompare(bName);
 | 
						|
    },
 | 
						|
    created: function(a, b) {
 | 
						|
        const aTime = Number(a.getAttribute('data-created'));
 | 
						|
        const bTime = Number(b.getAttribute('data-created'));
 | 
						|
        return bTime - aTime;
 | 
						|
    },
 | 
						|
    updated: function(a, b) {
 | 
						|
        const aTime = Number(a.getAttribute('data-updated'));
 | 
						|
        const bTime = Number(b.getAttribute('data-updated'));
 | 
						|
        return bTime - aTime;
 | 
						|
    },
 | 
						|
    chaptersFirst: function(a, b) {
 | 
						|
        const aType = a.getAttribute('data-type');
 | 
						|
        const bType = b.getAttribute('data-type');
 | 
						|
        if (aType === bType) {
 | 
						|
            return 0;
 | 
						|
        }
 | 
						|
        return (aType === 'chapter' ? -1 : 1);
 | 
						|
    },
 | 
						|
    chaptersLast: function(a, b) {
 | 
						|
        const aType = a.getAttribute('data-type');
 | 
						|
        const bType = b.getAttribute('data-type');
 | 
						|
        if (aType === bType) {
 | 
						|
            return 0;
 | 
						|
        }
 | 
						|
        return (aType === 'chapter' ? 1 : -1);
 | 
						|
    },
 | 
						|
};
 | 
						|
 | 
						|
class BookSort {
 | 
						|
 | 
						|
    constructor(elem) {
 | 
						|
        this.elem = elem;
 | 
						|
        this.sortContainer = elem.querySelector('[book-sort-boxes]');
 | 
						|
        this.input = elem.querySelector('[book-sort-input]');
 | 
						|
 | 
						|
        const initialSortBox = elem.querySelector('.sort-box');
 | 
						|
        this.setupBookSortable(initialSortBox);
 | 
						|
        this.setupSortPresets();
 | 
						|
 | 
						|
        window.$events.listen('entity-select-confirm', this.bookSelect.bind(this));
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Setup the handlers for the preset sort type buttons.
 | 
						|
     */
 | 
						|
    setupSortPresets() {
 | 
						|
        let lastSort = '';
 | 
						|
        let reverse = false;
 | 
						|
        const reversibleTypes = ['name', 'created', 'updated'];
 | 
						|
 | 
						|
        this.sortContainer.addEventListener('click', event => {
 | 
						|
            const sortButton = event.target.closest('.sort-box-options [data-sort]');
 | 
						|
            if (!sortButton) return;
 | 
						|
 | 
						|
            event.preventDefault();
 | 
						|
            const sortLists = sortButton.closest('.sort-box').querySelectorAll('ul');
 | 
						|
            const sort = sortButton.getAttribute('data-sort');
 | 
						|
 | 
						|
            reverse = (lastSort === sort) ? !reverse : false;
 | 
						|
            let sortFunction = sortOperations[sort];
 | 
						|
            if (reverse && reversibleTypes.includes(sort)) {
 | 
						|
                sortFunction = function(a, b) {
 | 
						|
                    return 0 - sortOperations[sort](a, b)
 | 
						|
                };
 | 
						|
            }
 | 
						|
 | 
						|
            for (let list of sortLists) {
 | 
						|
                const directItems = Array.from(list.children).filter(child => child.matches('li'));
 | 
						|
                directItems.sort(sortFunction).forEach(sortedItem => {
 | 
						|
                    list.appendChild(sortedItem);
 | 
						|
                });
 | 
						|
            }
 | 
						|
 | 
						|
            lastSort = sort;
 | 
						|
            this.updateMapInput();
 | 
						|
        });
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Handle book selection from the entity selector.
 | 
						|
     * @param {Object} entityInfo
 | 
						|
     */
 | 
						|
    bookSelect(entityInfo) {
 | 
						|
        const alreadyAdded = this.elem.querySelector(`[data-type="book"][data-id="${entityInfo.id}"]`) !== null;
 | 
						|
        if (alreadyAdded) return;
 | 
						|
 | 
						|
        const entitySortItemUrl = entityInfo.link + '/sort-item';
 | 
						|
        window.$http.get(entitySortItemUrl).then(resp => {
 | 
						|
            const wrap = document.createElement('div');
 | 
						|
            wrap.innerHTML = resp.data;
 | 
						|
            const newBookContainer = wrap.children[0];
 | 
						|
            this.sortContainer.append(newBookContainer);
 | 
						|
            this.setupBookSortable(newBookContainer);
 | 
						|
        });
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Setup the given book container element to have sortable items.
 | 
						|
     * @param {Element} bookContainer
 | 
						|
     */
 | 
						|
    setupBookSortable(bookContainer) {
 | 
						|
        const sortElems = [bookContainer.querySelector('.sort-list')];
 | 
						|
        sortElems.push(...bookContainer.querySelectorAll('.entity-list-item + ul'));
 | 
						|
 | 
						|
        const bookGroupConfig = {
 | 
						|
            name: 'book',
 | 
						|
            pull: ['book', 'chapter'],
 | 
						|
            put: ['book', 'chapter'],
 | 
						|
        };
 | 
						|
 | 
						|
        const chapterGroupConfig = {
 | 
						|
            name: 'chapter',
 | 
						|
            pull: ['book', 'chapter'],
 | 
						|
            put: function(toList, fromList, draggedElem) {
 | 
						|
                return draggedElem.getAttribute('data-type') === 'page';
 | 
						|
            }
 | 
						|
        };
 | 
						|
 | 
						|
        for (let sortElem of sortElems) {
 | 
						|
            new Sortable(sortElem, {
 | 
						|
                group: sortElem.classList.contains('sort-list') ? bookGroupConfig : chapterGroupConfig,
 | 
						|
                animation: 150,
 | 
						|
                fallbackOnBody: true,
 | 
						|
                swapThreshold: 0.65,
 | 
						|
                onSort: this.updateMapInput.bind(this),
 | 
						|
                dragClass: 'bg-white',
 | 
						|
                ghostClass: 'primary-background-light',
 | 
						|
                multiDrag: true,
 | 
						|
                multiDragKey: 'CTRL',
 | 
						|
                selectedClass: 'sortable-selected',
 | 
						|
            });
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Update the input with our sort data.
 | 
						|
     */
 | 
						|
    updateMapInput() {
 | 
						|
        const pageMap = this.buildEntityMap();
 | 
						|
        this.input.value = JSON.stringify(pageMap);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Build up a mapping of entities with their ordering and nesting.
 | 
						|
     * @returns {Array}
 | 
						|
     */
 | 
						|
    buildEntityMap() {
 | 
						|
        const entityMap = [];
 | 
						|
        const lists = this.elem.querySelectorAll('.sort-list');
 | 
						|
 | 
						|
        for (let list of lists) {
 | 
						|
            const bookId = list.closest('[data-type="book"]').getAttribute('data-id');
 | 
						|
            const directChildren = Array.from(list.children)
 | 
						|
                .filter(elem => elem.matches('[data-type="page"], [data-type="chapter"]'));
 | 
						|
            for (let i = 0; i < directChildren.length; i++) {
 | 
						|
                this.addBookChildToMap(directChildren[i], i, bookId, entityMap);
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        return entityMap;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Parse a sort item and add it to a data-map array.
 | 
						|
     * Parses sub0items if existing also.
 | 
						|
     * @param {Element} childElem
 | 
						|
     * @param {Number} index
 | 
						|
     * @param {Number} bookId
 | 
						|
     * @param {Array} entityMap
 | 
						|
     */
 | 
						|
    addBookChildToMap(childElem, index, bookId, entityMap) {
 | 
						|
        const type = childElem.getAttribute('data-type');
 | 
						|
        const parentChapter = false;
 | 
						|
        const childId = childElem.getAttribute('data-id');
 | 
						|
 | 
						|
        entityMap.push({
 | 
						|
            id: childId,
 | 
						|
            sort: index,
 | 
						|
            parentChapter: parentChapter,
 | 
						|
            type: type,
 | 
						|
            book: bookId
 | 
						|
        });
 | 
						|
 | 
						|
        const subPages = childElem.querySelectorAll('[data-type="page"]');
 | 
						|
        for (let i = 0; i < subPages.length; i++) {
 | 
						|
            entityMap.push({
 | 
						|
                id: subPages[i].getAttribute('data-id'),
 | 
						|
                sort: i,
 | 
						|
                parentChapter: childId,
 | 
						|
                type: 'page',
 | 
						|
                book: bookId
 | 
						|
            });
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
export default BookSort; |