search works good now :)
This commit is contained in:
@@ -20,6 +20,6 @@ public class SearchController : ZeroApiController
|
||||
{
|
||||
return new Paged<SearchResult>(new List<SearchResult>(), 0, query.Page, query.PageSize);
|
||||
}
|
||||
return await Service.Query(query.Search);
|
||||
return await Service.Query(query.Search, query.Page, query.PageSize);
|
||||
}
|
||||
}
|
||||
@@ -4,17 +4,22 @@
|
||||
|
||||
<script>
|
||||
import * as overlays from '../../services/overlay';
|
||||
import { useSearchStore } from './store';
|
||||
|
||||
export default {
|
||||
data: () => ({
|
||||
listener: null
|
||||
open: false,
|
||||
listener: null,
|
||||
store: null
|
||||
}),
|
||||
|
||||
mounted()
|
||||
{
|
||||
this.store = useSearchStore();
|
||||
|
||||
this.listener = e =>
|
||||
{
|
||||
if (e.key === "f" && (e.ctrlKey || e.metaKey))
|
||||
if (this.store.shortcutEnabled() && e.key === "f" && (e.ctrlKey || e.metaKey) && !this.open)
|
||||
{
|
||||
e.preventDefault();
|
||||
this.openSearch();
|
||||
@@ -32,13 +37,17 @@
|
||||
methods: {
|
||||
async openSearch()
|
||||
{
|
||||
this.open = true;
|
||||
|
||||
const result = await overlays.open({
|
||||
component: () => import('./overlay.vue'),
|
||||
autoclose: false,
|
||||
softdismiss: true,
|
||||
width: 780
|
||||
//class: 'app-search-overlay'
|
||||
width: 780,
|
||||
class: 'app-search-overlay'
|
||||
});
|
||||
|
||||
this.open = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,34 @@
|
||||
<template>
|
||||
<div class="app-search">
|
||||
<form class="app-search-form" @submit.prevent="onSubmit">
|
||||
<input ref="input" class="app-search-form-input" type="search" v-model="query" @input="onSearch" placeholder="Search..." />
|
||||
<input ref="input" class="app-search-form-input" type="search" v-model="query" @input="onSearch" v-localize:placeholder="'@search.input_placeholder'" />
|
||||
<ui-button class="app-search-submit" :submit="true" type="blank" icon="fth-search" />
|
||||
</form>
|
||||
<div v-if="list.items.length" class="app-search-items">
|
||||
<ui-link :to="item.url" v-for="item in list.items" :key="item.id" class="app-search-item" @click.native="config.close(true)">
|
||||
<ui-icon :symbol="item.icon" :size="18" class="app-search-item-icon" />
|
||||
<span class="app-search-item-text">
|
||||
<span class="app-search-item-name">{{item.name}} <span class="app-search-item-group" v-localize="item.group"></span></span>
|
||||
<span class="app-search-item-group" v-if="item.description">{{item.description}}</span>
|
||||
{{item.name}}<span class="-minor" v-if="item.description"> <span>–</span> {{item.description}}</span>
|
||||
</span>
|
||||
<span class="app-search-item-group" v-localize="item.group"></span>
|
||||
</ui-link>
|
||||
</div>
|
||||
</div>
|
||||
<div class="app-search-hint">
|
||||
<p v-localize:html="'@search.native_hint'"></p>
|
||||
<label class="ui-native-check is-right app-search-shortcut onbg">
|
||||
<input type="checkbox" v-model="useShortcut" @change="onShortcutUpdated" />
|
||||
<span class="ui-native-check-toggle"></span>
|
||||
<p v-localize:html="'@search.native_toggle'"></p>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import api from './api';
|
||||
import { debounce } from '../../utils';
|
||||
import { useSearchStore } from './store';
|
||||
|
||||
export default {
|
||||
|
||||
@@ -38,7 +47,10 @@
|
||||
totalPages: 1,
|
||||
items: []
|
||||
},
|
||||
onSearch: null
|
||||
onSearch: null,
|
||||
closeListener: null,
|
||||
useShortcut: true,
|
||||
store: null
|
||||
}),
|
||||
|
||||
created()
|
||||
@@ -48,17 +60,48 @@
|
||||
|
||||
mounted()
|
||||
{
|
||||
this.store = useSearchStore();
|
||||
this.useShortcut = this.store.shortcutEnabled();
|
||||
|
||||
this.$nextTick(() =>
|
||||
{
|
||||
this.$refs.input.focus();
|
||||
//this.$refs.input.select();
|
||||
});
|
||||
|
||||
this.closeListener = e =>
|
||||
{
|
||||
let isSearchCombi = this.store.shortcutEnabled() && e.key === "f" && (e.ctrlKey || e.metaKey);
|
||||
if (e.key === "Escape" || isSearchCombi)
|
||||
{
|
||||
if (!isSearchCombi)
|
||||
{
|
||||
e.preventDefault();
|
||||
}
|
||||
this.config.close(true);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', this.closeListener.bind(this));
|
||||
},
|
||||
|
||||
beforeDestroy()
|
||||
{
|
||||
document.removeEventListener('keydown', this.closeListener);
|
||||
},
|
||||
|
||||
methods: {
|
||||
async search()
|
||||
{
|
||||
const result = await api.query(this.query);
|
||||
if (!this.query)
|
||||
{
|
||||
this.list.items = [];
|
||||
this.list.page = 1;
|
||||
this.list.totalPages = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await api.query(this.query, { pageSize: 20 });
|
||||
|
||||
if (result.data)
|
||||
{
|
||||
@@ -66,6 +109,11 @@
|
||||
this.list.page = result.paging.page;
|
||||
this.list.totalPages = result.paging.totalPages;
|
||||
}
|
||||
},
|
||||
|
||||
onShortcutUpdated()
|
||||
{
|
||||
this.store.setShortcutEnabled(this.useShortcut);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,7 +122,12 @@
|
||||
<style lang="scss">
|
||||
.app-search-overlay .app-overlay
|
||||
{
|
||||
padding: var(--padding);
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.app-search
|
||||
{
|
||||
padding: var(--padding-s);
|
||||
}
|
||||
|
||||
.app-search-dialog
|
||||
@@ -112,7 +165,7 @@
|
||||
{
|
||||
display: grid;
|
||||
width: 100%;
|
||||
grid-template-columns: 26px 1fr auto;
|
||||
grid-template-columns: 20px 1fr auto;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
@@ -134,40 +187,71 @@
|
||||
.app-search-item-text
|
||||
{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
.app-search-item-name
|
||||
{
|
||||
font-size: var(--font-size);
|
||||
/*display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;*/
|
||||
}
|
||||
position: relative;
|
||||
top: 1px;
|
||||
font-size: var(--font-size);
|
||||
|
||||
.app-search-item-description
|
||||
{
|
||||
color: var(--color-text-dim);
|
||||
margin-top: 3px;
|
||||
.-minor
|
||||
{
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-text-dim);
|
||||
margin-left: 0.5em;
|
||||
|
||||
span
|
||||
{
|
||||
color: var(--color-text-dim-one);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.app-search-item-group
|
||||
{
|
||||
display: block;
|
||||
display: inline-flex;
|
||||
align-self: center;
|
||||
align-items: center;
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-text-dim);
|
||||
margin-top: 3px;
|
||||
//margin-left: 8px;
|
||||
color: var(--color-text);
|
||||
font-weight: 600;
|
||||
background: var(--color-table-highlight);
|
||||
padding: 4px 12px;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.app-search-item-icon
|
||||
{
|
||||
position: relative;
|
||||
top: -1px;
|
||||
left: 4px;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.app-search-hint
|
||||
{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 0;
|
||||
border-bottom-left-radius: var(--radius);
|
||||
border-bottom-right-radius: var(--radius);
|
||||
background: var(--color-bg-shade-2);
|
||||
border-top: 1px solid var(--color-line-dashed);
|
||||
padding: var(--padding-s) var(--padding-s);
|
||||
font-size: var(--font-size-s);
|
||||
align-items: center;
|
||||
color: var(--color-text-dim);
|
||||
|
||||
code
|
||||
{
|
||||
border: 1px dashed var(--color-line-dashed-onbg);
|
||||
}
|
||||
|
||||
p + p
|
||||
{
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.app-search-shortcut
|
||||
{
|
||||
font-size: var(--font-size-s);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,20 @@
|
||||
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
const SEARCH_BY_SHORTCUT_KEY = 'zero.search.shortcut';
|
||||
|
||||
export const useSearchStore = defineStore('zero.search', {
|
||||
state: () => ({}),
|
||||
|
||||
actions: {
|
||||
shortcutEnabled()
|
||||
{
|
||||
return localStorage.getItem(SEARCH_BY_SHORTCUT_KEY) !== 'false';
|
||||
},
|
||||
|
||||
setShortcutEnabled(enabled: boolean)
|
||||
{
|
||||
localStorage.setItem(SEARCH_BY_SHORTCUT_KEY, enabled.toString());
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -29,6 +29,7 @@ export interface OverlayOptions
|
||||
component?: any;
|
||||
theme?: OverlayTheme;
|
||||
model?: any;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
export interface OverlayInstance extends OverlayOptions
|
||||
@@ -139,7 +140,8 @@ export function open(options: OverlayOptions)
|
||||
softdismiss: options.display !== 'editor',
|
||||
theme: 'default',
|
||||
model: null,
|
||||
component: null
|
||||
component: null,
|
||||
class: options.class
|
||||
} as OverlayOptions, options) as OverlayOptions;
|
||||
|
||||
const instance = {
|
||||
|
||||
@@ -152,6 +152,11 @@ textarea
|
||||
{
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&.is-right
|
||||
{
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
}
|
||||
|
||||
.ui-native-check-toggle
|
||||
@@ -180,6 +185,8 @@ textarea
|
||||
font-family: var(--font-icon);
|
||||
color: transparent;
|
||||
font-size: var(--font-size-s);
|
||||
position: relative;
|
||||
top: -1px;
|
||||
}
|
||||
|
||||
input:focus ~ &
|
||||
@@ -199,4 +206,10 @@ textarea
|
||||
color: var(--color-checked-fg);
|
||||
}
|
||||
}
|
||||
|
||||
.ui-native-check.is-right &
|
||||
{
|
||||
margin-left: 10px;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
@@ -765,6 +765,10 @@
|
||||
"search_group": "Suche",
|
||||
"search": "Suchen"
|
||||
},
|
||||
"input_placeholder": "Gehe zu...",
|
||||
"native_hint": "Drücke <code>STRG+F</code> erneut um die native Browsersuche zu verwenden",
|
||||
"native_toggle": "Öffnen mit <code>STRG+F</code>",
|
||||
"noresults": "Keine Ergebnisse",
|
||||
"collection": {
|
||||
"page": "Seite",
|
||||
"mediafolder": "Medien"
|
||||
|
||||
@@ -811,6 +811,10 @@
|
||||
"search_group": "Search",
|
||||
"search": "Use search"
|
||||
},
|
||||
"input_placeholder": "Go to...",
|
||||
"native_hint": "Press <code>CTRL+F</code> again to use native browser search",
|
||||
"native_toggle": "Open on <code>CTRL+F</code>",
|
||||
"noresults": "No results",
|
||||
"collection": {
|
||||
"page": "Page",
|
||||
"mediaFolder": "Media folder"
|
||||
|
||||
@@ -18,7 +18,7 @@ public class SearchService : ISearchService
|
||||
}
|
||||
|
||||
|
||||
public async Task<Paged<SearchResult>> Query(string searchTerm)
|
||||
public async Task<Paged<SearchResult>> Query(string searchTerm, int page = 1, int pageSize = 10)
|
||||
{
|
||||
string[] searchParts = searchTerm.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries).Select(x =>
|
||||
{
|
||||
@@ -30,7 +30,7 @@ public class SearchService : ISearchService
|
||||
.Search(x => x.Name, searchParts, 2, @operator: SearchOperator.And)
|
||||
.Search(x => x.Fields, searchParts, 1, Raven.Client.Documents.SearchOptions.Or, @operator: SearchOperator.And)
|
||||
.OrderByScoreDescending()
|
||||
.Paging(1, 6)
|
||||
.Paging(page, pageSize)
|
||||
.As<ZeroEntity>()
|
||||
.ToListAsync();
|
||||
|
||||
@@ -70,5 +70,5 @@ public class SearchService : ISearchService
|
||||
|
||||
public interface ISearchService
|
||||
{
|
||||
Task<Paged<SearchResult>> Query(string searchTerm);
|
||||
Task<Paged<SearchResult>> Query(string searchTerm, int page = 1, int pageSize = 10);
|
||||
}
|
||||
Reference in New Issue
Block a user