]> BookStack Code Mirror - bookstack/commitdiff
Added AJAX-based search to books, Fixes #15
authorDan Brown <redacted>
Tue, 1 Sep 2015 14:35:11 +0000 (15:35 +0100)
committerDan Brown <redacted>
Tue, 1 Sep 2015 14:35:11 +0000 (15:35 +0100)
20 files changed:
app/Entity.php
app/Http/Controllers/SearchController.php
app/Http/routes.php
app/Repos/ChapterRepo.php
app/Repos/PageRepo.php
gulpfile.js
resources/assets/js/book-sidebar.js [new file with mode: 0644]
resources/assets/js/image-manager.js
resources/assets/js/jquery-extensions.js [new file with mode: 0644]
resources/assets/sass/_animations.scss
resources/assets/sass/_forms.scss
resources/assets/sass/styles.scss
resources/views/base.blade.php
resources/views/books/delete.blade.php
resources/views/books/show.blade.php
resources/views/chapters/delete.blade.php
resources/views/pages/delete.blade.php
resources/views/pages/show.blade.php
resources/views/search/all.blade.php
resources/views/search/book.blade.php [new file with mode: 0644]

index 0fd5d1e12e54515ec9aa07c59d653e9affaa7831..23b61e4173e31f3d33f1be43a877bffacbe468cd 100644 (file)
@@ -69,16 +69,21 @@ class Entity extends Model
      * Perform a full-text search on this entity.
      * @param string[] $fieldsToSearch
      * @param string[] $terms
+     * @param string[] array $wheres
      * @return mixed
      */
-    public static function fullTextSearch($fieldsToSearch, $terms)
+    public static function fullTextSearch($fieldsToSearch, $terms, $wheres = [])
     {
         $termString = '';
-        foreach($terms as $term) {
+        foreach ($terms as $term) {
             $termString .= $term . '* ';
         }
         $fields = implode(',', $fieldsToSearch);
-        return static::whereRaw('MATCH(' . $fields . ') AGAINST(? IN BOOLEAN MODE)', [$termString])->get();
+        $search = static::whereRaw('MATCH(' . $fields . ') AGAINST(? IN BOOLEAN MODE)', [$termString]);
+        foreach ($wheres as $whereTerm) {
+            $search->where($whereTerm[0], $whereTerm[1], $whereTerm[2]);
+        }
+        return $search->get();
     }
 
 }
index 074a4c777c7d7e9abe9f6d4f26c7fcca500cb69a..360e1139e476cc945d3c3586908488ab0fd6139b 100644 (file)
@@ -38,15 +38,33 @@ class SearchController extends Controller
      */
     public function searchAll(Request $request)
     {
-        if(!$request->has('term')) {
+        if (!$request->has('term')) {
             return redirect()->back();
         }
         $searchTerm = $request->get('term');
         $pages = $this->pageRepo->getBySearch($searchTerm);
         $books = $this->bookRepo->getBySearch($searchTerm);
         $chapters = $this->chapterRepo->getBySearch($searchTerm);
-        return view('search/all', ['pages' => $pages, 'books'=>$books, 'chapters' => $chapters, 'searchTerm' => $searchTerm]);
+        return view('search/all', ['pages' => $pages, 'books' => $books, 'chapters' => $chapters, 'searchTerm' => $searchTerm]);
     }
 
+    /**
+     * Searches all entities within a book.
+     * @param Request $request
+     * @param integer $bookId
+     * @return \Illuminate\View\View
+     * @internal param string $searchTerm
+     */
+    public function searchBook(Request $request, $bookId)
+    {
+        if (!$request->has('term')) {
+            return redirect()->back();
+        }
+        $searchTerm = $request->get('term');
+        $whereTerm = [['book_id', '=', $bookId]];
+        $pages = $this->pageRepo->getBySearch($searchTerm, $whereTerm);
+        $chapters = $this->chapterRepo->getBySearch($searchTerm, $whereTerm);
+        return view('search/book', ['pages' => $pages, 'chapters' => $chapters, 'searchTerm' => $searchTerm]);
+    }
 
 }
