Added Bookshelves to search system.

Also cleaned up and made search indexing system a little more efficient.
Closes #1023
This commit is contained in:
Dan Brown 2018-09-23 12:34:30 +01:00
parent eebfd8904e
commit 7b32aa163f
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
5 changed files with 36 additions and 24 deletions

View File

@ -1,6 +1,7 @@
<?php namespace BookStack\Services;
use BookStack\Book;
use BookStack\Bookshelf;
use BookStack\Chapter;
use BookStack\Entity;
use BookStack\Page;
@ -13,11 +14,16 @@ use Illuminate\Support\Collection;
class SearchService
{
protected $searchTerm;
protected $bookshelf;
protected $book;
protected $chapter;
protected $page;
protected $db;
protected $permissionService;
/**
* @var Entity[]
*/
protected $entities;
/**
@ -29,20 +35,23 @@ class SearchService
/**
* SearchService constructor.
* @param SearchTerm $searchTerm
* @param Bookshelf $bookshelf
* @param Book $book
* @param Chapter $chapter
* @param Page $page
* @param Connection $db
* @param PermissionService $permissionService
*/
public function __construct(SearchTerm $searchTerm, Book $book, Chapter $chapter, Page $page, Connection $db, PermissionService $permissionService)
public function __construct(SearchTerm $searchTerm, Bookshelf $bookshelf, Book $book, Chapter $chapter, Page $page, Connection $db, PermissionService $permissionService)
{
$this->searchTerm = $searchTerm;
$this->bookshelf = $bookshelf;
$this->book = $book;
$this->chapter = $chapter;
$this->page = $page;
$this->db = $db;
$this->entities = [
'bookshelf' => $this->bookshelf,
'page' => $this->page,
'chapter' => $this->chapter,
'book' => $this->book
@ -65,6 +74,7 @@ class SearchService
* @param string $entityType
* @param int $page
* @param int $count - Count of each entity to search, Total returned could can be larger and not guaranteed.
* @param string $action
* @return array[int, Collection];
*/
public function searchEntities($searchString, $entityType = 'all', $page = 1, $count = 20, $action = 'view')
@ -370,20 +380,12 @@ class SearchService
{
$this->searchTerm->truncate();
// Chunk through all books
$this->book->chunk(1000, function ($books) {
$this->indexEntities($books);
});
// Chunk through all chapters
$this->chapter->chunk(1000, function ($chapters) {
$this->indexEntities($chapters);
});
// Chunk through all pages
$this->page->chunk(1000, function ($pages) {
$this->indexEntities($pages);
});
foreach ($this->entities as $entityModel) {
$selectFields = ['id', 'name', $entityModel->textField];
$entityModel->newQuery()->select($selectFields)->chunk(1000, function ($entities) {
$this->indexEntities($entities);
});
}
}
/**

View File

@ -7,7 +7,8 @@ let data = {
type: {
page: true,
chapter: true,
book: true
book: true,
bookshelf: true,
},
exactTerms: [],
tagTerms: [],
@ -46,11 +47,7 @@ let methods = {
exactChange() {
let exactFilter = /"(.+?)"/g;
this.termString = this.termString.replace(exactFilter, '');
let matchesTerm = this.search.exactTerms.filter(term => {
return term.trim() !== '';
}).map(term => {
return `"${term}"`
}).join(' ');
let matchesTerm = this.search.exactTerms.filter(term => term.trim() !== '').map(term => `"${term}"`).join(' ');
this.appendTerm(matchesTerm);
},
@ -105,23 +102,24 @@ let methods = {
let match = searchString.match(typeFilter);
let type = this.search.type;
if (!match) {
type.page = type.book = type.chapter = true;
type.page = type.book = type.chapter = type.bookshelf = true;
return;
}
let splitTypes = match[1].replace(/ /g, '').split('|');
type.page = (splitTypes.indexOf('page') !== -1);
type.chapter = (splitTypes.indexOf('chapter') !== -1);
type.book = (splitTypes.indexOf('book') !== -1);
type.bookshelf = (splitTypes.indexOf('bookshelf') !== -1);
},
typeChange() {
let typeFilter = /{\s?type:\s?(.*?)\s?}/;
let type = this.search.type;
if (type.page === type.chapter && type.page === type.book) {
if (type.page === type.chapter === type.book === type.bookshelf) {
this.termString = this.termString.replace(typeFilter, '');
return;
}
let selectedTypes = Object.keys(type).filter(type => {return this.search.type[type];}).join('|');
let selectedTypes = Object.keys(type).filter(type => this.search.type[type]).join('|');
let typeTerm = '{type:'+selectedTypes+'}';
if (this.termString.match(typeFilter)) {
this.termString = this.termString.replace(typeFilter, typeTerm);

View File

@ -69,6 +69,7 @@ return [
/**
* Shelves
*/
'shelf' => 'Shelf',
'shelves' => 'Shelves',
'shelves_long' => 'Bookshelves',
'shelves_empty' => 'No shelves have been created',

View File

@ -22,7 +22,9 @@
<div class="form-group">
<label class="inline checkbox text-page"><input type="checkbox" v-on:change="typeChange" v-model="search.type.page" value="page">{{ trans('entities.page') }}</label>
<label class="inline checkbox text-chapter"><input type="checkbox" v-on:change="typeChange" v-model="search.type.chapter" value="chapter">{{ trans('entities.chapter') }}</label>
<br>
<label class="inline checkbox text-book"><input type="checkbox" v-on:change="typeChange" v-model="search.type.book" value="book">{{ trans('entities.book') }}</label>
<label class="inline checkbox text-bookshelf"><input type="checkbox" v-on:change="typeChange" v-model="search.type.bookshelf" value="bookshelf">{{ trans('entities.shelf') }}</label>
</div>
<h6 class="text-muted">{{ trans('entities.search_exact_matches') }}</h6>

View File

@ -1,6 +1,7 @@
<?php namespace Tests;
use BookStack\Bookshelf;
use BookStack\Chapter;
use BookStack\Page;
@ -17,6 +18,14 @@ class EntitySearchTest extends TestCase
$search->assertSee($page->name);
}
public function test_bookshelf_search()
{
$shelf = Bookshelf::first();
$search = $this->asEditor()->get('/search?term=' . urlencode(mb_substr($shelf->name, 0, 3)) . ' {type:bookshelf}');
$search->assertStatus(200);
$search->assertSee($shelf->name);
}
public function test_invalid_page_search()
{
$resp = $this->asEditor()->get('/search?term=' . urlencode('<p>test</p>'));