refactor: code split optional features + improve typing

pull/165/head
Evan You 4 years ago
parent 170d72892e
commit 08b9e17a93

@ -1,26 +1,27 @@
// exports in this file are exposed to themes and md files via 'vitepress'
// so the user can do `import { useRoute, useSiteData } from 'vitepress'`
import { ComponentOptions } from 'vue'
// generic types
export type { SiteData, PageData } from '/@types/shared'
export type { Router, Route } from './router'
// theme types
export * from './theme'
// composables
export { useRouter, useRoute, Router, Route } from './router'
export { useRouter, useRoute } from './router'
export { useSiteData } from './composables/siteData'
export { useSiteDataByRoute } from './composables/siteDataByRoute'
export { usePageData } from './composables/pageData'
export { useFrontmatter } from './composables/frontmatter'
// utilities
export { inBrowser, joinPath } from './utils'
// components
export { Content } from './components/Content'
import { ComponentOptions } from 'vue'
import _Debug from './components/Debug.vue'
const Debug = _Debug as ComponentOptions
export { Debug }

@ -10,3 +10,13 @@ declare module '@siteData' {
const data: string
export default data
}
// this module's typing is broken
declare module '@docsearch/js' {
function docsearch<T = any>(props: T): void
export default docsearch
}
declare module '@docsearch/css' {
export default string
}

