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:
parent
a3e7e754b9
commit
7cacbaadf0
3 changed files with 186 additions and 2 deletions
resources
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
12
resources/views/books/parts/sort-box-actions.blade.php
Normal file
12
resources/views/books/parts/sort-box-actions.blade.php
Normal 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>
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Reference in a new issue