feat(theme-press): add algolia search & fix siteConfig merge

This commit is contained in:
YunYouJun 2023-01-25 01:33:37 +08:00
parent f8b5d125a9
commit 38295a3129
11 changed files with 350 additions and 62 deletions

View File

@ -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"

View File

@ -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',

View File

@ -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,
}
}

View File

@ -1 +1,2 @@
export * from './composable'
export * from './options'

View File

@ -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>)
}

View File

@ -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">

View File

@ -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>

View File

@ -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" />

View File

@ -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>

View File

@ -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>

View File

@ -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,
}
}