@ -4,10 +4,7 @@
<NavBar>
<template #search>
<slot name="navbar-search">
<AlgoliaSearchBox
v-if="$site.themeConfig.algolia"
:options="$site.themeConfig.algolia"
/>
<AlgoliaSearchBox v-if="theme.algolia" :options="theme.algolia" />
</slot>
</template>
</NavBar>
@ -41,10 +38,10 @@
<template #top>
<slot name="page-top-ads">
<CarbonAds
v-if="$site.themeConfig.carbonAds"
:key="'carbon' + $page.path"
:code="$site.themeConfig.carbonAds.carbon"
:placement="$site.themeConfig.carbonAds.placement"
v-if="theme.carbonAds"
:key="'carbon' + page.relativePath"
:code="theme.carbonAds.carbon"
:placement="theme.carbonAds.placement"
/>
</slot>
<slot name="page-top" />
@ -53,10 +50,10 @@
<slot name="page-bottom" />
<slot name="page-bottom-ads">
<BuySellAds
v-if="$site.themeConfig.carbonAds"
:key="'custom' + $page.path"
:code="$site.themeConfig.carbonAds.custom"
:placement="$site.themeConfig.carbonAds.placement"
v-if="theme.carbonAds && theme.carbonAds.custom"
:key="'custom' + page.relativePath"
:code="theme.carbonAds.custom"
:placement="theme.carbonAds.placement"
/>
</slot>
</template>
@ -67,24 +64,42 @@
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import { ref, computed, watch, defineAsyncComponent } from 'vue'
import {
useRoute,
useSiteData,
usePageData,
useSiteDataByRoute
} from 'vitepress'
import type { DefaultTheme } from './config'
// components
import NavBar from './components/NavBar.vue'
import Home from './components/Home.vue'
import ToggleSideBarButton from './components/ToggleSideBarButton.vue'
import SideBar from './components/SideBar.vue'
import Page from './components/Page.vue'
import { useRoute, useSiteData, useSiteDataByRoute } from 'vitepress'
import AlgoliaSearchBox from './components/AlgoliaSearchBox.vue'
import CarbonAds from './components/CarbonAds.vue'
import BuySellAds from './components/BuySellAds.vue'
const CarbonAds = defineAsyncComponent(
() => import('./components/CarbonAds.vue')
)
const BuySellAds = defineAsyncComponent(
() => import('./components/BuySellAds.vue')
)
const AlgoliaSearchBox = defineAsyncComponent(
() => import('./components/AlgoliaSearchBox.vue')
)
// generic state
const route = useRoute()
const siteData = useSiteData()
const siteData = useSiteData<DefaultTheme.Config>()
const siteRouteData = useSiteDataByRoute()
const theme = computed(() => siteData.value.themeConfig)
const page = usePageData()
const openSideBar = ref(false)
// home
const enableHome = computed(() => !!route.data.frontmatter.home)
// navbar
const showNavbar = computed(() => {
const { themeConfig } = siteRouteData.value
const { frontmatter } = route.data
@ -99,6 +114,9 @@ const showNavbar = computed(() => {
)
})
// sidebar
const openSideBar = ref(false)
const showSidebar = computed(() => {
const { frontmatter } = route.data
const { themeConfig } = siteRouteData.value
@ -111,6 +129,17 @@ const showSidebar = computed(() => {
)
})
const toggleSidebar = (to?: boolean) => {
openSideBar.value = typeof to === 'boolean' ? to : !openSideBar.value
}
const hideSidebar = toggleSidebar.bind(null, false)
// close the sidebar when navigating to a different location
watch(route, hideSidebar)
// TODO: route only changes when the pathname changes
// listening to hashchange does nothing because it's prevented in router
// page classes
const pageClasses = computed(() => {
return [
{
@ -120,14 +149,4 @@ const pageClasses = computed(() => {
}
]
})
const toggleSidebar = (to) => {
openSideBar.value = typeof to === 'boolean' ? to : !openSideBar.value
}
const hideSidebar = toggleSidebar.bind(null, false)
// close the sidebar when navigating to a different location
watch(route, hideSidebar)
// TODO: route only changes when the pathname changes
// listening to hashchange does nothing because it's prevented in router
</script>

@ -2,23 +2,36 @@
<div class="algolia-search-box" id="docsearch" />
</template>
<script lang="ts">
// TODO: @vue/compiler-sfc currently has a bug that removes `import 'foo'`
// statements in <script setup> so we put it here for now
import '@docsearch/css'
</script>
<script setup lang="ts">
import type { AlgoliaSearchOptions } from 'algoliasearch'
import { useRoute, useRouter } from 'vitepress'
import { defineProps, getCurrentInstance, onMounted, watch } from 'vue'
import docsearch from '@docsearch/js'
import type { DefaultTheme } from '../config'
import type { DocSearchHit } from '@docsearch/react/dist/esm/types'
const { options } = defineProps<{
options: AlgoliaSearchOptions
const props = defineProps<{
options: DefaultTheme.AlgoliaSearchOptions
}>()
const vm = getCurrentInstance()
const route = useRoute()
const router = useRouter()
watch(() => options, (value) => { update(value) })
watch(
() => props.options,
(value) => {
update(value)
}
)
onMounted(() => {
initialize(options)
initialize(props.options)
})
function isSpecialClick(event: MouseEvent) {
@ -39,85 +52,87 @@ function getRelativePath(absoluteUrl: string) {
function update(options: any) {
if (vm && vm.vnode.el) {
vm.vnode.el.innerHTML = '<div class="algolia-search-box" id="docsearch"></div>'
vm.vnode.el.innerHTML =
'<div class="algolia-search-box" id="docsearch"></div>'
initialize(options)
}
}
function initialize(userOptions: any) {
Promise.all([
import('@docsearch/js'),
import('@docsearch/css')
]).then(([docsearch]) => {
docsearch.default(
Object.assign({}, userOptions, {
container: '#docsearch',
searchParameters: Object.assign({}, userOptions.searchParameters),
navigator: {
navigate: ({ suggestionUrl }: { suggestionUrl: string }) => {
const { pathname: hitPathname } = new URL(
window.location.origin + suggestionUrl
)
// Router doesn't handle same-page navigation so we use the native
// browser location API for anchor navigation
if (route.path === hitPathname) {
window.location.assign(window.location.origin + suggestionUrl)
} else {
router.go(suggestionUrl)
}
docsearch(
Object.assign({}, userOptions, {
container: '#docsearch',
searchParameters: Object.assign({}, userOptions.searchParameters),
navigator: {
navigate: ({ suggestionUrl }: { suggestionUrl: string }) => {
const { pathname: hitPathname } = new URL(
window.location.origin + suggestionUrl
)
// Router doesn't handle same-page navigation so we use the native
// browser location API for anchor navigation
if (route.path === hitPathname) {
window.location.assign(window.location.origin + suggestionUrl)
} else {
router.go(suggestionUrl)
}
},
}
},
transformItems: (items) => {
return items.map((item) => {
return Object.assign({}, item, {
url: getRelativePath(item.url)
})
transformItems: (items: DocSearchHit[]) => {
return items.map((item) => {
return Object.assign({}, item, {
url: getRelativePath(item.url)
})
},
hitComponent: ({ hit, children }) => {
const relativeHit = hit.url.startsWith('http')
? getRelativePath(hit.url as string)
: hit.url
return {
type: 'a',
ref: undefined,
constructor: undefined,
key: undefined,
props: {
href: hit.url,
onClick: (event: MouseEvent) => {
if (isSpecialClick(event)) {
return
}
// we rely on the native link scrolling when user is already on
// the right anchor because Router doesn't support duplicated
// history entries
if (route.path === relativeHit) {
return
}
// if the hits goes to another page, we prevent the native link
// behavior to leverage the Router loading feature
if (route.path !== relativeHit) {
event.preventDefault()
}
router.go(relativeHit)
},
children
}
})
},
hitComponent: ({
hit,
children
}: {
hit: DocSearchHit
children: any
}) => {
const relativeHit = hit.url.startsWith('http')
? getRelativePath(hit.url as string)
: hit.url
return {
type: 'a',
ref: undefined,
constructor: undefined,
key: undefined,
props: {
href: hit.url,
onClick: (event: MouseEvent) => {
if (isSpecialClick(event)) {
return
}
// we rely on the native link scrolling when user is already on
// the right anchor because Router doesn't support duplicated
// history entries
if (route.path === relativeHit) {
return
}
// if the hits goes to another page, we prevent the native link
// behavior to leverage the Router loading feature
if (route.path !== relativeHit) {
event.preventDefault()
}
router.go(relativeHit)
},
children
}
}
})
)
})
}
})
)
}
</script>
@ -139,7 +154,7 @@ function initialize(userOptions: any) {
.algolia-search-box .DocSearch-Button-Placeholder {
padding-left: 8px;
font-size: .9rem;
font-size: 0.9rem;
font-weight: 500;
}
}

@ -1,6 +1,5 @@
import { computed } from 'vue'
import { useRoute, useSiteData } from 'vitepress'
import { inBrowser } from '/@app/utils'
import { useRoute, useSiteData, inBrowser } from 'vitepress'
import type { DefaultTheme } from '../config'
export function useLocaleLinks() {

@ -1,5 +1,4 @@
import { useSiteData } from 'vitepress'
import { joinPath } from '/@app/utils'
import { useSiteData, joinPath } from 'vitepress'
export function useUrl() {
const site = useSiteData()

@ -3,7 +3,6 @@ export namespace DefaultTheme {
logo?: string
nav?: NavItem[] | false
sidebar?: SideBarConfig | MultiSideBarConfig
search?: SearchConfig | false
/**
* GitHub repository following the format <user>/<project>.
@ -62,6 +61,14 @@ export namespace DefaultTheme {
nextLink?: boolean
locales?: Record<string, LocaleConfig & Omit<Config, 'locales'>>
algolia?: AlgoliaSearchOptions
carbonAds?: {
carbon: string
custom?: string
placement: string
}
}
// navbar --------------------------------------------------------------------
@ -110,26 +117,19 @@ export namespace DefaultTheme {
children: SideBarItem[]
}
// search --------------------------------------------------------------------
export interface SearchConfig {
/**
* @default 5
*/
maxSuggestions?: number
/**
* @default ''
*/
// algolia ------------------------------------------------------------------
// partially copied from @docsearch/react/dist/esm/DocSearch.d.ts
export interface AlgoliaSearchOptions {
appId?: string
apiKey: string
indexName: string
placeholder?: string
algolia?: {
apiKey: string
indexName: string
}
searchParameters?: any
disableUserPersonalization?: boolean
initialQuery?: string
}
// locales --------------------------------------------------------------------
// locales -------------------------------------------------------------------
export interface LocaleConfig {
/**

@ -7,7 +7,6 @@
"lib": ["ESNext", "DOM"],
"types": ["vite"],
"paths": {
"/@app/*": ["app/*"],
"/@theme/*": ["theme-default/*"],
"/@shared/*": ["shared/*"],
"/@types/*": ["../../types/*"],

@ -16,11 +16,8 @@
"node/*": ["src/node/*"],
"shared/*": ["src/shared/*"],
"tests/*": ["__tests__/*"],
"/@app/*": ["src/client/app/*"],
"/@theme/*": ["src/client/theme-default/*"],
"/@shared/*": ["src/client/shared/*"],
"/@types/*": ["types/*"],
"vitepress": ["src/client/app/exports.ts"]
"/@types/*": ["types/*"]
}
},
"include": [

Loading…
Cancel
Save