0
0
Fork 0
mirror of https://github.com/BookStackApp/BookStack.git synced 2025-05-02 15:30:06 +00:00

Improved tag suggestion handling

- Aligned prefix-type filtering with back-end.
- Increased suggestion search cut-off from 3 to 4.
- Increased amount of suggestions shown.
- Ordered suggestions to be name asc, as you'd expect on search.
- Updated front-end filtering to use full search query, instead of
  truncated version, for further front-end filtering capability.

Related to 
This commit is contained in:
Dan Brown 2022-09-28 13:50:40 +01:00
parent 1ac1cf0c78
commit 8f3430d386
No known key found for this signature in database
GPG key ID: 46D9F943C24A2EF9
3 changed files with 17 additions and 19 deletions
app
Actions
Http/Controllers
resources/js/components

View file

@ -57,21 +57,21 @@ class TagRepo
* Get tag name suggestions from scanning existing tag names. * Get tag name suggestions from scanning existing tag names.
* If no search term is given the 50 most popular tag names are provided. * If no search term is given the 50 most popular tag names are provided.
*/ */
public function getNameSuggestions(?string $searchTerm): Collection public function getNameSuggestions(string $searchTerm): Collection
{ {
$query = Tag::query() $query = Tag::query()
->select('*', DB::raw('count(*) as count')) ->select('*', DB::raw('count(*) as count'))
->groupBy('name'); ->groupBy('name');
if ($searchTerm) { if ($searchTerm) {
$query = $query->where('name', 'LIKE', $searchTerm . '%')->orderBy('name', 'desc'); $query = $query->where('name', 'LIKE', $searchTerm . '%')->orderBy('name', 'asc');
} else { } else {
$query = $query->orderBy('count', 'desc')->take(50); $query = $query->orderBy('count', 'desc')->take(50);
} }
$query = $this->permissions->restrictEntityRelationQuery($query, 'tags', 'entity_id', 'entity_type'); $query = $this->permissions->restrictEntityRelationQuery($query, 'tags', 'entity_id', 'entity_type');
return $query->get(['name'])->pluck('name'); return $query->pluck('name');
} }
/** /**
@ -79,7 +79,7 @@ class TagRepo
* If no search is given the 50 most popular values are provided. * If no search is given the 50 most popular values are provided.
* Passing a tagName will only find values for a tags with a particular name. * Passing a tagName will only find values for a tags with a particular name.
*/ */
public function getValueSuggestions(?string $searchTerm, ?string $tagName): Collection public function getValueSuggestions(string $searchTerm, string $tagName): Collection
{ {
$query = Tag::query() $query = Tag::query()
->select('*', DB::raw('count(*) as count')) ->select('*', DB::raw('count(*) as count'))
@ -97,7 +97,7 @@ class TagRepo
$query = $this->permissions->restrictEntityRelationQuery($query, 'tags', 'entity_id', 'entity_type'); $query = $this->permissions->restrictEntityRelationQuery($query, 'tags', 'entity_id', 'entity_type');
return $query->get(['value'])->pluck('value'); return $query->pluck('value');
} }
/** /**

View file

@ -7,11 +7,8 @@ use Illuminate\Http\Request;
class TagController extends Controller class TagController extends Controller
{ {
protected $tagRepo; protected TagRepo $tagRepo;
/**
* TagController constructor.
*/
public function __construct(TagRepo $tagRepo) public function __construct(TagRepo $tagRepo)
{ {
$this->tagRepo = $tagRepo; $this->tagRepo = $tagRepo;
@ -46,7 +43,7 @@ class TagController extends Controller
*/ */
public function getNameSuggestions(Request $request) public function getNameSuggestions(Request $request)
{ {
$searchTerm = $request->get('search', null); $searchTerm = $request->get('search', '');
$suggestions = $this->tagRepo->getNameSuggestions($searchTerm); $suggestions = $this->tagRepo->getNameSuggestions($searchTerm);
return response()->json($suggestions); return response()->json($suggestions);
@ -57,8 +54,8 @@ class TagController extends Controller
*/ */
public function getValueSuggestions(Request $request) public function getValueSuggestions(Request $request)
{ {
$searchTerm = $request->get('search', null); $searchTerm = $request->get('search', '');
$tagName = $request->get('name', null); $tagName = $request->get('name', '');
$suggestions = $this->tagRepo->getValueSuggestions($searchTerm, $tagName); $suggestions = $this->tagRepo->getValueSuggestions($searchTerm, $tagName);
return response()->json($suggestions); return response()->json($suggestions);

View file

@ -88,14 +88,12 @@ class AutoSuggest {
} }
const nameFilter = this.getNameFilterIfNeeded(); const nameFilter = this.getNameFilterIfNeeded();
const search = this.input.value.slice(0, 3).toLowerCase(); const search = this.input.value.toLowerCase();
const suggestions = await this.loadSuggestions(search, nameFilter); const suggestions = await this.loadSuggestions(search, nameFilter);
let toShow = suggestions.slice(0, 6);
if (search.length > 0) { const toShow = suggestions.filter(val => {
toShow = suggestions.filter(val => { return search === '' || val.toLowerCase().startsWith(search);
return val.toLowerCase().includes(search); }).slice(0, 10);
}).slice(0, 6);
}
this.displaySuggestions(toShow); this.displaySuggestions(toShow);
} }
@ -111,6 +109,9 @@ class AutoSuggest {
* @returns {Promise<Object|String|*>} * @returns {Promise<Object|String|*>}
*/ */
async loadSuggestions(search, nameFilter = null) { async loadSuggestions(search, nameFilter = null) {
// Truncate search to prevent over numerous lookups
search = search.slice(0, 4);
const params = {search, name: nameFilter}; const params = {search, name: nameFilter};
const cacheKey = `${this.url}:${JSON.stringify(params)}`; const cacheKey = `${this.url}:${JSON.stringify(params)}`;