mirror of https://github.com/YunYouJun/valaxy
feat(theme-press): add algolia search & fix siteConfig merge
This commit is contained in:
parent
f8b5d125a9
commit
38295a3129
|
@ -11,7 +11,8 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"valaxy": "link:../packages/valaxy",
|
||||
"valaxy-theme-yun": "link:../packages/valaxy-theme-yun"
|
||||
"valaxy-theme-yun": "link:../packages/valaxy-theme-yun",
|
||||
"valaxy-addon-algolia": "link:../packages/valaxy-addon-algolia"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^2.0.19"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { defineValaxyConfig } from 'valaxy'
|
||||
import type { PressTheme } from 'valaxy-theme-press'
|
||||
import { addonAlgolia } from 'valaxy-addon-algolia'
|
||||
|
||||
const COMMIT_ID = process.env.CF_PAGES_COMMIT_SHA || process.env.COMMIT_REF
|
||||
const commitRef = COMMIT_ID?.slice(0, 8) || 'dev'
|
||||
|
@ -15,8 +16,21 @@ export default defineValaxyConfig<PressTheme.Config>({
|
|||
title: 'Valaxy',
|
||||
url: 'https://valaxy.site',
|
||||
description: 'Valaxy Site Docs',
|
||||
|
||||
search: {
|
||||
enable: true,
|
||||
type: 'algolia',
|
||||
},
|
||||
},
|
||||
|
||||
addons: [
|
||||
addonAlgolia({
|
||||
appId: 'UVMHTMG1T5',
|
||||
apiKey: '805f2584a8866388aa1631ff0348ddae',
|
||||
indexName: 'valaxy',
|
||||
}),
|
||||
],
|
||||
|
||||
theme: 'press',
|
||||
themeConfig: {
|
||||
logo: '/favicon.svg',
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { useSiteConfig } from 'valaxy'
|
||||
|
||||
/**
|
||||
* init algolia watch
|
||||
*/
|
||||
export const useAddonAlgolia = () => {
|
||||
const siteConfig = useSiteConfig()
|
||||
const isAlgolia = computed(() => siteConfig.value.search.type === 'algolia')
|
||||
const metaKey = ref('\'Meta\'')
|
||||
|
||||
// to avoid loading the docsearch js upfront (which is more than 1/3 of the
|
||||
// payload), we delay initializing it until the user has actually clicked or
|
||||
// hit the hotkey to invoke it.
|
||||
const loaded = ref(false)
|
||||
|
||||
/**
|
||||
* poll until open
|
||||
*/
|
||||
function poll() {
|
||||
// programmatically open the search box after initialize
|
||||
const e = new Event('keydown') as any
|
||||
|
||||
e.key = 'k'
|
||||
e.metaKey = true
|
||||
|
||||
window.dispatchEvent(e)
|
||||
|
||||
setTimeout(() => {
|
||||
if (!document.querySelector('.DocSearch-Modal'))
|
||||
poll()
|
||||
}, 16)
|
||||
}
|
||||
|
||||
function load() {
|
||||
if (!loaded.value) {
|
||||
loaded.value = true
|
||||
setTimeout(poll, 16)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (!isAlgolia.value)
|
||||
return
|
||||
|
||||
// meta key detect (same logic as in @docsearch/js)
|
||||
metaKey.value = /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform)
|
||||
? '\'⌘\''
|
||||
: '\'Ctrl\''
|
||||
|
||||
const handleSearchHotKey = (e: KeyboardEvent) => {
|
||||
if (e.key === 'k' && (e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault()
|
||||
load()
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
remove()
|
||||
}
|
||||
}
|
||||
|
||||
const remove = () => {
|
||||
window.removeEventListener('keydown', handleSearchHotKey)
|
||||
}
|
||||
window.addEventListener('keydown', handleSearchHotKey)
|
||||
|
||||
onUnmounted(remove)
|
||||
})
|
||||
|
||||
return {
|
||||
isAlgolia,
|
||||
loaded,
|
||||
metaKey,
|
||||
load,
|
||||
}
|
||||
}
|
|
@ -1 +1,2 @@
|
|||
export * from './composable'
|
||||
export * from './options'
|
||||
|
|
|
@ -8,7 +8,7 @@ import pkg from '../package.json'
|
|||
* get addon config
|
||||
* @returns
|
||||
*/
|
||||
export function useAddonAlgolia() {
|
||||
export function useAddonAlgoliaConfig() {
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
return computed(() => runtimeConfig.value.addons[pkg.name] as ValaxyAddon<AlgoliaSearchOptions>)
|
||||
}
|
||||
|
|
|
@ -5,39 +5,21 @@ import type { DocSearchHit } from '@docsearch/react/dist/esm/types'
|
|||
import { onMounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import type { AlgoliaSearchOptions } from '../types'
|
||||
import { useAddonAlgolia } from '../client'
|
||||
import { useAddonAlgoliaConfig } from '../client'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
const algolia = useAddonAlgolia()
|
||||
const algolia = useAddonAlgoliaConfig()
|
||||
|
||||
onMounted(() => {
|
||||
const options = algolia.value.options
|
||||
if (options && options.apiKey && options.appId && options.indexName) {
|
||||
initialize(options)
|
||||
setTimeout(poll, 16)
|
||||
if (!document.querySelector('.DocSearch-Container'))
|
||||
initialize(options)
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* poll until open
|
||||
*/
|
||||
function poll() {
|
||||
// programmatically open the search box after initialize
|
||||
const e = new Event('keydown') as any
|
||||
|
||||
e.key = 'k'
|
||||
e.metaKey = true
|
||||
|
||||
window.dispatchEvent(e)
|
||||
|
||||
setTimeout(() => {
|
||||
if (!document.querySelector('.DocSearch-Modal'))
|
||||
poll()
|
||||
}, 16)
|
||||
}
|
||||
|
||||
function initialize(userOptions: AlgoliaSearchOptions) {
|
||||
// note: multi-lang search support is removed since the theme
|
||||
// doesn't support multiple locales as of now.
|
||||
|
@ -118,7 +100,7 @@ function getRelativePath(absoluteUrl: string) {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div id="docsearch" class="hidden" />
|
||||
<div id="docsearch" />
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
|
@ -0,0 +1,213 @@
|
|||
<script lang="ts" setup>
|
||||
import { useSiteConfig } from 'valaxy/client'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useAddonAlgolia } from 'valaxy-addon-algolia'
|
||||
|
||||
const siteConfig = useSiteConfig()
|
||||
const { t } = useI18n()
|
||||
|
||||
const isAlgolia = computed(() => siteConfig.value.search.type === 'algolia')
|
||||
|
||||
const { loaded, load, metaKey } = useAddonAlgolia()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<AlgoliaSearchBox v-if="isAlgolia && loaded" />
|
||||
|
||||
<div v-else id="docsearch" @click="load">
|
||||
<button
|
||||
class="DocSearch DocSearch-Button"
|
||||
aria-label="Search"
|
||||
>
|
||||
<span class="DocSearch-Button-Container">
|
||||
<svg
|
||||
class="DocSearch-Search-Icon"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path
|
||||
d="M14.386 14.386l4.0877 4.0877-4.0877-4.0877c-2.9418 2.9419-7.7115 2.9419-10.6533 0-2.9419-2.9418-2.9419-7.7115 0-10.6533 2.9418-2.9419 7.7115-2.9419 10.6533 0 2.9419 2.9418 2.9419 7.7115 0 10.6533z"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
fill-rule="evenodd"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
<span class="DocSearch-Button-Placeholder">{{ t('search.placeholder') }}</span>
|
||||
</span>
|
||||
<span class="DocSearch-Button-Keys">
|
||||
<kbd class="DocSearch-Button-Key" />
|
||||
<kbd class="DocSearch-Button-Key">K</kbd>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.DocSearch-Button {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 32px;
|
||||
height: 55px;
|
||||
background: transparent;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
|
||||
.DocSearch-Button:hover {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.DocSearch-Button:focus {
|
||||
outline: 1px dotted;
|
||||
outline: 5px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
.DocSearch-Button:focus:not(:focus-visible) {
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.DocSearch-Button {
|
||||
justify-content: flex-start;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 8px;
|
||||
padding: 0 10px 0 12px;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
background-color: var(--va-c-bg-alt);
|
||||
}
|
||||
|
||||
.DocSearch-Button:hover {
|
||||
border-color: var(--va-c-brand);
|
||||
background: var(--va-c-bg-alt);
|
||||
}
|
||||
}
|
||||
|
||||
.DocSearch-Button .DocSearch-Button-Container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.DocSearch-Button .DocSearch-Search-Icon {
|
||||
position: relative;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: var(--va-c-text-1);
|
||||
fill: currentColor;
|
||||
transition: color 0.5s;
|
||||
}
|
||||
|
||||
.DocSearch-Button:hover .DocSearch-Search-Icon {
|
||||
color: var(--va-c-text-1);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.DocSearch-Button .DocSearch-Search-Icon {
|
||||
top: 1px;
|
||||
margin-right: 8px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
color: var(--va-c-text-2);
|
||||
}
|
||||
}
|
||||
|
||||
.DocSearch-Button .DocSearch-Button-Placeholder {
|
||||
display: none;
|
||||
margin-top: 2px;
|
||||
padding: 0 16px 0 0;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--va-c-text-2);
|
||||
transition: color 0.5s;
|
||||
}
|
||||
|
||||
.DocSearch-Button:hover .DocSearch-Button-Placeholder {
|
||||
color: var(--va-c-text-1);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.DocSearch-Button .DocSearch-Button-Placeholder {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.DocSearch-Button .DocSearch-Button-Keys {
|
||||
/*rtl:ignore*/
|
||||
direction: ltr;
|
||||
display: none;
|
||||
min-width: auto;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.DocSearch-Button .DocSearch-Button-Keys {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.DocSearch-Button .DocSearch-Button-Key {
|
||||
display: block;
|
||||
margin: 2px 0 0 0;
|
||||
border: 1px solid var(--va-c-divider);
|
||||
/*rtl:begin:ignore*/
|
||||
border-right: none;
|
||||
border-radius: 4px 0 0 4px;
|
||||
padding-left: 6px;
|
||||
/*rtl:end:ignore*/
|
||||
min-width: 0;
|
||||
width: auto;
|
||||
height: 22px;
|
||||
font-family: var(--va-font-sans);
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
transition: color 0.5s, border-color 0.5s;
|
||||
}
|
||||
|
||||
.DocSearch-Button .DocSearch-Button-Key + .DocSearch-Button-Key {
|
||||
/*rtl:begin:ignore*/
|
||||
border-right: 1px solid var(--va-c-divider);
|
||||
border-left: none;
|
||||
border-radius: 0 4px 4px 0;
|
||||
padding-left: 2px;
|
||||
padding-right: 6px;
|
||||
/*rtl:end:ignore*/
|
||||
}
|
||||
|
||||
.DocSearch-Button .DocSearch-Button-Key:first-child {
|
||||
font-size: 1px;
|
||||
letter-spacing: -12px;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.DocSearch-Button .DocSearch-Button-Key:first-child:after {
|
||||
content: v-bind(metaKey);
|
||||
font-size: 12px;
|
||||
letter-spacing: normal;
|
||||
color: var(--docsearch-muted-color);
|
||||
}
|
||||
|
||||
.DocSearch-Button .DocSearch-Button-Key:first-child > * {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dark .DocSearch-Footer {
|
||||
border-top: 1px solid var(--va-c-divider);
|
||||
}
|
||||
|
||||
.DocSearch-Form {
|
||||
border: 1px solid var(--va-c-brand);
|
||||
background-color: var(--va-c-white);
|
||||
}
|
||||
|
||||
.dark .DocSearch-Form {
|
||||
background-color: var(--va-c-bg-soft-mute);
|
||||
}
|
||||
</style>
|
|
@ -23,6 +23,7 @@ const themeConfig = useThemeConfig()
|
|||
<span class="inline-flex">{{ siteConfig.title }}</span>
|
||||
</router-link>
|
||||
<div class="self-stretch flex justify-center items-center text-sm leading-5">
|
||||
<PressNavBarSearch p="x-2" />
|
||||
<PressNavBarMenu p="x-2" />
|
||||
<PressNavBarTranslations p="x-2" />
|
||||
<PressNavBarAppearance p="x-2" />
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
<script lang="ts" setup>
|
||||
import { useSiteConfig } from 'valaxy'
|
||||
import { computed } from 'vue'
|
||||
|
||||
// ref vitepress search box
|
||||
const siteConfig = useSiteConfig()
|
||||
const isAlgolia = computed(() => siteConfig.value.search.type === 'algolia')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="siteConfig.search.enable" class="VPNavBarSearch">
|
||||
<PressAlgoliaSearch v-if="isAlgolia" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.VPNavBarSearch {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.VPNavBarSearch {
|
||||
flex-grow: 1;
|
||||
padding-left: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.VPNavBarSearch {
|
||||
padding-left: 32px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts" setup>
|
||||
import { computed } from '@vue/reactivity'
|
||||
import { useSiteConfig } from 'valaxy'
|
||||
import { onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import { ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useMagicKeys } from '@vueuse/core'
|
||||
|
||||
|
@ -20,49 +20,19 @@ function load() {
|
|||
|
||||
const isAlgolia = computed(() => siteConfig.value.search.type === 'algolia')
|
||||
|
||||
onMounted(() => {
|
||||
if (!isAlgolia.value)
|
||||
return
|
||||
|
||||
const handleSearchHotKey = (e: KeyboardEvent) => {
|
||||
if (e.key === 'k' && (e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault()
|
||||
load()
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
remove()
|
||||
}
|
||||
}
|
||||
|
||||
const remove = () => {
|
||||
window.removeEventListener('keydown', handleSearchHotKey)
|
||||
}
|
||||
|
||||
window.addEventListener('keydown', handleSearchHotKey)
|
||||
|
||||
onUnmounted(remove)
|
||||
})
|
||||
|
||||
const open = ref(false)
|
||||
|
||||
const trigger = () => {
|
||||
// todo, refactor shortcut
|
||||
const e = new Event('keydown') as any
|
||||
|
||||
e.key = 'k'
|
||||
e.metaKey = true
|
||||
}
|
||||
|
||||
const togglePopup = () => {
|
||||
open.value = !open.value
|
||||
|
||||
trigger()
|
||||
}
|
||||
|
||||
const { Meta_K } = useMagicKeys()
|
||||
|
||||
watch(Meta_K, (val) => {
|
||||
if (val)
|
||||
if (val) {
|
||||
togglePopup()
|
||||
load()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import type { UserSiteConfig } from 'valaxy'
|
||||
import { cyan } from 'kolorist'
|
||||
import { defu } from 'defu'
|
||||
import { defaultSiteConfig } from '..'
|
||||
import { logger } from '../logger'
|
||||
import { loadConfigFromFile } from './utils'
|
||||
|
||||
|
@ -26,7 +24,7 @@ export async function resolveSiteConfig(root: string) {
|
|||
const { config: userSiteConfig, configFile: siteConfigFile } = await resolveSiteConfigFromRoot(root)
|
||||
|
||||
return {
|
||||
siteConfig: defu(userSiteConfig, defaultSiteConfig),
|
||||
siteConfig: userSiteConfig,
|
||||
siteConfigFile,
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue