Showing
20 changed files
with
389 additions
and
218 deletions
| ... | @@ -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 | }); | ... | ... |
resources/assets/js/book-sidebar.js
0 → 100644
| 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 | +}); | ... | ... |
resources/assets/js/jquery-extensions.js
0 → 100644
| 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 | + <em class="text-muted">-or-</em> | ||
| 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 | - <em class="text-muted">-or-</em> | ||
| 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 <span class="text-muted">{{$searchTerm}}</span></h1> | 7 | <h1>Search Results <span class="text-muted">{{$searchTerm}}</span></h1> |
| 8 | 8 | ... | ... |
resources/views/search/book.blade.php
0 → 100644
| 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 | + |
-
Please register or sign in to post a comment