re-integrate backoffice search
This commit is contained in:
@@ -1,11 +0,0 @@
|
||||
using zero.Api.Endpoints.Search;
|
||||
|
||||
namespace zero.Api.Configuration;
|
||||
|
||||
public class ApiOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Configure search maps
|
||||
/// </summary>
|
||||
public SearchOptions Search { get; set; } = new();
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using zero.Api.Filters;
|
||||
using zero.Search;
|
||||
|
||||
namespace zero.Api.Endpoints.Search;
|
||||
|
||||
@@ -16,6 +16,10 @@ public class SearchController : ZeroApiController
|
||||
[HttpGet("")]
|
||||
public async Task<ActionResult<Paged>> Query([FromQuery] ListQuery<SearchResult> query)
|
||||
{
|
||||
if (!query.Search.HasValue())
|
||||
{
|
||||
return new Paged<SearchResult>(new List<SearchResult>(), 0, query.Page, query.PageSize);
|
||||
}
|
||||
return await Service.Query(query.Search);
|
||||
}
|
||||
}
|
||||
@@ -8,13 +8,5 @@ public class SearchModule : ZeroModule
|
||||
public override void ConfigureServices(IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddSingleton<IPermissionProvider, SearchPermissions>();
|
||||
services.AddScoped<ISearchService, SearchService>();
|
||||
|
||||
//services.Configure<RavenOptions>(opts =>
|
||||
//{
|
||||
// opts.Indexes.Add<zero_Backoffice_Search>();
|
||||
//});
|
||||
|
||||
services.AddOptions<SearchOptions>().Bind(configuration.GetSection("Zero:Search"));
|
||||
}
|
||||
}
|
||||
+1
-3
@@ -21,7 +21,6 @@ public class ZeroApiPlugin : ZeroPlugin
|
||||
|
||||
public override void ConfigureServices(IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddOptions<ApiOptions>().Bind(configuration.GetSection("Zero:Api")).Configure<IWebHostEnvironment>(ConfigureOptions);
|
||||
services.TryAddEnumerable(ServiceDescriptor.Transient<IConfigureOptions<MvcOptions>, ZeroApiMvcOptions>());
|
||||
services.AddTransient<IBackofficeApplicationResolverHandler, ApiApplicationResolverHandler>();
|
||||
services.AddTransient<ApiUnhandledExceptionMiddleware>();
|
||||
@@ -68,9 +67,8 @@ public class ZeroApiPlugin : ZeroPlugin
|
||||
}
|
||||
|
||||
|
||||
protected void ConfigureOptions(ApiOptions options, IWebHostEnvironment env)
|
||||
protected void ConfigureOptions(IWebHostEnvironment env)
|
||||
{
|
||||
options.Search.Enabled = true;
|
||||
//Map<Page>().Display((x, res, opts) =>
|
||||
//{
|
||||
// PageType pageType = opts.Pages.GetByAlias(x.PageTypeAlias);
|
||||
|
||||
@@ -59,7 +59,6 @@
|
||||
{
|
||||
await this.startup();
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -22,7 +22,8 @@ import
|
||||
integrationPlugin,
|
||||
userPlugin,
|
||||
linksPlugin,
|
||||
pageModulePlugin
|
||||
pageModulePlugin,
|
||||
searchPlugin
|
||||
} from '../modules';
|
||||
import editorPlugin from '../editor/plugin';
|
||||
import { ZeroSchema, ZeroSchemaExtension } from 'zero/schemas';
|
||||
@@ -35,7 +36,7 @@ import { Emitter, EventType } from 'mitt';
|
||||
plugins.push(
|
||||
editorPlugin, countryPlugin, applicationPlugin, settingsPlugin, languagePlugin,
|
||||
pagePlugin, linksPlugin, mediaPlugin, spacePlugin, mailTemplatePlugin,
|
||||
translationPlugin, integrationPlugin, userPlugin, pageModulePlugin
|
||||
translationPlugin, integrationPlugin, userPlugin, pageModulePlugin, searchPlugin
|
||||
);
|
||||
|
||||
|
||||
|
||||
@@ -11,4 +11,5 @@ export * from './translations';
|
||||
export * from './integrations';
|
||||
export * from './users';
|
||||
export * from './links';
|
||||
export * from './pageModules';
|
||||
export * from './pageModules';
|
||||
export * from './search';
|
||||
@@ -0,0 +1,5 @@
|
||||
import { get, post, put, del, ApiRequestConfig, ApiRequestQuery } from '../../services/request';
|
||||
|
||||
export default {
|
||||
query: (term: string, query?: ApiRequestQuery, config?: ApiRequestConfig) => get("search", { ...config, params: { ...query, search: term } })
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<ui-button icon="fth-search" :stroke="2.5" type="blank" class="app-nav-search" @click="openSearch" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as overlays from '../../services/overlay';
|
||||
|
||||
export default {
|
||||
data: () => ({
|
||||
listener: null
|
||||
}),
|
||||
|
||||
mounted()
|
||||
{
|
||||
this.listener = e =>
|
||||
{
|
||||
if (e.key === "f" && (e.ctrlKey || e.metaKey))
|
||||
{
|
||||
e.preventDefault();
|
||||
this.openSearch();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', this.listener.bind(this));
|
||||
},
|
||||
|
||||
beforeDestroy()
|
||||
{
|
||||
document.removeEventListener('keydown', this.listener);
|
||||
},
|
||||
|
||||
methods: {
|
||||
async openSearch()
|
||||
{
|
||||
const result = await overlays.open({
|
||||
component: () => import('./overlay.vue'),
|
||||
autoclose: false,
|
||||
softdismiss: true,
|
||||
width: 780
|
||||
//class: 'app-search-overlay'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,7 @@
|
||||
import api from './api';
|
||||
import plugin from './plugin';
|
||||
|
||||
export {
|
||||
api as searchApi,
|
||||
plugin as searchPlugin
|
||||
};
|
||||
@@ -0,0 +1,173 @@
|
||||
<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..." />
|
||||
<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-description" v-if="item.description">{{item.description}}</span>
|
||||
</span>
|
||||
</ui-link>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import api from './api';
|
||||
import { debounce } from '../../utils';
|
||||
|
||||
export default {
|
||||
|
||||
props: {
|
||||
model: Object,
|
||||
config: Object
|
||||
},
|
||||
|
||||
name: 'app-search',
|
||||
|
||||
data: () => ({
|
||||
open: false,
|
||||
query: null,
|
||||
list: {
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
items: []
|
||||
},
|
||||
onSearch: null
|
||||
}),
|
||||
|
||||
created()
|
||||
{
|
||||
this.onSearch = debounce(this.search, 300);
|
||||
},
|
||||
|
||||
mounted()
|
||||
{
|
||||
this.$nextTick(() =>
|
||||
{
|
||||
this.$refs.input.focus();
|
||||
//this.$refs.input.select();
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
async search()
|
||||
{
|
||||
const result = await api.query(this.query);
|
||||
|
||||
if (result.data)
|
||||
{
|
||||
this.list.items = result.data;
|
||||
this.list.page = result.paging.page;
|
||||
this.list.totalPages = result.paging.totalPages;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.app-search-overlay .app-overlay
|
||||
{
|
||||
padding: var(--padding);
|
||||
}
|
||||
|
||||
.app-search-dialog
|
||||
{
|
||||
}
|
||||
|
||||
.app-search-form
|
||||
{
|
||||
position: relative;
|
||||
}
|
||||
|
||||
input.app-search-form-input
|
||||
{
|
||||
padding-right: 48px;
|
||||
}
|
||||
|
||||
.app-search-submit
|
||||
{
|
||||
position: absolute;
|
||||
right: 0;
|
||||
height: 100%;
|
||||
width: 48px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.app-search-items
|
||||
{
|
||||
margin-top: var(--padding-s);
|
||||
font-size: var(--font-size);
|
||||
max-height: 490px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.app-search-item
|
||||
{
|
||||
display: grid;
|
||||
width: 100%;
|
||||
grid-template-columns: 26px 1fr auto;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
color: var(--color-text);
|
||||
padding: var(--padding-xs);
|
||||
border-radius: var(--radius);
|
||||
|
||||
&:hover, &:focus
|
||||
{
|
||||
background: var(--color-tree-selected);
|
||||
}
|
||||
|
||||
& + .app-search-item
|
||||
{
|
||||
margin-top: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.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;*/
|
||||
}
|
||||
|
||||
.app-search-item-description
|
||||
{
|
||||
color: var(--color-text-dim);
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.app-search-item-group
|
||||
{
|
||||
display: block;
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-text-dim);
|
||||
margin-top: 3px;
|
||||
//margin-left: 8px;
|
||||
}
|
||||
|
||||
.app-search-item-icon
|
||||
{
|
||||
position: relative;
|
||||
top: -1px;
|
||||
left: 4px;
|
||||
color: var(--color-text);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,11 @@
|
||||
import { ZeroPlugin, ZeroPluginOptions } from '../../core';
|
||||
import { defineAsyncComponent } from 'vue';
|
||||
|
||||
export default {
|
||||
name: "zero.search",
|
||||
|
||||
install(app: ZeroPluginOptions)
|
||||
{
|
||||
app.vue.component('app-search', defineAsyncComponent(() => import('./button.vue')));
|
||||
}
|
||||
} as ZeroPlugin;
|
||||
@@ -7,7 +7,7 @@
|
||||
<img src="/Assets/zero-dark.svg" class="show-dark" v-localize:alt="'@zero.name'" />
|
||||
</h1>
|
||||
|
||||
<!--<ui-button icon="fth-search" :stroke="2.5" type="blank" class="app-nav-search" @click="openSearch" />-->
|
||||
<app-search />
|
||||
</div>
|
||||
|
||||
<div v-if="currentApplication && appStore.applications.length > 0" class="app-nav-switch">
|
||||
@@ -173,11 +173,6 @@
|
||||
{
|
||||
this.compact = !this.compact;
|
||||
localStorage.setItem(compactCacheKey, this.compact.toString());
|
||||
},
|
||||
|
||||
openSearch()
|
||||
{
|
||||
//EventHub.$emit('app.search.open');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
this.rerender();
|
||||
});
|
||||
|
||||
EventHub.$on('app.search.open', () =>
|
||||
this.zero.events.on('app.search.open', () =>
|
||||
{
|
||||
this.search();
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using zero.Search;
|
||||
|
||||
namespace zero.Pages;
|
||||
|
||||
@@ -32,5 +33,18 @@ internal class ZeroPageModule : ZeroModule
|
||||
cfg.Add<PageFolder>(Constants.Pages.FolderAlias, "@page.folder.name", "@page.folder.description", "fth-folder");
|
||||
});
|
||||
});
|
||||
|
||||
services.Configure<ZeroSearchOptions>(opts =>
|
||||
{
|
||||
opts.Map<Page>().Display((x, res, opts) =>
|
||||
{
|
||||
FlavorConfig flavor = opts.For<FlavorOptions>().Get<Page>(x.Flavor);
|
||||
if (flavor != null)
|
||||
{
|
||||
res.Icon = flavor.Icon;
|
||||
}
|
||||
res.Url = "/pages/edit/" + x.Id;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -1,14 +1,14 @@
|
||||
using Raven.Client.Documents;
|
||||
|
||||
namespace zero.Api.Endpoints.Search;
|
||||
namespace zero.Search;
|
||||
|
||||
public class zero_Backoffice_Search : ZeroJavascriptIndex
|
||||
public class zero_Search : ZeroJavascriptIndex
|
||||
{
|
||||
public override void Setup(IZeroOptions options, IDocumentStore store)
|
||||
{
|
||||
// TODO index.Conventions is null, but needed for collection name retrieval
|
||||
|
||||
foreach (var map in options.For<SearchOptions>())
|
||||
foreach (var map in options.For<ZeroSearchOptions>())
|
||||
{
|
||||
Maps.Add(map.BuildInstruction(store));
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
using Raven.Client.Documents;
|
||||
|
||||
namespace zero.Api.Endpoints.Search;
|
||||
namespace zero.Search;
|
||||
|
||||
public class SearchIndexMap
|
||||
{
|
||||
@@ -66,7 +66,7 @@ public class SearchIndexMap<T> : SearchIndexMap where T : ZeroEntity
|
||||
{
|
||||
internal SearchIndexMap(string icon = null) : base(typeof(T), icon) { }
|
||||
|
||||
public SearchIndexMap<T> Icon(string icon)
|
||||
public virtual SearchIndexMap<T> Icon(string icon)
|
||||
{
|
||||
_Icon = icon;
|
||||
return this;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace zero.Api.Endpoints.Search;
|
||||
namespace zero.Search;
|
||||
|
||||
public class SearchResult
|
||||
{
|
||||
@@ -2,7 +2,7 @@
|
||||
using Raven.Client.Documents.Queries;
|
||||
using Raven.Client.Documents.Session;
|
||||
|
||||
namespace zero.Api.Endpoints.Search;
|
||||
namespace zero.Search;
|
||||
|
||||
public class SearchService : ISearchService
|
||||
{
|
||||
@@ -25,7 +25,7 @@ public class SearchService : ISearchService
|
||||
return "*" + x + "*";
|
||||
}).ToArray();
|
||||
|
||||
List<ZeroEntity> results = await Store.Session().Query<SearchIndexResult, zero_Backoffice_Search>()
|
||||
List<ZeroEntity> results = await Store.Session().Query<SearchIndexResult, zero_Search>()
|
||||
.Statistics(out QueryStatistics stats)
|
||||
.Search(x => x.Name, searchParts, 2, @operator: SearchOperator.And)
|
||||
.Search(x => x.Fields, searchParts, 1, Raven.Client.Documents.SearchOptions.Or, @operator: SearchOperator.And)
|
||||
@@ -35,7 +35,7 @@ public class SearchService : ISearchService
|
||||
|
||||
List<SearchResult> items = new();
|
||||
|
||||
IEnumerable<SearchIndexMap> maps = Options.For<SearchOptions>();
|
||||
IEnumerable<SearchIndexMap> maps = Options.For<ZeroSearchOptions>();
|
||||
|
||||
foreach (ZeroEntity result in results)
|
||||
{
|
||||
@@ -0,0 +1,19 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace zero.Search;
|
||||
|
||||
internal class ZeroSearchModule : ZeroModule
|
||||
{
|
||||
public override void ConfigureServices(IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddScoped<ISearchService, SearchService>();
|
||||
|
||||
services.Configure<RavenOptions>(opts =>
|
||||
{
|
||||
opts.Indexes.Add<zero_Search>();
|
||||
});
|
||||
|
||||
services.AddOptions<ZeroSearchOptions>().Bind(configuration.GetSection("Zero:Search"));
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace zero.Api.Endpoints.Search;
|
||||
namespace zero.Search;
|
||||
|
||||
public class SearchOptions : List<SearchIndexMap>
|
||||
public class ZeroSearchOptions : List<SearchIndexMap>
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
+2
-1
@@ -26,4 +26,5 @@ global using zero.Routing;
|
||||
global using zero.Stores;
|
||||
global using zero.Spaces;
|
||||
global using zero.Utils;
|
||||
global using zero.Validation;
|
||||
global using zero.Validation;
|
||||
global using zero.Search;
|
||||
@@ -56,6 +56,7 @@ public class ZeroBuilder
|
||||
Modules.Add<ZeroRoutingModule>();
|
||||
Modules.Add<ZeroSpaceModule>();
|
||||
Modules.Add<ZeroStoreModule>();
|
||||
Modules.Add<ZeroSearchModule>();
|
||||
|
||||
Modules.ConfigureServices(services, configuration);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user