From a3e7e754b94a77e6608e1bd35316f849c514a8e5 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 27 Jan 2023 11:16:17 +0000 Subject: [PATCH 1/6] Improves sortable ux - Fixes multi-select functionality. - Updated other books to be sticky. - Added some general intro/desc text. - Updated sort boxes to be collapsible. - Cleaned up other books styling. --- resources/js/components/book-sort.js | 12 +++++++----- resources/lang/en/entities.php | 2 ++ resources/sass/_layout.scss | 5 +++++ resources/sass/_lists.scss | 14 ++++++++++++++ resources/sass/styles.scss | 3 +++ .../views/books/parts/sort-box.blade.php | 19 +++++++++++++------ resources/views/books/sort.blade.php | 11 +++++++---- 7 files changed, 51 insertions(+), 15 deletions(-) diff --git a/resources/js/components/book-sort.js b/resources/js/components/book-sort.js index 3ffadf991..2722eb586 100644 --- a/resources/js/components/book-sort.js +++ b/resources/js/components/book-sort.js @@ -1,4 +1,4 @@ -import Sortable from "sortablejs"; +import Sortable, {MultiDrag} from "sortablejs"; import {Component} from "./component"; import {htmlToDom} from "../services/dom"; @@ -44,6 +44,8 @@ export class BookSort extends Component { this.sortContainer = this.$refs.sortContainer; this.input = this.$refs.input; + Sortable.mount(new MultiDrag()); + const initialSortBox = this.container.querySelector('.sort-box'); this.setupBookSortable(initialSortBox); this.setupSortPresets(); @@ -104,7 +106,7 @@ export class BookSort extends Component { } /** - * Setup the given book container element to have sortable items. + * Set up the given book container element to have sortable items. * @param {Element} bookContainer */ setupBookSortable(bookContainer) { @@ -125,8 +127,8 @@ export class BookSort extends Component { } }; - for (let sortElem of sortElems) { - new Sortable(sortElem, { + for (const sortElem of sortElems) { + Sortable.create(sortElem, { group: sortElem.classList.contains('sort-list') ? bookGroupConfig : chapterGroupConfig, animation: 150, fallbackOnBody: true, @@ -135,7 +137,7 @@ export class BookSort extends Component { dragClass: 'bg-white', ghostClass: 'primary-background-light', multiDrag: true, - multiDragKey: 'CTRL', + multiDragKey: 'Control', selectedClass: 'sortable-selected', }); } diff --git a/resources/lang/en/entities.php b/resources/lang/en/entities.php index fa2586f8d..5b019e848 100644 --- a/resources/lang/en/entities.php +++ b/resources/lang/en/entities.php @@ -141,6 +141,7 @@ return [ 'books_search_this' => 'Search this book', 'books_navigation' => 'Book Navigation', 'books_sort' => 'Sort Book Contents', + 'books_sort_desc' => 'Move chapters and pages within a book to reorganise its contents. Other books can be added which allows easy moving of chapters and pages between books.', 'books_sort_named' => 'Sort Book :bookName', 'books_sort_name' => 'Sort by Name', 'books_sort_created' => 'Sort by Created Date', @@ -148,6 +149,7 @@ return [ 'books_sort_chapters_first' => 'Chapters First', 'books_sort_chapters_last' => 'Chapters Last', 'books_sort_show_other' => 'Show Other Books', + 'books_sort_show_other_desc' => 'Add other books here to include them in the sort operation, and allow easy cross-book reorganisation.', 'books_sort_save' => 'Save New Order', 'books_copy' => 'Copy Book', 'books_copy_success' => 'Book successfully copied', diff --git a/resources/sass/_layout.scss b/resources/sass/_layout.scss index 4c7de600b..3fc419046 100644 --- a/resources/sass/_layout.scss +++ b/resources/sass/_layout.scss @@ -268,6 +268,11 @@ body.flexbox { } } +.sticky-top-m { + position: sticky; + top: $-m; +} + /** * Visibility */ diff --git a/resources/sass/_lists.scss b/resources/sass/_lists.scss index 86a89051f..1ae801267 100644 --- a/resources/sass/_lists.scss +++ b/resources/sass/_lists.scss @@ -302,6 +302,20 @@ .sortable-page-list li.placeholder:before { position: absolute; } +.sort-box summary { + list-style: none; + font-size: .9rem; + cursor: pointer; +} +.sort-box summary::-webkit-details-marker { + display: none; +} +details.sort-box summary .caret-container svg { + transition: transform ease-in-out 120ms; +} +details.sort-box[open] summary .caret-container svg { + transform: rotate(90deg); +} .activity-list-item { padding: $-s 0; diff --git a/resources/sass/styles.scss b/resources/sass/styles.scss index 23959d1f8..398d16fac 100644 --- a/resources/sass/styles.scss +++ b/resources/sass/styles.scss @@ -199,6 +199,9 @@ $loadingSize: 10px; .entity-item-snippet { display: none; } + h4 { + font-size: 14px; + } } } diff --git a/resources/views/books/parts/sort-box.blade.php b/resources/views/books/parts/sort-box.blade.php index ef9929e46..819f1e063 100644 --- a/resources/views/books/parts/sort-box.blade.php +++ b/resources/views/books/parts/sort-box.blade.php @@ -1,8 +1,15 @@ -
-
- @icon('book') - {{ $book->name }} -
+
+ +
+
+ @icon('caret-right') +
+
+ @icon('book') + {{ $book->name }} +
+
+
@@ -45,4 +52,4 @@ @endforeach -
\ No newline at end of file +
\ No newline at end of file diff --git a/resources/views/books/sort.blade.php b/resources/views/books/sort.blade.php index 077da101d..b778398a8 100644 --- a/resources/views/books/sort.blade.php +++ b/resources/views/books/sort.blade.php @@ -16,8 +16,10 @@
-
-

{{ trans('entities.books_sort') }}

+
+

{{ trans('entities.books_sort') }}

+

{{ trans('entities.books_sort_desc') }}

+
@include('books.parts.sort-box', ['book' => $book, 'bookChildren' => $bookChildren])
@@ -35,8 +37,9 @@
-
-

{{ trans('entities.books_sort_show_other') }}

+
+

{{ trans('entities.books_sort_show_other') }}

+

{{ trans('entities.books_sort_show_other_desc') }}

@include('entities.selector', ['name' => 'books_list', 'selectorSize' => 'compact', 'entityTypes' => 'book', 'entityPermission' => 'update', 'showAdd' => true]) From 7cacbaadf0dc2d9b94e163597bd2b3d2cc05be53 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 27 Jan 2023 13:08:35 +0000 Subject: [PATCH 2/6] Added functionality/logic for button-based sorting --- resources/js/components/book-sort.js | 167 ++++++++++++++++++ .../books/parts/sort-box-actions.blade.php | 12 ++ .../views/books/parts/sort-box.blade.php | 9 +- 3 files changed, 186 insertions(+), 2 deletions(-) create mode 100644 resources/views/books/parts/sort-box-actions.blade.php diff --git a/resources/js/components/book-sort.js b/resources/js/components/book-sort.js index 2722eb586..3c849c5c6 100644 --- a/resources/js/components/book-sort.js +++ b/resources/js/components/book-sort.js @@ -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); + } + } } \ No newline at end of file diff --git a/resources/views/books/parts/sort-box-actions.blade.php b/resources/views/books/parts/sort-box-actions.blade.php new file mode 100644 index 000000000..0c91f42da --- /dev/null +++ b/resources/views/books/parts/sort-box-actions.blade.php @@ -0,0 +1,12 @@ +
+ + + + + + + + + + +
\ No newline at end of file diff --git a/resources/views/books/parts/sort-box.blade.php b/resources/views/books/parts/sort-box.blade.php index 819f1e063..77a03f831 100644 --- a/resources/views/books/parts/sort-box.blade.php +++ b/resources/views/books/parts/sort-box.blade.php @@ -23,7 +23,8 @@
  • + data-updated="{{ $bookChild->updated_at->timestamp }}" tabindex="0"> +
    @icon('grip')
    @icon($bookChild->getType())
    @@ -33,17 +34,21 @@
  • + @include('books.parts.sort-box-actions') @if($bookChild->isA('chapter'))
      @foreach($bookChild->visible_pages as $page)
    • + data-updated="{{ $page->updated_at->timestamp }}" + tabindex="0"> +
      @icon('grip')
      @icon('page') {{ $page->name }}
      + @include('books.parts.sort-box-actions')
    • @endforeach
    From 40e112fc5b4896c52d5aa09ea2ad2a9da6eccfa1 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 27 Jan 2023 13:26:58 +0000 Subject: [PATCH 3/6] Extracted text & added dropdown for book sort move actions Primarily styling and testing left to do. --- resources/lang/en/entities.php | 11 ++++++- resources/sass/_lists.scss | 4 +-- .../books/parts/sort-box-actions.blade.php | 31 +++++++++++++------ .../views/books/parts/sort-box.blade.php | 2 +- 4 files changed, 34 insertions(+), 14 deletions(-) diff --git a/resources/lang/en/entities.php b/resources/lang/en/entities.php index 5b019e848..834cfacba 100644 --- a/resources/lang/en/entities.php +++ b/resources/lang/en/entities.php @@ -150,7 +150,16 @@ return [ 'books_sort_chapters_last' => 'Chapters Last', 'books_sort_show_other' => 'Show Other Books', 'books_sort_show_other_desc' => 'Add other books here to include them in the sort operation, and allow easy cross-book reorganisation.', - 'books_sort_save' => 'Save New Order', + 'books_sort_move_up' => 'Move Up', + 'books_sort_move_down' => 'Move Down', + 'books_sort_move_prev_book' => 'Move To Previous Book', + 'books_sort_move_next_book' => 'Move To Next Book', + 'books_sort_move_prev_chapter' => 'Move Into Previous Chapter', + 'books_sort_move_next_chapter' => 'Move Into Next Chapter', + 'books_sort_move_book_start' => 'Move To Start of Book', + 'books_sort_move_book_end' => 'Move To End of Book', + 'books_sort_move_before_chapter' => 'Move To Before Chapter', + 'books_sort_move_after_chapter' => 'Move To After Chapter', 'books_copy' => 'Copy Book', 'books_copy_success' => 'Book successfully copied', diff --git a/resources/sass/_lists.scss b/resources/sass/_lists.scss index 1ae801267..39b2afee6 100644 --- a/resources/sass/_lists.scss +++ b/resources/sass/_lists.scss @@ -232,7 +232,7 @@ } // Sortable Lists -.sortable-page-list, .sortable-page-list ul { +.sortable-page-list, .sortable-page-sublist { list-style: none; } .sort-box { @@ -278,7 +278,7 @@ > ul { margin-inline-start: 0; } - ul { + .sortable-page-sublist { margin-bottom: $-m; margin-top: 0; padding-inline-start: $-m; diff --git a/resources/views/books/parts/sort-box-actions.blade.php b/resources/views/books/parts/sort-box-actions.blade.php index 0c91f42da..c9a1d323e 100644 --- a/resources/views/books/parts/sort-box-actions.blade.php +++ b/resources/views/books/parts/sort-box-actions.blade.php @@ -1,12 +1,23 @@
    - - - - - - - - - - + + +
    \ No newline at end of file diff --git a/resources/views/books/parts/sort-box.blade.php b/resources/views/books/parts/sort-box.blade.php index 77a03f831..33448f483 100644 --- a/resources/views/books/parts/sort-box.blade.php +++ b/resources/views/books/parts/sort-box.blade.php @@ -36,7 +36,7 @@
    @include('books.parts.sort-box-actions') @if($bookChild->isA('chapter')) -
      +
        @foreach($bookChild->visible_pages as $page)
      • Date: Fri, 27 Jan 2023 16:25:06 +0000 Subject: [PATCH 4/6] Finished off design and fixing of sort buttons --- resources/js/components/book-sort.js | 18 ++++-------- resources/js/services/keyboard-navigation.js | 2 +- resources/lang/en/entities.php | 13 +++++---- resources/sass/_lists.scss | 19 +++++++++++- .../books/parts/sort-box-actions.blade.php | 8 +++-- .../views/books/parts/sort-box.blade.php | 29 +++++++++++-------- 6 files changed, 54 insertions(+), 35 deletions(-) diff --git a/resources/js/components/book-sort.js b/resources/js/components/book-sort.js index 3c849c5c6..e8ecd49a4 100644 --- a/resources/js/components/book-sort.js +++ b/resources/js/components/book-sort.js @@ -162,7 +162,7 @@ export class BookSort extends Component { } /** - * Setup the handlers for the item-level move buttons. + * Set up the handlers for the item-level move buttons. */ setupMoveActions() { // Handle move button click @@ -173,20 +173,12 @@ export class BookSort extends Component { 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. + * Set up the handlers for the preset sort type buttons. */ setupSortPresets() { let lastSort = ''; @@ -235,6 +227,9 @@ export class BookSort extends Component { this.sortContainer.append(newBookContainer); this.setupBookSortable(newBookContainer); this.updateMoveActionStateForAll(); + + const summary = newBookContainer.querySelector('summary'); + summary.focus(); }); } @@ -243,8 +238,7 @@ export class BookSort extends Component { * @param {Element} bookContainer */ setupBookSortable(bookContainer) { - const sortElems = [bookContainer.querySelector('.sort-list')]; - sortElems.push(...bookContainer.querySelectorAll('.entity-list-item + ul')); + const sortElems = Array.from(bookContainer.querySelectorAll('.sort-list, .sortable-page-sublist')); const bookGroupConfig = { name: 'book', diff --git a/resources/js/services/keyboard-navigation.js b/resources/js/services/keyboard-navigation.js index 0e1dcf1a7..0f866ceaa 100644 --- a/resources/js/services/keyboard-navigation.js +++ b/resources/js/services/keyboard-navigation.js @@ -86,7 +86,7 @@ export class KeyboardNavigationHandler { */ #getFocusable() { const focusable = []; - const selector = '[tabindex]:not([tabindex="-1"]),[href],button:not([tabindex="-1"]),input:not([type=hidden])'; + const selector = '[tabindex]:not([tabindex="-1"]),[href],button:not([tabindex="-1"],[disabled]),input:not([type=hidden])'; for (const container of this.containers) { focusable.push(...container.querySelectorAll(selector)) } diff --git a/resources/lang/en/entities.php b/resources/lang/en/entities.php index 834cfacba..8bf805774 100644 --- a/resources/lang/en/entities.php +++ b/resources/lang/en/entities.php @@ -149,17 +149,18 @@ return [ 'books_sort_chapters_first' => 'Chapters First', 'books_sort_chapters_last' => 'Chapters Last', 'books_sort_show_other' => 'Show Other Books', + 'books_sort_save' => 'Save New Order', 'books_sort_show_other_desc' => 'Add other books here to include them in the sort operation, and allow easy cross-book reorganisation.', 'books_sort_move_up' => 'Move Up', 'books_sort_move_down' => 'Move Down', - 'books_sort_move_prev_book' => 'Move To Previous Book', - 'books_sort_move_next_book' => 'Move To Next Book', + 'books_sort_move_prev_book' => 'Move to Previous Book', + 'books_sort_move_next_book' => 'Move to Next Book', 'books_sort_move_prev_chapter' => 'Move Into Previous Chapter', 'books_sort_move_next_chapter' => 'Move Into Next Chapter', - 'books_sort_move_book_start' => 'Move To Start of Book', - 'books_sort_move_book_end' => 'Move To End of Book', - 'books_sort_move_before_chapter' => 'Move To Before Chapter', - 'books_sort_move_after_chapter' => 'Move To After Chapter', + 'books_sort_move_book_start' => 'Move to Start of Book', + 'books_sort_move_book_end' => 'Move to End of Book', + 'books_sort_move_before_chapter' => 'Move to Before Chapter', + 'books_sort_move_after_chapter' => 'Move to After Chapter', 'books_copy' => 'Copy Book', 'books_copy_success' => 'Book successfully copied', diff --git a/resources/sass/_lists.scss b/resources/sass/_lists.scss index 39b2afee6..33e500d6a 100644 --- a/resources/sass/_lists.scss +++ b/resources/sass/_lists.scss @@ -267,7 +267,7 @@ .entity-list-item > span:first-child { align-self: flex-start; } - .sortable-selected .entity-list-item, .sortable-selected .entity-list-item:hover { + .sortable-selected, .sortable-selected:hover { outline: 1px dotted var(--color-primary); background-color: var(--color-primary-light) !important; } @@ -284,6 +284,7 @@ padding-inline-start: $-m; } li { + @include lightDark(background-color, #FFF, #222); border: 1px solid; @include lightDark(border-color, #DDD, #666); margin-top: -1px; @@ -316,6 +317,22 @@ details.sort-box summary .caret-container svg { details.sort-box[open] summary .caret-container svg { transform: rotate(90deg); } +.sort-box-actions .icon-button { + opacity: .6; +} +.sort-box .flex-container-row:hover .sort-box-actions .icon-button, +.sort-box .flex-container-row:focus-within .sort-box-actions .icon-button { + opacity: 1; +} +.sort-box-actions .icon-button[disabled] { + visibility: hidden; +} +.sort-box-actions .dropdown-menu button[disabled] { + display: none; +} +.sort-list-handle { + cursor: grab; +} .activity-list-item { padding: $-s 0; diff --git a/resources/views/books/parts/sort-box-actions.blade.php b/resources/views/books/parts/sort-box-actions.blade.php index c9a1d323e..cd5a59e15 100644 --- a/resources/views/books/parts/sort-box-actions.blade.php +++ b/resources/views/books/parts/sort-box-actions.blade.php @@ -1,10 +1,12 @@ -
        - - +
        \ No newline at end of file From 87e371ffde35043b8d889d4012fee0fbdf5a0e36 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 27 Jan 2023 17:39:51 +0000 Subject: [PATCH 6/6] Added prevention of nested chapters on sort --- resources/js/components/book-sort.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/resources/js/components/book-sort.js b/resources/js/components/book-sort.js index 6e56e43a5..5ae283fd0 100644 --- a/resources/js/components/book-sort.js +++ b/resources/js/components/book-sort.js @@ -260,7 +260,8 @@ export class BookSort extends Component { animation: 150, fallbackOnBody: true, swapThreshold: 0.65, - onSort: () => { + onSort: (event) => { + this.ensureNoNestedChapters() this.updateMapInput(); this.updateMoveActionStateForAll(); }, @@ -273,6 +274,20 @@ export class BookSort extends Component { } } + /** + * Handle nested chapters by moving them to the parent book. + * Needed since sorting with multi-sort only checks group rules based on the active item, + * not all in group, therefore need to manually check after a sort. + * Must be done before updating the map input. + */ + ensureNoNestedChapters() { + const nestedChapters = this.container.querySelectorAll('[data-type="chapter"] [data-type="chapter"]'); + for (const chapter of nestedChapters) { + const parentChapter = chapter.parentElement.closest('[data-type="chapter"]'); + parentChapter.insertAdjacentElement('afterend', chapter); + } + } + /** * Update the input with our sort data. */