]> BookStack Code Mirror - bookstack/commitdiff
Added book sort helper buttons
authorDan Brown <redacted>
Sun, 17 Feb 2019 11:44:02 +0000 (11:44 +0000)
committerDan Brown <redacted>
Sun, 17 Feb 2019 11:44:02 +0000 (11:44 +0000)
resources/assets/sass/_lists.scss
resources/lang/en/entities.php
resources/views/books/sort-box.blade.php
resources/views/books/sort.blade.php
resources/views/partials/breadcrumbs.blade.php

index 19c81066c09460dda28ebd0fb719d4e769ce92df..c6226f0096029dcf3d97c94dc0c677c00c2af8af 100644 (file)
 // Sortable Lists
 .sortable-page-list, .sortable-page-list ul {
   list-style: none;
-  background-color: #FFF;
 }
 .sort-box {
   margin-bottom: $-m;
-  padding: 0 $-l 0 $-l;
-  border-left: 4px solid $color-book;
+  border: 2px solid rgba($color-book, 0.6);
+  padding: $-m $-xl;
+  border-radius: 4px;
+}
+.sort-box-options {
+  display: flex;
+  flex-wrap: wrap;
+  justify-content: space-between;
+}
+.sort-box-options .button {
+  margin-left: 0;
 }
 .sortable-page-list {
   margin-left: 0;
+  padding: 0;
+  .entity-list-item > span:first-child {
+    align-self: flex-start;
+  }
+  .entity-list-item > div {
+    display: block;
+    flex: 1;
+  }
   > ul {
     margin-left: 0;
   }
   ul {
-    margin-bottom: 0;
+    margin-bottom: $-m;
     margin-top: 0;
-    box-shadow: 0 0 1px 0 rgba(0, 0, 0, 0.1);
+    padding-left: $-m;
   }
   li {
     border: 1px solid #DDD;
-    padding: $-xs $-s;
     margin-top: -1px;
     min-height: 38px;
     &.text-chapter {
@@ -278,7 +293,6 @@ ul.pagination {
   align-items: center;
   background-color: transparent;
   border: 0;
-  cursor: pointer;
   width: 100%;
   position: relative;
   h4 a {
@@ -293,14 +307,18 @@ ul.pagination {
     flex: 1;
     text-align: left;
   }
-  &:hover {
+  &:not(.no-hover) {
+    cursor: pointer;
+  }
+  &:not(.no-hover):hover {
     text-decoration: none;
     background-color: #DDD;
     border-radius: 4px;
   }
 }
 
-.card .entity-list-item:hover {
+
+.card .entity-list-item:not(.no-hover):hover {
   background-color: #F2F2F2;
 }
 .card .entity-list-item .entity-list-item:hover {
index bf0e2172d439b7401b2625932f4cfc171d551886..dffea3e755e8c91b13177ea0d6cd7b7458eabd98 100644 (file)
@@ -125,6 +125,11 @@ return [
     'books_navigation' => 'Book Navigation',
     'books_sort' => 'Sort Book Contents',
     'books_sort_named' => 'Sort Book :bookName',
+    'books_sort_name' => 'Sort by Name',
+    'books_sort_created' => 'Sort by Created Date',
+    'books_sort_updated' => 'Sort by Updated Date',
+    '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',
 
index cff3c8984384b878096ca74798dd61cdbfe5db72..98f0af87eeeaa711408a6c010956bb68d9c273a3 100644 (file)
@@ -1,20 +1,48 @@
 <div class="sort-box" data-type="book" data-id="{{ $book->id }}">
-    <h3 class="text-book">@icon('book'){{ $book->name }}</h3>
+    <h5 class="text-book entity-list-item no-hover py-xs pl-none">
+        <span>@icon('book')</span>
+        <span>{{ $book->name }}</span>
+    </h5>
+    <div class="sort-box-options pb-sm">
+        <a href="#" data-sort="name" class="button outline small">{{ trans('entities.books_sort_name') }}</a>
+        <a href="#" data-sort="created" class="button outline small">{{ trans('entities.books_sort_created') }}</a>
+        <a href="#" data-sort="updated" class="button outline small">{{ trans('entities.books_sort_updated') }}</a>
+        <a href="#" data-sort="chaptersFirst" class="button outline small">{{ trans('entities.books_sort_chapters_first') }}</a>
+        <a href="#" data-sort="chaptersLast" class="button outline small">{{ trans('entities.books_sort_chapters_last') }}</a>
+    </div>
     <ul class="sortable-page-list sort-list">
+
         @foreach($bookChildren as $bookChild)
-            <li data-id="{{$bookChild->id}}" data-type="{{ $bookChild->getClassName() }}" class="text-{{ $bookChild->getClassName() }}">
-                @icon($bookChild->isA('chapter') ? 'chapter' : 'page'){{ $bookChild->name }}
+            <li class="text-{{ $bookChild->getClassName() }}"
+                data-id="{{$bookChild->id}}" data-type="{{ $bookChild->getClassName() }}"
+                data-name="{{ $bookChild->name }}" data-created="{{ $bookChild->created_at->timestamp }}"
+                data-updated="{{ $bookChild->updated_at->timestamp }}">
+                <div class="entity-list-item">
+                    <span>@icon($bookChild->getType()) </span>
+                    <div>
+                        {{ $bookChild->name }}
+                        <div>
+
+                        </div>
+                    </div>
+                </div>
                 @if($bookChild->isA('chapter'))
                     <ul>
                         @foreach($bookChild->pages as $page)
-                            <li data-id="{{$page->id}}" class="text-page" data-type="page">
-                                @icon('page')
-                                {{ $page->name }}
+                            <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 }}">
+                                <div class="entity-list-item">
+                                    <span>@icon('page')</span>
+                                    <span>{{ $page->name }}</span>
+                                </div>
                             </li>
                         @endforeach
                     </ul>
                 @endif
             </li>
         @endforeach
+
     </ul>
 </div>
\ No newline at end of file
index 00ab90f4fc05a9c73975800d468e7ff79252f193..4483af35bd7d5d19bf02a5b448c17b5436839edf 100644 (file)
@@ -1,5 +1,7 @@
 @extends('simple-layout')
 
+{{--TODO - Load books in via selector interface--}}
+
 @section('body')
 
     <div class="container">
@@ -7,7 +9,10 @@
         <div class="my-l">
             @include('partials.breadcrumbs', ['crumbs' => [
                 $book,
-                $book->getUrl('/sort') => trans('entities.books_sort')
+                $book->getUrl('/sort') => [
+                    'text' => trans('entities.books_sort'),
+                    'icon' => 'sort',
+                ]
             ]])
         </div>
 
@@ -16,7 +21,7 @@
                 <div class="card content-wrap">
                     <h1 class="list-heading">{{ trans('entities.books_sort') }}</h1>
                     <div id="sort-boxes">
-                        @include('books/sort-box', ['book' => $book, 'bookChildren' => $bookChildren])
+                        @include('books.sort-box', ['book' => $book, 'bookChildren' => $bookChildren])
                     </div>
 
                     <form action="{{ $book->getUrl('/sort') }}" method="POST">
     <script>
         $(document).ready(function() {
 
-            var sortableOptions = {
+            const $container = $('#sort-boxes');
+
+            // Sortable options
+            const sortableOptions = {
                 group: 'serialization',
-                onDrop: function($item, container, _super) {
-                    var pageMap = buildEntityMap();
-                    $('#sort-tree-input').val(JSON.stringify(pageMap));
+                containerSelector: 'ul',
+                itemPath: '',
+                itemSelector: 'li',
+                onDrop: function ($item, container, _super) {
+                    updateMapInput();
                     _super($item, container);
                 },
-                isValidTarget: function  ($item, container) {
+                isValidTarget: function ($item, container) {
                     // Prevent nested chapters
-                    return !($item.is('[data-type="chapter"]') && container.target.closest('li').attr('data-type') == 'chapter');
+                    return !($item.is('[data-type="chapter"]') && container.target.closest('li').attr('data-type') === 'chapter');
                 }
             };
 
-            var group = $('.sort-list').sortable(sortableOptions);
+            // Create our sortable group
+            let group = $('.sort-list').sortable(sortableOptions);
 
+            // Add additional books into the view on select.
             $('#additional-books').on('click', 'a', function(e) {
                 e.preventDefault();
-                var $link = $(this);
-                var url = $link.attr('href');
+
+                const $link = $(this);
+                const url = $link.attr('href');
                 $.get(url, function(data) {
-                    $('#sort-boxes').append(data);
+                    $container.append(data);
                     group.sortable("destroy");
-                    $('.sort-list').sortable(sortableOptions);
+                    group = $('.sort-list').sortable(sortableOptions);
                 });
                 $link.remove();
             });
 
+            /**
+             * 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() {
-                var entityMap = [];
-                var $lists = $('.sort-list');
+                const entityMap = [];
+                const $lists = $('.sort-list');
                 $lists.each(function(listIndex) {
-                    var list = $(this);
-                    var bookId = list.closest('[data-type="book"]').attr('data-id');
-                    var $directChildren = list.find('> [data-type="page"], > [data-type="chapter"]');
+                    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) {
-                        var $childElem = $(this);
-                        var type = $childElem.attr('data-type');
-                        var parentChapter = false;
-                        var childId = $childElem.attr('data-id');
+                        const $childElem = $(this);
+                        const type = $childElem.attr('data-type');
+                        const parentChapter = false;
+                        const childId = $childElem.attr('data-id');
+
                         entityMap.push({
                             id: childId,
                             sort: directChildIndex,
                             type: type,
                             book: bookId
                         });
-                        $chapterChildren = $childElem.find('[data-type="page"]').each(function(pageIndex) {
-                            var $chapterChild = $(this);
+
+                        $childElem.find('[data-type="page"]').each(function(pageIndex) {
+                            const $chapterChild = $(this);
                             entityMap.push({
                                 id: $chapterChild.attr('data-id'),
                                 sort: pageIndex,
                                 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-update'));
+                    const bTime = Number(b.getAttribute('data-update'));
+                    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 reversableTypes = ['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 && reversableTypes.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
index d30712530332a35ba66cbb1fe4f60ad8bdcb270b..54d7b74cbf05cced74b3e5a9ba4f0255be8e680e 100644 (file)
             <a href="{{  baseUrl($key)  }}">
                 {{ $crumb }}
             </a>
-        @else
+        @elseif (is_array($crumb))
+            <a href="{{  baseUrl($key)  }}">
+                @icon($crumb['icon']) {{ $crumb['text'] }}
+            </a>
+        @elseif($crumb instanceof \BookStack\Entities\Entity)
             <a href="{{ $crumb->getUrl() }}" class="text-{{$crumb->getType()}}">
                 @icon($crumb->getType()){{ $crumb->getShortName() }}
             </a>