feat(docs): zh-Hans for docs
@@ -1,17 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { useEditLink } from '../composables/edit-link'
|
||||
import { discord, github, sponsor } from '../meta'
|
||||
|
||||
const editLink = useEditLink()
|
||||
const { t } = useI18n()
|
||||
|
||||
const links = computed(() => [
|
||||
{ label: editLink.value.text, url: editLink.value.url, icon: 'lucide:pencil-line' },
|
||||
{ label: 'Star on GitHub', url: github, icon: 'lucide:star' },
|
||||
{ label: 'Chat on Discord', url: discord, icon: 'lucide:messages-square' },
|
||||
{ label: 'Support the project', url: sponsor, icon: 'lucide:hand-heart' },
|
||||
{ label: t('docs.theme.doc.community.star-github.title'), url: github, icon: 'lucide:star' },
|
||||
{ label: t('docs.theme.doc.community.discord.title'), url: discord, icon: 'lucide:messages-square' },
|
||||
{ label: t('docs.theme.doc.community.support.title'), url: sponsor, icon: 'lucide:hand-heart' },
|
||||
])
|
||||
</script>
|
||||
|
||||
@@ -19,7 +21,7 @@ const links = computed(() => [
|
||||
<div>
|
||||
<div class="pt-4">
|
||||
<div class="mb-2 text-sm font-bold">
|
||||
Community
|
||||
{{ t('docs.theme.doc.community.title') }}
|
||||
</div>
|
||||
|
||||
<div class="mt-4 space-y-3 lg:space-y-2">
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { useData } from 'vitepress'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import DocFooterLastUpdated from './DocFooterLastUpdated.vue'
|
||||
|
||||
@@ -9,6 +10,7 @@ import { useEditLink } from '../composables/edit-link'
|
||||
import { usePrevNext } from '../composables/prev-next'
|
||||
|
||||
const { theme, page, frontmatter } = useData()
|
||||
const { t } = useI18n()
|
||||
|
||||
const editLink = useEditLink()
|
||||
const control = usePrevNext()
|
||||
@@ -78,7 +80,7 @@ const showFooter = computed(
|
||||
>
|
||||
<span
|
||||
class="text-muted-foreground group-hover:text-foreground text-xs transition-all duration-200 ease-in-out"
|
||||
v-html="theme.docFooter?.prev || 'Previous page'"
|
||||
v-html="theme.docFooter?.prev || t('docs.theme.doc.previous-page.title')"
|
||||
/>
|
||||
<p class="mt-2 inline-flex items-center gap-1">
|
||||
<Icon icon="lucide:arrow-left" />
|
||||
@@ -97,7 +99,7 @@ const showFooter = computed(
|
||||
>
|
||||
<span
|
||||
class="text-muted-foreground group-hover:text-foreground text-xs transition-all duration-200 ease-in-out"
|
||||
v-html="theme.docFooter?.next || 'Next page'"
|
||||
v-html="theme.docFooter?.next || t('docs.theme.doc.next-page.title')"
|
||||
/>
|
||||
|
||||
<p class="mt-2 inline-flex items-center gap-1">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { onContentUpdated, useData } from 'vitepress'
|
||||
import { ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import DocOutlineItem from '../components/DocOutlineItem.vue'
|
||||
|
||||
@@ -11,6 +12,7 @@ defineProps<{
|
||||
}>()
|
||||
|
||||
const { page, frontmatter, theme } = useData()
|
||||
const { t } = useI18n()
|
||||
|
||||
onContentUpdated(() => {
|
||||
getHeaders(frontmatter.value.outline ?? theme.value.outline)
|
||||
@@ -39,7 +41,7 @@ useActiveAnchor(container, marker)
|
||||
class="trunc mb-2 text-sm font-bold"
|
||||
role="heading"
|
||||
>
|
||||
On this page
|
||||
{{ t('docs.theme.doc.outline.title') }}
|
||||
</div>
|
||||
|
||||
<div class="border-muted border-l font-sans">
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useScroll } from '@vueuse/core'
|
||||
import { DialogContent, DialogDescription, DialogOverlay, DialogPortal, DialogRoot, DialogTitle, DialogTrigger } from 'reka-ui'
|
||||
import { useData, useRoute } from 'vitepress'
|
||||
import { computed, ref, toRefs, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import DocSidebarItem from '../components/DocSidebarItem.vue'
|
||||
|
||||
@@ -13,6 +14,7 @@ import { flatten } from '../functions/flatten'
|
||||
|
||||
const { path } = toRefs(useRoute())
|
||||
const { page, theme } = useData()
|
||||
const { t } = useI18n()
|
||||
|
||||
const isSidebarOpen = ref(false)
|
||||
const sidebar = computed(() => (theme.value.sidebar as (DefaultTheme.SidebarItem & { icon?: string })[]))
|
||||
@@ -26,6 +28,12 @@ const sectionTabs = computed(() => sidebar.value
|
||||
.filter(i => !!i?.link),
|
||||
)
|
||||
|
||||
function isCharacterPage(link?: string) {
|
||||
if (!link)
|
||||
return false
|
||||
return link.includes('/characters') || link.includes('/characters/')
|
||||
}
|
||||
|
||||
const { arrivedState } = useScroll(globalThis.window)
|
||||
const { top } = toRefs(arrivedState)
|
||||
|
||||
@@ -42,7 +50,7 @@ watch(path, () => {
|
||||
<div class="h-full items-center justify-between hidden md:flex">
|
||||
<div class="h-full flex items-center">
|
||||
<a
|
||||
v-for="tab in sectionTabs.filter(i => i.label !== 'Characters')"
|
||||
v-for="tab in sectionTabs.filter(i => !isCharacterPage(i.link))"
|
||||
:key="tab.label"
|
||||
:href="tab.link"
|
||||
:class="{ '!after:bg-primary !text-foreground': `/${page.relativePath}`.includes(tab.link?.split('/').slice(0, -1).join('/') || '') }"
|
||||
@@ -60,7 +68,7 @@ watch(path, () => {
|
||||
|
||||
<div class="h-full flex items-center">
|
||||
<a
|
||||
v-for="tab in sectionTabs.filter(i => i.label === 'Characters')"
|
||||
v-for="tab in sectionTabs.filter(i => isCharacterPage(i.link))"
|
||||
:key="tab.label"
|
||||
:href="tab.link"
|
||||
:class="{ '!after:bg-primary !text-foreground': page.relativePath.includes(tab.label?.toLowerCase() ?? '') }"
|
||||
@@ -93,7 +101,7 @@ watch(path, () => {
|
||||
|
||||
<DialogPortal>
|
||||
<DialogOverlay class="data-[state=open]:animate-fadeIn data-[state=closed]:animate-fadeOut fixed inset-0 z-50 bg-black/80" />
|
||||
<DialogContent class="bg-background border-muted data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left data-[state=open]:animate-enterFromLeft data-[state=closed]:animate-exitToLeft fixed inset-y-0 left-0 z-50 h-full w-3/4 gap-4 border-r pr-0 shadow-lg transition ease-in-out sm:max-w-sm">
|
||||
<DialogContent class="border-muted bg-background data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left data-[state=open]:animate-enterFromLeft data-[state=closed]:animate-exitToLeft fixed inset-y-0 left-0 z-50 h-full w-3/4 gap-4 border-r pr-0 shadow-lg transition ease-in-out sm:max-w-sm">
|
||||
<DialogTitle class="sr-only">
|
||||
Sidebar menu
|
||||
</DialogTitle>
|
||||
@@ -156,7 +164,7 @@ watch(path, () => {
|
||||
icon="lucide:scan-face"
|
||||
class="text-lg"
|
||||
/>
|
||||
Characters
|
||||
{{ t('docs.theme.pages.characters.title') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -15,11 +15,13 @@ defineProps<{
|
||||
<template>
|
||||
<DropdownMenuRoot>
|
||||
<DropdownMenuTrigger class="text-muted-foreground hover:text-foreground data-[state=open]:text-foreground mx-0 h-full inline-flex items-center justify-between px-2 py-2 text-nowrap text-sm font-semibold md:mx-3 md:px-0">
|
||||
<span>{{ label }}</span>
|
||||
<Icon
|
||||
icon="lucide:chevron-down"
|
||||
class="ml-1 text-lg"
|
||||
/>
|
||||
<slot name="trigger">
|
||||
<span>{{ label }}</span>
|
||||
<Icon
|
||||
icon="lucide:chevron-down"
|
||||
class="ml-1 text-lg"
|
||||
/>
|
||||
</slot>
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuPortal>
|
||||
@@ -34,7 +36,7 @@ defineProps<{
|
||||
:initial="{ opacity: 0, y: -10 }"
|
||||
:animate="{ opacity: 1, y: 0 }"
|
||||
:exit="{ opacity: 0, y: -10 }"
|
||||
class="border-muted z-10 border rounded-lg bg-white/30 p-2 shadow-sm backdrop-blur-md dark:bg-neutral-800/30"
|
||||
class="border-muted z-10 border rounded-lg bg-white/60 p-2 shadow-sm backdrop-blur-md transition-shadow duration-200 ease-in-out dark:bg-neutral-800/30 focus:shadow-lg hover:shadow-lg"
|
||||
>
|
||||
<DropdownMenuItem
|
||||
v-for="item in items"
|
||||
@@ -46,10 +48,11 @@ defineProps<{
|
||||
<a
|
||||
v-if="item.link"
|
||||
:href="item.link"
|
||||
target="_blank"
|
||||
:target="item.link.startsWith('http') || item.link.startsWith('https') ? '_blank' : undefined"
|
||||
>
|
||||
<span>{{ item.text }}</span>
|
||||
<Icon
|
||||
v-if="item.link.startsWith('http') || item.link.startsWith('https')"
|
||||
icon="lucide:arrow-up-right"
|
||||
class="ml-2 text-base"
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import homeCover from '../assets/home-cover-2025-07-15.png'
|
||||
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -13,20 +17,20 @@ import homeCover from '../assets/home-cover-2025-07-15.png'
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative max-w-prose text-center text-slate-900 dark:text-white">
|
||||
A container of souls of AI waifu / virtual characters to bring them into our worlds.
|
||||
{{ t('docs.theme.home.subtitle') }}
|
||||
</div>
|
||||
<div class="relative z-10 w-full flex justify-center gap-4">
|
||||
<a
|
||||
href="https://github.com/moeru-ai/airi/releases/latest"
|
||||
class="rounded-lg bg-primary/60 px-4 py-2 text-slate-100 font-extrabold outline-none backdrop-blur-md dark:bg-primary/30 dark:text-cyan-200 focus:outline-none"
|
||||
>
|
||||
Download
|
||||
{{ t('docs.theme.home.download.title') }}
|
||||
</a>
|
||||
<a
|
||||
href="./overview/"
|
||||
class="rounded-lg bg-white/60 px-4 py-2 text-slate-900 font-extrabold outline-none backdrop-blur-md dark:bg-white/20 dark:text-slate-100 focus:outline-none"
|
||||
>
|
||||
Get Started
|
||||
{{ t('docs.theme.home.get-started.title') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,12 +3,16 @@ import { Icon } from '@iconify/vue'
|
||||
import { DropdownMenuContent, DropdownMenuItem, DropdownMenuPortal, DropdownMenuRoot, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, Separator } from 'reka-ui'
|
||||
import { useData, useRoute } from 'vitepress'
|
||||
import { ref, toRefs, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import DropdownMenu from '../components/DropdownMenu.vue'
|
||||
import ThemeToggle from '../components/ThemeToggle.vue'
|
||||
import DropdownMenu from './DropdownMenu.vue'
|
||||
import NavbarLanguage from './NavbarLanguage.vue'
|
||||
import NavbarLanguageSubMenu from './NavbarLanguageSubMenu.vue'
|
||||
import ThemeToggle from './ThemeToggle.vue'
|
||||
|
||||
const { path } = toRefs(useRoute())
|
||||
const { theme } = useData()
|
||||
const { t } = useI18n()
|
||||
|
||||
const isPopoverOpen = ref(false)
|
||||
|
||||
@@ -40,10 +44,13 @@ watch(path, () => {
|
||||
</template>
|
||||
|
||||
<Separator
|
||||
class="bg-muted mx-4 h-4 w-px"
|
||||
class="bg-muted ml-2 mr-2 h-4 w-px"
|
||||
decorative
|
||||
orientation="vertical"
|
||||
/>
|
||||
|
||||
<NavbarLanguage />
|
||||
|
||||
<ThemeToggle />
|
||||
|
||||
<Separator
|
||||
@@ -68,10 +75,7 @@ watch(path, () => {
|
||||
<div class="lg:hidden">
|
||||
<DropdownMenuRoot v-model:open="isPopoverOpen">
|
||||
<DropdownMenuTrigger class="rounded-lg p-2">
|
||||
<Icon
|
||||
icon="lucide:ellipsis"
|
||||
class="text-lg"
|
||||
/>
|
||||
<Icon icon="lucide:ellipsis" class="text-lg" />
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuPortal>
|
||||
@@ -79,7 +83,7 @@ watch(path, () => {
|
||||
side="bottom"
|
||||
:side-offset="5"
|
||||
align="end"
|
||||
class="bg-card will-change-[transform,opacity] border-muted data-[state=open]:data-[side=bottom]:animate-slideUpAndFade z-10 w-[180px] border rounded-xl p-2 shadow-[0_10px_38px_-10px_hsla(206,22%,7%,.35),0_10px_20px_-15px_hsla(206,22%,7%,.2)] focus:shadow-[0_10px_38px_-10px_hsla(206,22%,7%,.35),0_10px_20px_-15px_hsla(206,22%,7%,.2),0_0_0_2px_green7]"
|
||||
class="bg-card will-change-[transform,opacity] border-muted data-[state=open]:data-[side=bottom]:animate-slideUpAndFade z-10 w-[180px] border rounded-xl p-2 shadow-sm focus:shadow-lg"
|
||||
>
|
||||
<nav class="flex flex-col">
|
||||
<template
|
||||
@@ -99,14 +103,13 @@ watch(path, () => {
|
||||
<DropdownMenuSub v-else-if="nav.items">
|
||||
<DropdownMenuSubTrigger class="text-muted-foreground h-full w-full inline-flex items-center justify-between rounded p-2 text-sm font-semibold hover:bg-primary/10 hover:text-primary">
|
||||
<span>{{ nav.text }}</span>
|
||||
<Icon
|
||||
icon="lucide:chevron-down"
|
||||
class="ml-1 text-lg"
|
||||
/>
|
||||
<Icon icon="lucide:chevron-down" class="ml-1 text-lg" />
|
||||
</DropdownMenuSubTrigger>
|
||||
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuSubContent class="bg-card border-muted will-change-[transform,opacity] data-[state=open]:data-[side=bottom]:animate-slideUpAndFade z-10 w-[180px] border rounded-xl p-2 shadow-[0_10px_38px_-10px_hsla(206,22%,7%,.35),0_10px_20px_-15px_hsla(206,22%,7%,.2)] focus:shadow-[0_10px_38px_-10px_hsla(206,22%,7%,.35),0_10px_20px_-15px_hsla(206,22%,7%,.2),0_0_0_2px_green7]">
|
||||
<DropdownMenuSubContent
|
||||
class="border-muted will-change-[transform,opacity] data-[state=open]:data-[side=bottom]:animate-slideUpAndFade z-10 w-[180px] border rounded-xl bg-white/60 p-2 shadow-sm backdrop-blur-md transition-shadow duration-200 ease-in-out dark:bg-neutral-800/30 focus:shadow-lg"
|
||||
>
|
||||
<DropdownMenuItem
|
||||
v-for="item in nav.items"
|
||||
:key="item.text"
|
||||
@@ -119,11 +122,7 @@ watch(path, () => {
|
||||
class="w-full flex items-center justify-between"
|
||||
>
|
||||
<span>{{ item.text }}</span>
|
||||
<Icon
|
||||
icon="lucide:arrow-up-right"
|
||||
class="ml-2 text-base"
|
||||
/>
|
||||
|
||||
<Icon icon="lucide:arrow-up-right" class="ml-2 text-base" />
|
||||
</a>
|
||||
<div v-else>
|
||||
{{ item.text }}
|
||||
@@ -144,7 +143,7 @@ watch(path, () => {
|
||||
<label
|
||||
for="theme-toggle"
|
||||
class="text-muted-foreground font-semibold"
|
||||
>Appearance</label>
|
||||
>{{ t('docs.theme.navbar.appearance.title') }}</label>
|
||||
<DropdownMenuItem
|
||||
as-child
|
||||
@select.prevent
|
||||
@@ -153,6 +152,15 @@ watch(path, () => {
|
||||
</DropdownMenuItem>
|
||||
</div>
|
||||
|
||||
<div class="mt-2 flex items-center justify-between text-sm">
|
||||
<DropdownMenuItem
|
||||
as-child
|
||||
@select.prevent
|
||||
>
|
||||
<NavbarLanguageSubMenu hide-icon />
|
||||
</DropdownMenuItem>
|
||||
</div>
|
||||
|
||||
<Separator
|
||||
class="bg-muted my-2 h-px w-full"
|
||||
decorative
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
<script setup lang="ts">
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { useData, useRoute } from 'vitepress'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import DropdownMenu from './DropdownMenu.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
hideIcon?: boolean
|
||||
}>()
|
||||
|
||||
const { site, lang } = useData()
|
||||
const route = useRoute()
|
||||
const { t } = useI18n()
|
||||
|
||||
const languages = computed(() => {
|
||||
const replacedLink = (targetLang: string) => {
|
||||
return route.path.replace(`/${lang.value}/`, `/${targetLang}/`)
|
||||
}
|
||||
|
||||
return Object.values(site.value.locales).map(locale => ({
|
||||
text: locale.label,
|
||||
link: replacedLink(locale.lang!),
|
||||
}))
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenu
|
||||
:label="t('docs.theme.navbar.language.title')"
|
||||
:items="languages"
|
||||
>
|
||||
<template #trigger>
|
||||
<Icon
|
||||
v-if="!props.hideIcon"
|
||||
icon="lucide:globe"
|
||||
class="text-lg"
|
||||
/>
|
||||
<Icon
|
||||
icon="lucide:chevron-down"
|
||||
class="ml-1 text-lg"
|
||||
/>
|
||||
</template>
|
||||
</DropdownMenu>
|
||||
</template>
|
||||
@@ -0,0 +1,53 @@
|
||||
<script setup lang="ts">
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { DropdownMenuItem, DropdownMenuPortal, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger } from 'reka-ui'
|
||||
import { useData, useRoute } from 'vitepress'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { site, lang } = useData()
|
||||
const route = useRoute()
|
||||
const { t } = useI18n()
|
||||
|
||||
const languages = computed(() => {
|
||||
const replacedLink = (targetLang: string) => {
|
||||
return route.path.replace(`/${lang.value}/`, `/${targetLang}/`)
|
||||
}
|
||||
|
||||
return Object.values(site.value.locales).map(locale => ({
|
||||
text: locale.label,
|
||||
link: replacedLink(locale.lang!),
|
||||
}))
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger class="text-muted-foreground h-full w-full inline-flex items-center justify-between rounded p-2 text-sm font-semibold hover:bg-primary/10 hover:text-primary">
|
||||
<span>{{ t('docs.theme.navbar.language.title') }}</span>
|
||||
<Icon icon="lucide:chevron-down" class="ml-1 text-lg" />
|
||||
</DropdownMenuSubTrigger>
|
||||
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuSubContent class="border-muted bg-card will-change-[transform,opacity] data-[state=open]:data-[side=bottom]:animate-slideUpAndFade z-10 w-[180px] border rounded-xl p-2 shadow-[0_10px_38px_-10px_hsla(206,22%,7%,.35),0_10px_20px_-15px_hsla(206,22%,7%,.2)] focus:shadow-[0_10px_38px_-10px_hsla(206,22%,7%,.35),0_10px_20px_-15px_hsla(206,22%,7%,.2),0_0_0_2px_green7]">
|
||||
<DropdownMenuItem
|
||||
v-for="item in languages"
|
||||
:key="item.text"
|
||||
class="text-muted-foreground h-full w-full inline-flex items-center rounded p-2 text-sm font-semibold hover:bg-primary/10 hover:text-primary"
|
||||
>
|
||||
<a
|
||||
v-if="item.link"
|
||||
:href="item.link"
|
||||
target="_blank"
|
||||
class="w-full flex items-center justify-between"
|
||||
>
|
||||
<span>{{ item.text }}</span>
|
||||
</a>
|
||||
<div v-else>
|
||||
{{ item.text }}
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuPortal>
|
||||
</DropdownMenuSub>
|
||||
</template>
|
||||
@@ -12,6 +12,7 @@ import { computedAsync, debouncedWatch } from '@vueuse/core'
|
||||
import { DialogClose, ListboxContent, ListboxFilter, ListboxItem, ListboxRoot } from 'reka-ui'
|
||||
import { useData } from 'vitepress'
|
||||
import { markRaw, nextTick, onMounted, ref, shallowRef, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { LRUCache } from '../functions/cache'
|
||||
|
||||
@@ -20,6 +21,7 @@ const emits = defineEmits<{
|
||||
}>()
|
||||
|
||||
const { localeIndex } = useData()
|
||||
const { t } = useI18n()
|
||||
|
||||
const filterText = ref('')
|
||||
const enableNoResults = ref(false)
|
||||
@@ -141,7 +143,7 @@ function formMarkRegex(terms: Set<string>) {
|
||||
<ListboxFilter
|
||||
v-model="filterText"
|
||||
class="placeholder:text-muted-foreground h-12 w-full flex-1 bg-transparent text-sm outline-none md:h-14 focus:outline-none"
|
||||
placeholder="Search documentation"
|
||||
:placeholder="t('docs.theme.search.placeholder')"
|
||||
auto-focus
|
||||
/>
|
||||
<DialogClose>
|
||||
|
||||
@@ -4,11 +4,13 @@ import { useMagicKeys, whenever } from '@vueuse/core'
|
||||
import { AnimatePresence, Motion } from 'motion-v'
|
||||
import { DialogContent, DialogDescription, DialogOverlay, DialogPortal, DialogRoot, DialogTitle, DialogTrigger } from 'reka-ui'
|
||||
import { defineAsyncComponent, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const SearchCommandBox = defineAsyncComponent(() => import('./SearchCommandBox.vue'))
|
||||
|
||||
const open = ref(false)
|
||||
const { meta_k } = useMagicKeys()
|
||||
const { t } = useI18n()
|
||||
|
||||
whenever(meta_k, (n) => {
|
||||
if (n)
|
||||
@@ -26,7 +28,7 @@ function handleClose() {
|
||||
<DialogRoot v-model:open="open">
|
||||
<DialogTrigger class="border-muted hover:bg-muted text-muted-foreground text-md md:bg-card flex items-center rounded-lg px-3 py-[7px] transition-colors duration-200 ease-in-out space-x-2 md:border md:text-sm">
|
||||
<Icon icon="lucide:search" />
|
||||
<span class="w-24 text-left hidden lg:w-40 md:inline-flex">Search</span>
|
||||
<span class="w-24 text-left hidden lg:w-40 md:inline-flex">{{ t('docs.theme.search.title') }}</span>
|
||||
<span class="text-xs hidden md:inline-flex">
|
||||
<kbd>⌘ K</kbd>
|
||||
</span>
|
||||
|
||||
@@ -25,14 +25,8 @@ watchPostEffect(() => {
|
||||
<SwitchThumb
|
||||
class="text-muted-foreground border-muted bg-background my-auto h-5 w-5 flex translate-x-0.5 items-center justify-center border rounded-full text-xs will-change-transform data-[state=checked]:translate-x-5 !transition-transform"
|
||||
>
|
||||
<Icon
|
||||
v-if="isDark"
|
||||
icon="lucide:moon-star"
|
||||
/>
|
||||
<Icon
|
||||
v-else
|
||||
icon="lucide:sun"
|
||||
/>
|
||||
<Icon v-if="isDark" icon="lucide:moon-star" />
|
||||
<Icon v-else icon="lucide:sun" />
|
||||
</SwitchThumb>
|
||||
</SwitchRoot>
|
||||
</template>
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { useData } from 'vitepress'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
export function useEditLink() {
|
||||
const { theme, page } = useData()
|
||||
const { t } = useI18n()
|
||||
|
||||
return computed(() => {
|
||||
const { text = 'Edit this page', pattern = '' } = theme.value.editLink || {}
|
||||
const { text = t('docs.theme.doc.community.edit.title'), pattern = '' } = theme.value.editLink || {}
|
||||
let url: string
|
||||
if (typeof pattern === 'function') {
|
||||
url = pattern(page.value)
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import type { DefaultTheme } from 'vitepress'
|
||||
|
||||
import { posix } from 'node:path'
|
||||
import { join, posix, resolve } from 'node:path'
|
||||
import { env } from 'node:process'
|
||||
|
||||
import anchor from 'markdown-it-anchor'
|
||||
import unocss from 'unocss/vite'
|
||||
import yaml from 'unplugin-yaml/vite'
|
||||
|
||||
import { footnote } from '@mdit/plugin-footnote'
|
||||
import { tasklist } from '@mdit/plugin-tasklist'
|
||||
@@ -65,55 +66,115 @@ export default defineConfig({
|
||||
base: env.BASE_URL || '/',
|
||||
lastUpdated: true,
|
||||
sitemap: { hostname: ogUrl },
|
||||
themeConfig: {
|
||||
// https://vitepress.dev/reference/default-theme-config
|
||||
nav: [
|
||||
{ text: 'Docs', link: withBase('/overview/') },
|
||||
{ text: 'Blog', link: withBase('/blog/') },
|
||||
{
|
||||
text: `v${version}`,
|
||||
items: [
|
||||
{ text: 'Release Notes ', link: releases },
|
||||
],
|
||||
},
|
||||
],
|
||||
outline: {
|
||||
level: 'deep',
|
||||
},
|
||||
logo: '/favicon.svg',
|
||||
|
||||
sidebar: [
|
||||
{
|
||||
text: 'Overview',
|
||||
icon: 'lucide:rocket',
|
||||
items: [
|
||||
{ text: 'Introduction', link: withBase('/overview/') },
|
||||
{ text: 'About AI Vtuber', link: withBase('/overview/about-ai-vtuber') },
|
||||
{ text: 'About Neuro-sama', link: withBase('/overview/about-neuro-sama') },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Guides',
|
||||
icon: 'lucide:book-open',
|
||||
items: [
|
||||
{ text: 'Contributing', link: withBase('/guides/contributing/') },
|
||||
locales: {
|
||||
'root': {
|
||||
label: 'English',
|
||||
lang: 'en',
|
||||
themeConfig: {
|
||||
// https://vitepress.dev/reference/default-theme-config
|
||||
nav: [
|
||||
{ text: 'Docs', link: withBase('/en/overview/') },
|
||||
{ text: 'Blog', link: withBase('/en/blog/') },
|
||||
{
|
||||
text: 'Design Guidelines',
|
||||
link: withBase('/guides/design-guidelines/'),
|
||||
text: `v${version}`,
|
||||
items: [
|
||||
{ text: 'Resources', link: withBase('/guides/contributing/design-guidelines/resources') },
|
||||
{ text: 'Tools', link: withBase('/guides/contributing/design-guidelines/tools') },
|
||||
{ text: 'Release Notes ', link: releases },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Characters',
|
||||
icon: 'lucide:scan-face',
|
||||
link: withBase('/characters/'),
|
||||
},
|
||||
] as (DefaultTheme.SidebarItem & { icon?: string })[],
|
||||
outline: {
|
||||
level: 'deep',
|
||||
},
|
||||
logo: '/favicon.svg',
|
||||
|
||||
sidebar: [
|
||||
{
|
||||
text: 'Overview',
|
||||
icon: 'lucide:rocket',
|
||||
items: [
|
||||
{ text: 'Introduction', link: withBase('/en/overview/') },
|
||||
{ text: 'About AI VTuber', link: withBase('/en/overview/about-ai-vtuber') },
|
||||
{ text: 'About Neuro-sama', link: withBase('/en/overview/about-neuro-sama') },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Guides',
|
||||
icon: 'lucide:book-open',
|
||||
items: [
|
||||
{ text: 'Contributing', link: withBase('/en/guides/contributing/') },
|
||||
{
|
||||
text: 'Design Guidelines',
|
||||
link: withBase('/en/guides/design-guidelines/'),
|
||||
items: [
|
||||
{ text: 'Resources', link: withBase('/en/guides/contributing/design-guidelines/resources') },
|
||||
{ text: 'Tools', link: withBase('/en/guides/contributing/design-guidelines/tools') },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Characters',
|
||||
icon: 'lucide:scan-face',
|
||||
link: withBase('/en/characters/'),
|
||||
},
|
||||
] as (DefaultTheme.SidebarItem & { icon?: string })[],
|
||||
},
|
||||
},
|
||||
'zh-Hans': {
|
||||
label: '简体中文',
|
||||
lang: 'zh-Hans',
|
||||
themeConfig: {
|
||||
// https://vitepress.dev/reference/default-theme-config
|
||||
nav: [
|
||||
{ text: '文档', link: withBase('/zh-Hans/overview/') },
|
||||
{ text: '博客 / 开发日志', link: withBase('/zh-Hans/blog/') },
|
||||
{
|
||||
text: `v${version}`,
|
||||
items: [
|
||||
{ text: '发布说明 ', link: releases },
|
||||
],
|
||||
},
|
||||
],
|
||||
outline: {
|
||||
level: 'deep',
|
||||
},
|
||||
logo: '/favicon.svg',
|
||||
|
||||
sidebar: [
|
||||
{
|
||||
text: '概览',
|
||||
icon: 'lucide:rocket',
|
||||
items: [
|
||||
{ text: '介绍', link: withBase('/zh-Hans/overview/') },
|
||||
{ text: '有关 AI VTuber', link: withBase('/zh-Hans/overview/about-ai-vtuber') },
|
||||
{ text: '有关 Neuro-sama', link: withBase('/zh-Hans/overview/about-neuro-sama') },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: '指南',
|
||||
icon: 'lucide:book-open',
|
||||
items: [
|
||||
{ text: '贡献指南', link: withBase('/zh-Hans/guides/contributing/') },
|
||||
{
|
||||
text: '设计指引',
|
||||
link: withBase('/zh-Hans/guides/design-guidelines/'),
|
||||
items: [
|
||||
{ text: '参考资源', link: withBase('/zh-Hans/guides/contributing/design-guidelines/resources') },
|
||||
{ text: '工具', link: withBase('/zh-Hans/guides/contributing/design-guidelines/tools') },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: '角色',
|
||||
icon: 'lucide:scan-face',
|
||||
link: withBase('/zh-Hans/characters/'),
|
||||
},
|
||||
] as (DefaultTheme.SidebarItem & { icon?: string })[],
|
||||
},
|
||||
},
|
||||
},
|
||||
themeConfig: {
|
||||
socialLinks: [
|
||||
{ icon: 'discord', link: discord },
|
||||
{ icon: 'github', link: github },
|
||||
@@ -177,8 +238,14 @@ export default defineConfig({
|
||||
pageData.frontmatter.sidebar = pageData.frontmatter.layout !== 'showcase'
|
||||
},
|
||||
vite: {
|
||||
resolve: {
|
||||
alias: {
|
||||
'@proj-airi/i18n': resolve(join(import.meta.dirname, '..', '..', 'packages', 'i18n', 'src')),
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
unocss(),
|
||||
yaml(),
|
||||
],
|
||||
css: {
|
||||
postcss: {
|
||||
|
||||
@@ -2,25 +2,26 @@
|
||||
import { useScroll } from '@vueuse/core'
|
||||
import { TooltipProvider } from 'reka-ui'
|
||||
import { useData } from 'vitepress'
|
||||
import { computed, toRefs } from 'vue'
|
||||
import { computed, toRefs, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
// import HomePageDemo from '../components/HomePageDemo.vue'
|
||||
import Home from '../components/Home.vue'
|
||||
import Navbar from '../components/Navbar.vue'
|
||||
import SearchTrigger from '../components/SearchTrigger.vue'
|
||||
import Docs from './Docs.vue'
|
||||
import Showcase from './Showcase.vue'
|
||||
|
||||
const { site, theme, frontmatter } = useData<{
|
||||
logo: string
|
||||
}>()
|
||||
const { site, theme, frontmatter, lang } = useData<{ logo: string }>()
|
||||
const { locale } = useI18n()
|
||||
const { arrivedState } = useScroll(globalThis.window)
|
||||
|
||||
const { top } = toRefs(arrivedState)
|
||||
const logo = computed(() => theme.value.logo)
|
||||
const title = computed(() => site.value.title)
|
||||
const layout = computed(() => frontmatter.value.layout)
|
||||
const isHome = computed(() => layout.value === 'home')
|
||||
|
||||
const { arrivedState } = useScroll(globalThis.window)
|
||||
const { top } = toRefs(arrivedState)
|
||||
watch(lang, () => locale.value = lang.value, { immediate: true })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import type { Theme } from 'vitepress'
|
||||
|
||||
import messages from '@proj-airi/i18n/locales'
|
||||
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
import Layout from '../custom/Layout.vue'
|
||||
|
||||
import '@unocss/reset/tailwind.css'
|
||||
@@ -8,6 +12,14 @@ import './style.css'
|
||||
|
||||
export default {
|
||||
Layout,
|
||||
enhanceApp(_ctx) {
|
||||
enhanceApp({ app }) {
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: 'en',
|
||||
fallbackLocale: 'en',
|
||||
messages,
|
||||
})
|
||||
|
||||
app.use(i18n)
|
||||
},
|
||||
} satisfies Theme
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 207 10% 3.9%;
|
||||
|
||||
--card: 207 8% 97%;
|
||||
--card: 207 4% 100%;
|
||||
--card-foreground: 0 10% 3.9%;
|
||||
|
||||
--popover: 0 0% 100%;
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
---
|
||||
sidebar: false
|
||||
outline: false
|
||||
community: false
|
||||
---
|
||||
|
||||
<script setup>
|
||||
import { data } from '../../.vitepress/theme/blog.data'
|
||||
import BlogPosts from '../../.vitepress/components/BlogPosts.vue'
|
||||
</script>
|
||||
|
||||
<BlogPosts :data="data" />
|
||||
|
Before Width: | Height: | Size: 201 KiB After Width: | Height: | Size: 201 KiB |
|
Before Width: | Height: | Size: 201 KiB After Width: | Height: | Size: 201 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 133 KiB |
|
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 132 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 176 KiB After Width: | Height: | Size: 176 KiB |
|
Before Width: | Height: | Size: 165 KiB After Width: | Height: | Size: 165 KiB |
|
Before Width: | Height: | Size: 165 KiB After Width: | Height: | Size: 165 KiB |
|
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 142 KiB |
|
Before Width: | Height: | Size: 174 KiB After Width: | Height: | Size: 174 KiB |
|
Before Width: | Height: | Size: 181 KiB After Width: | Height: | Size: 181 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 117 KiB |
|
Before Width: | Height: | Size: 125 KiB After Width: | Height: | Size: 125 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 222 KiB After Width: | Height: | Size: 222 KiB |
|
Before Width: | Height: | Size: 202 KiB After Width: | Height: | Size: 202 KiB |
|
Before Width: | Height: | Size: 193 KiB After Width: | Height: | Size: 193 KiB |
|
Before Width: | Height: | Size: 168 KiB After Width: | Height: | Size: 168 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 1019 KiB After Width: | Height: | Size: 1019 KiB |
|
Before Width: | Height: | Size: 588 KiB After Width: | Height: | Size: 588 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 122 KiB |
|
Before Width: | Height: | Size: 410 KiB After Width: | Height: | Size: 410 KiB |
|
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 171 KiB |
|
Before Width: | Height: | Size: 334 KiB After Width: | Height: | Size: 334 KiB |
|
Before Width: | Height: | Size: 244 KiB After Width: | Height: | Size: 244 KiB |
|
Before Width: | Height: | Size: 393 KiB After Width: | Height: | Size: 393 KiB |
|
Before Width: | Height: | Size: 219 KiB After Width: | Height: | Size: 219 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 117 KiB |
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 140 KiB |
|
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 144 KiB |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 677 KiB After Width: | Height: | Size: 677 KiB |
|
Before Width: | Height: | Size: 665 KiB After Width: | Height: | Size: 665 KiB |
|
Before Width: | Height: | Size: 225 KiB After Width: | Height: | Size: 225 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 378 KiB After Width: | Height: | Size: 378 KiB |
|
Before Width: | Height: | Size: 373 KiB After Width: | Height: | Size: 373 KiB |
|
Before Width: | Height: | Size: 14 MiB After Width: | Height: | Size: 14 MiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB |