mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-06-05 17:16:52 +03:00
Added functionality/logic for button-based sorting
This commit is contained in:
parent
a3e7e754b9
commit
7cacbaadf0
@ -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 {
|
export class BookSort extends Component {
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
@ -49,10 +156,35 @@ export class BookSort extends Component {
|
|||||||
const initialSortBox = this.container.querySelector('.sort-box');
|
const initialSortBox = this.container.querySelector('.sort-box');
|
||||||
this.setupBookSortable(initialSortBox);
|
this.setupBookSortable(initialSortBox);
|
||||||
this.setupSortPresets();
|
this.setupSortPresets();
|
||||||
|
this.setupMoveActions();
|
||||||
|
|
||||||
window.$events.listen('entity-select-confirm', this.bookSelect.bind(this));
|
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.
|
* Setup the handlers for the preset sort type buttons.
|
||||||
*/
|
*/
|
||||||
@ -102,6 +234,7 @@ export class BookSort extends Component {
|
|||||||
const newBookContainer = htmlToDom(resp.data);
|
const newBookContainer = htmlToDom(resp.data);
|
||||||
this.sortContainer.append(newBookContainer);
|
this.sortContainer.append(newBookContainer);
|
||||||
this.setupBookSortable(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() }}"
|
<li class="text-{{ $bookChild->getType() }}"
|
||||||
data-id="{{$bookChild->id}}" data-type="{{ $bookChild->getType() }}"
|
data-id="{{$bookChild->id}}" data-type="{{ $bookChild->getType() }}"
|
||||||
data-name="{{ $bookChild->name }}" data-created="{{ $bookChild->created_at->timestamp }}"
|
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">
|
<div class="entity-list-item">
|
||||||
<span>@icon($bookChild->getType()) </span>
|
<span>@icon($bookChild->getType()) </span>
|
||||||
<div>
|
<div>
|
||||||
@ -33,17 +34,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@include('books.parts.sort-box-actions')
|
||||||
@if($bookChild->isA('chapter'))
|
@if($bookChild->isA('chapter'))
|
||||||
<ul>
|
<ul>
|
||||||
@foreach($bookChild->visible_pages as $page)
|
@foreach($bookChild->visible_pages as $page)
|
||||||
<li class="text-page"
|
<li class="text-page"
|
||||||
data-id="{{$page->id}}" data-type="page"
|
data-id="{{$page->id}}" data-type="page"
|
||||||
data-name="{{ $page->name }}" data-created="{{ $page->created_at->timestamp }}"
|
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">
|
<div class="entity-list-item">
|
||||||
<span>@icon('page')</span>
|
<span>@icon('page')</span>
|
||||||
<span>{{ $page->name }}</span>
|
<span>{{ $page->name }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
@include('books.parts.sort-box-actions')
|
||||||
</li>
|
</li>
|
||||||
@endforeach
|
@endforeach
|
||||||
</ul>
|
</ul>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user