mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 01:36:00 -04:00
Replaced jquery sortable with sortablejs
This commit is contained in:
parent
2eba8c611e
commit
d87eb277dd
15
package-lock.json
generated
15
package-lock.json
generated
@ -3435,21 +3435,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.4.1.tgz",
|
||||||
"integrity": "sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw=="
|
"integrity": "sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw=="
|
||||||
},
|
},
|
||||||
"jquery-sortable": {
|
|
||||||
"version": "0.9.13",
|
|
||||||
"resolved": "https://registry.npmjs.org/jquery-sortable/-/jquery-sortable-0.9.13.tgz",
|
|
||||||
"integrity": "sha1-HL+2VQE6B0c3BXHwbiL1JKAP+6I=",
|
|
||||||
"requires": {
|
|
||||||
"jquery": "^2.1.2"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"jquery": {
|
|
||||||
"version": "2.2.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/jquery/-/jquery-2.2.4.tgz",
|
|
||||||
"integrity": "sha1-LInWiJterFIqfuoywUUhVZxsvwI="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"js-base64": {
|
"js-base64": {
|
||||||
"version": "2.5.1",
|
"version": "2.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.1.tgz",
|
||||||
|
@ -26,7 +26,6 @@
|
|||||||
"codemirror": "^5.47.0",
|
"codemirror": "^5.47.0",
|
||||||
"dropzone": "^5.5.1",
|
"dropzone": "^5.5.1",
|
||||||
"jquery": "^3.4.1",
|
"jquery": "^3.4.1",
|
||||||
"jquery-sortable": "^0.9.13",
|
|
||||||
"markdown-it": "^8.4.2",
|
"markdown-it": "^8.4.2",
|
||||||
"markdown-it-task-lists": "^2.1.1",
|
"markdown-it-task-lists": "^2.1.1",
|
||||||
"sortablejs": "^1.9.0",
|
"sortablejs": "^1.9.0",
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
!function(d,B,m,f){function v(a,b){var c=Math.max(0,a[0]-b[0],b[0]-a[1]),e=Math.max(0,a[2]-b[1],b[1]-a[3]);return c+e}function w(a,b,c,e){var k=a.length;e=e?"offset":"position";for(c=c||0;k--;){var g=a[k].el?a[k].el:d(a[k]),l=g[e]();l.left+=parseInt(g.css("margin-left"),10);l.top+=parseInt(g.css("margin-top"),10);b[k]=[l.left-c,l.left+g.outerWidth()+c,l.top-c,l.top+g.outerHeight()+c]}}function p(a,b){var c=b.offset();return{left:a.left-c.left,top:a.top-c.top}}function x(a,b,c){b=[b.left,b.top];c=
|
|
||||||
c&&[c.left,c.top];for(var e,k=a.length,d=[];k--;)e=a[k],d[k]=[k,v(e,b),c&&v(e,c)];return d=d.sort(function(a,b){return b[1]-a[1]||b[2]-a[2]||b[0]-a[0]})}function q(a){this.options=d.extend({},n,a);this.containers=[];this.options.rootGroup||(this.scrollProxy=d.proxy(this.scroll,this),this.dragProxy=d.proxy(this.drag,this),this.dropProxy=d.proxy(this.drop,this),this.placeholder=d(this.options.placeholder),a.isValidTarget||(this.options.isValidTarget=f))}function t(a,b){this.el=a;this.options=d.extend({},
|
|
||||||
z,b);this.group=q.get(this.options);this.rootGroup=this.options.rootGroup||this.group;this.handle=this.rootGroup.options.handle||this.rootGroup.options.itemSelector;var c=this.rootGroup.options.itemPath;this.target=c?this.el.find(c):this.el;this.target.on(r.start,this.handle,d.proxy(this.dragInit,this));this.options.drop&&this.group.containers.push(this)}var r,z={drag:!0,drop:!0,exclude:"",nested:!0,vertical:!0},n={afterMove:function(a,b,c){},containerPath:"",containerSelector:"ol, ul",distance:0,
|
|
||||||
delay:0,handle:"",itemPath:"",itemSelector:"li",bodyClass:"dragging",draggedClass:"dragged",isValidTarget:function(a,b){return!0},onCancel:function(a,b,c,e){},onDrag:function(a,b,c,e){a.css(b)},onDragStart:function(a,b,c,e){a.css({height:a.outerHeight(),width:a.outerWidth()});a.addClass(b.group.options.draggedClass);d("body").addClass(b.group.options.bodyClass)},onDrop:function(a,b,c,e){a.removeClass(b.group.options.draggedClass).removeAttr("style");d("body").removeClass(b.group.options.bodyClass)},
|
|
||||||
onMousedown:function(a,b,c){if(!c.target.nodeName.match(/^(input|select|textarea)$/i))return c.preventDefault(),!0},placeholderClass:"placeholder",placeholder:'<li class="placeholder"></li>',pullPlaceholder:!0,serialize:function(a,b,c){a=d.extend({},a.data());if(c)return[b];b[0]&&(a.children=b);delete a.subContainers;delete a.sortable;return a},tolerance:0},s={},y=0,A={left:0,top:0,bottom:0,right:0};r={start:"touchstart.sortable mousedown.sortable",drop:"touchend.sortable touchcancel.sortable mouseup.sortable",
|
|
||||||
drag:"touchmove.sortable mousemove.sortable",scroll:"scroll.sortable"};q.get=function(a){s[a.group]||(a.group===f&&(a.group=y++),s[a.group]=new q(a));return s[a.group]};q.prototype={dragInit:function(a,b){this.$document=d(b.el[0].ownerDocument);var c=d(a.target).closest(this.options.itemSelector);c.length&&(this.item=c,this.itemContainer=b,!this.item.is(this.options.exclude)&&this.options.onMousedown(this.item,n.onMousedown,a)&&(this.setPointer(a),this.toggleListeners("on"),this.setupDelayTimer(),
|
|
||||||
this.dragInitDone=!0))},drag:function(a){if(!this.dragging){if(!this.distanceMet(a)||!this.delayMet)return;this.options.onDragStart(this.item,this.itemContainer,n.onDragStart,a);this.item.before(this.placeholder);this.dragging=!0}this.setPointer(a);this.options.onDrag(this.item,p(this.pointer,this.item.offsetParent()),n.onDrag,a);a=this.getPointer(a);var b=this.sameResultBox,c=this.options.tolerance;(!b||b.top-c>a.top||b.bottom+c<a.top||b.left-c>a.left||b.right+c<a.left)&&!this.searchValidTarget()&&
|
|
||||||
(this.placeholder.detach(),this.lastAppendedItem=f)},drop:function(a){this.toggleListeners("off");this.dragInitDone=!1;if(this.dragging){if(this.placeholder.closest("html")[0])this.placeholder.before(this.item).detach();else this.options.onCancel(this.item,this.itemContainer,n.onCancel,a);this.options.onDrop(this.item,this.getContainer(this.item),n.onDrop,a);this.clearDimensions();this.clearOffsetParent();this.lastAppendedItem=this.sameResultBox=f;this.dragging=!1}},searchValidTarget:function(a,b){a||
|
|
||||||
(a=this.relativePointer||this.pointer,b=this.lastRelativePointer||this.lastPointer);for(var c=x(this.getContainerDimensions(),a,b),e=c.length;e--;){var d=c[e][0];if(!c[e][1]||this.options.pullPlaceholder)if(d=this.containers[d],!d.disabled){if(!this.$getOffsetParent()){var g=d.getItemOffsetParent();a=p(a,g);b=p(b,g)}if(d.searchValidTarget(a,b))return!0}}this.sameResultBox&&(this.sameResultBox=f)},movePlaceholder:function(a,b,c,e){var d=this.lastAppendedItem;if(e||!d||d[0]!==b[0])b[c](this.placeholder),
|
|
||||||
this.lastAppendedItem=b,this.sameResultBox=e,this.options.afterMove(this.placeholder,a,b)},getContainerDimensions:function(){this.containerDimensions||w(this.containers,this.containerDimensions=[],this.options.tolerance,!this.$getOffsetParent());return this.containerDimensions},getContainer:function(a){return a.closest(this.options.containerSelector).data(m)},$getOffsetParent:function(){if(this.offsetParent===f){var a=this.containers.length-1,b=this.containers[a].getItemOffsetParent();if(!this.options.rootGroup)for(;a--;)if(b[0]!=
|
|
||||||
this.containers[a].getItemOffsetParent()[0]){b=!1;break}this.offsetParent=b}return this.offsetParent},setPointer:function(a){a=this.getPointer(a);if(this.$getOffsetParent()){var b=p(a,this.$getOffsetParent());this.lastRelativePointer=this.relativePointer;this.relativePointer=b}this.lastPointer=this.pointer;this.pointer=a},distanceMet:function(a){a=this.getPointer(a);return Math.max(Math.abs(this.pointer.left-a.left),Math.abs(this.pointer.top-a.top))>=this.options.distance},getPointer:function(a){var b=
|
|
||||||
a.originalEvent||a.originalEvent.touches&&a.originalEvent.touches[0];return{left:a.pageX||b.pageX,top:a.pageY||b.pageY}},setupDelayTimer:function(){var a=this;this.delayMet=!this.options.delay;this.delayMet||(clearTimeout(this._mouseDelayTimer),this._mouseDelayTimer=setTimeout(function(){a.delayMet=!0},this.options.delay))},scroll:function(a){this.clearDimensions();this.clearOffsetParent()},toggleListeners:function(a){var b=this;d.each(["drag","drop","scroll"],function(c,e){b.$document[a](r[e],b[e+
|
|
||||||
"Proxy"])})},clearOffsetParent:function(){this.offsetParent=f},clearDimensions:function(){this.traverse(function(a){a._clearDimensions()})},traverse:function(a){a(this);for(var b=this.containers.length;b--;)this.containers[b].traverse(a)},_clearDimensions:function(){this.containerDimensions=f},_destroy:function(){s[this.options.group]=f}};t.prototype={dragInit:function(a){var b=this.rootGroup;!this.disabled&&!b.dragInitDone&&this.options.drag&&this.isValidDrag(a)&&b.dragInit(a,this)},isValidDrag:function(a){return 1==
|
|
||||||
a.which||"touchstart"==a.type&&1==a.originalEvent.touches.length},searchValidTarget:function(a,b){var c=x(this.getItemDimensions(),a,b),e=c.length,d=this.rootGroup,g=!d.options.isValidTarget||d.options.isValidTarget(d.item,this);if(!e&&g)return d.movePlaceholder(this,this.target,"append"),!0;for(;e--;)if(d=c[e][0],!c[e][1]&&this.hasChildGroup(d)){if(this.getContainerGroup(d).searchValidTarget(a,b))return!0}else if(g)return this.movePlaceholder(d,a),!0},movePlaceholder:function(a,b){var c=d(this.items[a]),
|
|
||||||
e=this.itemDimensions[a],k="after",g=c.outerWidth(),f=c.outerHeight(),h=c.offset(),h={left:h.left,right:h.left+g,top:h.top,bottom:h.top+f};this.options.vertical?b.top<=(e[2]+e[3])/2?(k="before",h.bottom-=f/2):h.top+=f/2:b.left<=(e[0]+e[1])/2?(k="before",h.right-=g/2):h.left+=g/2;this.hasChildGroup(a)&&(h=A);this.rootGroup.movePlaceholder(this,c,k,h)},getItemDimensions:function(){this.itemDimensions||(this.items=this.$getChildren(this.el,"item").filter(":not(."+this.group.options.placeholderClass+
|
|
||||||
", ."+this.group.options.draggedClass+")").get(),w(this.items,this.itemDimensions=[],this.options.tolerance));return this.itemDimensions},getItemOffsetParent:function(){var a=this.el;return"relative"===a.css("position")||"absolute"===a.css("position")||"fixed"===a.css("position")?a:a.offsetParent()},hasChildGroup:function(a){return this.options.nested&&this.getContainerGroup(a)},getContainerGroup:function(a){var b=d.data(this.items[a],"subContainers");if(b===f){var c=this.$getChildren(this.items[a],
|
|
||||||
"container"),b=!1;c[0]&&(b=d.extend({},this.options,{rootGroup:this.rootGroup,group:y++}),b=c[m](b).data(m).group);d.data(this.items[a],"subContainers",b)}return b},$getChildren:function(a,b){var c=this.rootGroup.options,e=c[b+"Path"],c=c[b+"Selector"];a=d(a);e&&(a=a.find(e));return a.children(c)},_serialize:function(a,b){var c=this,e=this.$getChildren(a,b?"item":"container").not(this.options.exclude).map(function(){return c._serialize(d(this),!b)}).get();return this.rootGroup.options.serialize(a,
|
|
||||||
e,b)},traverse:function(a){d.each(this.items||[],function(b){(b=d.data(this,"subContainers"))&&b.traverse(a)});a(this)},_clearDimensions:function(){this.itemDimensions=f},_destroy:function(){var a=this;this.target.off(r.start,this.handle);this.el.removeData(m);this.options.drop&&(this.group.containers=d.grep(this.group.containers,function(b){return b!=a}));d.each(this.items||[],function(){d.removeData(this,"subContainers")})}};var u={enable:function(){this.traverse(function(a){a.disabled=!1})},disable:function(){this.traverse(function(a){a.disabled=
|
|
||||||
!0})},serialize:function(){return this._serialize(this.el,!0)},refresh:function(){this.traverse(function(a){a._clearDimensions()})},destroy:function(){this.traverse(function(a){a._destroy()})}};d.extend(t.prototype,u);d.fn[m]=function(a){var b=Array.prototype.slice.call(arguments,1);return this.map(function(){var c=d(this),e=c.data(m);if(e&&u[a])return u[a].apply(e,b)||this;e||a!==f&&"object"!==typeof a||c.data(m,new t(c,a));return this})}}(jQuery,window,"sortable");
|
|
@ -142,7 +142,7 @@ These are the great open-source projects used to help build BookStack:
|
|||||||
* [CodeMirror](https://codemirror.net)
|
* [CodeMirror](https://codemirror.net)
|
||||||
* [Vue.js](http://vuejs.org/)
|
* [Vue.js](http://vuejs.org/)
|
||||||
* [Axios](https://github.com/mzabriskie/axios)
|
* [Axios](https://github.com/mzabriskie/axios)
|
||||||
* [jQuery Sortable](https://johnny.github.io/jquery-sortable/)
|
* [Sortable](https://github.com/SortableJS/Sortable) & [Vue.Draggable](https://github.com/SortableJS/Vue.Draggable)
|
||||||
* [Google Material Icons](https://material.io/icons/)
|
* [Google Material Icons](https://material.io/icons/)
|
||||||
* [Dropzone.js](http://www.dropzonejs.com/)
|
* [Dropzone.js](http://www.dropzonejs.com/)
|
||||||
* [clipboard.js](https://clipboardjs.com/)
|
* [clipboard.js](https://clipboardjs.com/)
|
||||||
|
204
resources/assets/js/components/book-sort.js
Normal file
204
resources/assets/js/components/book-sort.js
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
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',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
@ -24,6 +24,7 @@ import triLayout from "./tri-layout";
|
|||||||
import breadcrumbListing from "./breadcrumb-listing";
|
import breadcrumbListing from "./breadcrumb-listing";
|
||||||
import permissionsTable from "./permissions-table";
|
import permissionsTable from "./permissions-table";
|
||||||
import customCheckbox from "./custom-checkbox";
|
import customCheckbox from "./custom-checkbox";
|
||||||
|
import bookSort from "./book-sort";
|
||||||
|
|
||||||
const componentMapping = {
|
const componentMapping = {
|
||||||
'dropdown': dropdown,
|
'dropdown': dropdown,
|
||||||
@ -52,6 +53,7 @@ const componentMapping = {
|
|||||||
'breadcrumb-listing': breadcrumbListing,
|
'breadcrumb-listing': breadcrumbListing,
|
||||||
'permissions-table': permissionsTable,
|
'permissions-table': permissionsTable,
|
||||||
'custom-checkbox': customCheckbox,
|
'custom-checkbox': customCheckbox,
|
||||||
|
'book-sort': bookSort,
|
||||||
};
|
};
|
||||||
|
|
||||||
window.components = {};
|
window.components = {};
|
||||||
|
@ -59,8 +59,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Entity background colors
|
* Standard & Entity background colors
|
||||||
*/
|
*/
|
||||||
|
.bg-white {
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
}
|
||||||
.bg-book {
|
.bg-book {
|
||||||
background-color: $color-book;
|
background-color: $color-book;
|
||||||
}
|
}
|
||||||
|
@ -16,16 +16,16 @@
|
|||||||
|
|
||||||
<div class="grid left-focus gap-xl">
|
<div class="grid left-focus gap-xl">
|
||||||
<div>
|
<div>
|
||||||
<div class="card content-wrap">
|
<div book-sort class="card content-wrap">
|
||||||
<h1 class="list-heading mb-l">{{ trans('entities.books_sort') }}</h1>
|
<h1 class="list-heading mb-l">{{ trans('entities.books_sort') }}</h1>
|
||||||
<div id="sort-boxes">
|
<div book-sort-boxes>
|
||||||
@include('books.sort-box', ['book' => $book, 'bookChildren' => $bookChildren])
|
@include('books.sort-box', ['book' => $book, 'bookChildren' => $bookChildren])
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form action="{{ $book->getUrl('/sort') }}" method="POST">
|
<form action="{{ $book->getUrl('/sort') }}" method="POST">
|
||||||
{!! csrf_field() !!}
|
{!! csrf_field() !!}
|
||||||
<input type="hidden" name="_method" value="PUT">
|
<input type="hidden" name="_method" value="PUT">
|
||||||
<input type="hidden" id="sort-tree-input" name="sort-tree">
|
<input book-sort-input type="hidden" name="sort-tree">
|
||||||
<div class="list text-right">
|
<div class="list text-right">
|
||||||
<a href="{{ $book->getUrl() }}" class="button outline">{{ trans('common.cancel') }}</a>
|
<a href="{{ $book->getUrl() }}" class="button outline">{{ trans('common.cancel') }}</a>
|
||||||
<button class="button primary" type="submit">{{ trans('entities.books_sort_save') }}</button>
|
<button class="button primary" type="submit">{{ trans('entities.books_sort_save') }}</button>
|
||||||
@ -47,157 +47,3 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
@stop
|
@stop
|
||||||
|
|
||||||
@section('scripts')
|
|
||||||
<script src="{{ baseUrl("/libs/jquery-sortable/jquery-sortable.min.js") }}"></script>
|
|
||||||
<script>
|
|
||||||
$(document).ready(function() {
|
|
||||||
|
|
||||||
const $container = $('#sort-boxes');
|
|
||||||
|
|
||||||
// Sortable options
|
|
||||||
const sortableOptions = {
|
|
||||||
group: 'serialization',
|
|
||||||
containerSelector: 'ul',
|
|
||||||
itemPath: '',
|
|
||||||
itemSelector: 'li',
|
|
||||||
onDrop: function ($item, container, _super) {
|
|
||||||
updateMapInput();
|
|
||||||
_super($item, container);
|
|
||||||
},
|
|
||||||
isValidTarget: function ($item, container) {
|
|
||||||
// Prevent nested chapters
|
|
||||||
return !($item.is('[data-type="chapter"]') && container.target.closest('li').attr('data-type') === 'chapter');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create our sortable group
|
|
||||||
let group = $('.sort-list').sortable(sortableOptions);
|
|
||||||
|
|
||||||
// Add book on selection confirm
|
|
||||||
window.$events.listen('entity-select-confirm', function(entityInfo) {
|
|
||||||
const alreadyAdded = $container.find(`[data-type="book"][data-id="${entityInfo.id}"]`).length > 0;
|
|
||||||
if (alreadyAdded) return;
|
|
||||||
|
|
||||||
const entitySortItemUrl = entityInfo.link + '/sort-item';
|
|
||||||
window.$http.get(entitySortItemUrl).then(resp => {
|
|
||||||
$container.append(resp.data);
|
|
||||||
group.sortable("destroy");
|
|
||||||
group = $('.sort-list').sortable(sortableOptions);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the input with our sort data.
|
|
||||||
*/
|
|
||||||
function updateMapInput() {
|
|
||||||
const pageMap = buildEntityMap();
|
|
||||||
$('#sort-tree-input').val(JSON.stringify(pageMap));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build up a mapping of entities with their ordering and nesting.
|
|
||||||
* @returns {Array}
|
|
||||||
*/
|
|
||||||
function buildEntityMap() {
|
|
||||||
const entityMap = [];
|
|
||||||
const $lists = $('.sort-list');
|
|
||||||
$lists.each(function(listIndex) {
|
|
||||||
const $list = $(this);
|
|
||||||
const bookId = $list.closest('[data-type="book"]').attr('data-id');
|
|
||||||
const $directChildren = $list.find('> [data-type="page"], > [data-type="chapter"]');
|
|
||||||
$directChildren.each(function(directChildIndex) {
|
|
||||||
const $childElem = $(this);
|
|
||||||
const type = $childElem.attr('data-type');
|
|
||||||
const parentChapter = false;
|
|
||||||
const childId = $childElem.attr('data-id');
|
|
||||||
|
|
||||||
entityMap.push({
|
|
||||||
id: childId,
|
|
||||||
sort: directChildIndex,
|
|
||||||
parentChapter: parentChapter,
|
|
||||||
type: type,
|
|
||||||
book: bookId
|
|
||||||
});
|
|
||||||
|
|
||||||
$childElem.find('[data-type="page"]').each(function(pageIndex) {
|
|
||||||
const $chapterChild = $(this);
|
|
||||||
entityMap.push({
|
|
||||||
id: $chapterChild.attr('data-id'),
|
|
||||||
sort: pageIndex,
|
|
||||||
parentChapter: childId,
|
|
||||||
type: 'page',
|
|
||||||
book: bookId
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return entityMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let lastSort = '';
|
|
||||||
let reverse = false;
|
|
||||||
const reversibleTypes = ['name', 'created', 'updated'];
|
|
||||||
|
|
||||||
$container.on('click', '.sort-box-options [data-sort]', function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
const $sortLists = $(this).closest('.sort-box').find('ul');
|
|
||||||
const sort = $(this).attr('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)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
$sortLists.each(function() {
|
|
||||||
const $list = $(this);
|
|
||||||
$list.children('li').sort(sortFunction).appendTo($list);
|
|
||||||
});
|
|
||||||
|
|
||||||
lastSort = sort;
|
|
||||||
updateMapInput();
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@stop
|
|
||||||
|
Loading…
Reference in New Issue
Block a user