public function search(Request $request)
{
$searchTerm = $request->get('term');
-// $paginationAppends = $request->only('term'); TODO - Check pagination
$this->setPageTitle(trans('entities.search_for_term', ['term' => $searchTerm]));
- $entities = $this->searchService->searchEntities($searchTerm);
+ $page = $request->has('page') && is_int(intval($request->get('page'))) ? intval($request->get('page')) : 1;
+ $nextPageLink = baseUrl('/search?term=' . urlencode($searchTerm) . '&page=' . ($page+1));
+
+ $results = $this->searchService->searchEntities($searchTerm, 'all', $page, 20);
+ $hasNextPage = $this->searchService->searchEntities($searchTerm, 'all', $page+1, 20)['count'] > 0;
return view('search/all', [
- 'entities' => $entities,
- 'searchTerm' => $searchTerm
+ 'entities' => $results['results'],
+ 'totalResults' => $results['total'],
+ 'searchTerm' => $searchTerm,
+ 'hasNextPage' => $hasNextPage,
+ 'nextPageLink' => $nextPageLink
]);
}
use Illuminate\Database\Connection;
use Illuminate\Database\Query\Builder;
use Illuminate\Database\Query\JoinClause;
-use Illuminate\Support\Collection;
class SearchService
{
* @param string $entityType
* @param int $page
* @param int $count
- * @return Collection
+ * @return array[int, Collection];
*/
- public function searchEntities($searchString, $entityType = 'all', $page = 0, $count = 20)
+ public function searchEntities($searchString, $entityType = 'all', $page = 1, $count = 20)
{
$terms = $this->parseSearchString($searchString);
$entityTypes = array_keys($this->entities);
$entityTypesToSearch = explode('|', $terms['filters']['type']);
}
- // TODO - Check drafts don't show up in results
+ $total = 0;
+
foreach ($entityTypesToSearch as $entityType) {
if (!in_array($entityType, $entityTypes)) continue;
$search = $this->searchEntityTable($terms, $entityType, $page, $count);
+ $total += $this->searchEntityTable($terms, $entityType, $page, $count, true);
$results = $results->merge($search);
}
- return $results->sortByDesc('score');
+ return [
+ 'total' => $total,
+ 'count' => count($results),
+ 'results' => $results->sortByDesc('score')
+ ];
}
/**
* @param string $entityType
* @param int $page
* @param int $count
- * @return \Illuminate\Database\Eloquent\Collection|static[]
+ * @param bool $getCount Return the total count of the search
+ * @return \Illuminate\Database\Eloquent\Collection|int|static[]
*/
- public function searchEntityTable($terms, $entityType = 'page', $page = 0, $count = 20)
+ public function searchEntityTable($terms, $entityType = 'page', $page = 1, $count = 20, $getCount = false)
{
$entity = $this->getEntity($entityType);
$entitySelect = $entity->newQuery();
if (method_exists($this, $functionName)) $this->$functionName($entitySelect, $entity, $filterValue);
}
- $entitySelect->skip($page * $count)->take($count);
$query = $this->permissionService->enforceEntityRestrictions($entityType, $entitySelect, 'view');
+ if ($getCount) return $query->count();
+
+ $query = $query->skip(($page-1) * $count)->take($count);
return $query->get();
}
protected function filterCreatedBy(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
{
- if (!is_numeric($input)) return;
+ if (!is_numeric($input) && $input !== 'me') return;
+ if ($input === 'me') $input = user()->id;
$query->where('created_by', '=', $input);
}
protected function filterUpdatedBy(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
{
- if (!is_numeric($input)) return;
+ if (!is_numeric($input) && $input !== 'me') return;
+ if ($input === 'me') $input = user()->id;
$query->where('updated_by', '=', $input);
}
exactTerms: [],
tagTerms: [],
option: {},
- dates: {}
+ dates: {
+ updated_after: false,
+ updated_before: false,
+ created_after: false,
+ created_before: false,
+ }
}
};
},
optionParse(searchString) {
- let optionFilter = /{([a-z_-]+?)}/gi;
+ let optionFilter = /{([a-z_\-:]+?)}/gi;
let matches;
while ((matches = optionFilter.exec(searchString)) !== null) {
this.search.option[matches[1].toLowerCase()] = true;
},
enableDate(optionName) {
- this.search.dates[optionName] = moment().format('YYYY-MM-DD');
+ this.search.dates[optionName.toLowerCase()] = moment().format('YYYY-MM-DD');
+ 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);
}
};
this.exactParse(this.termString);
this.tagParse(this.termString);
this.optionParse(this.termString);
+ this.dateParse(this.termString);
}
module.exports = {
.anim.fadeIn {
opacity: 0;
animation-name: fadeIn;
- animation-duration: 160ms;
+ animation-duration: 180ms;
animation-timing-function: ease-in-out;
animation-fill-mode: forwards;
}
label.radio, label.checkbox {
font-weight: 400;
+ user-select: none;
input[type="radio"], input[type="checkbox"] {
margin-right: $-xs;
}
}
+label.inline.checkbox {
+ margin-right: $-m;
+}
+
label + p.small {
margin-bottom: 0.8em;
}
-input[type="text"], input[type="number"], input[type="email"], input[type="search"], input[type="url"], input[type="password"], select, textarea {
+table.form-table {
+ max-width: 100%;
+ td {
+ overflow: hidden;
+ padding: $-xxs/2 0;
+ }
+}
+
+input[type="text"], input[type="number"], input[type="email"], input[type="date"], input[type="search"], input[type="url"], input[type="password"], select, textarea {
@extend .input-base;
}
+input[type=date] {
+ width: 190px;
+}
+
.toggle-switch {
display: inline-block;
background-color: #BBB;
@import "grid";
@import "blocks";
@import "buttons";
-@import "forms";
@import "tables";
+@import "forms";
@import "animations";
@import "tinymce";
@import "highlightjs";
@import "lists";
@import "pages";
-[v-cloak], [v-show] {display: none;}
+[v-cloak], [v-show] {
+ display: none; opacity: 0;
+ animation-name: none !important;
+}
+
[ng\:cloak], [ng-cloak], .ng-cloak {
display: none !important;
-
-
-
-
-
* Search
*/
'search_results' => 'Suchergebnisse',
- 'search_results_page' => 'Seiten-Suchergebnisse',
- 'search_results_chapter' => 'Kapitel-Suchergebnisse',
- 'search_results_book' => 'Buch-Suchergebnisse',
'search_clear' => 'Suche zurücksetzen',
- 'search_view_pages' => 'Zeige alle passenden Seiten',
- 'search_view_chapters' => 'Zeige alle passenden Kapitel',
- 'search_view_books' => 'Zeige alle passenden Bücher',
'search_no_pages' => 'Es wurden keine passenden Suchergebnisse gefunden',
'search_for_term' => 'Suche nach :term',
- 'search_page_for_term' => 'Suche nach :term in Seiten',
- 'search_chapter_for_term' => 'Suche nach :term in Kapiteln',
- 'search_book_for_term' => 'Suche nach :term in Büchern',
/**
* Books
'search_clear' => 'Clear Search',
'reset' => 'Reset',
'remove' => 'Remove',
+ 'add' => 'Add',
/**
* Search
*/
'search_results' => 'Search Results',
- 'search_results_page' => 'Page Search Results',
- 'search_results_chapter' => 'Chapter Search Results',
- 'search_results_book' => 'Book Search Results',
+ 'search_total_results_found' => ':count result found|:count total results found',
'search_clear' => 'Clear Search',
- 'search_view_pages' => 'View all matches pages',
- 'search_view_chapters' => 'View all matches chapters',
- 'search_view_books' => 'View all matches books',
'search_no_pages' => 'No pages matched this search',
'search_for_term' => 'Search for :term',
- 'search_page_for_term' => 'Page search for :term',
- 'search_chapter_for_term' => 'Chapter search for :term',
- 'search_book_for_term' => 'Books search for :term',
+ 'search_more' => 'More Results',
+ 'search_filters' => 'Search Filters',
+ 'search_content_type' => 'Content Type',
+ 'search_exact_matches' => 'Exact Matches',
+ 'search_tags' => 'Tag Searches',
+ 'search_viewed_by_me' => 'Viewed by me',
+ 'search_not_viewed_by_me' => 'Not viewed by me',
+ 'search_permissions_set' => 'Permissions set',
+ 'search_created_by_me' => 'Created by me',
+ 'search_updated_by_me' => 'Updated by me',
+ 'search_updated_before' => 'Updated before',
+ 'search_updated_after' => 'Updated after',
+ 'search_created_before' => 'Created before',
+ 'search_created_after' => 'Created after',
+ 'search_set_date' => 'Set Date',
+ 'search_update' => 'Update Search',
/**
* Books
* Search
*/
'search_results' => 'Buscar resultados',
- 'search_results_page' => 'resultados de búsqueda en página',
- 'search_results_chapter' => 'Resultados de búsqueda en capítulo ',
- 'search_results_book' => 'Resultados de búsqueda en libro',
'search_clear' => 'Limpiar resultados',
- 'search_view_pages' => 'Ver todas las páginas que concuerdan',
- 'search_view_chapters' => 'Ver todos los capítulos que concuerdan',
- 'search_view_books' => 'Ver todos los libros que concuerdan',
'search_no_pages' => 'Ninguna página encontrada para la búsqueda',
'search_for_term' => 'Busqueda por :term',
- 'search_page_for_term' => 'Búsqueda de página por :term',
- 'search_chapter_for_term' => 'Búsqueda por capítulo de :term',
- 'search_book_for_term' => 'Búsqueda en libro de :term',
/**
* Books
* Search
*/
'search_results' => 'Résultats de recherche',
- 'search_results_page' => 'Résultats de recherche des pages',
- 'search_results_chapter' => 'Résultats de recherche des chapitres',
- 'search_results_book' => 'Résultats de recherche des livres',
'search_clear' => 'Réinitialiser la recherche',
- 'search_view_pages' => 'Voir toutes les pages correspondantes',
- 'search_view_chapters' => 'Voir tous les chapitres correspondants',
- 'search_view_books' => 'Voir tous les livres correspondants',
'search_no_pages' => 'Aucune page correspondant à cette recherche',
'search_for_term' => 'recherche pour :term',
- 'search_page_for_term' => 'Recherche de page pour :term',
- 'search_chapter_for_term' => 'Recherche de chapitre pour :term',
- 'search_book_for_term' => 'Recherche de livres pour :term',
/**
* Books
* Search
*/
'search_results' => 'Zoekresultaten',
- 'search_results_page' => 'Pagina Zoekresultaten',
- 'search_results_chapter' => 'Hoofdstuk Zoekresultaten',
- 'search_results_book' => 'Boek Zoekresultaten',
'search_clear' => 'Zoekopdracht wissen',
- 'search_view_pages' => 'Bekijk alle gevonden pagina\'s',
- 'search_view_chapters' => 'Bekijk alle gevonden hoofdstukken',
- 'search_view_books' => 'Bekijk alle gevonden boeken',
'search_no_pages' => 'Er zijn geen pagina\'s gevonden',
'search_for_term' => 'Zoeken op :term',
- 'search_page_for_term' => 'Pagina doorzoeken op :term',
- 'search_chapter_for_term' => 'Hoofdstuk doorzoeken op :term',
- 'search_book_for_term' => 'Boeken doorzoeken op :term',
/**
* Books
* Search
*/
'search_results' => 'Resultado(s) da Pesquisa',
- 'search_results_page' => 'Resultado(s) de Pesquisa de Página',
- 'search_results_chapter' => 'Resultado(s) de Pesquisa de Capítulo',
- 'search_results_book' => 'Resultado(s) de Pesquisa de Livro',
'search_clear' => 'Limpar Pesquisa',
- 'search_view_pages' => 'Visualizar todas as páginas correspondentes',
- 'search_view_chapters' => 'Visualizar todos os capítulos correspondentes',
- 'search_view_books' => 'Visualizar todos os livros correspondentes',
'search_no_pages' => 'Nenhuma página corresponde à pesquisa',
'search_for_term' => 'Pesquisar por :term',
- 'search_page_for_term' => 'Pesquisar Página por :term',
- 'search_chapter_for_term' => 'Pesquisar Capítulo por :term',
- 'search_book_for_term' => 'Pesquisar Livros por :term',
/**
* Books
<input type="hidden" name="searchTerm" value="{{$searchTerm}}">
<div id="search-system">
+
<div class="faded-small toolbar">
<div class="container">
<div class="row">
<div class="col-sm-12 faded">
<div class="breadcrumbs">
- <a href="{{ baseUrl("/search/all?term={$searchTerm}") }}" class="text-button"><i class="zmdi zmdi-search"></i>{{ $searchTerm }}</a>
+ <a href="{{ baseUrl("/search?term=" . urlencode($searchTerm)) }}" class="text-button"><i class="zmdi zmdi-search"></i>{{ trans('entities.search_for_term', ['term' => $searchTerm]) }}</a>
</div>
</div>
</div>
</div>
</div>
-
<div class="container" ng-non-bindable id="searchSystem">
- <h1>{{ trans('entities.search_results') }}</h1>
-
- <input type="text" v-model="termString">
-
<div class="row">
<div class="col-md-6">
+ <h1>{{ trans('entities.search_results') }}</h1>
+ <h6 class="text-muted">{{ trans_choice('entities.search_total_results_found', $totalResults, ['count' => $totalResults]) }}</h6>
@include('partials/entity-list', ['entities' => $entities])
+ @if ($hasNextPage)
+ <a href="{{ $nextPageLink }}" class="button">{{ trans('entities.search_more') }}</a>
+ @endif
</div>
<div class="col-md-5 col-md-offset-1">
- <h3>Search Filters</h3>
+ <h3>{{ trans('entities.search_filters') }}</h3>
- <form v-on:submit="updateSearch" v-cloak>
- <p><strong>Content Type</strong></p>
+ <form v-on:submit="updateSearch" v-cloak class="v-cloak anim fadeIn">
+ <h6 class="text-muted">{{ trans('entities.search_content_type') }}</h6>
<div class="form-group">
- <label><input type="checkbox" v-on:change="typeChange" v-model="search.type.page" value="page"> Page</label>
- <label><input type="checkbox" v-on:change="typeChange" v-model="search.type.chapter" value="chapter"> Chapter</label>
- <label><input type="checkbox" v-on:change="typeChange" v-model="search.type.book" value="book"> Book</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-book"><input type="checkbox" v-on:change="typeChange" v-model="search.type.book" value="book">{{ trans('entities.book') }}</label>
</div>
- <p><strong>Exact Matches</strong></p>
+ <h6 class="text-muted">{{ trans('entities.search_exact_matches') }}</h6>
<table cellpadding="0" cellspacing="0" border="0" class="no-style">
<tr v-for="(term, i) in search.exactTerms">
<td style="padding: 0 12px 6px 0;">
- <input class="exact-input" v-on:input="exactChange" type="text" v-model="search.exactTerms[i]"></td>
+ <input class="exact-input outline" v-on:input="exactChange" type="text" v-model="search.exactTerms[i]"></td>
<td>
- <button type="button" class="text-button" v-on:click="removeExact(i)">
- <i class="zmdi zmdi-close-circle-o"></i>
+ <button type="button" class="text-neg text-button" v-on:click="removeExact(i)">
+ <i class="zmdi zmdi-close"></i>
</button>
</td>
</tr>
<tr>
<td colspan="2">
<button type="button" class="text-button" v-on:click="addExact">
- <i class="zmdi zmdi-plus-circle-o"></i>Add exact match term
+ <i class="zmdi zmdi-plus-circle-o"></i>{{ trans('common.add') }}
</button>
</td>
</tr>
</table>
- <p><strong>Tag Searches</strong></p>
+ <h6 class="text-muted">{{ trans('entities.search_tags') }}</h6>
<table cellpadding="0" cellspacing="0" border="0" class="no-style">
<tr v-for="(term, i) in search.tagTerms">
<td style="padding: 0 12px 6px 0;">
- <input class="tag-input" v-on:input="tagChange" type="text" v-model="search.tagTerms[i]"></td>
+ <input class="tag-input outline" v-on:input="tagChange" type="text" v-model="search.tagTerms[i]"></td>
<td>
- <button type="button" class="text-button" v-on:click="removeTag(i)">
- <i class="zmdi zmdi-close-circle-o"></i>
+ <button type="button" class="text-neg text-button" v-on:click="removeTag(i)">
+ <i class="zmdi zmdi-close"></i>
</button>
</td>
</tr>
<tr>
<td colspan="2">
<button type="button" class="text-button" v-on:click="addTag">
- <i class="zmdi zmdi-plus-circle-o"></i>Add tag search
+ <i class="zmdi zmdi-plus-circle-o"></i>{{ trans('common.add') }}
</button>
</td>
</tr>
</table>
- <p><strong>Options</strong></p>
- <label>
+ <h6 class="text-muted">Options</h6>
+ <label class="checkbox">
<input type="checkbox" v-on:change="optionChange('viewed_by_me')"
v-model="search.option.viewed_by_me" value="page">
- Viewed by me
+ {{ trans('entities.search_viewed_by_me') }}
</label>
- <label>
+ <label class="checkbox">
<input type="checkbox" v-on:change="optionChange('not_viewed_by_me')"
v-model="search.option.not_viewed_by_me" value="page">
- Not viewed by me
+ {{ trans('entities.search_not_viewed_by_me') }}
+ </label>
+ <label class="checkbox">
+ <input type="checkbox" v-on:change="optionChange('is_restricted')"
+ v-model="search.option.is_restricted" value="page">
+ {{ trans('entities.search_permissions_set') }}
+ </label>
+ <label class="checkbox">
+ <input type="checkbox" v-on:change="optionChange('created_by:me')"
+ v-model="search.option['created_by:me']" value="page">
+ {{ trans('entities.search_created_by_me') }}
+ </label>
+ <label class="checkbox">
+ <input type="checkbox" v-on:change="optionChange('updated_by:me')"
+ v-model="search.option['updated_by:me']" value="page">
+ {{ trans('entities.search_updated_by_me') }}
</label>
- <p><strong>Date Options</strong></p>
- <table cellpadding="0" cellspacing="0" border="0" class="no-style">
+ <h6 class="text-muted">Date Options</h6>
+ <table cellpadding="0" cellspacing="0" border="0" class="no-style form-table">
<tr>
- <td>Updated After</td>
- <td style="padding: 0 12px 6px 0;">
- <input v-if="search.dates.updated_after" class="tag-input" v-on:input="tagChange" type="date" v-model="search.dates.updated_after" pattern="[0-9]{4}-[0-9]{2}-[0-9]{2}">
- <button type="button" class="text-button" v-if="!search.dates.updated_after" v-on:click="enableDate('updated_at')">Set Date</button>
+ <td width="200">{{ trans('entities.search_updated_after') }}</td>
+ <td width="80">
+ <button type="button" class="text-button" v-if="!search.dates.updated_after"
+ v-on:click="enableDate('updated_after')">{{ trans('entities.search_set_date') }}</button>
+
</td>
+ </tr>
+ <tr v-if="search.dates.updated_after">
<td>
- <button v-if="search.dates.updated_after" type="button" class="text-button" v-on:click="search.dates.updated_after = false">
- <i class="zmdi zmdi-close-circle-o"></i>
+ <input v-if="search.dates.updated_after" class="tag-input"
+ v-on:input="dateChange('updated_after')" type="date" v-model="search.dates.updated_after"
+ pattern="[0-9]{4}-[0-9]{2}-[0-9]{2}">
+ </td>
+ <td>
+ <button v-if="search.dates.updated_after" type="button" class="text-neg text-button"
+ v-on:click="dateRemove('updated_after')">
+ <i class="zmdi zmdi-close"></i>
</button>
</td>
</tr>
<tr>
- <td colspan="2">
- <button type="button" class="text-button" v-on:click="addTag">
- <i class="zmdi zmdi-plus-circle-o"></i>Add tag search
+ <td>{{ trans('entities.search_updated_before') }}</td>
+ <td>
+ <button type="button" class="text-button" v-if="!search.dates.updated_before"
+ v-on:click="enableDate('updated_before')">{{ trans('entities.search_set_date') }}</button>
+
+ </td>
+ </tr>
+ <tr v-if="search.dates.updated_before">
+ <td>
+ <input v-if="search.dates.updated_before" class="tag-input"
+ v-on:input="dateChange('updated_before')" type="date" v-model="search.dates.updated_before"
+ pattern="[0-9]{4}-[0-9]{2}-[0-9]{2}">
+ </td>
+ <td>
+ <button v-if="search.dates.updated_before" type="button" class="text-neg text-button"
+ v-on:click="dateRemove('updated_before')">
+ <i class="zmdi zmdi-close"></i>
+ </button>
+ </td>
+ </tr>
+ <tr>
+ <td>{{ trans('entities.search_created_after') }}</td>
+ <td>
+ <button type="button" class="text-button" v-if="!search.dates.created_after"
+ v-on:click="enableDate('created_after')">{{ trans('entities.search_set_date') }}</button>
+
+ </td>
+ </tr>
+ <tr v-if="search.dates.created_after">
+ <td>
+ <input v-if="search.dates.created_after" class="tag-input"
+ v-on:input="dateChange('created_after')" type="date" v-model="search.dates.created_after"
+ pattern="[0-9]{4}-[0-9]{2}-[0-9]{2}">
+ </td>
+ <td>
+ <button v-if="search.dates.created_after" type="button" class="text-neg text-button"
+ v-on:click="dateRemove('created_after')">
+ <i class="zmdi zmdi-close"></i>
+ </button>
+ </td>
+ </tr>
+ <tr>
+ <td>{{ trans('entities.search_created_before') }}</td>
+ <td>
+ <button type="button" class="text-button" v-if="!search.dates.created_before"
+ v-on:click="enableDate('created_before')">{{ trans('entities.search_set_date') }}</button>
+
+ </td>
+ </tr>
+ <tr v-if="search.dates.created_before">
+ <td>
+ <input v-if="search.dates.created_before" class="tag-input"
+ v-on:input="dateChange('created_before')" type="date" v-model="search.dates.created_before"
+ pattern="[0-9]{4}-[0-9]{2}-[0-9]{2}">
+ </td>
+ <td>
+ <button v-if="search.dates.created_before" type="button" class="text-neg text-button"
+ v-on:click="dateRemove('created_before')">
+ <i class="zmdi zmdi-close"></i>
</button>
</td>
</tr>
</table>
- <button type="submit" class="button pos">Update Search</button>
+ <button type="submit" class="button primary">{{ trans('entities.search_update') }}</button>
</form>
-
</div>
</div>