提交 36027151 编写于 作者: D DCloud_LXH

wip: searchPgae

上级 9f70e132
<template>
<div id="search-container">
<div class="search-navbar">
<div class="search-navbar-wrap">
<div class="search-navbar-header navbar">
<div class="main-navbar">
<NavbarLogo />
<div class="main-navbar-links can-hide">
<div class="main-navbar-item active"></div>
</div>
</div>
<div class="sub-navbar">
<div class="search-wrap">
<div class="input-wrap">
<input
class="search-input"
:placeholder="placeholder"
type="text"
v-model="searchValue"
/>
<span class="search-input-btn">
<button @click="search">
<svg
width="16"
height="16"
viewBox="0 0 16 16"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11.33 10.007l4.273 4.273a.502.502 0 0 1 .005.709l-.585.584a.499.499 0 0 1-.709-.004L10.046 11.3a6.278 6.278 0 1 1 1.284-1.294zm.012-3.729a5.063 5.063 0 1 0-10.127 0 5.063 5.063 0 0 0 10.127 0z"
></path>
</svg>
</button>
</span>
</div>
<div class="search-category">
<div class="navbar">
<div class="main-navbar">
<div class="main-navbar-links">
<template v-for="(item, index) in category">
<div :class="mainNavLinkClass(index)" :key="item.text">
<a href="javascript:;" @click="categoryIndex = index">
{{ item.text }}
</a>
</div>
</template>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="search-result"></div>
</div>
</template>
<script>
import NavbarLogo from '../NavbarLogo.vue';
import { search as searchClient } from './searchClient';
import { forbidScroll } from '../../util';
const resolveRoutePathFromUrl = (url, base = '/') =>
url
// remove url origin
.replace(/^(https?:)?\/\/[^/]*/, '')
// remove site base
.replace(new RegExp(`^${base}`), '/');
export default {
name: 'DcloudSearchPage',
props: ['options'],
components: { NavbarLogo },
data() {
return {
placeholder: '搜索内容',
snippetLength: 10,
searchValue: '',
category: Object.freeze([
{
text: 'uni-app',
},
{
text: 'uniCloud',
},
{
text: '问答社区',
},
{
text: '插件市场',
},
]),
categoryIndex: 0,
};
},
mounted() {
this.$nextTick(forbidScroll);
const isMobileMediaQuery = window.matchMedia('(max-width: 750px)');
if (isMobileMediaQuery.matches) {
this.snippetLength = 5;
}
},
methods: {
search() {
this.searchByAlgolia(this.searchValue);
},
searchByAlgolia(query = '', page = 0) {
const { searchParameters = {} } = this.options;
return searchClient(
Object.assign({}, this.options, {
query,
page,
snippetLength: this.snippetLength,
searchParameters: {
...searchParameters,
facetFilters: [`lang:${this.$lang}`].concat(searchParameters.facetFilters || []),
},
transformItems: items =>
items.map(item => {
// the `item.url` is full url with protocol and hostname
// so we have to transform it to vue-router path
return {
...item,
url: resolveRoutePathFromUrl(item.url, this.$site.base),
};
}),
})
);
},
mainNavLinkClass(index) {
return ['main-navbar-item', this.categoryIndex === index ? 'active' : ''];
},
},
};
</script>
<style lang="stylus">
$svg-color = #b1b2b3;
$svg-hover-color = #9b9b9b;
#search-container{
position fixed
width 100vw
height 100vh
left 0
top 0
z-index 200
background-color #fff
.sub-navbar {
width: 80%;
max-width: 960px;
min-width: 720px;
margin: 0 auto;
.search-wrap {
width: 100%;
display: inline-block;
vertical-align: middle;
position: relative;
}
.input-wrap {
margin-top: 24px;
position: relative;
display: flex;
align-items: center;
.search-input-btn {
display: flex;
flex-direction: column;
justify-content: center;
padding: 0;
font-size: 0;
background-color: #fff;
button {
width: 40px;
font-family: inherit;
font-size: 100%;
margin: 0;
outline: 0;
background-color: transparent;
padding: 0;
border-width: 0;
vertical-align: middle;
cursor: pointer;
svg {
fill: $svg-color;
&:hover {
fill: $svg-hover-color;
}
}
}
}
.search-input {
width: 100%;
height: 56px;
font-size: 16px;
border: none;
box-sizing: border-box;
outline: none;
padding: 1px 10px;
border-radius: 4px;
}
.search-input-btn {
height: 56px;
}
}
.search-category {
.main-navbar-links {
width: 100%;
padding: 0;
.main-navbar-item {
padding: 0 6%;
}
}
}
}
}
</style>
import algoliasearch from 'algoliasearch/dist/algoliasearch-lite.esm.browser';
import { removeHighlightTags, groupBy } from '../../util'
let searchClient
function createSearchClient(appId, apiKey) {
if (searchClient) return searchClient
searchClient = algoliasearch(appId, apiKey);
searchClient.addAlgoliaAgent('dcloudsearch', '1.0.0');
return searchClient
}
export function search({ query, indexName, appId, apiKey, searchParameters = {}, snippetLength = 0, transformItems = () => { }, ...args }) {
return createSearchClient(appId, apiKey)
.search([
{
query,
indexName,
params: {
attributesToRetrieve: [
'hierarchy.lvl0',
'hierarchy.lvl1',
'hierarchy.lvl2',
'hierarchy.lvl3',
'hierarchy.lvl4',
'hierarchy.lvl5',
'hierarchy.lvl6',
'content',
'type',
'url',
],
attributesToSnippet: [
`hierarchy.lvl1:${snippetLength}`,
`hierarchy.lvl2:${snippetLength}`,
`hierarchy.lvl3:${snippetLength}`,
`hierarchy.lvl4:${snippetLength}`,
`hierarchy.lvl5:${snippetLength}`,
`hierarchy.lvl6:${snippetLength}`,
`content:${snippetLength}`,
],
snippetEllipsisText: '',
highlightPreTag: '<mark>',
highlightPostTag: '</mark>',
hitsPerPage: 20,
...args,
...searchParameters,
},
},
])
.catch((error) => {
throw error;
})
.then(({ results }) => {
const { hits, nbHits } = results[0];
const sources = groupBy(hits, (hit) => removeHighlightTags(hit));
return Object.values(sources).map(
(items, index) => {
return {
sourceId: `hits${index}`,
onSelect({ item, event }) {
// saveRecentSearch(item);
// if (!event.shiftKey && !event.ctrlKey && !event.metaKey) {
// onClose();
// }
},
getItemUrl({ item }) {
return item.url;
},
getItems() {
return Object.values(
groupBy(items, (item) => item.hierarchy.lvl1)
)
.map(transformItems)
.map((groupedHits) =>
groupedHits.map((item) => {
return {
...item,
__docsearch_parent:
item.type !== 'lvl1' &&
groupedHits.find(
(siblingItem) =>
siblingItem.type === 'lvl1' &&
siblingItem.hierarchy.lvl1 ===
item.hierarchy.lvl1
),
};
})
).flat();
},
};
}
);
});
}
\ No newline at end of file
......@@ -3,29 +3,7 @@
<div class="main-navbar">
<!-- <SidebarButton @toggle-sidebar="$emit('toggle-sidebar')" /> -->
<a
href="https://www.dcloud.io"
class="home-link"
>
<img
v-if="$site.themeConfig.logo"
class="logo"
:src="$withBase($site.themeConfig.logo)"
:alt="$siteTitle"
>
<img
v-if="$site.themeConfig.titleLogo"
class="title-logo can-hide"
:src="$withBase($site.themeConfig.titleLogo)"
:alt="$siteTitle"
>
<span
v-else-if="$siteTitle"
ref="siteName"
class="site-name"
:class="{ 'can-hide': $site.themeConfig.logo }"
>{{ $siteTitle }}</span>
</a>
<NavbarLogo />
<div class="main-navbar-links can-hide">
<template v-for="(item, index) in customNavBar">
......@@ -82,6 +60,7 @@ import SearchBox from './SearchBox'
import SidebarButton from '@theme/components/SidebarButton.vue'
import NavLinks from '@theme/components/NavLinks.vue'
import MainNavbarLink from './MainNavbarLink.vue';
import NavbarLogo from './NavbarLogo.vue';
import navInject from '../mixin/navInject';
import { forbidScroll, os } from '../util';
......@@ -95,7 +74,8 @@ export default {
NavLinks,
MainNavbarLink,
SearchBox,
AlgoliaSearchBox
AlgoliaSearchBox,
NavbarLogo
},
data () {
......
<template>
<a href="https://www.dcloud.io" class="home-link">
<img
v-if="$site.themeConfig.logo"
class="logo"
:src="$withBase($site.themeConfig.logo)"
:alt="$siteTitle"
/>
<img
v-if="$site.themeConfig.titleLogo"
class="title-logo can-hide"
:src="$withBase($site.themeConfig.titleLogo)"
:alt="$siteTitle"
/>
<span
v-else-if="$siteTitle"
ref="siteName"
class="site-name"
:class="{ 'can-hide': $site.themeConfig.logo }"
>
{{ $siteTitle }}
</span>
</a>
</template>
......@@ -42,6 +42,8 @@
<Footer />
</template>
</Page>
<DcloudSearchPage :options="algolia"/>
</div>
</template>
......@@ -52,6 +54,7 @@ import Page from '@theme/components/Page.vue'
import Sidebar from '@theme/components/Sidebar.vue'
import Footer from '@theme/components/Footer.vue';
import SiderBarBottom from '../components/SiderBarBottom.vue';
import DcloudSearchPage from '../components/DcloudSearchPage';
import { resolveSidebarItems, forbidScroll, BaiduStat } from '../util'
import navProvider from '../mixin/navProvider';
......@@ -64,7 +67,8 @@ export default {
Sidebar,
Navbar,
Footer,
SiderBarBottom
SiderBarBottom,
DcloudSearchPage
},
data () {
return {
......@@ -114,6 +118,9 @@ export default {
},
userPageClass
]
},
algolia () {
return this.$themeLocaleConfig.algolia || this.$site.themeConfig.algolia || {}
}
},
mounted () {
......
import Vue from 'vue';
export * from './searchUtils';
export const isServer = Vue.prototype.$isServer
export const hashRE = /#.*$/
export const extRE = /\.(md|html)$/
......
export function groupBy(values, predicate) {
return values.reduce((acc, item) => {
const key = predicate(item);
if (!acc.hasOwnProperty(key)) {
acc[key] = [];
}
// We limit each section to show 5 hits maximum.
// This acts as a frontend alternative to `distinct`.
if (acc[key].length < 5) {
acc[key].push(item);
}
return acc;
}, {});
}
const regexHighlightTags = /(<mark>|<\/mark>)/g;
const regexHasHighlightTags = RegExp(regexHighlightTags.source);
export function removeHighlightTags(hit) {
const internalDocSearchHit = hit
if (!internalDocSearchHit.__docsearch_parent && !hit._highlightResult) {
return hit.hierarchy.lvl0;
}
const { value } =
(internalDocSearchHit.__docsearch_parent
? internalDocSearchHit.__docsearch_parent?._highlightResult?.hierarchy
?.lvl0
: hit._highlightResult?.hierarchy?.lvl0) || {};
return value && regexHasHighlightTags.test(value)
? value.replace(regexHighlightTags, '')
: value;
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册