]> BookStack Code Mirror - bookstack/blob - resources/views/books/sort.blade.php
771a68fcbc2ce2504f574df0daa7366e608d921a
[bookstack] / resources / views / books / sort.blade.php
1 @extends('simple-layout')
2
3 {{--TODO - Load books in via selector interface--}}
4
5 @section('body')
6
7     <div class="container">
8
9         <div class="my-s">
10             @include('partials.breadcrumbs', ['crumbs' => [
11                 $book,
12                 $book->getUrl('/sort') => [
13                     'text' => trans('entities.books_sort'),
14                     'icon' => 'sort',
15                 ]
16             ]])
17         </div>
18
19         <div class="grid left-focus large-gap">
20             <div>
21                 <div class="card content-wrap">
22                     <h1 class="list-heading">{{ trans('entities.books_sort') }}</h1>
23                     <div id="sort-boxes">
24                         @include('books.sort-box', ['book' => $book, 'bookChildren' => $bookChildren])
25                     </div>
26
27                     <form action="{{ $book->getUrl('/sort') }}" method="POST">
28                         {!! csrf_field() !!}
29                         <input type="hidden" name="_method" value="PUT">
30                         <input type="hidden" id="sort-tree-input" name="sort-tree">
31                         <div class="list text-right">
32                             <a href="{{ $book->getUrl() }}" class="button outline">{{ trans('common.cancel') }}</a>
33                             <button class="button primary" type="submit">{{ trans('entities.books_sort_save') }}</button>
34                         </div>
35                     </form>
36                 </div>
37             </div>
38
39             <div>
40                 @if(count($books) > 1)
41                     <div class="card content-wrap">
42                         <h2 class="list-heading">{{ trans('entities.books_sort_show_other') }}</h2>
43                         <div id="additional-books">
44                             @foreach($books as $otherBook)
45                                 @if($otherBook->id !== $book->id)
46                                     <div>
47                                         <a href="{{ $otherBook->getUrl('/sort-item') }}" class="text-book">@icon('book'){{ $otherBook->name }}</a>
48                                     </div>
49                                 @endif
50                             @endforeach
51                         </div>
52                     </div>
53                 @endif
54             </div>
55         </div>
56
57     </div>
58
59 @stop
60
61 @section('scripts')
62     <script src="{{ baseUrl("/libs/jquery-sortable/jquery-sortable.min.js") }}"></script>
63     <script>
64         $(document).ready(function() {
65
66             const $container = $('#sort-boxes');
67
68             // Sortable options
69             const sortableOptions = {
70                 group: 'serialization',
71                 containerSelector: 'ul',
72                 itemPath: '',
73                 itemSelector: 'li',
74                 onDrop: function ($item, container, _super) {
75                     updateMapInput();
76                     _super($item, container);
77                 },
78                 isValidTarget: function ($item, container) {
79                     // Prevent nested chapters
80                     return !($item.is('[data-type="chapter"]') && container.target.closest('li').attr('data-type') === 'chapter');
81                 }
82             };
83
84             // Create our sortable group
85             let group = $('.sort-list').sortable(sortableOptions);
86
87             // Add additional books into the view on select.
88             $('#additional-books').on('click', 'a', function(e) {
89                 e.preventDefault();
90
91                 const $link = $(this);
92                 const url = $link.attr('href');
93                 $.get(url, function(data) {
94                     $container.append(data);
95                     group.sortable("destroy");
96                     group = $('.sort-list').sortable(sortableOptions);
97                 });
98                 $link.remove();
99             });
100
101             /**
102              * Update the input with our sort data.
103              */
104             function updateMapInput() {
105                 const pageMap = buildEntityMap();
106                 $('#sort-tree-input').val(JSON.stringify(pageMap));
107             }
108
109             /**
110              * Build up a mapping of entities with their ordering and nesting.
111              * @returns {Array}
112              */
113             function buildEntityMap() {
114                 const entityMap = [];
115                 const $lists = $('.sort-list');
116                 $lists.each(function(listIndex) {
117                     const $list = $(this);
118                     const bookId = $list.closest('[data-type="book"]').attr('data-id');
119                     const $directChildren = $list.find('> [data-type="page"], > [data-type="chapter"]');
120                     $directChildren.each(function(directChildIndex) {
121                         const $childElem = $(this);
122                         const type = $childElem.attr('data-type');
123                         const parentChapter = false;
124                         const childId = $childElem.attr('data-id');
125
126                         entityMap.push({
127                             id: childId,
128                             sort: directChildIndex,
129                             parentChapter: parentChapter,
130                             type: type,
131                             book: bookId
132                         });
133
134                         $childElem.find('[data-type="page"]').each(function(pageIndex) {
135                             const $chapterChild = $(this);
136                             entityMap.push({
137                                 id: $chapterChild.attr('data-id'),
138                                 sort: pageIndex,
139                                 parentChapter: childId,
140                                 type: 'page',
141                                 book: bookId
142                             });
143                         });
144
145                     });
146                 });
147                 return entityMap;
148             }
149
150
151             // Auto sort control
152             const sortOperations = {
153                 name: function(a, b) {
154                     const aName = a.getAttribute('data-name').trim().toLowerCase();
155                     const bName = b.getAttribute('data-name').trim().toLowerCase();
156                     return aName.localeCompare(bName);
157                 },
158                 created: function(a, b) {
159                     const aTime = Number(a.getAttribute('data-created'));
160                     const bTime = Number(b.getAttribute('data-created'));
161                     return bTime - aTime;
162                 },
163                 updated: function(a, b) {
164                     const aTime = Number(a.getAttribute('data-update'));
165                     const bTime = Number(b.getAttribute('data-update'));
166                     return bTime - aTime;
167                 },
168                 chaptersFirst: function(a, b) {
169                     const aType = a.getAttribute('data-type');
170                     const bType = b.getAttribute('data-type');
171                     if (aType === bType) {
172                         return 0;
173                     }
174                     return (aType === 'chapter' ? -1 : 1);
175                 },
176                 chaptersLast: function(a, b) {
177                     const aType = a.getAttribute('data-type');
178                     const bType = b.getAttribute('data-type');
179                     if (aType === bType) {
180                         return 0;
181                     }
182                     return (aType === 'chapter' ? 1 : -1);
183                 },
184             };
185
186             let lastSort = '';
187             let reverse = false;
188             const reversableTypes = ['name', 'created', 'updated'];
189
190             $container.on('click', '.sort-box-options [data-sort]', function(event) {
191                 event.preventDefault();
192                 const $sortLists = $(this).closest('.sort-box').find('ul');
193                 const sort = $(this).attr('data-sort');
194
195                 reverse = (lastSort === sort) ? !reverse : false;
196                 let sortFunction = sortOperations[sort];
197                 if (reverse && reversableTypes.includes(sort)) {
198                    sortFunction = function(a, b) {
199                        return 0 - sortOperations[sort](a, b)
200                    };
201                 }
202
203                 $sortLists.each(function() {
204                     const $list = $(this);
205                     $list.children('li').sort(sortFunction).appendTo($list);
206                 });
207
208                 lastSort = sort;
209                 updateMapInput();
210             });
211
212         });
213     </script>
214 @stop