mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 05:36:00 +00:00
Added Bookshelves to search system.
Also cleaned up and made search indexing system a little more efficient. Closes #1023
This commit is contained in:
parent
eebfd8904e
commit
7b32aa163f
@ -1,6 +1,7 @@
|
|||||||
<?php namespace BookStack\Services;
|
<?php namespace BookStack\Services;
|
||||||
|
|
||||||
use BookStack\Book;
|
use BookStack\Book;
|
||||||
|
use BookStack\Bookshelf;
|
||||||
use BookStack\Chapter;
|
use BookStack\Chapter;
|
||||||
use BookStack\Entity;
|
use BookStack\Entity;
|
||||||
use BookStack\Page;
|
use BookStack\Page;
|
||||||
@ -13,11 +14,16 @@ use Illuminate\Support\Collection;
|
|||||||
class SearchService
|
class SearchService
|
||||||
{
|
{
|
||||||
protected $searchTerm;
|
protected $searchTerm;
|
||||||
|
protected $bookshelf;
|
||||||
protected $book;
|
protected $book;
|
||||||
protected $chapter;
|
protected $chapter;
|
||||||
protected $page;
|
protected $page;
|
||||||
protected $db;
|
protected $db;
|
||||||
protected $permissionService;
|
protected $permissionService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Entity[]
|
||||||
|
*/
|
||||||
protected $entities;
|
protected $entities;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -29,20 +35,23 @@ class SearchService
|
|||||||
/**
|
/**
|
||||||
* SearchService constructor.
|
* SearchService constructor.
|
||||||
* @param SearchTerm $searchTerm
|
* @param SearchTerm $searchTerm
|
||||||
|
* @param Bookshelf $bookshelf
|
||||||
* @param Book $book
|
* @param Book $book
|
||||||
* @param Chapter $chapter
|
* @param Chapter $chapter
|
||||||
* @param Page $page
|
* @param Page $page
|
||||||
* @param Connection $db
|
* @param Connection $db
|
||||||
* @param PermissionService $permissionService
|
* @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->searchTerm = $searchTerm;
|
||||||
|
$this->bookshelf = $bookshelf;
|
||||||
$this->book = $book;
|
$this->book = $book;
|
||||||
$this->chapter = $chapter;
|
$this->chapter = $chapter;
|
||||||
$this->page = $page;
|
$this->page = $page;
|
||||||
$this->db = $db;
|
$this->db = $db;
|
||||||
$this->entities = [
|
$this->entities = [
|
||||||
|
'bookshelf' => $this->bookshelf,
|
||||||
'page' => $this->page,
|
'page' => $this->page,
|
||||||
'chapter' => $this->chapter,
|
'chapter' => $this->chapter,
|
||||||
'book' => $this->book
|
'book' => $this->book
|
||||||
@ -65,6 +74,7 @@ class SearchService
|
|||||||
* @param string $entityType
|
* @param string $entityType
|
||||||
* @param int $page
|
* @param int $page
|
||||||
* @param int $count - Count of each entity to search, Total returned could can be larger and not guaranteed.
|
* @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];
|
* @return array[int, Collection];
|
||||||
*/
|
*/
|
||||||
public function searchEntities($searchString, $entityType = 'all', $page = 1, $count = 20, $action = 'view')
|
public function searchEntities($searchString, $entityType = 'all', $page = 1, $count = 20, $action = 'view')
|
||||||
@ -370,20 +380,12 @@ class SearchService
|
|||||||
{
|
{
|
||||||
$this->searchTerm->truncate();
|
$this->searchTerm->truncate();
|
||||||
|
|
||||||
// Chunk through all books
|
foreach ($this->entities as $entityModel) {
|
||||||
$this->book->chunk(1000, function ($books) {
|
$selectFields = ['id', 'name', $entityModel->textField];
|
||||||
$this->indexEntities($books);
|
$entityModel->newQuery()->select($selectFields)->chunk(1000, function ($entities) {
|
||||||
});
|
$this->indexEntities($entities);
|
||||||
|
});
|
||||||
// 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);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -7,7 +7,8 @@ let data = {
|
|||||||
type: {
|
type: {
|
||||||
page: true,
|
page: true,
|
||||||
chapter: true,
|
chapter: true,
|
||||||
book: true
|
book: true,
|
||||||
|
bookshelf: true,
|
||||||
},
|
},
|
||||||
exactTerms: [],
|
exactTerms: [],
|
||||||
tagTerms: [],
|
tagTerms: [],
|
||||||
@ -46,11 +47,7 @@ let methods = {
|
|||||||
exactChange() {
|
exactChange() {
|
||||||
let exactFilter = /"(.+?)"/g;
|
let exactFilter = /"(.+?)"/g;
|
||||||
this.termString = this.termString.replace(exactFilter, '');
|
this.termString = this.termString.replace(exactFilter, '');
|
||||||
let matchesTerm = this.search.exactTerms.filter(term => {
|
let matchesTerm = this.search.exactTerms.filter(term => term.trim() !== '').map(term => `"${term}"`).join(' ');
|
||||||
return term.trim() !== '';
|
|
||||||
}).map(term => {
|
|
||||||
return `"${term}"`
|
|
||||||
}).join(' ');
|
|
||||||
this.appendTerm(matchesTerm);
|
this.appendTerm(matchesTerm);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -105,23 +102,24 @@ let methods = {
|
|||||||
let match = searchString.match(typeFilter);
|
let match = searchString.match(typeFilter);
|
||||||
let type = this.search.type;
|
let type = this.search.type;
|
||||||
if (!match) {
|
if (!match) {
|
||||||
type.page = type.book = type.chapter = true;
|
type.page = type.book = type.chapter = type.bookshelf = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let splitTypes = match[1].replace(/ /g, '').split('|');
|
let splitTypes = match[1].replace(/ /g, '').split('|');
|
||||||
type.page = (splitTypes.indexOf('page') !== -1);
|
type.page = (splitTypes.indexOf('page') !== -1);
|
||||||
type.chapter = (splitTypes.indexOf('chapter') !== -1);
|
type.chapter = (splitTypes.indexOf('chapter') !== -1);
|
||||||
type.book = (splitTypes.indexOf('book') !== -1);
|
type.book = (splitTypes.indexOf('book') !== -1);
|
||||||
|
type.bookshelf = (splitTypes.indexOf('bookshelf') !== -1);
|
||||||
},
|
},
|
||||||
|
|
||||||
typeChange() {
|
typeChange() {
|
||||||
let typeFilter = /{\s?type:\s?(.*?)\s?}/;
|
let typeFilter = /{\s?type:\s?(.*?)\s?}/;
|
||||||
let type = this.search.type;
|
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, '');
|
this.termString = this.termString.replace(typeFilter, '');
|
||||||
return;
|
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+'}';
|
let typeTerm = '{type:'+selectedTypes+'}';
|
||||||
if (this.termString.match(typeFilter)) {
|
if (this.termString.match(typeFilter)) {
|
||||||
this.termString = this.termString.replace(typeFilter, typeTerm);
|
this.termString = this.termString.replace(typeFilter, typeTerm);
|
||||||
|
@ -69,6 +69,7 @@ return [
|
|||||||
/**
|
/**
|
||||||
* Shelves
|
* Shelves
|
||||||
*/
|
*/
|
||||||
|
'shelf' => 'Shelf',
|
||||||
'shelves' => 'Shelves',
|
'shelves' => 'Shelves',
|
||||||
'shelves_long' => 'Bookshelves',
|
'shelves_long' => 'Bookshelves',
|
||||||
'shelves_empty' => 'No shelves have been created',
|
'shelves_empty' => 'No shelves have been created',
|
||||||
|
@ -22,7 +22,9 @@
|
|||||||
<div class="form-group">
|
<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-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>
|
<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-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>
|
</div>
|
||||||
|
|
||||||
<h6 class="text-muted">{{ trans('entities.search_exact_matches') }}</h6>
|
<h6 class="text-muted">{{ trans('entities.search_exact_matches') }}</h6>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<?php namespace Tests;
|
<?php namespace Tests;
|
||||||
|
|
||||||
|
|
||||||
|
use BookStack\Bookshelf;
|
||||||
use BookStack\Chapter;
|
use BookStack\Chapter;
|
||||||
use BookStack\Page;
|
use BookStack\Page;
|
||||||
|
|
||||||
@ -17,6 +18,14 @@ class EntitySearchTest extends TestCase
|
|||||||
$search->assertSee($page->name);
|
$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()
|
public function test_invalid_page_search()
|
||||||
{
|
{
|
||||||
$resp = $this->asEditor()->get('/search?term=' . urlencode('<p>test</p>'));
|
$resp = $this->asEditor()->get('/search?term=' . urlencode('<p>test</p>'));
|
||||||
|
Loading…
Reference in New Issue
Block a user