0
0
Fork 0
mirror of https://github.com/BookStackApp/BookStack.git synced 2025-04-16 09:41:05 +00:00

Added functionality/logic for button-based sorting

This commit is contained in:
Dan Brown 2023-01-27 13:08:35 +00:00
parent a3e7e754b9
commit 7cacbaadf0
No known key found for this signature in database
GPG key ID: 46D9F943C24A2EF9
3 changed files with 186 additions and 2 deletions
resources

View file

@ -37,6 +37,113 @@ const sortOperations = {
},
};
/**
* The available move actions.
* The active function indicates if the action is possible for the given item.
* The run function performs the move.
* @type {{up: {active(Element, ?Element, Element): boolean, run(Element, ?Element, Element)}}}
*/
const moveActions = {
up: {
active(elem, parent, book) {
return !(elem.previousElementSibling === null && !parent);
},
run(elem, parent, book) {
const newSibling = elem.previousElementSibling || parent;
newSibling.insertAdjacentElement('beforebegin', elem);
}
},
down: {
active(elem, parent, book) {
return !(elem.nextElementSibling === null && !parent);
},
run(elem, parent, book) {
const newSibling = elem.nextElementSibling || parent;
newSibling.insertAdjacentElement('afterend', elem);
}
},
next_book: {
active(elem, parent, book) {
return book.nextElementSibling !== null;
},
run(elem, parent, book) {
const newList = book.nextElementSibling.querySelector('ul');
newList.prepend(elem);
}
},
prev_book: {
active(elem, parent, book) {
return book.previousElementSibling !== null;
},
run(elem, parent, book) {
const newList = book.previousElementSibling.querySelector('ul');
newList.appendChild(elem);
}
},
next_chapter: {
active(elem, parent, book) {
return elem.dataset.type === 'page' && this.getNextChapter(elem, parent);
},
run(elem, parent, book) {
const nextChapter = this.getNextChapter(elem, parent);
nextChapter.querySelector('ul').prepend(elem);
},
getNextChapter(elem, parent) {
const topLevel = (parent || elem);
const topItems = Array.from(topLevel.parentElement.children);
const index = topItems.indexOf(topLevel);
return topItems.slice(index + 1).find(elem => elem.dataset.type === 'chapter');
}
},
prev_chapter: {
active(elem, parent, book) {
return elem.dataset.type === 'page' && this.getPrevChapter(elem, parent);
},
run(elem, parent, book) {
const prevChapter = this.getPrevChapter(elem, parent);
prevChapter.querySelector('ul').append(elem);
},
getPrevChapter(elem, parent) {
const topLevel = (parent || elem);
const topItems = Array.from(topLevel.parentElement.children);
const index = topItems.indexOf(topLevel);
return topItems.slice(0, index).reverse().find(elem => elem.dataset.type === 'chapter');
}
},
book_end: {
active(elem, parent, book) {
return parent || (parent === null && elem.nextElementSibling);
},
run(elem, parent, book) {
book.querySelector('ul').append(elem);
}
},
book_start: {
active(elem, parent, book) {
return parent || (parent === null && elem.previousElementSibling);
},
run(elem, parent, book) {
book.querySelector('ul').prepend(elem);
}
},
before_chapter: {
active(elem, parent, book) {
return parent;
},
run(elem, parent, book) {
parent.insertAdjacentElement('beforebegin', elem);
}
},
after_chapter: {
active(elem, parent, book) {
return parent;
},
run(elem, parent, book) {
parent.insertAdjacentElement('afterend', elem);
}
},
};
export class BookSort extends Component {
setup() {
@ -49,10 +156,35 @@ export class BookSort extends Component {
const initialSortBox = this.container.querySelector('.sort-box');
this.setupBookSortable(initialSortBox);
this.setupSortPresets();
this.setupMoveActions();
window.$events.listen('entity-select-confirm', this.bookSelect.bind(this));
}
/**
* Setup the handlers for the item-level move buttons.
*/
setupMoveActions() {
// Handle move button click
this.container.addEventListener('click', event => {
if (event.target.matches('[data-move]')) {
const action = event.target.getAttribute('data-move');
const sortItem = event.target.closest('[data-id]');
this.runSortAction(sortItem, action);
}
});
// TODO - Probably can remove this
// // Handle action updating on likely use
// this.container.addEventListener('focusin', event => {
// const sortItem = event.target.closest('[data-type="chapter"],[data-type="page"]');
// if (sortItem) {
// this.updateMoveActionState(sortItem);
// }
// });
this.updateMoveActionStateForAll();
}
/**
* Setup the handlers for the preset sort type buttons.
*/
@ -102,6 +234,7 @@ export class BookSort extends Component {
const newBookContainer = htmlToDom(resp.data);
this.sortContainer.append(newBookContainer);
this.setupBookSortable(newBookContainer);
this.updateMoveActionStateForAll();
});
}
@ -204,4 +337,38 @@ export class BookSort extends Component {
}
}
/**
* Run the given sort action up the provided sort item.
* @param {Element} item
* @param {String} action
*/
runSortAction(item, action) {
const parentItem = item.parentElement.closest('li[data-id]');
const parentBook = item.parentElement.closest('[data-type="book"]');
moveActions[action].run(item, parentItem, parentBook);
this.updateMapInput();
this.updateMoveActionStateForAll();
item.scrollIntoView({behavior: 'smooth', block: 'nearest'});
item.focus();
}
/**
* Update the state of the available move actions on this item.
* @param {Element} item
*/
updateMoveActionState(item) {
const parentItem = item.parentElement.closest('li[data-id]');
const parentBook = item.parentElement.closest('[data-type="book"]');
for (const [action, functions] of Object.entries(moveActions)) {
const moveButton = item.querySelector(`[data-move="${action}"]`);
moveButton.disabled = !functions.active(item, parentItem, parentBook);
}
}
updateMoveActionStateForAll() {
const items = this.container.querySelectorAll('[data-type="chapter"],[data-type="page"]');
for (const item of items) {
this.updateMoveActionState(item);
}
}
}

