mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 05:36:00 +00:00
Added AJAX-based search to books, Fixes #15
This commit is contained in:
parent
9a82d27548
commit
03f5f9e9b9
@ -69,16 +69,21 @@ class Entity extends Model
|
|||||||
* Perform a full-text search on this entity.
|
* Perform a full-text search on this entity.
|
||||||
* @param string[] $fieldsToSearch
|
* @param string[] $fieldsToSearch
|
||||||
* @param string[] $terms
|
* @param string[] $terms
|
||||||
|
* @param string[] array $wheres
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public static function fullTextSearch($fieldsToSearch, $terms)
|
public static function fullTextSearch($fieldsToSearch, $terms, $wheres = [])
|
||||||
{
|
{
|
||||||
$termString = '';
|
$termString = '';
|
||||||
foreach($terms as $term) {
|
foreach ($terms as $term) {
|
||||||
$termString .= $term . '* ';
|
$termString .= $term . '* ';
|
||||||
}
|
}
|
||||||
$fields = implode(',', $fieldsToSearch);
|
$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();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -38,15 +38,33 @@ class SearchController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function searchAll(Request $request)
|
public function searchAll(Request $request)
|
||||||
{
|
{
|
||||||
if(!$request->has('term')) {
|
if (!$request->has('term')) {
|
||||||
return redirect()->back();
|
return redirect()->back();
|
||||||
}
|
}
|
||||||
$searchTerm = $request->get('term');
|
$searchTerm = $request->get('term');
|
||||||
$pages = $this->pageRepo->getBySearch($searchTerm);
|
$pages = $this->pageRepo->getBySearch($searchTerm);
|
||||||
$books = $this->bookRepo->getBySearch($searchTerm);
|
$books = $this->bookRepo->getBySearch($searchTerm);
|
||||||
$chapters = $this->chapterRepo->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]);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -66,6 +66,7 @@ Route::group(['middleware' => 'auth'], function () {
|
|||||||
|
|
||||||
// Search
|
// Search
|
||||||
Route::get('/search/all', 'SearchController@searchAll');
|
Route::get('/search/all', 'SearchController@searchAll');
|
||||||
|
Route::get('/search/book/{bookId}', 'SearchController@searchBook');
|
||||||
|
|
||||||
// Other Pages
|
// Other Pages
|
||||||
Route::get('/', 'HomeController@index');
|
Route::get('/', 'HomeController@index');
|
||||||
|
@ -67,10 +67,10 @@ class ChapterRepo
|
|||||||
return $slug;
|
return $slug;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getBySearch($term)
|
public function getBySearch($term, $whereTerms = [])
|
||||||
{
|
{
|
||||||
$terms = explode(' ', preg_quote(trim($term)));
|
$terms = explode(' ', preg_quote(trim($term)));
|
||||||
$chapters = $this->chapter->fullTextSearch(['name', 'description'], $terms);
|
$chapters = $this->chapter->fullTextSearch(['name', 'description'], $terms, $whereTerms);
|
||||||
$words = join('|', $terms);
|
$words = join('|', $terms);
|
||||||
foreach ($chapters as $chapter) {
|
foreach ($chapters as $chapter) {
|
||||||
//highlight
|
//highlight
|
||||||
|
@ -59,10 +59,10 @@ class PageRepo
|
|||||||
$page->delete();
|
$page->delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getBySearch($term)
|
public function getBySearch($term, $whereTerms = [])
|
||||||
{
|
{
|
||||||
$terms = explode(' ', preg_quote(trim($term)));
|
$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.
|
// Add highlights to page text.
|
||||||
$words = join('|', $terms);
|
$words = join('|', $terms);
|
||||||
|
@ -14,4 +14,6 @@ var elixir = require('laravel-elixir');
|
|||||||
elixir(function(mix) {
|
elixir(function(mix) {
|
||||||
mix.sass('styles.scss');
|
mix.sass('styles.scss');
|
||||||
mix.scripts('image-manager.js', 'public/js/image-manager.js');
|
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');
|
||||||
});
|
});
|
||||||
|
31
resources/assets/js/book-sidebar.js
Normal file
31
resources/assets/js/book-sidebar.js
Normal file
@ -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 = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
@ -1,33 +1,6 @@
|
|||||||
|
|
||||||
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) {
|
window.ImageManager = new Vue({
|
||||||
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',
|
el: '#image-manager',
|
||||||
|
|
||||||
@ -39,38 +12,38 @@ jQuery.fn.showFailure = function(messageMap) {
|
|||||||
selectedImage: false
|
selectedImage: false
|
||||||
},
|
},
|
||||||
|
|
||||||
created: function() {
|
created: function () {
|
||||||
// Get initial images
|
// Get initial images
|
||||||
this.fetchData(this.page);
|
this.fetchData(this.page);
|
||||||
},
|
},
|
||||||
|
|
||||||
ready: function() {
|
ready: function () {
|
||||||
// Create dropzone
|
// Create dropzone
|
||||||
this.setupDropZone();
|
this.setupDropZone();
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
fetchData: function() {
|
fetchData: function () {
|
||||||
var _this = this;
|
var _this = this;
|
||||||
$.getJSON('/images/all/' + _this.page, function(data) {
|
this.$http.get('/images/all/' + _this.page, function (data) {
|
||||||
_this.images = _this.images.concat(data.images);
|
_this.images = _this.images.concat(data.images);
|
||||||
_this.hasMore = data.hasMore;
|
_this.hasMore = data.hasMore;
|
||||||
_this.page++;
|
_this.page++;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
setupDropZone: function() {
|
setupDropZone: function () {
|
||||||
var _this = this;
|
var _this = this;
|
||||||
var dropZone = new Dropzone(_this.$$.dropZone, {
|
var dropZone = new Dropzone(_this.$$.dropZone, {
|
||||||
url: '/upload/image',
|
url: '/upload/image',
|
||||||
init: function() {
|
init: function () {
|
||||||
var dz = this;
|
var dz = this;
|
||||||
this.on("sending", function(file, xhr, data) {
|
this.on("sending", function (file, xhr, data) {
|
||||||
data.append("_token", document.querySelector('meta[name=token]').getAttribute('content'));
|
data.append("_token", document.querySelector('meta[name=token]').getAttribute('content'));
|
||||||
});
|
});
|
||||||
this.on("success", function(file, data) {
|
this.on("success", function (file, data) {
|
||||||
_this.images.unshift(data);
|
_this.images.unshift(data);
|
||||||
$(file.previewElement).fadeOut(400, function() {
|
$(file.previewElement).fadeOut(400, function () {
|
||||||
dz.removeFile(file);
|
dz.removeFile(file);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -78,66 +51,66 @@ jQuery.fn.showFailure = function(messageMap) {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
imageClick: function(image) {
|
imageClick: function (image) {
|
||||||
var dblClickTime = 380;
|
var dblClickTime = 380;
|
||||||
var cTime = (new Date()).getTime();
|
var cTime = (new Date()).getTime();
|
||||||
var timeDiff = cTime - this.cClickTime;
|
var timeDiff = cTime - this.cClickTime;
|
||||||
if(this.cClickTime !== 0 && timeDiff < dblClickTime && this.selectedImage === image) {
|
if (this.cClickTime !== 0 && timeDiff < dblClickTime && this.selectedImage === image) {
|
||||||
// DoubleClick
|
// DoubleClick
|
||||||
if(this.callback) {
|
if (this.callback) {
|
||||||
this.callback(image);
|
this.callback(image);
|
||||||
}
|
}
|
||||||
this.hide();
|
this.hide();
|
||||||
} else {
|
} else {
|
||||||
this.selectedImage = (this.selectedImage===image) ? false : image;
|
this.selectedImage = (this.selectedImage === image) ? false : image;
|
||||||
}
|
}
|
||||||
this.cClickTime = cTime;
|
this.cClickTime = cTime;
|
||||||
},
|
},
|
||||||
|
|
||||||
selectButtonClick: function() {
|
selectButtonClick: function () {
|
||||||
if(this.callback) {
|
if (this.callback) {
|
||||||
this.callback(this.selectedImage);
|
this.callback(this.selectedImage);
|
||||||
}
|
}
|
||||||
this.hide();
|
this.hide();
|
||||||
},
|
},
|
||||||
|
|
||||||
show: function(callback) {
|
show: function (callback) {
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
this.$$.overlay.style.display = 'block';
|
this.$$.overlay.style.display = 'block';
|
||||||
},
|
},
|
||||||
|
|
||||||
overlayClick: function(e) {
|
overlayClick: function (e) {
|
||||||
if(e.target.className==='overlay') {
|
if (e.target.className === 'overlay') {
|
||||||
this.hide();
|
this.hide();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
hide: function() {
|
hide: function () {
|
||||||
this.$$.overlay.style.display = 'none';
|
this.$$.overlay.style.display = 'none';
|
||||||
},
|
},
|
||||||
|
|
||||||
saveImageDetails: function(e) {
|
saveImageDetails: function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var _this = this;
|
var _this = this;
|
||||||
var form = $(_this.$$.imageForm);
|
var form = $(_this.$$.imageForm);
|
||||||
$.ajax('/images/update/' + _this.selectedImage.id, {
|
$.ajax('/images/update/' + _this.selectedImage.id, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
data: form.serialize()
|
data: form.serialize()
|
||||||
}).done(function() {
|
}).done(function () {
|
||||||
form.showSuccess('Image name updated');
|
form.showSuccess('Image name updated');
|
||||||
}).fail(function(jqXHR) {
|
}).fail(function (jqXHR) {
|
||||||
form.showFailure(jqXHR.responseJSON);
|
form.showFailure(jqXHR.responseJSON);
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteImage: function(e) {
|
deleteImage: function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var _this = this;
|
var _this = this;
|
||||||
var form = $(_this.$$.imageDeleteForm);
|
var form = $(_this.$$.imageDeleteForm);
|
||||||
$.ajax('/images/' + _this.selectedImage.id, {
|
$.ajax('/images/' + _this.selectedImage.id, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
data: form.serialize()
|
data: form.serialize()
|
||||||
}).done(function() {
|
}).done(function () {
|
||||||
_this.images.splice(_this.images.indexOf(_this.selectedImage), 1);
|
_this.images.splice(_this.images.indexOf(_this.selectedImage), 1);
|
||||||
_this.selectedImage = false;
|
_this.selectedImage = false;
|
||||||
$(_this.$$.imageTitle).showSuccess('Image Deleted');
|
$(_this.$$.imageTitle).showSuccess('Image Deleted');
|
||||||
@ -146,9 +119,4 @@ jQuery.fn.showFailure = function(messageMap) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
window.ImageManager = ImageManager;
|
|
||||||
|
|
||||||
|
|
||||||
})();
|
|
||||||
|
43
resources/assets/js/jquery-extensions.js
vendored
Normal file
43
resources/assets/js/jquery-extensions.js
vendored
Normal file
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
@ -16,6 +16,26 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.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 {
|
.anim.notification {
|
||||||
transform: translate3d(580px, 0, 0);
|
transform: translate3d(580px, 0, 0);
|
||||||
animation-name: notification;
|
animation-name: notification;
|
||||||
|
@ -90,11 +90,28 @@ input[type="text"], input[type="number"], input[type="email"], input[type="searc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.description-input textarea {
|
.description-input textarea {
|
||||||
@extend .inline-input-style;
|
@extend .inline-input-style;
|
||||||
font-size: $fs-m;
|
font-size: $fs-m;
|
||||||
color: #666;
|
color: #666;
|
||||||
width: 100%;
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -57,19 +57,25 @@ header {
|
|||||||
}
|
}
|
||||||
|
|
||||||
form.search-box {
|
form.search-box {
|
||||||
padding-top: $-l *0.9;
|
margin-top: $-l *0.9;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
input {
|
input {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
border: none;
|
border: none;
|
||||||
border-bottom: 2px solid #EEE;
|
border-bottom: 2px solid #EEE;
|
||||||
color: #EEE;
|
color: #EEE;
|
||||||
padding-left: $-l;
|
padding-right: $-l;
|
||||||
outline: 0;
|
outline: 0;
|
||||||
}
|
}
|
||||||
i {
|
a {
|
||||||
margin-right: -$-l;
|
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;
|
padding: $-l $-l $-l 0;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
|
&:hover {
|
||||||
|
color: #FFF;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-title input {
|
.page-title input {
|
||||||
@ -538,3 +548,12 @@ ul.dropdown {
|
|||||||
border-bottom: 1px solid #DDD;
|
border-bottom: 1px solid #DDD;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.search-results > h3 a {
|
||||||
|
font-size: 0.66em;
|
||||||
|
color: $primary;
|
||||||
|
padding-left: $-m;
|
||||||
|
i {
|
||||||
|
padding-right: $-s;
|
||||||
|
}
|
||||||
|
}
|
@ -14,24 +14,12 @@
|
|||||||
|
|
||||||
<!-- Scripts -->
|
<!-- Scripts -->
|
||||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
|
<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/bootstrap/dist/js/bootstrap.js"></script>
|
||||||
<script src="/bower/jquery-sortable/source/js/jquery-sortable.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/dropzone/dist/min/dropzone.min.js"></script>
|
||||||
<script src="/bower/vue/dist/vue.min.js"></script>
|
<script src="/bower/vue/dist/vue.min.js"></script>
|
||||||
<script>
|
<script src="/bower/vue-resource/dist/vue-resource.min.js"></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>
|
|
||||||
|
|
||||||
@yield('head')
|
@yield('head')
|
||||||
</head>
|
</head>
|
||||||
@ -57,8 +45,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-md-3 text-right">
|
<div class="col-md-3 text-right">
|
||||||
<form action="/search/all" method="GET" class="search-box">
|
<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 : '' }}">
|
<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>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
|
@ -10,8 +10,8 @@
|
|||||||
<form action="{{$book->getUrl()}}" method="POST">
|
<form action="{{$book->getUrl()}}" method="POST">
|
||||||
{!! csrf_field() !!}
|
{!! csrf_field() !!}
|
||||||
<input type="hidden" name="_method" value="DELETE">
|
<input type="hidden" name="_method" value="DELETE">
|
||||||
<button type="submit" class="button neg">Confirm</button>
|
|
||||||
<a href="{{$book->getUrl()}}" class="button">Cancel</a>
|
<a href="{{$book->getUrl()}}" class="button">Cancel</a>
|
||||||
|
<button type="submit" class="button neg">Confirm</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -27,11 +27,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="container">
|
<div class="container" id="book-dashboard">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-7">
|
<div class="col-md-7">
|
||||||
|
|
||||||
<h1>{{$book->name}}</h1>
|
<h1>{{$book->name}}</h1>
|
||||||
|
<div class="book-content anim fadeIn" v-if="!searching">
|
||||||
<p class="text-muted">{{$book->description}}</p>
|
<p class="text-muted">{{$book->description}}</p>
|
||||||
|
|
||||||
<div class="page-list">
|
<div class="page-list">
|
||||||
@ -67,24 +68,39 @@
|
|||||||
</p>
|
</p>
|
||||||
<hr>
|
<hr>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
|
||||||
|
|
||||||
<p class="text-muted small">
|
<p class="text-muted small">
|
||||||
Created {{$book->created_at->diffForHumans()}} @if($book->createdBy) by {{$book->createdBy->name}} @endif
|
Created {{$book->created_at->diffForHumans()}} @if($book->createdBy) by {{$book->createdBy->name}} @endif
|
||||||
<br>
|
<br>
|
||||||
Last Updated {{$book->updated_at->diffForHumans()}} @if($book->createdBy) by {{$book->updatedBy->name}} @endif
|
Last Updated {{$book->updated_at->diffForHumans()}} @if($book->createdBy) by {{$book->updatedBy->name}} @endif
|
||||||
</p>
|
</p>
|
||||||
|
</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>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-4 col-md-offset-1">
|
<div class="col-md-4 col-md-offset-1">
|
||||||
<div class="margin-top large"><br></div>
|
<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>
|
<h3>Recent Activity</h3>
|
||||||
@include('partials/activity-list', ['activity' => Activity::entityActivity($book, 20, 0)])
|
@include('partials/activity-list', ['activity' => Activity::entityActivity($book, 20, 0)])
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -99,4 +115,6 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<script src="/js/book-sidebar.js"></script>
|
||||||
|
|
||||||
@stop
|
@stop
|
@ -11,7 +11,7 @@
|
|||||||
<form action="{{$chapter->getUrl()}}" method="POST">
|
<form action="{{$chapter->getUrl()}}" method="POST">
|
||||||
{!! csrf_field() !!}
|
{!! csrf_field() !!}
|
||||||
<input type="hidden" name="_method" value="DELETE">
|
<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>
|
<button type="submit" class="button neg">Confirm</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,12 +4,12 @@
|
|||||||
|
|
||||||
<div class="page-content">
|
<div class="page-content">
|
||||||
<h1>Delete Page</h1>
|
<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">
|
<form action="{{$page->getUrl()}}" method="POST">
|
||||||
{!! csrf_field() !!}
|
{!! csrf_field() !!}
|
||||||
<input type="hidden" name="_method" value="DELETE">
|
<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>
|
<button type="submit" class="button neg">Confirm</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -44,7 +44,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
<div class="page-content">
|
<div class="page-content anim fadeIn">
|
||||||
@include('pages/page-display')
|
@include('pages/page-display')
|
||||||
<hr>
|
<hr>
|
||||||
<p class="text-muted small">
|
<p class="text-muted small">
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
|
|
||||||
<div class="container">
|
<div class="container anim fadeIn">
|
||||||
|
|
||||||
<h1>Search Results <span class="text-muted">{{$searchTerm}}</span></h1>
|
<h1>Search Results <span class="text-muted">{{$searchTerm}}</span></h1>
|
||||||
|
|
||||||
|
41
resources/views/search/book.blade.php
Normal file
41
resources/views/search/book.blade.php
Normal file
@ -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
|
||||||
|
|
Loading…
Reference in New Issue
Block a user