diff --git a/app/Entities/SearchOptions.php b/app/Entities/SearchOptions.php
new file mode 100644
index 000000000..af9156953
--- /dev/null
+++ b/app/Entities/SearchOptions.php
@@ -0,0 +1,137 @@
+ $value) {
+ $instance->$type = $value;
+ }
+ return $instance;
+ }
+
+ /**
+ * Create a new instance from a request.
+ * Will look for a classic string term and use that
+ * Otherwise we'll use the details from an advanced search form.
+ */
+ public static function fromRequest(Request $request): SearchOptions
+ {
+ if ($request->has('term')) {
+ return static::fromString($request->get('term'));
+ }
+
+ $instance = new static();
+ $inputs = $request->only(['search', 'types', 'filters', 'exact', 'tags']);
+ $instance->searches = explode(' ', $inputs['search'] ?? []);
+ $instance->exacts = array_filter($inputs['exact'] ?? []);
+ $instance->tags = array_filter($inputs['tags'] ?? []);
+ foreach (($inputs['filters'] ?? []) as $filterKey => $filterVal) {
+ if (empty($filterVal)) {
+ continue;
+ }
+ $instance->filters[$filterKey] = $filterVal === 'true' ? '' : $filterVal;
+ }
+ if (isset($inputs['types']) && count($inputs['types']) < 4) {
+ $instance->filters['type'] = implode('|', $inputs['types']);
+ }
+ return $instance;
+ }
+
+ /**
+ * Decode a search string into an array of terms.
+ */
+ protected static function decode(string $searchString): array
+ {
+ $terms = [
+ 'searches' => [],
+ 'exacts' => [],
+ 'tags' => [],
+ 'filters' => []
+ ];
+
+ $patterns = [
+ 'exacts' => '/"(.*?)"/',
+ 'tags' => '/\[(.*?)\]/',
+ 'filters' => '/\{(.*?)\}/'
+ ];
+
+ // Parse special terms
+ foreach ($patterns as $termType => $pattern) {
+ $matches = [];
+ preg_match_all($pattern, $searchString, $matches);
+ if (count($matches) > 0) {
+ $terms[$termType] = $matches[1];
+ $searchString = preg_replace($pattern, '', $searchString);
+ }
+ }
+
+ // Parse standard terms
+ foreach (explode(' ', trim($searchString)) as $searchTerm) {
+ if ($searchTerm !== '') {
+ $terms['searches'][] = $searchTerm;
+ }
+ }
+
+ // Split filter values out
+ $splitFilters = [];
+ foreach ($terms['filters'] as $filter) {
+ $explodedFilter = explode(':', $filter, 2);
+ $splitFilters[$explodedFilter[0]] = (count($explodedFilter) > 1) ? $explodedFilter[1] : '';
+ }
+ $terms['filters'] = $splitFilters;
+
+ return $terms;
+ }
+
+ /**
+ * Encode this instance to a search string.
+ */
+ public function toString(): string
+ {
+ $string = implode(' ', $this->searches ?? []);
+
+ foreach ($this->exacts as $term) {
+ $string .= ' "' . $term . '"';
+ }
+
+ foreach ($this->tags as $term) {
+ $string .= " [{$term}]";
+ }
+
+ foreach ($this->filters as $filterName => $filterVal) {
+ $string .= ' {' . $filterName . ($filterVal ? ':' . $filterVal : '') . '}';
+ }
+
+ return $string;
+ }
+
+}
\ No newline at end of file
diff --git a/app/Entities/SearchService.php b/app/Entities/SearchService.php
index ee9b87786..11b731cd0 100644
--- a/app/Entities/SearchService.php
+++ b/app/Entities/SearchService.php
@@ -39,10 +39,6 @@ class SearchService
/**
* SearchService constructor.
- * @param SearchTerm $searchTerm
- * @param EntityProvider $entityProvider
- * @param Connection $db
- * @param PermissionService $permissionService
*/
public function __construct(SearchTerm $searchTerm, EntityProvider $entityProvider, Connection $db, PermissionService $permissionService)
{
@@ -54,7 +50,6 @@ class SearchService
/**
* Set the database connection
- * @param Connection $connection
*/
public function setConnection(Connection $connection)
{
@@ -63,23 +58,18 @@ class SearchService
/**
* Search all entities in the system.
- * @param string $searchString
- * @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];
+ * The provided count is for each entity to search,
+ * Total returned could can be larger and not guaranteed.
*/
- public function searchEntities($searchString, $entityType = 'all', $page = 1, $count = 20, $action = 'view')
+ public function searchEntities(SearchOptions $searchOpts, string $entityType = 'all', int $page = 1, int $count = 20, string $action = 'view'): array
{
- $terms = $this->parseSearchString($searchString);
$entityTypes = array_keys($this->entityProvider->all());
$entityTypesToSearch = $entityTypes;
if ($entityType !== 'all') {
$entityTypesToSearch = $entityType;
- } else if (isset($terms['filters']['type'])) {
- $entityTypesToSearch = explode('|', $terms['filters']['type']);
+ } else if (isset($searchOpts->filters['type'])) {
+ $entityTypesToSearch = explode('|', $searchOpts->filters['type']);
}
$results = collect();
@@ -90,8 +80,8 @@ class SearchService
if (!in_array($entityType, $entityTypes)) {
continue;
}
- $search = $this->searchEntityTable($terms, $entityType, $page, $count, $action);
- $entityTotal = $this->searchEntityTable($terms, $entityType, $page, $count, $action, true);
+ $search = $this->searchEntityTable($searchOpts, $entityType, $page, $count, $action);
+ $entityTotal = $this->searchEntityTable($searchOpts, $entityType, $page, $count, $action, true);
if ($entityTotal > $page * $count) {
$hasMore = true;
}
@@ -103,29 +93,26 @@ class SearchService
'total' => $total,
'count' => count($results),
'has_more' => $hasMore,
- 'results' => $results->sortByDesc('score')->values()
+ 'results' => $results->sortByDesc('score')->values(),
];
}
/**
* Search a book for entities
- * @param integer $bookId
- * @param string $searchString
- * @return Collection
*/
- public function searchBook($bookId, $searchString)
+ public function searchBook(int $bookId, string $searchString): Collection
{
- $terms = $this->parseSearchString($searchString);
+ $opts = SearchOptions::fromString($searchString);
$entityTypes = ['page', 'chapter'];
- $entityTypesToSearch = isset($terms['filters']['type']) ? explode('|', $terms['filters']['type']) : $entityTypes;
+ $entityTypesToSearch = isset($opts->filters['type']) ? explode('|', $opts->filters['type']) : $entityTypes;
$results = collect();
foreach ($entityTypesToSearch as $entityType) {
if (!in_array($entityType, $entityTypes)) {
continue;
}
- $search = $this->buildEntitySearchQuery($terms, $entityType)->where('book_id', '=', $bookId)->take(20)->get();
+ $search = $this->buildEntitySearchQuery($opts, $entityType)->where('book_id', '=', $bookId)->take(20)->get();
$results = $results->merge($search);
}
return $results->sortByDesc('score')->take(20);
@@ -133,30 +120,23 @@ class SearchService
/**
* Search a book for entities
- * @param integer $chapterId
- * @param string $searchString
- * @return Collection
*/
- public function searchChapter($chapterId, $searchString)
+ public function searchChapter(int $chapterId, string $searchString): Collection
{
- $terms = $this->parseSearchString($searchString);
- $pages = $this->buildEntitySearchQuery($terms, 'page')->where('chapter_id', '=', $chapterId)->take(20)->get();
+ $opts = SearchOptions::fromString($searchString);
+ $pages = $this->buildEntitySearchQuery($opts, 'page')->where('chapter_id', '=', $chapterId)->take(20)->get();
return $pages->sortByDesc('score');
}
/**
* Search across a particular entity type.
- * @param array $terms
- * @param string $entityType
- * @param int $page
- * @param int $count
- * @param string $action
- * @param bool $getCount Return the total count of the search
+ * Setting getCount = true will return the total
+ * matching instead of the items themselves.
* @return \Illuminate\Database\Eloquent\Collection|int|static[]
*/
- public function searchEntityTable($terms, $entityType = 'page', $page = 1, $count = 20, $action = 'view', $getCount = false)
+ public function searchEntityTable(SearchOptions $searchOpts, string $entityType = 'page', int $page = 1, int $count = 20, string $action = 'view', bool $getCount = false)
{
- $query = $this->buildEntitySearchQuery($terms, $entityType, $action);
+ $query = $this->buildEntitySearchQuery($searchOpts, $entityType, $action);
if ($getCount) {
return $query->count();
}
@@ -167,22 +147,18 @@ class SearchService
/**
* Create a search query for an entity
- * @param array $terms
- * @param string $entityType
- * @param string $action
- * @return EloquentBuilder
*/
- protected function buildEntitySearchQuery($terms, $entityType = 'page', $action = 'view')
+ protected function buildEntitySearchQuery(SearchOptions $searchOpts, string $entityType = 'page', string $action = 'view'): EloquentBuilder
{
$entity = $this->entityProvider->get($entityType);
$entitySelect = $entity->newQuery();
// Handle normal search terms
- if (count($terms['search']) > 0) {
+ if (count($searchOpts->searches) > 0) {
$subQuery = $this->db->table('search_terms')->select('entity_id', 'entity_type', \DB::raw('SUM(score) as score'));
$subQuery->where('entity_type', '=', $entity->getMorphClass());
- $subQuery->where(function (Builder $query) use ($terms) {
- foreach ($terms['search'] as $inputTerm) {
+ $subQuery->where(function (Builder $query) use ($searchOpts) {
+ foreach ($searchOpts->searches as $inputTerm) {
$query->orWhere('term', 'like', $inputTerm .'%');
}
})->groupBy('entity_type', 'entity_id');
@@ -193,9 +169,9 @@ class SearchService
}
// Handle exact term matching
- if (count($terms['exact']) > 0) {
- $entitySelect->where(function (EloquentBuilder $query) use ($terms, $entity) {
- foreach ($terms['exact'] as $inputTerm) {
+ if (count($searchOpts->exacts) > 0) {
+ $entitySelect->where(function (EloquentBuilder $query) use ($searchOpts, $entity) {
+ foreach ($searchOpts->exacts as $inputTerm) {
$query->where(function (EloquentBuilder $query) use ($inputTerm, $entity) {
$query->where('name', 'like', '%'.$inputTerm .'%')
->orWhere($entity->textField, 'like', '%'.$inputTerm .'%');
@@ -205,12 +181,12 @@ class SearchService
}
// Handle tag searches
- foreach ($terms['tags'] as $inputTerm) {
+ foreach ($searchOpts->tags as $inputTerm) {
$this->applyTagSearch($entitySelect, $inputTerm);
}
// Handle filters
- foreach ($terms['filters'] as $filterTerm => $filterValue) {
+ foreach ($searchOpts->filters as $filterTerm => $filterValue) {
$functionName = Str::camel('filter_' . $filterTerm);
if (method_exists($this, $functionName)) {
$this->$functionName($entitySelect, $entity, $filterValue);
@@ -220,60 +196,10 @@ class SearchService
return $this->permissionService->enforceEntityRestrictions($entityType, $entitySelect, $action);
}
-
- /**
- * Parse a search string into components.
- * @param $searchString
- * @return array
- */
- protected function parseSearchString($searchString)
- {
- $terms = [
- 'search' => [],
- 'exact' => [],
- 'tags' => [],
- 'filters' => []
- ];
-
- $patterns = [
- 'exact' => '/"(.*?)"/',
- 'tags' => '/\[(.*?)\]/',
- 'filters' => '/\{(.*?)\}/'
- ];
-
- // Parse special terms
- foreach ($patterns as $termType => $pattern) {
- $matches = [];
- preg_match_all($pattern, $searchString, $matches);
- if (count($matches) > 0) {
- $terms[$termType] = $matches[1];
- $searchString = preg_replace($pattern, '', $searchString);
- }
- }
-
- // Parse standard terms
- foreach (explode(' ', trim($searchString)) as $searchTerm) {
- if ($searchTerm !== '') {
- $terms['search'][] = $searchTerm;
- }
- }
-
- // Split filter values out
- $splitFilters = [];
- foreach ($terms['filters'] as $filter) {
- $explodedFilter = explode(':', $filter, 2);
- $splitFilters[$explodedFilter[0]] = (count($explodedFilter) > 1) ? $explodedFilter[1] : '';
- }
- $terms['filters'] = $splitFilters;
-
- return $terms;
- }
-
/**
* Get the available query operators as a regex escaped list.
- * @return mixed
*/
- protected function getRegexEscapedOperators()
+ protected function getRegexEscapedOperators(): string
{
$escapedOperators = [];
foreach ($this->queryOperators as $operator) {
@@ -284,11 +210,8 @@ class SearchService
/**
* Apply a tag search term onto a entity query.
- * @param EloquentBuilder $query
- * @param string $tagTerm
- * @return mixed
*/
- protected function applyTagSearch(EloquentBuilder $query, $tagTerm)
+ protected function applyTagSearch(EloquentBuilder $query, string $tagTerm): EloquentBuilder
{
preg_match("/^(.*?)((".$this->getRegexEscapedOperators().")(.*?))?$/", $tagTerm, $tagSplit);
$query->whereHas('tags', function (EloquentBuilder $query) use ($tagSplit) {
@@ -318,7 +241,6 @@ class SearchService
/**
* Index the given entity.
- * @param Entity $entity
*/
public function indexEntity(Entity $entity)
{
diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php
index a5d57741d..8105843b5 100644
--- a/app/Http/Controllers/SearchController.php
+++ b/app/Http/Controllers/SearchController.php
@@ -6,6 +6,7 @@ use BookStack\Entities\Bookshelf;
use BookStack\Entities\Entity;
use BookStack\Entities\Managers\EntityContext;
use BookStack\Entities\SearchService;
+use BookStack\Entities\SearchOptions;
use Illuminate\Http\Request;
class SearchController extends Controller
@@ -33,20 +34,22 @@ class SearchController extends Controller
*/
public function search(Request $request)
{
- $searchTerm = $request->get('term');
- $this->setPageTitle(trans('entities.search_for_term', ['term' => $searchTerm]));
+ $searchOpts = SearchOptions::fromRequest($request);
+ $fullSearchString = $searchOpts->toString();
+ $this->setPageTitle(trans('entities.search_for_term', ['term' => $fullSearchString]));
$page = intval($request->get('page', '0')) ?: 1;
- $nextPageLink = url('/search?term=' . urlencode($searchTerm) . '&page=' . ($page+1));
+ $nextPageLink = url('/search?term=' . urlencode($fullSearchString) . '&page=' . ($page+1));
- $results = $this->searchService->searchEntities($searchTerm, 'all', $page, 20);
+ $results = $this->searchService->searchEntities($searchOpts, 'all', $page, 20);
return view('search.all', [
'entities' => $results['results'],
'totalResults' => $results['total'],
- 'searchTerm' => $searchTerm,
+ 'searchTerm' => $fullSearchString,
'hasNextPage' => $results['has_more'],
- 'nextPageLink' => $nextPageLink
+ 'nextPageLink' => $nextPageLink,
+ 'options' => $searchOpts,
]);
}
@@ -84,7 +87,7 @@ class SearchController extends Controller
// Search for entities otherwise show most popular
if ($searchTerm !== false) {
$searchTerm .= ' {type:'. implode('|', $entityTypes) .'}';
- $entities = $this->searchService->searchEntities($searchTerm, 'all', 1, 20, $permission)['results'];
+ $entities = $this->searchService->searchEntities(SearchOptions::fromString($searchTerm), 'all', 1, 20, $permission)['results'];
} else {
$entities = $this->viewService->getPopular(20, 0, $entityTypes, $permission);
}
diff --git a/resources/js/components/add-remove-rows.js b/resources/js/components/add-remove-rows.js
new file mode 100644
index 000000000..81eeb43c4
--- /dev/null
+++ b/resources/js/components/add-remove-rows.js
@@ -0,0 +1,31 @@
+import {onChildEvent} from "../services/dom";
+
+/**
+ * AddRemoveRows
+ * Allows easy row add/remove controls onto a table.
+ * Needs a model row to use when adding a new row.
+ * @extends {Component}
+ */
+class AddRemoveRows {
+ setup() {
+ this.modelRow = this.$refs.model;
+ this.addButton = this.$refs.add;
+ this.removeSelector = this.$opts.removeSelector;
+ this.setupListeners();
+ }
+
+ setupListeners() {
+ this.addButton.addEventListener('click', e => {
+ const clone = this.modelRow.cloneNode(true);
+ clone.classList.remove('hidden');
+ this.modelRow.parentNode.insertBefore(clone, this.modelRow);
+ });
+
+ onChildEvent(this.$el, this.removeSelector, 'click', (e) => {
+ const row = e.target.closest('tr');
+ row.remove();
+ });
+ }
+}
+
+export default AddRemoveRows;
\ No newline at end of file
diff --git a/resources/js/components/optional-input.js b/resources/js/components/optional-input.js
new file mode 100644
index 000000000..eab58e42a
--- /dev/null
+++ b/resources/js/components/optional-input.js
@@ -0,0 +1,28 @@
+import {onSelect} from "../services/dom";
+
+class OptionalInput {
+ setup() {
+ this.removeButton = this.$refs.remove;
+ this.showButton = this.$refs.show;
+ this.input = this.$refs.input;
+ this.setupListeners();
+ }
+
+ setupListeners() {
+ onSelect(this.removeButton, () => {
+ this.input.value = '';
+ this.input.classList.add('hidden');
+ this.removeButton.classList.add('hidden');
+ this.showButton.classList.remove('hidden');
+ });
+
+ onSelect(this.showButton, () => {
+ this.input.classList.remove('hidden');
+ this.removeButton.classList.remove('hidden');
+ this.showButton.classList.add('hidden');
+ });
+ }
+
+}
+
+export default OptionalInput;
\ No newline at end of file
diff --git a/resources/js/vues/search.js b/resources/js/vues/search.js
deleted file mode 100644
index c0b828b96..000000000
--- a/resources/js/vues/search.js
+++ /dev/null
@@ -1,193 +0,0 @@
-import * as Dates from "../services/dates";
-
-let data = {
- terms: '',
- termString : '',
- search: {
- type: {
- page: true,
- chapter: true,
- book: true,
- bookshelf: true,
- },
- exactTerms: [],
- tagTerms: [],
- option: {},
- dates: {
- updated_after: false,
- updated_before: false,
- created_after: false,
- created_before: false,
- }
- }
-};
-
-let computed = {
-
-};
-
-let methods = {
-
- appendTerm(term) {
- this.termString += ' ' + term;
- this.termString = this.termString.replace(/\s{2,}/g, ' ');
- this.termString = this.termString.replace(/^\s+/, '');
- this.termString = this.termString.replace(/\s+$/, '');
- },
-
- exactParse(searchString) {
- this.search.exactTerms = [];
- let exactFilter = /"(.+?)"/g;
- let matches;
- while ((matches = exactFilter.exec(searchString)) !== null) {
- this.search.exactTerms.push(matches[1]);
- }
- },
-
- exactChange() {
- let exactFilter = /"(.+?)"/g;
- this.termString = this.termString.replace(exactFilter, '');
- let matchesTerm = this.search.exactTerms.filter(term => term.trim() !== '').map(term => `"${term}"`).join(' ');
- this.appendTerm(matchesTerm);
- },
-
- addExact() {
- this.search.exactTerms.push('');
- setTimeout(() => {
- let exactInputs = document.querySelectorAll('.exact-input');
- exactInputs[exactInputs.length - 1].focus();
- }, 100);
- },
-
- removeExact(index) {
- this.search.exactTerms.splice(index, 1);
- this.exactChange();
- },
-
- tagParse(searchString) {
- this.search.tagTerms = [];
- let tagFilter = /\[(.+?)\]/g;
- let matches;
- while ((matches = tagFilter.exec(searchString)) !== null) {
- this.search.tagTerms.push(matches[1]);
- }
- },
-
- tagChange() {
- let tagFilter = /\[(.+?)\]/g;
- this.termString = this.termString.replace(tagFilter, '');
- let matchesTerm = this.search.tagTerms.filter(term => {
- return term.trim() !== '';
- }).map(term => {
- return `[${term}]`
- }).join(' ');
- this.appendTerm(matchesTerm);
- },
-
- addTag() {
- this.search.tagTerms.push('');
- setTimeout(() => {
- let tagInputs = document.querySelectorAll('.tag-input');
- tagInputs[tagInputs.length - 1].focus();
- }, 100);
- },
-
- removeTag(index) {
- this.search.tagTerms.splice(index, 1);
- this.tagChange();
- },
-
- typeParse(searchString) {
- let typeFilter = /{\s?type:\s?(.*?)\s?}/;
- let match = searchString.match(typeFilter);
- let type = this.search.type;
- if (!match) {
- 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.book === type.bookshelf) {
- this.termString = this.termString.replace(typeFilter, '');
- return;
- }
- 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);
- return;
- }
- this.appendTerm(typeTerm);
- },
-
- optionParse(searchString) {
- let optionFilter = /{([a-z_\-:]+?)}/gi;
- let matches;
- while ((matches = optionFilter.exec(searchString)) !== null) {
- this.search.option[matches[1].toLowerCase()] = true;
- }
- },
-
- optionChange(optionName) {
- let isChecked = this.search.option[optionName];
- if (isChecked) {
- this.appendTerm(`{${optionName}}`);
- } else {
- this.termString = this.termString.replace(`{${optionName}}`, '');
- }
- },
-
- updateSearch(e) {
- e.preventDefault();
- window.location = window.baseUrl('/search?term=' + encodeURIComponent(this.termString));
- },
-
- enableDate(optionName) {
- this.search.dates[optionName.toLowerCase()] = Dates.getCurrentDay();
- this.dateChange(optionName);
- },
-
- dateParse(searchString) {
- let dateFilter = /{([a-z_\-]+?):([a-z_\-0-9]+?)}/gi;
- let dateTags = Object.keys(this.search.dates);
- let matches;
- while ((matches = dateFilter.exec(searchString)) !== null) {
- if (dateTags.indexOf(matches[1]) === -1) continue;
- this.search.dates[matches[1].toLowerCase()] = matches[2];
- }
- },
-
- dateChange(optionName) {
- let dateFilter = new RegExp('{\\s?'+optionName+'\\s?:([a-z_\\-0-9]+?)}', 'gi');
- this.termString = this.termString.replace(dateFilter, '');
- if (!this.search.dates[optionName]) return;
- this.appendTerm(`{${optionName}:${this.search.dates[optionName]}}`);
- },
-
- dateRemove(optionName) {
- this.search.dates[optionName] = false;
- this.dateChange(optionName);
- }
-
-};
-
-function created() {
- this.termString = document.querySelector('[name=searchTerm]').value;
- this.typeParse(this.termString);
- this.exactParse(this.termString);
- this.tagParse(this.termString);
- this.optionParse(this.termString);
- this.dateParse(this.termString);
-}
-
-export default {
- data, computed, methods, created
-};
diff --git a/resources/js/vues/vues.js b/resources/js/vues/vues.js
index ec192372d..125d541ce 100644
--- a/resources/js/vues/vues.js
+++ b/resources/js/vues/vues.js
@@ -4,7 +4,6 @@ function exists(id) {
return document.getElementById(id) !== null;
}
-import searchSystem from "./search";
import entityDashboard from "./entity-dashboard";
import codeEditor from "./code-editor";
import imageManager from "./image-manager";
@@ -13,7 +12,6 @@ import attachmentManager from "./attachment-manager";
import pageEditor from "./page-editor";
let vueMapping = {
- 'search-system': searchSystem,
'entity-dashboard': entityDashboard,
'code-editor': codeEditor,
'image-manager': imageManager,
diff --git a/resources/lang/en/entities.php b/resources/lang/en/entities.php
index 6bbc723b0..bb5c0078d 100644
--- a/resources/lang/en/entities.php
+++ b/resources/lang/en/entities.php
@@ -47,7 +47,8 @@ return [
'search_no_pages' => 'No pages matched this search',
'search_for_term' => 'Search for :term',
'search_more' => 'More Results',
- 'search_filters' => 'Search Filters',
+ 'search_advanced' => 'Advanced Search',
+ 'search_terms' => 'Search Terms',
'search_content_type' => 'Content Type',
'search_exact_matches' => 'Exact Matches',
'search_tags' => 'Tag Searches',
diff --git a/resources/sass/_layout.scss b/resources/sass/_layout.scss
index 595713feb..4d044245a 100644
--- a/resources/sass/_layout.scss
+++ b/resources/sass/_layout.scss
@@ -141,7 +141,7 @@ body.flexbox {
}
.hidden {
- display: none;
+ display: none !important;
}
.float {
diff --git a/resources/views/search/all.blade.php b/resources/views/search/all.blade.php
index f19e560a2..df137bd2a 100644
--- a/resources/views/search/all.blade.php
+++ b/resources/views/search/all.blade.php
@@ -1,186 +1,62 @@
@extends('simple-layout')
@section('body')
-
-
-
-
-
-
-
+
-
{{ trans('entities.search_filters') }}
+
{{ trans('entities.search_advanced') }}
-
@@ -188,17 +64,19 @@
-
+
{{ trans('entities.search_results') }}
+
+
{{ trans_choice('entities.search_total_results_found', $totalResults, ['count' => $totalResults]) }}
@include('partials.entity-list', ['entities' => $entities, 'showPath' => true])
+
@if($hasNextPage)
{{ trans('entities.search_more') }}
diff --git a/resources/views/search/form/boolean-filter.blade.php b/resources/views/search/form/boolean-filter.blade.php
new file mode 100644
index 000000000..1dc9bf0c5
--- /dev/null
+++ b/resources/views/search/form/boolean-filter.blade.php
@@ -0,0 +1,12 @@
+{{--
+$filters - Array of search filter values
+$name - Name of filter to limit use.
+$value - Value of filter to use
+--}}
+
+
+ {{ $slot }}
+
\ No newline at end of file
diff --git a/resources/views/search/form/date-filter.blade.php b/resources/views/search/form/date-filter.blade.php
new file mode 100644
index 000000000..05ab4c134
--- /dev/null
+++ b/resources/views/search/form/date-filter.blade.php
@@ -0,0 +1,29 @@
+{{--
+@filters - Active search filters
+@name - Name of filter
+--}}
+
\ No newline at end of file
diff --git a/resources/views/search/form/term-list.blade.php b/resources/views/search/form/term-list.blade.php
new file mode 100644
index 000000000..435de73f1
--- /dev/null
+++ b/resources/views/search/form/term-list.blade.php
@@ -0,0 +1,25 @@
+{{--
+@type - Type of term (exact, tag)
+@currentList
+--}}
+
\ No newline at end of file
diff --git a/resources/views/search/form/type-filter.blade.php b/resources/views/search/form/type-filter.blade.php
new file mode 100644
index 000000000..b885ebd7a
--- /dev/null
+++ b/resources/views/search/form/type-filter.blade.php
@@ -0,0 +1,10 @@
+{{--
+@checked - If the option should be pre-checked
+@entity - Entity Name
+@transKey - Translation Key
+--}}
+
+ {{ trans('entities.' . $transKey) }}
+
\ No newline at end of file
diff --git a/tests/Entity/SearchOptionsTest.php b/tests/Entity/SearchOptionsTest.php
new file mode 100644
index 000000000..727db5533
--- /dev/null
+++ b/tests/Entity/SearchOptionsTest.php
@@ -0,0 +1,43 @@
+assertEquals(['cat'], $options->searches);
+ $this->assertEquals(['dog'], $options->exacts);
+ $this->assertEquals(['tag=good'], $options->tags);
+ $this->assertEquals(['is_tree' => ''], $options->filters);
+ }
+
+ public function test_to_string_includes_all_items_in_the_correct_format()
+ {
+ $expected = 'cat "dog" [tag=good] {is_tree}';
+ $options = new SearchOptions;
+ $options->searches = ['cat'];
+ $options->exacts = ['dog'];
+ $options->tags = ['tag=good'];
+ $options->filters = ['is_tree' => ''];
+
+ $output = $options->toString();
+ foreach (explode(' ', $expected) as $term) {
+ $this->assertStringContainsString($term, $output);
+ }
+ }
+
+ public function test_correct_filter_values_are_set_from_string()
+ {
+ $opts = SearchOptions::fromString('{is_tree} {name:dan} {cat:happy}');
+
+ $this->assertEquals([
+ 'is_tree' => '',
+ 'name' => 'dan',
+ 'cat' => 'happy',
+ ], $opts->filters);
+ }
+}