View file

@ -0,0 +1,12 @@
<div class="sort-box-actions">
<button type="button" data-move="up">Move Up</button>
<button type="button" data-move="down">Move Down</button>
<button type="button" data-move="prev_book">Move To Previous Book</button>
<button type="button" data-move="next_book">Move To Next Book</button>
<button type="button" data-move="prev_chapter">Move Into Previous Chapter</button>
<button type="button" data-move="next_chapter">Move Into Next Chapter</button>
<button type="button" data-move="book_start">Move To Start of Book</button>
<button type="button" data-move="book_end">Move To End of Book</button>
<button type="button" data-move="before_chapter">Move To Before Chapter</button>
<button type="button" data-move="after_chapter">Move To After Chapter</button>
</div>

View file

@ -23,7 +23,8 @@
<li class="text-{{ $bookChild->getType() }}"
data-id="{{$bookChild->id}}" data-type="{{ $bookChild->getType() }}"
data-name="{{ $bookChild->name }}" data-created="{{ $bookChild->created_at->timestamp }}"
data-updated="{{ $bookChild->updated_at->timestamp }}">
data-updated="{{ $bookChild->updated_at->timestamp }}" tabindex="0">
<div class="text-muted sort-list-handle">@icon('grip')</div>
<div class="entity-list-item">
<span>@icon($bookChild->getType()) </span>
<div>
@ -33,17 +34,21 @@
</div>
</div>
</div>
@include('books.parts.sort-box-actions')
@if($bookChild->isA('chapter'))
<ul>
@foreach($bookChild->visible_pages as $page)
<li class="text-page"
data-id="{{$page->id}}" data-type="page"
data-name="{{ $page->name }}" data-created="{{ $page->created_at->timestamp }}"
data-updated="{{ $page->updated_at->timestamp }}">
data-updated="{{ $page->updated_at->timestamp }}"
tabindex="0">
<div class="text-muted sort-list-handle">@icon('grip')</div>
<div class="entity-list-item">
<span>@icon('page')</span>
<span>{{ $page->name }}</span>
</div>
@include('books.parts.sort-box-actions')
</li>
@endforeach
</ul>