Dan Brown

Added AJAX-based search to books, Fixes #15

...@@ -69,16 +69,21 @@ class Entity extends Model ...@@ -69,16 +69,21 @@ class Entity extends Model
69 * Perform a full-text search on this entity. 69 * Perform a full-text search on this entity.
70 * @param string[] $fieldsToSearch 70 * @param string[] $fieldsToSearch
71 * @param string[] $terms 71 * @param string[] $terms
72 + * @param string[] array $wheres
72 * @return mixed 73 * @return mixed
73 */ 74 */
74 - public static function fullTextSearch($fieldsToSearch, $terms) 75 + public static function fullTextSearch($fieldsToSearch, $terms, $wheres = [])
75 { 76 {
76 $termString = ''; 77 $termString = '';
77 - foreach($terms as $term) { 78 + foreach ($terms as $term) {
78 $termString .= $term . '* '; 79 $termString .= $term . '* ';
79 } 80 }
80 $fields = implode(',', $fieldsToSearch); 81 $fields = implode(',', $fieldsToSearch);
81 - return static::whereRaw('MATCH(' . $fields . ') AGAINST(? IN BOOLEAN MODE)', [$termString])->get(); 82 + $search = static::whereRaw('MATCH(' . $fields . ') AGAINST(? IN BOOLEAN MODE)', [$termString]);
83 + foreach ($wheres as $whereTerm) {
84 + $search->where($whereTerm[0], $whereTerm[1], $whereTerm[2]);
85 + }
86 + return $search->get();
82 } 87 }
83 88
84 } 89 }
......
...@@ -38,15 +38,33 @@ class SearchController extends Controller ...@@ -38,15 +38,33 @@ class SearchController extends Controller
38 */ 38 */
39 public function searchAll(Request $request) 39 public function searchAll(Request $request)
40 { 40 {
41 - if(!$request->has('term')) { 41 + if (!$request->has('term')) {
42 return redirect()->back(); 42 return redirect()->back();
43 } 43 }
44 $searchTerm = $request->get('term'); 44 $searchTerm = $request->get('term');
45 $pages = $this->pageRepo->getBySearch($searchTerm); 45 $pages = $this->pageRepo->getBySearch($searchTerm);
46 $books = $this->bookRepo->getBySearch($searchTerm); 46 $books = $this->bookRepo->getBySearch($searchTerm);
47 $chapters = $this->chapterRepo->getBySearch($searchTerm); 47 $chapters = $this->chapterRepo->getBySearch($searchTerm);
48 - return view('search/all', ['pages' => $pages, 'books'=>$books, 'chapters' => $chapters, 'searchTerm' => $searchTerm]); 48 + return view('search/all', ['pages' => $pages, 'books' => $books, 'chapters' => $chapters, 'searchTerm' => $searchTerm]);
49 } 49 }
50 50
51 + /**
52 + * Searches all entities within a book.
53 + * @param Request $request
54 + * @param integer $bookId
55 + * @return \Illuminate\View\View
56 + * @internal param string $searchTerm
57 + */
58 + public function searchBook(Request $request, $bookId)
59 + {
60 + if (!$request->has('term')) {
61 + return redirect()->back();
62 + }
63 + $searchTerm = $request->get('term');
64 + $whereTerm = [['book_id', '=', $bookId]];
65 + $pages = $this->pageRepo->getBySearch($searchTerm, $whereTerm);
66 + $chapters = $this->chapterRepo->getBySearch($searchTerm, $whereTerm);
67 + return view('search/book', ['pages' => $pages, 'chapters' => $chapters, 'searchTerm' => $searchTerm]);
68 + }
51 69
52 } 70 }
......
...@@ -66,6 +66,7 @@ Route::group(['middleware' => 'auth'], function () { ...@@ -66,6 +66,7 @@ Route::group(['middleware' => 'auth'], function () {
66 66
67 // Search 67 // Search
68 Route::get('/search/all', 'SearchController@searchAll'); 68 Route::get('/search/all', 'SearchController@searchAll');
69 + Route::get('/search/book/{bookId}', 'SearchController@searchBook');
69 70
70 // Other Pages 71 // Other Pages
71 Route::get('/', 'HomeController@index'); 72 Route::get('/', 'HomeController@index');
......
...@@ -67,10 +67,10 @@ class ChapterRepo ...@@ -67,10 +67,10 @@ class ChapterRepo
67 return $slug; 67 return $slug;
68 } 68 }
69 69
70 - public function getBySearch($term) 70 + public function getBySearch($term, $whereTerms = [])
71 { 71 {
72 $terms = explode(' ', preg_quote(trim($term))); 72 $terms = explode(' ', preg_quote(trim($term)));
73 - $chapters = $this->chapter->fullTextSearch(['name', 'description'], $terms); 73 + $chapters = $this->chapter->fullTextSearch(['name', 'description'], $terms, $whereTerms);
74 $words = join('|', $terms); 74 $words = join('|', $terms);
75 foreach ($chapters as $chapter) { 75 foreach ($chapters as $chapter) {
76 //highlight 76 //highlight
......
...@@ -59,10 +59,10 @@ class PageRepo ...@@ -59,10 +59,10 @@ class PageRepo
59 $page->delete(); 59 $page->delete();
60 } 60 }
61 61
62 - public function getBySearch($term) 62 + public function getBySearch($term, $whereTerms = [])
63 { 63 {
64 $terms = explode(' ', preg_quote(trim($term))); 64 $terms = explode(' ', preg_quote(trim($term)));
65 - $pages = $this->page->fullTextSearch(['name', 'text'], $terms); 65 + $pages = $this->page->fullTextSearch(['name', 'text'], $terms, $whereTerms);
66 66
67 // Add highlights to page text. 67 // Add highlights to page text.
68 $words = join('|', $terms); 68 $words = join('|', $terms);
......
...@@ -14,4 +14,6 @@ var elixir = require('laravel-elixir'); ...@@ -14,4 +14,6 @@ var elixir = require('laravel-elixir');
14 elixir(function(mix) { 14 elixir(function(mix) {
15 mix.sass('styles.scss'); 15 mix.sass('styles.scss');
16 mix.scripts('image-manager.js', 'public/js/image-manager.js'); 16 mix.scripts('image-manager.js', 'public/js/image-manager.js');
17 + mix.scripts('book-sidebar.js', 'public/js/book-sidebar.js');
18 + mix.scripts('jquery-extensions.js', 'public/js/jquery-extensions.js');
17 }); 19 });
......
1 +var bookDashboard = new Vue({
2 + el: '#book-dashboard',
3 + data: {
4 + searching: false,
5 + searchTerm: '',
6 + searchResults: ''
7 + },
8 + methods: {
9 + searchBook: function (e) {
10 + e.preventDefault();
11 + var term = this.searchTerm;
12 + if (term.length == 0) return;
13 + this.searching = true;
14 + this.searchResults = '';
15 + var searchUrl = this.$$.form.getAttribute('action');
16 + searchUrl += '?term=' + encodeURIComponent(term);
17 + this.$http.get(searchUrl, function (data) {
18 + this.$set('searchResults', data);
19 + });
20 + },
21 + checkSearchForm: function (e) {
22 + if (this.searchTerm.length < 1) {
23 + this.searching = false;
24 + }
25 + },
26 + clearSearch: function(e) {
27 + this.searching = false;
28 + this.searchTerm = '';
29 + }
30 + }
31 +});
...\ No newline at end of file ...\ No newline at end of file
1 1
2 -jQuery.fn.showSuccess = function(message) {
3 - var elem = $(this);
4 - var success = $('<div class="text-pos" style="display:none;"><i class="zmdi zmdi-check-circle"></i>'+message+'</div>');
5 - elem.after(success);
6 - success.slideDown(400, function() {
7 - setTimeout(function() {success.slideUp(400, function() {
8 - success.remove();
9 - })}, 2000);
10 - });
11 -};
12 -
13 -jQuery.fn.showFailure = function(messageMap) {
14 - var elem = $(this);
15 - $.each(messageMap, function(key, messages) {
16 - var input = elem.find('[name="'+key+'"]').last();
17 - var fail = $('<div class="text-neg" style="display:none;"><i class="zmdi zmdi-alert-circle"></i>'+messages.join("\n")+'</div>');
18 - input.after(fail);
19 - fail.slideDown(400, function() {
20 - setTimeout(function() {fail.slideUp(400, function() {
21 - fail.remove();
22 - })}, 2000);
23 - });
24 - });
25 -
26 -};
27 -
28 -(function() {
29 -
30 - var ImageManager = new Vue({
31 -
32 - el: '#image-manager',
33 -
34 - data: {
35 - images: [],
36 - hasMore: false,
37 - page: 0,
38 - cClickTime: 0,
39 - selectedImage: false
40 - },
41 -
42 - created: function() {
43 - // Get initial images
44 - this.fetchData(this.page);
45 - },
46 2
47 - ready: function() { 3 +window.ImageManager = new Vue({
48 - // Create dropzone 4 +
49 - this.setupDropZone(); 5 + el: '#image-manager',
6 +
7 + data: {
8 + images: [],
9 + hasMore: false,
10 + page: 0,
11 + cClickTime: 0,
12 + selectedImage: false
13 + },
14 +
15 + created: function () {
16 + // Get initial images
17 + this.fetchData(this.page);
18 + },
19 +
20 + ready: function () {
21 + // Create dropzone
22 + this.setupDropZone();
23 + },
24 +
25 + methods: {
26 + fetchData: function () {
27 + var _this = this;
28 + this.$http.get('/images/all/' + _this.page, function (data) {
29 + _this.images = _this.images.concat(data.images);
30 + _this.hasMore = data.hasMore;
31 + _this.page++;
32 + });
50 }, 33 },
51 34
52 - methods: { 35 + setupDropZone: function () {
53 - fetchData: function() { 36 + var _this = this;
54 - var _this = this; 37 + var dropZone = new Dropzone(_this.$$.dropZone, {
55 - $.getJSON('/images/all/' + _this.page, function(data) { 38 + url: '/upload/image',
56 - _this.images = _this.images.concat(data.images); 39 + init: function () {
57 - _this.hasMore = data.hasMore; 40 + var dz = this;
58 - _this.page++; 41 + this.on("sending", function (file, xhr, data) {
59 - }); 42 + data.append("_token", document.querySelector('meta[name=token]').getAttribute('content'));
60 - }, 43 + });
61 - 44 + this.on("success", function (file, data) {
62 - setupDropZone: function() { 45 + _this.images.unshift(data);
63 - var _this = this; 46 + $(file.previewElement).fadeOut(400, function () {
64 - var dropZone = new Dropzone(_this.$$.dropZone, { 47 + dz.removeFile(file);
65 - url: '/upload/image',
66 - init: function() {
67 - var dz = this;
68 - this.on("sending", function(file, xhr, data) {
69 - data.append("_token", document.querySelector('meta[name=token]').getAttribute('content'));
70 }); 48 });
71 - this.on("success", function(file, data) { 49 + });
72 - _this.images.unshift(data);
73 - $(file.previewElement).fadeOut(400, function() {
74 - dz.removeFile(file);
75 - });
76 - });
77 - }
78 - });
79 - },
80 -
81 - imageClick: function(image) {
82 - var dblClickTime = 380;
83 - var cTime = (new Date()).getTime();
84 - var timeDiff = cTime - this.cClickTime;
85 - if(this.cClickTime !== 0 && timeDiff < dblClickTime && this.selectedImage === image) {
86 - // DoubleClick
87 - if(this.callback) {
88 - this.callback(image);
89 - }
90 - this.hide();
91 - } else {
92 - this.selectedImage = (this.selectedImage===image) ? false : image;
93 } 50 }
94 - this.cClickTime = cTime; 51 + });
95 - }, 52 + },
96 53
97 - selectButtonClick: function() { 54 + imageClick: function (image) {
98 - if(this.callback) { 55 + var dblClickTime = 380;
99 - this.callback(this.selectedImage); 56 + var cTime = (new Date()).getTime();
57 + var timeDiff = cTime - this.cClickTime;
58 + if (this.cClickTime !== 0 && timeDiff < dblClickTime && this.selectedImage === image) {
59 + // DoubleClick
60 + if (this.callback) {
61 + this.callback(image);
100 } 62 }
101 this.hide(); 63 this.hide();
102 - }, 64 + } else {
103 - 65 + this.selectedImage = (this.selectedImage === image) ? false : image;
104 - show: function(callback) {
105 - this.callback = callback;
106 - this.$$.overlay.style.display = 'block';
107 - },
108 -
109 - overlayClick: function(e) {
110 - if(e.target.className==='overlay') {
111 - this.hide();
112 - }
113 - },
114 -
115 - hide: function() {
116 - this.$$.overlay.style.display = 'none';
117 - },
118 -
119 - saveImageDetails: function(e) {
120 - e.preventDefault();
121 - var _this = this;
122 - var form = $(_this.$$.imageForm);
123 - $.ajax('/images/update/' + _this.selectedImage.id, {
124 - method: 'PUT',
125 - data: form.serialize()
126 - }).done(function() {
127 - form.showSuccess('Image name updated');
128 - }).fail(function(jqXHR) {
129 - form.showFailure(jqXHR.responseJSON);
130 - })
131 - },
132 -
133 - deleteImage: function(e) {
134 - e.preventDefault();
135 - var _this = this;
136 - var form = $(_this.$$.imageDeleteForm);
137 - $.ajax('/images/' + _this.selectedImage.id, {
138 - method: 'DELETE',
139 - data: form.serialize()
140 - }).done(function() {
141 - _this.images.splice(_this.images.indexOf(_this.selectedImage), 1);
142 - _this.selectedImage = false;
143 - $(_this.$$.imageTitle).showSuccess('Image Deleted');
144 - })
145 } 66 }
67 + this.cClickTime = cTime;
68 + },
146 69
147 - } 70 + selectButtonClick: function () {
71 + if (this.callback) {
72 + this.callback(this.selectedImage);
73 + }
74 + this.hide();
75 + },
148 76
149 - }); 77 + show: function (callback) {
78 + this.callback = callback;
79 + this.$$.overlay.style.display = 'block';
80 + },
150 81
151 - window.ImageManager = ImageManager; 82 + overlayClick: function (e) {
83 + if (e.target.className === 'overlay') {
84 + this.hide();
85 + }
86 + },
87 +
88 + hide: function () {
89 + this.$$.overlay.style.display = 'none';
90 + },
91 +
92 + saveImageDetails: function (e) {
93 + e.preventDefault();
94 + var _this = this;
95 + var form = $(_this.$$.imageForm);
96 + $.ajax('/images/update/' + _this.selectedImage.id, {
97 + method: 'PUT',
98 + data: form.serialize()
99 + }).done(function () {
100 + form.showSuccess('Image name updated');
101 + }).fail(function (jqXHR) {
102 + form.showFailure(jqXHR.responseJSON);
103 + })
104 + },
105 +
106 + deleteImage: function (e) {
107 + e.preventDefault();
108 + var _this = this;
109 + var form = $(_this.$$.imageDeleteForm);
110 + $.ajax('/images/' + _this.selectedImage.id, {
111 + method: 'DELETE',
112 + data: form.serialize()
113 + }).done(function () {
114 + _this.images.splice(_this.images.indexOf(_this.selectedImage), 1);
115 + _this.selectedImage = false;
116 + $(_this.$$.imageTitle).showSuccess('Image Deleted');
117 + })
118 + }
152 119
120 + }
153 121
154 -})();
...\ No newline at end of file ...\ No newline at end of file
122 +});
......
1 +
2 +jQuery.fn.smoothScrollTo = function() {
3 + if(this.length === 0) return;
4 + $('body').animate({
5 + scrollTop: this.offset().top - 60 // Adjust to change final scroll position top margin
6 + }, 800); // Adjust to change animations speed (ms)
7 + return this;
8 +};
9 +$.expr[":"].contains = $.expr.createPseudo(function(arg) {
10 + return function( elem ) {
11 + return $(elem).text().toUpperCase().indexOf(arg.toUpperCase()) >= 0;
12 + };
13 +});
14 +
15 +jQuery.fn.showSuccess = function (message) {
16 + var elem = $(this);
17 + var success = $('<div class="text-pos" style="display:none;"><i class="zmdi zmdi-check-circle"></i>' + message + '</div>');
18 + elem.after(success);
19 + success.slideDown(400, function () {
20 + setTimeout(function () {
21 + success.slideUp(400, function () {
22 + success.remove();
23 + })
24 + }, 2000);
25 + });
26 +};
27 +
28 +jQuery.fn.showFailure = function (messageMap) {
29 + var elem = $(this);
30 + $.each(messageMap, function (key, messages) {
31 + var input = elem.find('[name="' + key + '"]').last();
32 + var fail = $('<div class="text-neg" style="display:none;"><i class="zmdi zmdi-alert-circle"></i>' + messages.join("\n") + '</div>');
33 + input.after(fail);
34 + fail.slideDown(400, function () {
35 + setTimeout(function () {
36 + fail.slideUp(400, function () {
37 + fail.remove();
38 + })
39 + }, 2000);
40 + });
41 + });
42 +
43 +};
...\ No newline at end of file ...\ No newline at end of file
...@@ -16,6 +16,26 @@ ...@@ -16,6 +16,26 @@
16 } 16 }
17 } 17 }
18 18
19 +.anim.searchResult {
20 + opacity: 0;
21 + transform: translate3d(580px, 0, 0);
22 + animation-name: searchResult;
23 + animation-duration: 220ms;
24 + animation-fill-mode: forwards;
25 + animation-timing-function: cubic-bezier(.62,.28,.23,.99);
26 +}
27 +
28 +@keyframes searchResult {
29 + 0% {
30 + opacity: 0;
31 + transform: translate3d(400px, 0, 0);
32 + }
33 + 100% {
34 + opacity: 1;
35 + transform: translate3d(0, 0, 0);
36 + }
37 +}
38 +
19 .anim.notification { 39 .anim.notification {
20 transform: translate3d(580px, 0, 0); 40 transform: translate3d(580px, 0, 0);
21 animation-name: notification; 41 animation-name: notification;
......
...@@ -90,11 +90,28 @@ input[type="text"], input[type="number"], input[type="email"], input[type="searc ...@@ -90,11 +90,28 @@ input[type="text"], input[type="number"], input[type="email"], input[type="searc
90 } 90 }
91 } 91 }
92 92
93 -
94 -
95 .description-input textarea { 93 .description-input textarea {
96 @extend .inline-input-style; 94 @extend .inline-input-style;
97 font-size: $fs-m; 95 font-size: $fs-m;
98 color: #666; 96 color: #666;
99 width: 100%; 97 width: 100%;
98 +}
99 +
100 +.search-box {
101 + button {
102 + background-color: transparent;
103 + border: none;
104 + color: $primary;
105 + padding: 0;
106 + margin: 0;
107 + cursor: pointer;
108 + margin-left: $-s;
109 + }
110 + button[type="submit"] {
111 + margin-left: -$-l;
112 + }
113 + input {
114 + padding-right: $-l;
115 + width: 300px;
116 + }
100 } 117 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -57,19 +57,25 @@ header { ...@@ -57,19 +57,25 @@ header {
57 } 57 }
58 58
59 form.search-box { 59 form.search-box {
60 - padding-top: $-l *0.9; 60 + margin-top: $-l *0.9;
61 display: inline-block; 61 display: inline-block;
62 + position: relative;
62 input { 63 input {
63 background-color: transparent; 64 background-color: transparent;
64 border-radius: 0; 65 border-radius: 0;
65 border: none; 66 border: none;
66 border-bottom: 2px solid #EEE; 67 border-bottom: 2px solid #EEE;
67 color: #EEE; 68 color: #EEE;
68 - padding-left: $-l; 69 + padding-right: $-l;
69 outline: 0; 70 outline: 0;
70 } 71 }
71 - i { 72 + a {
72 - margin-right: -$-l; 73 + vertical-align: top;
74 + margin-left: -$-l;
75 + color: #FFF;
76 + top: 0;
77 + display: inline-block;
78 + position: absolute;
73 } 79 }
74 } 80 }
75 81
...@@ -121,6 +127,10 @@ body.flexbox { ...@@ -121,6 +127,10 @@ body.flexbox {
121 padding: $-l $-l $-l 0; 127 padding: $-l $-l $-l 0;
122 vertical-align: top; 128 vertical-align: top;
123 line-height: 1; 129 line-height: 1;
130 + &:hover {
131 + color: #FFF;
132 + text-decoration: none;
133 + }
124 } 134 }
125 135
126 .page-title input { 136 .page-title input {
...@@ -537,4 +547,13 @@ ul.dropdown { ...@@ -537,4 +547,13 @@ ul.dropdown {
537 li.border-bottom { 547 li.border-bottom {
538 border-bottom: 1px solid #DDD; 548 border-bottom: 1px solid #DDD;
539 } 549 }
550 +}
551 +
552 +.search-results > h3 a {
553 + font-size: 0.66em;
554 + color: $primary;
555 + padding-left: $-m;
556 + i {
557 + padding-right: $-s;
558 + }
540 } 559 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -14,24 +14,12 @@ ...@@ -14,24 +14,12 @@
14 14
15 <!-- Scripts --> 15 <!-- Scripts -->
16 <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script> 16 <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
17 + <script src="/js/jquery-extensions.js"></script>
17 <script src="/bower/bootstrap/dist/js/bootstrap.js"></script> 18 <script src="/bower/bootstrap/dist/js/bootstrap.js"></script>
18 <script src="/bower/jquery-sortable/source/js/jquery-sortable.js"></script> 19 <script src="/bower/jquery-sortable/source/js/jquery-sortable.js"></script>
19 <script src="/bower/dropzone/dist/min/dropzone.min.js"></script> 20 <script src="/bower/dropzone/dist/min/dropzone.min.js"></script>
20 <script src="/bower/vue/dist/vue.min.js"></script> 21 <script src="/bower/vue/dist/vue.min.js"></script>
21 - <script> 22 + <script src="/bower/vue-resource/dist/vue-resource.min.js"></script>
22 - $.fn.smoothScrollTo = function() {
23 - if(this.length === 0) return;
24 - $('body').animate({
25 - scrollTop: this.offset().top - 60 // Adjust to change final scroll position top margin
26 - }, 800); // Adjust to change animations speed (ms)
27 - return this;
28 - };
29 - $.expr[":"].contains = $.expr.createPseudo(function(arg) {
30 - return function( elem ) {
31 - return $(elem).text().toUpperCase().indexOf(arg.toUpperCase()) >= 0;
32 - };
33 - });
34 - </script>
35 23
36 @yield('head') 24 @yield('head')
37 </head> 25 </head>
...@@ -57,8 +45,8 @@ ...@@ -57,8 +45,8 @@
57 </div> 45 </div>
58 <div class="col-md-3 text-right"> 46 <div class="col-md-3 text-right">
59 <form action="/search/all" method="GET" class="search-box"> 47 <form action="/search/all" method="GET" class="search-box">
60 - <i class="zmdi zmdi-search"></i>
61 <input type="text" name="term" tabindex="2" value="{{ isset($searchTerm) ? $searchTerm : '' }}"> 48 <input type="text" name="term" tabindex="2" value="{{ isset($searchTerm) ? $searchTerm : '' }}">
49 + <a onclick="$(this).closest('form').submit();"><i class="zmdi zmdi-search"></i></a>
62 </form> 50 </form>
63 </div> 51 </div>
64 <div class="col-md-6"> 52 <div class="col-md-6">
......
...@@ -10,8 +10,8 @@ ...@@ -10,8 +10,8 @@
10 <form action="{{$book->getUrl()}}" method="POST"> 10 <form action="{{$book->getUrl()}}" method="POST">
11 {!! csrf_field() !!} 11 {!! csrf_field() !!}
12 <input type="hidden" name="_method" value="DELETE"> 12 <input type="hidden" name="_method" value="DELETE">
13 - <button type="submit" class="button neg">Confirm</button>
14 <a href="{{$book->getUrl()}}" class="button">Cancel</a> 13 <a href="{{$book->getUrl()}}" class="button">Cancel</a>
14 + <button type="submit" class="button neg">Confirm</button>
15 </form> 15 </form>
16 </div> 16 </div>
17 17
......
...@@ -27,61 +27,77 @@ ...@@ -27,61 +27,77 @@
27 </div> 27 </div>
28 28
29 29
30 - <div class="container"> 30 + <div class="container" id="book-dashboard">
31 <div class="row"> 31 <div class="row">
32 <div class="col-md-7"> 32 <div class="col-md-7">
33 33
34 <h1>{{$book->name}}</h1> 34 <h1>{{$book->name}}</h1>
35 - <p class="text-muted">{{$book->description}}</p> 35 + <div class="book-content anim fadeIn" v-if="!searching">
36 - 36 + <p class="text-muted">{{$book->description}}</p>
37 - <div class="page-list"> 37 +
38 - <hr> 38 + <div class="page-list">
39 - @if(count($book->children()) > 0) 39 + <hr>
40 - @foreach($book->children() as $childElement) 40 + @if(count($book->children()) > 0)
41 - <div class="book-child"> 41 + @foreach($book->children() as $childElement)
42 - <h3> 42 + <div class="book-child">
43 - <a href="{{ $childElement->getUrl() }}" class="{{ $childElement->getName() }}"> 43 + <h3>
44 - <i class="zmdi {{ $childElement->isA('chapter') ? 'zmdi-collection-bookmark chapter-toggle':'zmdi-file-text'}}"></i>{{ $childElement->name }} 44 + <a href="{{ $childElement->getUrl() }}" class="{{ $childElement->getName() }}">
45 - </a> 45 + <i class="zmdi {{ $childElement->isA('chapter') ? 'zmdi-collection-bookmark chapter-toggle':'zmdi-file-text'}}"></i>{{ $childElement->name }}
46 - </h3> 46 + </a>
47 - <p class="text-muted"> 47 + </h3>
48 - {{$childElement->getExcerpt()}} 48 + <p class="text-muted">
49 - </p> 49 + {{$childElement->getExcerpt()}}
50 - 50 + </p>
51 - @if($childElement->isA('chapter') && count($childElement->pages) > 0) 51 +
52 - <div class="inset-list"> 52 + @if($childElement->isA('chapter') && count($childElement->pages) > 0)
53 - @foreach($childElement->pages as $page) 53 + <div class="inset-list">
54 - <h4><a href="{{$page->getUrl()}}"><i class="zmdi zmdi-file-text"></i>{{$page->name}}</a></h4> 54 + @foreach($childElement->pages as $page)
55 - @endforeach 55 + <h4><a href="{{$page->getUrl()}}"><i class="zmdi zmdi-file-text"></i>{{$page->name}}</a></h4>
56 - </div> 56 + @endforeach
57 - @endif 57 + </div>
58 - </div> 58 + @endif
59 + </div>
60 + <hr>
61 + @endforeach
62 + @else
63 + <p class="text-muted">No pages or chapters have been created for this book.</p>
64 + <p>
65 + <a href="{{$book->getUrl() . '/page/create'}}" class="text-page"><i class="zmdi zmdi-file-text"></i>Create a new page</a>
66 + &nbsp;&nbsp;<em class="text-muted">-or-</em>&nbsp;&nbsp;&nbsp;
67 + <a href="{{$book->getUrl() . '/chapter/create'}}" class="text-chapter"><i class="zmdi zmdi-collection-bookmark"></i>Add a chapter</a>
68 + </p>
59 <hr> 69 <hr>
60 - @endforeach 70 + @endif
61 - @else 71 + <p class="text-muted small">
62 - <p class="text-muted">No pages or chapters have been created for this book.</p> 72 + Created {{$book->created_at->diffForHumans()}} @if($book->createdBy) by {{$book->createdBy->name}} @endif
63 - <p> 73 + <br>
64 - <a href="{{$book->getUrl() . '/page/create'}}" class="text-page"><i class="zmdi zmdi-file-text"></i>Create a new page</a> 74 + Last Updated {{$book->updated_at->diffForHumans()}} @if($book->createdBy) by {{$book->updatedBy->name}} @endif
65 - &nbsp;&nbsp;<em class="text-muted">-or-</em>&nbsp;&nbsp;&nbsp;
66 - <a href="{{$book->getUrl() . '/chapter/create'}}" class="text-chapter"><i class="zmdi zmdi-collection-bookmark"></i>Add a chapter</a>
67 </p> 75 </p>
68 - <hr> 76 + </div>
69 - @endif 77 + </div>
78 + <div class="search-results" v-if="searching">
79 + <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>
80 + <div v-html="searchResults"></div>
70 </div> 81 </div>
71 -
72 - <p class="text-muted small">
73 - Created {{$book->created_at->diffForHumans()}} @if($book->createdBy) by {{$book->createdBy->name}} @endif
74 - <br>
75 - Last Updated {{$book->updated_at->diffForHumans()}} @if($book->createdBy) by {{$book->updatedBy->name}} @endif
76 - </p>
77 82
78 83
79 </div> 84 </div>
80 85
81 <div class="col-md-4 col-md-offset-1"> 86 <div class="col-md-4 col-md-offset-1">
82 - <div class="margin-top large"><br></div> 87 + <div class="margin-top large"></div>
83 - <h3>Recent Activity</h3> 88 + {{--<h3>Search This Book</h3>--}}
84 - @include('partials/activity-list', ['activity' => Activity::entityActivity($book, 20, 0)]) 89 + <div class="search-box">
90 + <form v-on="submit: searchBook, input: checkSearchForm" v-el="form" action="/search/book/{{ $book->id }}">
91 + {!! csrf_field() !!}
92 + <input v-model="searchTerm" type="text" name="term" placeholder="Search This Book">
93 + <button type="submit"><i class="zmdi zmdi-search"></i></button>
94 + <button v-if="searching" v-on="click: clearSearch" type="button primary"><i class="zmdi zmdi-close"></i></button>
95 + </form>
96 + </div>
97 + <div class="activity anim fadeIn">
98 + <h3>Recent Activity</h3>
99 + @include('partials/activity-list', ['activity' => Activity::entityActivity($book, 20, 0)])
100 + </div>
85 </div> 101 </div>
86 </div> 102 </div>
87 </div> 103 </div>
...@@ -99,4 +115,6 @@ ...@@ -99,4 +115,6 @@
99 }); 115 });
100 </script> 116 </script>
101 117
118 + <script src="/js/book-sidebar.js"></script>
119 +
102 @stop 120 @stop
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
11 <form action="{{$chapter->getUrl()}}" method="POST"> 11 <form action="{{$chapter->getUrl()}}" method="POST">
12 {!! csrf_field() !!} 12 {!! csrf_field() !!}
13 <input type="hidden" name="_method" value="DELETE"> 13 <input type="hidden" name="_method" value="DELETE">
14 - <a href="{{$chapter->getUrl()}}" class="button muted">Cancel</a> 14 + <a href="{{$chapter->getUrl()}}" class="button primary">Cancel</a>
15 <button type="submit" class="button neg">Confirm</button> 15 <button type="submit" class="button neg">Confirm</button>
16 </form> 16 </form>
17 </div> 17 </div>
......
...@@ -4,12 +4,12 @@ ...@@ -4,12 +4,12 @@
4 4
5 <div class="page-content"> 5 <div class="page-content">
6 <h1>Delete Page</h1> 6 <h1>Delete Page</h1>
7 - <p>Are you sure you want to delete this page?</p> 7 + <p class="text-neg">Are you sure you want to delete this page?</p>
8 8
9 <form action="{{$page->getUrl()}}" method="POST"> 9 <form action="{{$page->getUrl()}}" method="POST">
10 {!! csrf_field() !!} 10 {!! csrf_field() !!}
11 <input type="hidden" name="_method" value="DELETE"> 11 <input type="hidden" name="_method" value="DELETE">
12 - <a href="{{$page->getUrl()}}" class="button muted">Cancel</a> 12 + <a href="{{$page->getUrl()}}" class="button primary">Cancel</a>
13 <button type="submit" class="button neg">Confirm</button> 13 <button type="submit" class="button neg">Confirm</button>
14 </form> 14 </form>
15 </div> 15 </div>
......
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
44 </div> 44 </div>
45 </div> 45 </div>
46 <div class="col-md-9"> 46 <div class="col-md-9">
47 - <div class="page-content"> 47 + <div class="page-content anim fadeIn">
48 @include('pages/page-display') 48 @include('pages/page-display')
49 <hr> 49 <hr>
50 <p class="text-muted small"> 50 <p class="text-muted small">
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
2 2
3 @section('content') 3 @section('content')
4 4
5 - <div class="container"> 5 + <div class="container anim fadeIn">
6 6
7 <h1>Search Results&nbsp;&nbsp;&nbsp; <span class="text-muted">{{$searchTerm}}</span></h1> 7 <h1>Search Results&nbsp;&nbsp;&nbsp; <span class="text-muted">{{$searchTerm}}</span></h1>
8 8
......
1 +
2 +<div class="page-list">
3 + @if(count($pages) > 0)
4 + @foreach($pages as $page)
5 + <div class="book-child anim searchResult">
6 + <h3>
7 + <a href="{{$page->getUrl() . '#' . $searchTerm}}" class="page">
8 + <i class="zmdi zmdi-file-text"></i>{{$page->name}}
9 + </a>
10 + </h3>
11 +
12 + <p class="text-muted">
13 + {!! $page->searchSnippet !!}
14 + </p>
15 + <hr>
16 + </div>
17 + @endforeach
18 + @else
19 + <p class="text-muted">No pages matched this search</p>
20 + @endif
21 +</div>
22 +
23 +@if(count($chapters) > 0)
24 + <div class="page-list">
25 + @foreach($chapters as $chapter)
26 + <div class="book-child anim searchResult">
27 + <h3>
28 + <a href="{{$chapter->getUrl()}}" class="text-chapter">
29 + <i class="zmdi zmdi-collection-bookmark"></i>{{$chapter->name}}
30 + </a>
31 + </h3>
32 +
33 + <p class="text-muted">
34 + {!! $chapter->searchSnippet !!}
35 + </p>
36 + <hr>
37 + </div>
38 + @endforeach
39 + </div>
40 +@endif
41 +