index c7ff66f215678eafeef4851f7a2905ef8498dea7..b4e515f3e48ebcd8ae9b586354e4c60730985a4e 100644 (file)
@@ -66,6 +66,7 @@ Route::group(['middleware' => 'auth'], function () {
 
     // Search
     Route::get('/search/all', 'SearchController@searchAll');
+    Route::get('/search/book/{bookId}', 'SearchController@searchBook');
 
     // Other Pages
     Route::get('/', 'HomeController@index');
index 0dcaa5fbdf6e755b2692b158a26399084e04a7cc..b423ee8ba9d0062994a5cf0691b83e23d65bf535 100644 (file)
@@ -67,10 +67,10 @@ class ChapterRepo
         return $slug;
     }
 
-    public function getBySearch($term)
+    public function getBySearch($term, $whereTerms = [])
     {
         $terms = explode(' ', preg_quote(trim($term)));
-        $chapters = $this->chapter->fullTextSearch(['name', 'description'], $terms);
+        $chapters = $this->chapter->fullTextSearch(['name', 'description'], $terms, $whereTerms);
         $words = join('|', $terms);
         foreach ($chapters as $chapter) {
             //highlight
index 51a3e8ce9f698ed85b6944e265053f31e05052e2..1d2e08580c05b1baa1149e760223edaaa213a229 100644 (file)
@@ -59,10 +59,10 @@ class PageRepo
         $page->delete();
     }
 
-    public function getBySearch($term)
+    public function getBySearch($term, $whereTerms = [])
     {
         $terms = explode(' ', preg_quote(trim($term)));
-        $pages = $this->page->fullTextSearch(['name', 'text'], $terms);
+        $pages = $this->page->fullTextSearch(['name', 'text'], $terms, $whereTerms);
 
         // Add highlights to page text.
         $words = join('|', $terms);
index f6a86d4c983029100a52f0db8ed60927a858b398..36693223d1c3fc4c61e6358c592979a4e7db2622 100644 (file)
@@ -14,4 +14,6 @@ var elixir = require('laravel-elixir');
 elixir(function(mix) {
     mix.sass('styles.scss');
     mix.scripts('image-manager.js', 'public/js/image-manager.js');
+    mix.scripts('book-sidebar.js', 'public/js/book-sidebar.js');
+    mix.scripts('jquery-extensions.js', 'public/js/jquery-extensions.js');
 });
diff --git a/resources/assets/js/book-sidebar.js b/resources/assets/js/book-sidebar.js
new file mode 100644 (file)
index 0000000..1315843
--- /dev/null
@@ -0,0 +1,31 @@
+var bookDashboard = new Vue({
+    el: '#book-dashboard',
+    data: {
+        searching: false,
+        searchTerm: '',
+        searchResults: ''
+    },
+    methods: {
+        searchBook: function (e) {
+            e.preventDefault();
+            var term = this.searchTerm;
+            if (term.length == 0) return;
+            this.searching = true;
+            this.searchResults = '';
+            var searchUrl = this.$$.form.getAttribute('action');
+            searchUrl += '?term=' + encodeURIComponent(term);
+            this.$http.get(searchUrl, function (data) {
+                this.$set('searchResults', data);
+            });
+        },
+        checkSearchForm: function (e) {
+            if (this.searchTerm.length < 1) {
+                this.searching = false;
+            }
+        },
+        clearSearch: function(e) {
+            this.searching = false;
+            this.searchTerm = '';
+        }
+    }
+});
\ No newline at end of file
index 4f8695ff80ed4711b18ba6f5902997437bfa9965..73a7fb4a2c9bc059c3d8663d8ddb96d79d39f82f 100644 (file)
 
-jQuery.fn.showSuccess = function(message) {
-    var elem = $(this);
-    var success = $('<div class="text-pos" style="display:none;"><i class="zmdi zmdi-check-circle"></i>'+message+'</div>');
-    elem.after(success);
-    success.slideDown(400, function() {
-        setTimeout(function() {success.slideUp(400, function() {
-            success.remove();
-        })}, 2000);
-    });
-};
-
-jQuery.fn.showFailure = function(messageMap) {
-    var elem = $(this);
-    $.each(messageMap, function(key, messages) {
-        var input = elem.find('[name="'+key+'"]').last();
-        var fail = $('<div class="text-neg" style="display:none;"><i class="zmdi zmdi-alert-circle"></i>'+messages.join("\n")+'</div>');
-        input.after(fail);
-        fail.slideDown(400, function() {
-            setTimeout(function() {fail.slideUp(400, function() {
-                fail.remove();
-            })}, 2000);
-        });
-    });
-
-};
-
-(function() {
-
-    var ImageManager = new Vue({
-
-        el: '#image-manager',
-
-        data: {
-            images: [],
-            hasMore: false,
-            page: 0,
-            cClickTime: 0,
-            selectedImage: false
-        },
-
-        created: function() {
-            // Get initial images
-            this.fetchData(this.page);
-        },
 
-        ready: function() {
-            // Create dropzone
-            this.setupDropZone();
+window.ImageManager = new Vue({
+
+    el: '#image-manager',
+
+    data: {
+        images: [],
+        hasMore: false,
+        page: 0,
+        cClickTime: 0,
+        selectedImage: false
+    },
+
+    created: function () {
+        // Get initial images
+        this.fetchData(this.page);
+    },
+
+    ready: function () {
+        // Create dropzone
+        this.setupDropZone();
+    },
+
+    methods: {
+        fetchData: function () {
+            var _this = this;
+            this.$http.get('/images/all/' + _this.page, function (data) {
+                _this.images = _this.images.concat(data.images);
+                _this.hasMore = data.hasMore;
+                _this.page++;
+            });
         },
 
-        methods: {
-            fetchData: function() {
-                var _this = this;
-                $.getJSON('/images/all/' + _this.page, function(data) {
-                    _this.images = _this.images.concat(data.images);
-                    _this.hasMore = data.hasMore;
-                    _this.page++;
-                });
-            },
-
-            setupDropZone: function() {
-                var _this = this;
-                var dropZone = new Dropzone(_this.$$.dropZone, {
-                    url: '/upload/image',
-                    init: function() {
-                        var dz = this;
-                        this.on("sending", function(file, xhr, data) {
-                            data.append("_token", document.querySelector('meta[name=token]').getAttribute('content'));
+        setupDropZone: function () {
+            var _this = this;
+            var dropZone = new Dropzone(_this.$$.dropZone, {
+                url: '/upload/image',
+                init: function () {
+                    var dz = this;
+                    this.on("sending", function (file, xhr, data) {
+                        data.append("_token", document.querySelector('meta[name=token]').getAttribute('content'));
+                    });
+                    this.on("success", function (file, data) {
+                        _this.images.unshift(data);
+                        $(file.previewElement).fadeOut(400, function () {
+                            dz.removeFile(file);
                         });
-                        this.on("success", function(file, data) {
-                            _this.images.unshift(data);
-                            $(file.previewElement).fadeOut(400, function() {
-                                dz.removeFile(file);
-                            });
-                        });
-                    }
-                });
-            },
-
-            imageClick: function(image) {
-                var dblClickTime = 380;
-                var cTime = (new Date()).getTime();
-                var timeDiff = cTime - this.cClickTime;
-                if(this.cClickTime !== 0 && timeDiff < dblClickTime && this.selectedImage === image) {
-                    // DoubleClick
-                    if(this.callback) {
-                        this.callback(image);
-                    }
-                    this.hide();
-                } else {
-                    this.selectedImage = (this.selectedImage===image) ? false : image;
+                    });
                 }
-                this.cClickTime = cTime;
-            },
+            });
+        },
 
-            selectButtonClick: function() {
-                if(this.callback) {
-                    this.callback(this.selectedImage);
+        imageClick: function (image) {
+            var dblClickTime = 380;
+            var cTime = (new Date()).getTime();
+            var timeDiff = cTime - this.cClickTime;
+            if (this.cClickTime !== 0 && timeDiff < dblClickTime && this.selectedImage === image) {
+                // DoubleClick
+                if (this.callback) {
+                    this.callback(image);
                 }
                 this.hide();
-            },
-
-            show: function(callback) {
-                this.callback = callback;
-                this.$$.overlay.style.display = 'block';
-            },
-
-            overlayClick: function(e) {
-              if(e.target.className==='overlay') {
-                  this.hide();
-              }
-            },
-
-            hide: function() {
-              this.$$.overlay.style.display = 'none';
-            },
-
-            saveImageDetails: function(e) {
-                e.preventDefault();
-                var _this = this;
-                var form = $(_this.$$.imageForm);
-                $.ajax('/images/update/' + _this.selectedImage.id, {
-                    method: 'PUT',
-                    data: form.serialize()
-                }).done(function() {
-                    form.showSuccess('Image name updated');
-                }).fail(function(jqXHR) {
-                    form.showFailure(jqXHR.responseJSON);
-                })
-            },
-
-            deleteImage: function(e) {
-                e.preventDefault();
-                var _this = this;
-                var form = $(_this.$$.imageDeleteForm);
-                $.ajax('/images/' + _this.selectedImage.id, {
-                    method: 'DELETE',
-                    data: form.serialize()
-                }).done(function() {
-                    _this.images.splice(_this.images.indexOf(_this.selectedImage), 1);
-                    _this.selectedImage = false;
-                    $(_this.$$.imageTitle).showSuccess('Image Deleted');
-                })
+            } else {
+                this.selectedImage = (this.selectedImage === image) ? false : image;
             }
+            this.cClickTime = cTime;
+        },
 
-        }
+        selectButtonClick: function () {
+            if (this.callback) {
+                this.callback(this.selectedImage);
+            }
+            this.hide();
+        },
 
-    });
+        show: function (callback) {
+            this.callback = callback;
+            this.$$.overlay.style.display = 'block';
+        },
 
-    window.ImageManager = ImageManager;
+        overlayClick: function (e) {
+            if (e.target.className === 'overlay') {
+                this.hide();
+            }
+        },
+
+        hide: function () {
+            this.$$.overlay.style.display = 'none';
+        },
+
+        saveImageDetails: function (e) {
+            e.preventDefault();
+            var _this = this;
+            var form = $(_this.$$.imageForm);
+            $.ajax('/images/update/' + _this.selectedImage.id, {
+                method: 'PUT',
+                data: form.serialize()
+            }).done(function () {
+                form.showSuccess('Image name updated');
+            }).fail(function (jqXHR) {
+                form.showFailure(jqXHR.responseJSON);
+            })
+        },
+
+        deleteImage: function (e) {
+            e.preventDefault();
+            var _this = this;
+            var form = $(_this.$$.imageDeleteForm);
+            $.ajax('/images/' + _this.selectedImage.id, {
+                method: 'DELETE',
+                data: form.serialize()
+            }).done(function () {
+                _this.images.splice(_this.images.indexOf(_this.selectedImage), 1);
+                _this.selectedImage = false;
+                $(_this.$$.imageTitle).showSuccess('Image Deleted');
+            })
+        }
 
+    }
 
-})();
\ No newline at end of file
+});
diff --git a/resources/assets/js/jquery-extensions.js b/resources/assets/js/jquery-extensions.js
new file mode 100644 (file)
index 0000000..c28c5d1
--- /dev/null
@@ -0,0 +1,43 @@
+
+jQuery.fn.smoothScrollTo = function() {
+    if(this.length === 0) return;
+    $('body').animate({
+        scrollTop: this.offset().top - 60 // Adjust to change final scroll position top margin
+    }, 800); // Adjust to change animations speed (ms)
+    return this;
+};
+$.expr[":"].contains = $.expr.createPseudo(function(arg) {
+    return function( elem ) {
+        return $(elem).text().toUpperCase().indexOf(arg.toUpperCase()) >= 0;
+    };
+});
+
+jQuery.fn.showSuccess = function (message) {
+    var elem = $(this);
+    var success = $('<div class="text-pos" style="display:none;"><i class="zmdi zmdi-check-circle"></i>' + message + '</div>');
+    elem.after(success);
+    success.slideDown(400, function () {
+        setTimeout(function () {
+            success.slideUp(400, function () {
+                success.remove();
+            })
+        }, 2000);
+    });
+};
+
+jQuery.fn.showFailure = function (messageMap) {
+    var elem = $(this);
+    $.each(messageMap, function (key, messages) {
+        var input = elem.find('[name="' + key + '"]').last();
+        var fail = $('<div class="text-neg" style="display:none;"><i class="zmdi zmdi-alert-circle"></i>' + messages.join("\n") + '</div>');
+        input.after(fail);
+        fail.slideDown(400, function () {
+            setTimeout(function () {
+                fail.slideUp(400, function () {
+                    fail.remove();
+                })
+            }, 2000);
+        });
+    });
+
+};
\ No newline at end of file
index 8d3336489e155354043a11a807260c0290a05ac6..cc1d42be976e1ebbd3951b47dcf89461072440b1 100644 (file)
   }
 }
 
+.anim.searchResult {
+  opacity: 0;
+  transform: translate3d(580px, 0, 0);
+  animation-name: searchResult;
+  animation-duration: 220ms;
+  animation-fill-mode: forwards;
+  animation-timing-function: cubic-bezier(.62,.28,.23,.99);
+}
+
+@keyframes searchResult {
+  0% {
+    opacity: 0;
+    transform: translate3d(400px, 0, 0);
+  }
+  100% {
+    opacity: 1;
+    transform: translate3d(0, 0, 0);
+  }
+}
+
 .anim.notification {
   transform: translate3d(580px, 0, 0);
   animation-name: notification;
index 49716154fe6704e4bfd0a732fb5aaa746b52bb10..9536b93f399b595917d8ab819150787d29e4ad70 100644 (file)
@@ -90,11 +90,28 @@ input[type="text"], input[type="number"], input[type="email"], input[type="searc
   }
 }
 
-
-
 .description-input textarea {
   @extend .inline-input-style;
   font-size: $fs-m;
   color: #666;
   width: 100%;
+}
+
+.search-box {
+  button {
+    background-color: transparent;
+    border: none;
+    color: $primary;
+    padding: 0;
+    margin: 0;
+    cursor: pointer;
+    margin-left: $-s;
+  }
+  button[type="submit"] {
+    margin-left: -$-l;
+  }
+  input {
+    padding-right: $-l;
+    width: 300px;
+  }
 }
\ No newline at end of file
index 0101aed9f25d3203e61f6905c658de927da43b4f..665315287745e679db57e79a337f10851df15a72 100644 (file)
@@ -57,19 +57,25 @@ header {
 }
 
 form.search-box {
-  padding-top: $-l *0.9;
+  margin-top: $-l *0.9;
   display: inline-block;
+  position: relative;
   input {
     background-color: transparent;
     border-radius: 0;
     border: none;
     border-bottom: 2px solid #EEE;
     color: #EEE;
-    padding-left: $-l;
+    padding-right: $-l;
     outline: 0;
   }
-  i {
-    margin-right: -$-l;
+  a {
+    vertical-align: top;
+    margin-left: -$-l;
+    color: #FFF;
+    top: 0;
+    display: inline-block;
+    position: absolute;
   }
 }
 
@@ -121,6 +127,10 @@ body.flexbox {
   padding: $-l $-l $-l 0;
   vertical-align: top;
   line-height: 1;
+  &:hover {
+    color: #FFF;
+    text-decoration: none;
+  }
 }
 
 .page-title input {
@@ -537,4 +547,13 @@ ul.dropdown {
   li.border-bottom {
     border-bottom: 1px solid #DDD;
   }
+}
+
+.search-results > h3 a {
+  font-size: 0.66em;
+  color: $primary;
+  padding-left: $-m;
+  i {
+    padding-right: $-s;
+  }
 }
\ No newline at end of file
index f20126b372b327539909f2e2acdd42a735c2c6ee..3c692317f8473d9f1840e7e473a5cc51a515fff4 100644 (file)
 
     <!-- Scripts -->
     <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
+    <script src="/js/jquery-extensions.js"></script>
     <script src="/bower/bootstrap/dist/js/bootstrap.js"></script>
     <script src="/bower/jquery-sortable/source/js/jquery-sortable.js"></script>
     <script src="/bower/dropzone/dist/min/dropzone.min.js"></script>
     <script src="/bower/vue/dist/vue.min.js"></script>
-    <script>
-        $.fn.smoothScrollTo = function() {
-            if(this.length === 0) return;
-            $('body').animate({
-                scrollTop: this.offset().top - 60 // Adjust to change final scroll position top margin
-            }, 800); // Adjust to change animations speed (ms)
-            return this;
-        };
-        $.expr[":"].contains = $.expr.createPseudo(function(arg) {
-            return function( elem ) {
-                return $(elem).text().toUpperCase().indexOf(arg.toUpperCase()) >= 0;
-            };
-        });
-    </script>
+    <script src="/bower/vue-resource/dist/vue-resource.min.js"></script>
 
     @yield('head')
 </head>
@@ -57,8 +45,8 @@
                 </div>
                 <div class="col-md-3 text-right">
                     <form action="/search/all" method="GET" class="search-box">
-                        <i class="zmdi zmdi-search"></i>
                         <input type="text" name="term" tabindex="2" value="{{ isset($searchTerm) ? $searchTerm : '' }}">
+                        <a onclick="$(this).closest('form').submit();"><i class="zmdi zmdi-search"></i></a>
                     </form>
                 </div>
                 <div class="col-md-6">
index 30965071cefbf8792cb50c9714175d6ac6d686f8..6ec8cdb3bd5a28a324326d89105fc53c7801d930 100644 (file)
@@ -10,8 +10,8 @@
         <form action="{{$book->getUrl()}}" method="POST">
             {!! csrf_field() !!}
             <input type="hidden" name="_method" value="DELETE">
-            <button type="submit" class="button neg">Confirm</button>
             <a href="{{$book->getUrl()}}" class="button">Cancel</a>
+            <button type="submit" class="button neg">Confirm</button>
         </form>
     </div>
 
index b44cd850571b03d2c1f98f91e089b07355ecda77..6e1aaf7b484c7ff481cff3a9c4a73d7b9000423b 100644 (file)
     </div>
 
 
-    <div class="container">
+    <div class="container" id="book-dashboard">
         <div class="row">
             <div class="col-md-7">
 
                 <h1>{{$book->name}}</h1>
-                <p class="text-muted">{{$book->description}}</p>
-
-                <div class="page-list">
-                    <hr>
-                    @if(count($book->children()) > 0)
-                        @foreach($book->children() as $childElement)
-                            <div class="book-child">
-                                <h3>
-                                    <a href="{{ $childElement->getUrl() }}" class="{{ $childElement->getName() }}">
-                                        <i class="zmdi {{ $childElement->isA('chapter') ? 'zmdi-collection-bookmark chapter-toggle':'zmdi-file-text'}}"></i>{{ $childElement->name }}
-                                    </a>
-                                </h3>
-                                <p class="text-muted">
-                                    {{$childElement->getExcerpt()}}
-                                </p>
-
-                                @if($childElement->isA('chapter') && count($childElement->pages) > 0)
-                                    <div class="inset-list">
-                                        @foreach($childElement->pages as $page)
-                                            <h4><a href="{{$page->getUrl()}}"><i class="zmdi zmdi-file-text"></i>{{$page->name}}</a></h4>
-                                        @endforeach
-                                    </div>
-                                @endif
-                            </div>
+                <div class="book-content anim fadeIn" v-if="!searching">
+                    <p class="text-muted">{{$book->description}}</p>
+
+                    <div class="page-list">
+                        <hr>
+                        @if(count($book->children()) > 0)
+                            @foreach($book->children() as $childElement)
+                                <div class="book-child">
+                                    <h3>
+                                        <a href="{{ $childElement->getUrl() }}" class="{{ $childElement->getName() }}">
+                                            <i class="zmdi {{ $childElement->isA('chapter') ? 'zmdi-collection-bookmark chapter-toggle':'zmdi-file-text'}}"></i>{{ $childElement->name }}
+                                        </a>
+                                    </h3>
+                                    <p class="text-muted">
+                                        {{$childElement->getExcerpt()}}
+                                    </p>
+
+                                    @if($childElement->isA('chapter') && count($childElement->pages) > 0)
+                                        <div class="inset-list">
+                                            @foreach($childElement->pages as $page)
+                                                <h4><a href="{{$page->getUrl()}}"><i class="zmdi zmdi-file-text"></i>{{$page->name}}</a></h4>
+                                            @endforeach
+                                        </div>
+                                    @endif
+                                </div>
+                                <hr>
+                            @endforeach
+                        @else
+                            <p class="text-muted">No pages or chapters have been created for this book.</p>
+                            <p>
+                                <a href="{{$book->getUrl() . '/page/create'}}" class="text-page"><i class="zmdi zmdi-file-text"></i>Create a new page</a>
+                                &nbsp;&nbsp;<em class="text-muted">-or-</em>&nbsp;&nbsp;&nbsp;
+                                <a href="{{$book->getUrl() . '/chapter/create'}}" class="text-chapter"><i class="zmdi zmdi-collection-bookmark"></i>Add a chapter</a>
+                            </p>
                             <hr>
-                        @endforeach
-                    @else
-                        <p class="text-muted">No pages or chapters have been created for this book.</p>
-                        <p>
-                            <a href="{{$book->getUrl() . '/page/create'}}" class="text-page"><i class="zmdi zmdi-file-text"></i>Create a new page</a>
-                             &nbsp;&nbsp;<em class="text-muted">-or-</em>&nbsp;&nbsp;&nbsp;
-                            <a href="{{$book->getUrl() . '/chapter/create'}}" class="text-chapter"><i class="zmdi zmdi-collection-bookmark"></i>Add a chapter</a>
+                        @endif
+                        <p class="text-muted small">
+                            Created {{$book->created_at->diffForHumans()}} @if($book->createdBy) by {{$book->createdBy->name}} @endif
+                            <br>
+                            Last Updated {{$book->updated_at->diffForHumans()}} @if($book->createdBy) by {{$book->updatedBy->name}} @endif
                         </p>
-                        <hr>
-                    @endif
+                    </div>
+                </div>
+                <div class="search-results" v-if="searching">
+                    <h3 class="text-muted">Search Results <a v-if="searching" v-on="click: clearSearch" class="text-small"><i class="zmdi zmdi-close"></i>Clear Search</a></h3>
+                    <div v-html="searchResults"></div>
                 </div>
-
-                <p class="text-muted small">
-                    Created {{$book->created_at->diffForHumans()}} @if($book->createdBy) by {{$book->createdBy->name}} @endif
-                    <br>
-                    Last Updated {{$book->updated_at->diffForHumans()}} @if($book->createdBy) by {{$book->updatedBy->name}} @endif
-                </p>
 
 
             </div>
 
             <div class="col-md-4 col-md-offset-1">
-                <div class="margin-top large"><br></div>
-                <h3>Recent Activity</h3>
-                @include('partials/activity-list', ['activity' => Activity::entityActivity($book, 20, 0)])
+                <div class="margin-top large"></div>
+                {{--<h3>Search This Book</h3>--}}
+                <div class="search-box">
+                    <form v-on="submit: searchBook, input: checkSearchForm" v-el="form" action="/search/book/{{ $book->id }}">
+                        {!! csrf_field() !!}
+                        <input v-model="searchTerm" type="text" name="term" placeholder="Search This Book">
+                        <button type="submit"><i class="zmdi zmdi-search"></i></button>
+                        <button v-if="searching" v-on="click: clearSearch" type="button primary"><i class="zmdi zmdi-close"></i></button>
+                    </form>
+                </div>
+                <div class="activity anim fadeIn">
+                    <h3>Recent Activity</h3>
+                    @include('partials/activity-list', ['activity' => Activity::entityActivity($book, 20, 0)])
+                </div>
             </div>
         </div>
     </div>
         });
     </script>
 
+    <script src="/js/book-sidebar.js"></script>
+
 @stop
\ No newline at end of file
index 1607e7b6a47cb76afb887ec8b36227d43de54d8f..512535776ebc422846e6de5e3ef21e9d6dcd78e0 100644 (file)
@@ -11,7 +11,7 @@
         <form action="{{$chapter->getUrl()}}" method="POST">
             {!! csrf_field() !!}
             <input type="hidden" name="_method" value="DELETE">
-            <a href="{{$chapter->getUrl()}}" class="button muted">Cancel</a>
+            <a href="{{$chapter->getUrl()}}" class="button primary">Cancel</a>
             <button type="submit" class="button neg">Confirm</button>
         </form>
     </div>
index 3d199cd1da358f2cc19f0f448cc1b7424f6a9996..45d87f151d6b41cdba763a5872680a8b42f49698 100644 (file)
@@ -4,12 +4,12 @@
 
     <div class="page-content">
         <h1>Delete Page</h1>
-        <p>Are you sure you want to delete this page?</p>
+        <p class="text-neg">Are you sure you want to delete this page?</p>
 
         <form action="{{$page->getUrl()}}" method="POST">
             {!! csrf_field() !!}
             <input type="hidden" name="_method" value="DELETE">
-            <a href="{{$page->getUrl()}}" class="button muted">Cancel</a>
+            <a href="{{$page->getUrl()}}" class="button primary">Cancel</a>
             <button type="submit" class="button neg">Confirm</button>
         </form>
     </div>
index 46bd1714ca8d595804923464c659f900c8d414b6..51f6156698330486c3041b03df5d68afe79cf505 100644 (file)
@@ -44,7 +44,7 @@
                 </div>
             </div>
             <div class="col-md-9">
-                <div class="page-content">
+                <div class="page-content anim fadeIn">
                     @include('pages/page-display')
                     <hr>
                     <p class="text-muted small">
index f2c922de2c32bf62635b8bc6d8c603cba1b7d547..3cb61b761c42bbeddc5c7ac088079b994d9ec8b2 100644 (file)
@@ -2,7 +2,7 @@
 
 @section('content')
 
-    <div class="container">
+    <div class="container anim fadeIn">
 
         <h1>Search Results&nbsp;&nbsp;&nbsp; <span class="text-muted">{{$searchTerm}}</span></h1>
 
diff --git a/resources/views/search/book.blade.php b/resources/views/search/book.blade.php
new file mode 100644 (file)
index 0000000..329c388
--- /dev/null
@@ -0,0 +1,41 @@
+
+<div class="page-list">
+    @if(count($pages) > 0)
+        @foreach($pages as $page)
+            <div class="book-child anim searchResult">
+                <h3>
+                    <a href="{{$page->getUrl() . '#' . $searchTerm}}" class="page">
+                        <i class="zmdi zmdi-file-text"></i>{{$page->name}}
+                    </a>
+                </h3>
+
+                <p class="text-muted">
+                    {!! $page->searchSnippet !!}
+                </p>
+                <hr>
+            </div>
+        @endforeach
+    @else
+        <p class="text-muted">No pages matched this search</p>
+    @endif
+</div>
+
+@if(count($chapters) > 0)
+    <div class="page-list">
+        @foreach($chapters as $chapter)
+            <div class="book-child anim searchResult">
+                <h3>
+                    <a href="{{$chapter->getUrl()}}" class="text-chapter">
+                        <i class="zmdi zmdi-collection-bookmark"></i>{{$chapter->name}}
+                    </a>
+                </h3>
+
+                <p class="text-muted">
+                    {!! $chapter->searchSnippet !!}
+                </p>
+                <hr>
+            </div>
+        @endforeach
+    </div>
+@endif
+