Перенос ветки для демо1 из док-ии Vega
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { normalizeLink } from 'vitepress/dist/client/theme-default/support/utils'
|
||||
import { EXTERNAL_URL_RE } from 'vitepress/dist/client/shared'
|
||||
|
||||
interface Props {
|
||||
tag?: string
|
||||
size?: 'medium' | 'big'
|
||||
theme?: 'brand' | 'alt' | 'sponsor'
|
||||
text: string
|
||||
href?: string
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
size: 'medium',
|
||||
theme: 'brand'
|
||||
})
|
||||
|
||||
const isExternal = computed(
|
||||
() => props.href && EXTERNAL_URL_RE.test(props.href)
|
||||
)
|
||||
|
||||
const component = computed(() => {
|
||||
return props.tag || props.href ? 'a' : 'button'
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component
|
||||
:is="component"
|
||||
class="VPButton CustomButton"
|
||||
:class="[size, theme]"
|
||||
:href="href ? normalizeLink(href) : undefined"
|
||||
:target="isExternal ? '_blank' : undefined"
|
||||
:rel="isExternal ? 'noreferrer' : undefined"
|
||||
>
|
||||
{{ text }}
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.VPButton {
|
||||
display: inline-block;
|
||||
border: 1px solid transparent;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
transition: color 0.25s, border-color 0.25s, background-color 0.25s;
|
||||
}
|
||||
|
||||
.VPButton:active {
|
||||
transition: color 0.1s, border-color 0.1s, background-color 0.1s;
|
||||
}
|
||||
|
||||
.VPButton.medium {
|
||||
border-radius: 12px;
|
||||
padding: 0 20px;
|
||||
line-height: 48px;
|
||||
height: 48px;
|
||||
font-size: 17px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.VPButton.big {
|
||||
border-radius: 12px;
|
||||
padding: 0 24px;
|
||||
line-height: 46px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.VPButton.brand {
|
||||
border-color: var(--vp-button-brand-border);
|
||||
color: var(--color-button-contained-text-color);
|
||||
background-color: var(--vp-button-brand-bg);
|
||||
}
|
||||
|
||||
.VPButton.brand:hover {
|
||||
border-color: var(--vp-button-brand-hover-border);
|
||||
color: var(--color-button-contained-text-color);
|
||||
background-color: var(--vp-button-brand-hover-bg);
|
||||
}
|
||||
|
||||
.VPButton.brand:active {
|
||||
border-color: var(--vp-button-brand-active-border);
|
||||
color: var(--color-button-contained-text-color);
|
||||
background-color: var(--vp-button-brand-active-bg);
|
||||
}
|
||||
|
||||
.VPButton.alt {
|
||||
border-color: var(--vp-button-alt-border);
|
||||
color: var(--vp-button-alt-text);
|
||||
background-color: var(--vp-button-alt-bg);
|
||||
}
|
||||
|
||||
.VPButton.alt:hover {
|
||||
border-color: var(--vp-button-alt-hover-border);
|
||||
color: var(--vp-button-alt-hover-text);
|
||||
background-color: var(--vp-button-alt-hover-bg);
|
||||
}
|
||||
|
||||
.VPButton.alt:active {
|
||||
border-color: var(--vp-button-alt-active-border);
|
||||
color: var(--vp-button-alt-active-text);
|
||||
background-color: var(--vp-button-alt-active-bg);
|
||||
}
|
||||
|
||||
.VPButton.sponsor {
|
||||
border-color: var(--vp-button-sponsor-border);
|
||||
color: var(--vp-button-sponsor-text);
|
||||
background-color: var(--vp-button-sponsor-bg);
|
||||
}
|
||||
|
||||
.VPButton.sponsor:hover {
|
||||
border-color: var(--vp-button-sponsor-hover-border);
|
||||
color: var(--vp-button-sponsor-hover-text);
|
||||
background-color: var(--vp-button-sponsor-hover-bg);
|
||||
}
|
||||
|
||||
.VPButton.sponsor:active {
|
||||
border-color: var(--vp-button-sponsor-active-border);
|
||||
color: var(--vp-button-sponsor-active-text);
|
||||
background-color: var(--vp-button-sponsor-active-bg);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,95 @@
|
||||
<script setup lang="ts">
|
||||
import NotFound from 'vitepress/dist/client/theme-default/NotFound.vue'
|
||||
import { useData } from 'vitepress/dist/client/theme-default/composables/data'
|
||||
import { useSidebar } from 'vitepress/dist/client/theme-default/composables/sidebar'
|
||||
import CustomDoc from './CustomDoc.vue'
|
||||
import VPHome from 'vitepress/dist/client/theme-default/components/VPHome.vue'
|
||||
import VPPage from 'vitepress/dist/client/theme-default/components/VPPage.vue'
|
||||
|
||||
const { page, frontmatter } = useData()
|
||||
const { hasSidebar } = useSidebar()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="VPContent CustomContent"
|
||||
id="VPContent"
|
||||
:class="{
|
||||
'has-sidebar': hasSidebar,
|
||||
'is-home': frontmatter.layout === 'home'
|
||||
}"
|
||||
>
|
||||
<slot name="not-found" v-if="page.isNotFound"><NotFound /></slot>
|
||||
|
||||
<VPPage v-else-if="frontmatter.layout === 'page'">
|
||||
<template #page-top><slot name="page-top" /></template>
|
||||
<template #page-bottom><slot name="page-bottom" /></template>
|
||||
</VPPage>
|
||||
|
||||
<VPHome v-else-if="frontmatter.layout === 'home'">
|
||||
<template #home-hero-before><slot name="home-hero-before" /></template>
|
||||
<template #home-hero-info><slot name="home-hero-info" /></template>
|
||||
<template #home-hero-image><slot name="home-hero-image" /></template>
|
||||
<template #home-hero-after><slot name="home-hero-after" /></template>
|
||||
<template #home-features-before><slot name="home-features-before" /></template>
|
||||
<template #home-features-after><slot name="home-features-after" /></template>
|
||||
</VPHome>
|
||||
|
||||
<component
|
||||
v-else-if="frontmatter.layout && frontmatter.layout !== 'doc'"
|
||||
:is="frontmatter.layout"
|
||||
/>
|
||||
|
||||
<CustomDoc v-else>
|
||||
<template #doc-top><slot name="doc-top" /></template>
|
||||
<template #doc-bottom><slot name="doc-bottom" /></template>
|
||||
|
||||
<template #doc-footer-before><slot name="doc-footer-before" /></template>
|
||||
<template #doc-before><slot name="doc-before" /></template>
|
||||
<template #doc-after><slot name="doc-after" /></template>
|
||||
|
||||
<template #aside-top><slot name="aside-top" /></template>
|
||||
<template #aside-outline-before><slot name="aside-outline-before" /></template>
|
||||
<template #aside-outline-after><slot name="aside-outline-after" /></template>
|
||||
<template #aside-ads-before><slot name="aside-ads-before" /></template>
|
||||
<template #aside-ads-after><slot name="aside-ads-after" /></template>
|
||||
<template #aside-bottom><slot name="aside-bottom" /></template>
|
||||
</CustomDoc>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.VPContent {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 0;
|
||||
margin: var(--vp-layout-top-height, 0px) auto 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.VPContent.is-home {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.VPContent.has-sidebar {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.VPContent {
|
||||
padding-top: var(--vp-nav-height);
|
||||
}
|
||||
|
||||
.VPContent.has-sidebar {
|
||||
margin: var(--vp-layout-top-height, 0px) 0 0;
|
||||
padding-left: var(--vp-sidebar-width);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1440px) {
|
||||
.VPContent.has-sidebar {
|
||||
padding-right: calc((100vw - var(--vp-layout-max-width)) / 2);
|
||||
// padding-left: calc((100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,193 @@
|
||||
<script setup lang="ts">
|
||||
import { useRoute } from 'vitepress'
|
||||
import { computed } from 'vue'
|
||||
import { useData } from 'vitepress/dist/client/theme-default/composables/data'
|
||||
import { useSidebar } from 'vitepress/dist/client/theme-default/composables/sidebar'
|
||||
import VPDocAside from 'vitepress/dist/client/theme-default/components/VPDocAside.vue'
|
||||
import VPDocFooter from 'vitepress/dist/client/theme-default/components/VPDocFooter.vue'
|
||||
|
||||
const { theme } = useData()
|
||||
|
||||
const route = useRoute()
|
||||
const { hasSidebar, hasAside, leftAside } = useSidebar()
|
||||
|
||||
const pageName = computed(() =>
|
||||
route.path.replace(/[./]+/g, '_').replace(/_html$/, '')
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="VPDoc CustomDoc"
|
||||
:class="{ 'has-sidebar': hasSidebar, 'has-aside': hasAside }"
|
||||
>
|
||||
<slot name="doc-top" />
|
||||
<div class="container">
|
||||
<div v-if="hasAside" class="aside" :class="{'left-aside': leftAside}">
|
||||
<div class="aside-curtain" />
|
||||
<div class="aside-container">
|
||||
<div class="aside-content">
|
||||
<VPDocAside>
|
||||
<template #aside-top><slot name="aside-top" /></template>
|
||||
<template #aside-bottom><slot name="aside-bottom" /></template>
|
||||
<template #aside-outline-before><slot name="aside-outline-before" /></template>
|
||||
<template #aside-outline-after><slot name="aside-outline-after" /></template>
|
||||
<template #aside-ads-before><slot name="aside-ads-before" /></template>
|
||||
<template #aside-ads-after><slot name="aside-ads-after" /></template>
|
||||
</VPDocAside>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div class="content-container">
|
||||
<slot name="doc-before" />
|
||||
<main class="main">
|
||||
<Content
|
||||
class="vp-doc"
|
||||
:class="[
|
||||
pageName,
|
||||
theme.externalLinkIcon && 'external-link-icon-enabled'
|
||||
]"
|
||||
/>
|
||||
</main>
|
||||
<VPDocFooter>
|
||||
<template #doc-footer-before><slot name="doc-footer-before" /></template>
|
||||
</VPDocFooter>
|
||||
<slot name="doc-after" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<slot name="doc-bottom" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.VPDoc {
|
||||
padding: 32px 24px 96px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.VPDoc {
|
||||
padding: 48px 32px 128px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.VPDoc {
|
||||
padding: 48px 32px 0;
|
||||
}
|
||||
|
||||
.VPDoc:not(.has-sidebar) .container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
max-width: 992px;
|
||||
}
|
||||
|
||||
.VPDoc:not(.has-sidebar) .content {
|
||||
max-width: 752px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1280px) {
|
||||
.VPDoc .container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.VPDoc .aside {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1440px) {
|
||||
.VPDoc:not(.has-sidebar) .content {
|
||||
max-width: 784px;
|
||||
}
|
||||
|
||||
.VPDoc:not(.has-sidebar) .container {
|
||||
max-width: 1104px;
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.aside {
|
||||
position: relative;
|
||||
display: none;
|
||||
order: 2;
|
||||
flex-grow: 1;
|
||||
padding-left: 32px;
|
||||
width: 100%;
|
||||
max-width: 256px;
|
||||
}
|
||||
|
||||
.left-aside {
|
||||
order: 1;
|
||||
padding-left: unset;
|
||||
padding-right: 32px;
|
||||
}
|
||||
|
||||
.aside-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
padding-top: calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + var(--vp-doc-top-height, 0px) + 48px);
|
||||
width: 224px;
|
||||
height: 100vh;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.aside-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.aside-curtain {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
z-index: 10;
|
||||
width: 224px;
|
||||
height: 32px;
|
||||
background: linear-gradient(transparent, var(--vp-c-bg) 70%);
|
||||
}
|
||||
|
||||
.aside-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: calc(100vh - (var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 48px));
|
||||
padding-bottom: 32px;
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.content {
|
||||
padding: 0 32px 128px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1280px) {
|
||||
.content {
|
||||
order: 1;
|
||||
margin: 0;
|
||||
min-width: 640px;
|
||||
}
|
||||
}
|
||||
|
||||
.content-container {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.VPDoc.has-aside .content-container {
|
||||
max-width: 990px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,149 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useData } from 'vitepress/dist/client/theme-default/composables/data'
|
||||
import { useEditLink } from 'vitepress/dist/client/theme-default/composables/edit-link'
|
||||
import { usePrevNext } from 'vitepress/dist/client/theme-default/composables/prev-next'
|
||||
import VPIconEdit from 'vitepress/dist/client/theme-default/components/icons/VPIconEdit.vue'
|
||||
import VPLink from 'vitepress/dist/client/theme-default/components/VPLink.vue'
|
||||
import VPDocFooterLastUpdated from 'vitepress/dist/client/theme-default/components/VPDocFooterLastUpdated.vue'
|
||||
|
||||
const { theme, page, frontmatter } = useData()
|
||||
|
||||
const editLink = useEditLink()
|
||||
const control = usePrevNext()
|
||||
|
||||
const hasEditLink = computed(() => {
|
||||
return theme.value.editLink && frontmatter.value.editLink !== false
|
||||
})
|
||||
const hasLastUpdated = computed(() => {
|
||||
return page.value.lastUpdated && frontmatter.value.lastUpdated !== false
|
||||
})
|
||||
const showFooter = computed(() => {
|
||||
return hasEditLink.value || hasLastUpdated.value || control.value.prev || control.value.next
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<footer v-if="showFooter" class="VPDocFooter CustomDocFooter">
|
||||
<slot name="doc-footer-before" />
|
||||
|
||||
<div v-if="hasEditLink || hasLastUpdated" class="edit-info">
|
||||
<div v-if="hasEditLink" class="edit-link">
|
||||
<VPLink class="edit-link-button" :href="editLink.url" :no-icon="true">
|
||||
<VPIconEdit class="edit-link-icon" aria-label="edit icon"/>
|
||||
{{ editLink.text }}
|
||||
</VPLink>
|
||||
</div>
|
||||
|
||||
<div v-if="hasLastUpdated" class="last-updated">
|
||||
<VPDocFooterLastUpdated />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav v-if="control.prev?.link || control.next?.link" class="prev-next">
|
||||
<div class="pager">
|
||||
<VPLink v-if="control.prev?.link" class="pager-link prev" :href="control.prev.link">
|
||||
<span class="desc" v-html="theme.docFooter?.prev || 'Previous page'"></span>
|
||||
<span class="title" v-html="control.prev.text"></span>
|
||||
</VPLink>
|
||||
</div>
|
||||
<div class="pager">
|
||||
<VPLink v-if="control.next?.link" class="pager-link next" :href="control.next.link">
|
||||
<span class="desc" v-html="theme.docFooter?.next || 'Next page'"></span>
|
||||
<span class="title" v-html="control.next.text"></span>
|
||||
</VPLink>
|
||||
</div>
|
||||
</nav>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.VPDocFooter {
|
||||
margin-top: 64px;
|
||||
}
|
||||
|
||||
.edit-info {
|
||||
padding-bottom: 18px;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.edit-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-bottom: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-link-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: 0;
|
||||
line-height: 32px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--vp-c-brand-1);
|
||||
transition: color 0.25s;
|
||||
}
|
||||
|
||||
.edit-link-button:hover {
|
||||
color: var(--vp-c-brand-2);
|
||||
}
|
||||
|
||||
.edit-link-icon {
|
||||
margin-right: 8px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.prev-next {
|
||||
border-top: 1px solid var(--vp-c-divider);
|
||||
padding-top: 24px;
|
||||
display: grid;
|
||||
grid-row-gap: 8px;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.prev-next {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
grid-column-gap: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.pager-link {
|
||||
display: block;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
padding: 11px 16px 13px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
|
||||
.pager-link:hover {
|
||||
border-color: var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.pager-link.next {
|
||||
margin-left: auto;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.desc {
|
||||
display: block;
|
||||
line-height: 20px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.title {
|
||||
display: block;
|
||||
line-height: 20px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #1a73e8;
|
||||
transition: color 0.25s;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,129 @@
|
||||
<script setup lang="ts">
|
||||
import type { DefaultTheme } from 'vitepress/theme'
|
||||
import VPImage from 'vitepress/dist/client/theme-default/components/VPImage.vue'
|
||||
import VPLink from 'vitepress/dist/client/theme-default/components/VPLink.vue'
|
||||
import VPIconArrowRight from 'vitepress/dist/client/theme-default/components/icons/VPIconArrowRight.vue'
|
||||
|
||||
defineProps<{
|
||||
icon?: DefaultTheme.FeatureIcon
|
||||
title: string
|
||||
details?: string
|
||||
link?: string
|
||||
linkText?: string
|
||||
rel?: string
|
||||
target?: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VPLink
|
||||
class="VPFeature CustomFeature"
|
||||
:href="link"
|
||||
:rel="rel"
|
||||
:target="target"
|
||||
:no-icon="true"
|
||||
:tag="link ? 'a' : 'div'"
|
||||
>
|
||||
<article class="box">
|
||||
<div v-if="typeof icon === 'object' && icon.wrap" class="icon">
|
||||
<VPImage
|
||||
:image="icon"
|
||||
:alt="icon.alt"
|
||||
:height="icon.height || 48"
|
||||
:width="icon.width || 48"
|
||||
/>
|
||||
</div>
|
||||
<VPImage
|
||||
v-else-if="typeof icon === 'object'"
|
||||
:image="icon"
|
||||
:alt="icon.alt"
|
||||
:height="icon.height || 48"
|
||||
:width="icon.width || 48"
|
||||
/>
|
||||
<div v-else-if="icon" class="icon" v-html="icon"></div>
|
||||
<h2 class="title" v-html="title"></h2>
|
||||
<p v-if="details" class="details" v-html="details"></p>
|
||||
|
||||
<div v-if="linkText" class="link-text">
|
||||
<p class="link-text-value">
|
||||
{{ linkText }} <VPIconArrowRight class="link-text-icon" />
|
||||
</p>
|
||||
</div>
|
||||
</article>
|
||||
</VPLink>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.VPFeature {
|
||||
display: block;
|
||||
border: 1px solid var(--vp-c-bg-soft);
|
||||
border-radius: 12px;
|
||||
height: 100%;
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
transition: border-color 0.25s, background-color 0.25s;
|
||||
}
|
||||
|
||||
.VPFeature.link:hover {
|
||||
border-color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 24px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.box > :deep(.VPImage) {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 6px;
|
||||
background-color: var(--vp-c-default-soft);
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
font-size: 24px;
|
||||
transition: background-color 0.25s;
|
||||
}
|
||||
|
||||
.title {
|
||||
line-height: 24px;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
color: var(--color-text-active);
|
||||
}
|
||||
|
||||
.details {
|
||||
flex-grow: 1;
|
||||
padding-top: 8px;
|
||||
line-height: 24px;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: var(--color-text-inactive);
|
||||
}
|
||||
|
||||
.link-text {
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.link-text-value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.link-text-icon {
|
||||
display: inline-block;
|
||||
margin-left: 6px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
fill: currentColor;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,329 @@
|
||||
<script setup lang="ts">
|
||||
import { type Ref, inject } from 'vue'
|
||||
import type { DefaultTheme } from 'vitepress/theme'
|
||||
import CustomButton from './CustomButton.vue'
|
||||
import VPImage from 'vitepress/dist/client/theme-default/components/VPImage.vue'
|
||||
|
||||
export interface HeroAction {
|
||||
theme?: 'brand' | 'alt'
|
||||
text: string
|
||||
link: string
|
||||
}
|
||||
|
||||
defineProps<{
|
||||
name?: string
|
||||
text?: string
|
||||
tagline?: string
|
||||
image?: DefaultTheme.ThemeableImage
|
||||
actions?: HeroAction[]
|
||||
}>()
|
||||
|
||||
const heroImageSlotExists = inject('hero-image-slot-exists') as Ref<boolean>
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="VPHero" :class="{ 'has-image': image || heroImageSlotExists }">
|
||||
<div class="container">
|
||||
<div class="main">
|
||||
<slot name="home-hero-info">
|
||||
<h1 v-if="name" class="name">
|
||||
<span v-html="name" class="clip"></span>
|
||||
</h1>
|
||||
<p v-if="text" v-html="text" class="text"></p>
|
||||
<p v-if="tagline" v-html="tagline" class="tagline"></p>
|
||||
</slot>
|
||||
|
||||
<div v-if="actions" class="actions">
|
||||
<div v-for="action in actions" :key="action.link" class="action">
|
||||
<CustomButton
|
||||
tag="a"
|
||||
size="medium"
|
||||
:theme="action.theme"
|
||||
:text="action.text"
|
||||
:href="action.link"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="image || heroImageSlotExists" class="image">
|
||||
<div class="image-container">
|
||||
<div class="image-bg" />
|
||||
<slot name="home-hero-image">
|
||||
<VPImage v-if="image" class="image-src" :image="image" />
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.VPHero {
|
||||
margin-top: calc((var(--vp-nav-height) + var(--vp-layout-top-height, 0px)) * -1);
|
||||
padding: calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 48px) 24px 48px;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.VPHero {
|
||||
padding: calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 80px) 48px 64px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.VPHero {
|
||||
padding: calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 80px) 64px 64px;
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0 auto;
|
||||
max-width: 1152px;
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.container {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
.main {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
order: 2;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.VPHero.has-image .container {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.VPHero.has-image .container {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.main {
|
||||
order: 1;
|
||||
width: calc((100% / 3) * 2);
|
||||
}
|
||||
|
||||
.VPHero.has-image .main {
|
||||
max-width: 592px;
|
||||
}
|
||||
}
|
||||
|
||||
.name,
|
||||
.text {
|
||||
max-width: 392px;
|
||||
letter-spacing: -0.4px;
|
||||
line-height: 40px;
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.VPHero.has-image .name,
|
||||
.VPHero.has-image .text {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.name {
|
||||
color: var(--vp-home-hero-name-color);
|
||||
}
|
||||
|
||||
.clip {
|
||||
background: var(--color-gradient-magma);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
-webkit-text-fill-color: var(--vp-home-hero-name-color);
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.name,
|
||||
.text {
|
||||
max-width: 576px;
|
||||
line-height: 56px;
|
||||
font-size: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.name,
|
||||
.text {
|
||||
line-height: 64px;
|
||||
font-size: 56px;
|
||||
}
|
||||
|
||||
.VPHero.has-image .name,
|
||||
.VPHero.has-image .text {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.tagline {
|
||||
padding-top: 8px;
|
||||
max-width: 392px;
|
||||
line-height: 28px;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
white-space: pre-wrap;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.VPHero.has-image .tagline {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.tagline {
|
||||
padding-top: 12px;
|
||||
max-width: 576px;
|
||||
line-height: 32px;
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.tagline {
|
||||
line-height: 36px;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.VPHero.has-image .tagline {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin: -6px;
|
||||
padding-top: 24px;
|
||||
}
|
||||
|
||||
.VPHero.has-image .actions {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.actions {
|
||||
padding-top: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.VPHero.has-image .actions {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
.action {
|
||||
flex-shrink: 0;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.image {
|
||||
order: 1;
|
||||
margin: -76px -24px -48px;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.image {
|
||||
margin: -108px -24px -48px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.image {
|
||||
flex-grow: 1;
|
||||
order: 2;
|
||||
margin: 0;
|
||||
min-height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.image-container {
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
width: 320px;
|
||||
height: 320px;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.image-container {
|
||||
width: 392px;
|
||||
height: 392px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.image-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/*rtl:ignore*/
|
||||
transform: translate(-32px, -32px);
|
||||
}
|
||||
}
|
||||
|
||||
.image-bg {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
/*rtl:ignore*/
|
||||
left: 50%;
|
||||
border-radius: 50%;
|
||||
width: 192px;
|
||||
height: 192px;
|
||||
background-image: var(--vp-home-hero-image-background-image);
|
||||
filter: var(--vp-home-hero-image-filter);
|
||||
/*rtl:ignore*/
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.image-bg {
|
||||
width: 256px;
|
||||
height: 256px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.image-bg {
|
||||
width: 320px;
|
||||
height: 320px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.image-src) {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
/*rtl:ignore*/
|
||||
left: 50%;
|
||||
max-width: 192px;
|
||||
max-height: 192px;
|
||||
/*rtl:ignore*/
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
:deep(.image-src) {
|
||||
max-width: 256px;
|
||||
max-height: 256px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
:deep(.image-src) {
|
||||
max-width: 320px;
|
||||
max-height: 320px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,943 @@
|
||||
<script lang="ts" setup>
|
||||
import localSearchIndex from '@localSearchIndex'
|
||||
import {
|
||||
computedAsync,
|
||||
debouncedWatch,
|
||||
onKeyStroke,
|
||||
useEventListener,
|
||||
useLocalStorage,
|
||||
useScrollLock,
|
||||
useSessionStorage
|
||||
} from '@vueuse/core'
|
||||
import { useFocusTrap } from '@vueuse/integrations/useFocusTrap'
|
||||
import Mark from 'mark.js/src/vanilla.js'
|
||||
import MiniSearch, { type SearchResult } from 'minisearch'
|
||||
import { dataSymbol, inBrowser, useRouter } from 'vitepress'
|
||||
import {
|
||||
computed,
|
||||
createApp,
|
||||
markRaw,
|
||||
nextTick,
|
||||
onBeforeUnmount,
|
||||
onMounted,
|
||||
ref,
|
||||
shallowRef,
|
||||
watch,
|
||||
watchEffect,
|
||||
type Ref
|
||||
} from 'vue'
|
||||
import type { ModalTranslations } from 'vitepress/types/local-search'
|
||||
import { pathToFile } from 'vitepress/dist/client/app/utils'
|
||||
import { escapeRegExp } from 'vitepress/dist/client/shared'
|
||||
import { useData } from 'vitepress/dist/client/theme-default/composables/data'
|
||||
import { LRUCache } from 'vitepress/dist/client/theme-default/support/lru'
|
||||
import { createSearchTranslate } from 'vitepress/dist/client/theme-default/support/translation'
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'close'): void
|
||||
}>()
|
||||
|
||||
const el = shallowRef<HTMLElement>()
|
||||
const resultsEl = shallowRef<HTMLElement>()
|
||||
|
||||
/* Search */
|
||||
|
||||
const searchIndexData = shallowRef(localSearchIndex)
|
||||
|
||||
// hmr
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.accept('/@localSearchIndex', (m) => {
|
||||
if (m) {
|
||||
searchIndexData.value = m.default
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
interface Result {
|
||||
title: string
|
||||
titles: string[]
|
||||
text?: string
|
||||
}
|
||||
|
||||
const vitePressData = useData()
|
||||
const { activate } = useFocusTrap(el, {
|
||||
immediate: true,
|
||||
allowOutsideClick: true,
|
||||
clickOutsideDeactivates: true,
|
||||
escapeDeactivates: true
|
||||
})
|
||||
const { localeIndex, theme } = vitePressData
|
||||
const searchIndex = computedAsync(async () =>
|
||||
markRaw(
|
||||
MiniSearch.loadJSON<Result>(
|
||||
(await searchIndexData.value[localeIndex.value]?.())?.default,
|
||||
{
|
||||
fields: ['title', 'titles', 'text'],
|
||||
storeFields: ['title', 'titles'],
|
||||
searchOptions: {
|
||||
fuzzy: 0.2,
|
||||
prefix: true,
|
||||
boost: { title: 4, text: 2, titles: 1 },
|
||||
...(theme.value.search?.provider === 'local' &&
|
||||
theme.value.search.options?.miniSearch?.searchOptions)
|
||||
},
|
||||
...(theme.value.search?.provider === 'local' &&
|
||||
theme.value.search.options?.miniSearch?.options)
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
const disableQueryPersistence = computed(() => {
|
||||
return (
|
||||
theme.value.search?.provider === 'local' &&
|
||||
theme.value.search.options?.disableQueryPersistence === true
|
||||
)
|
||||
})
|
||||
|
||||
const filterText = disableQueryPersistence.value
|
||||
? ref('')
|
||||
: useSessionStorage('vitepress:local-search-filter', '')
|
||||
|
||||
const showDetailedList = useLocalStorage(
|
||||
'vitepress:local-search-detailed-list',
|
||||
theme.value.search?.provider === 'local' &&
|
||||
theme.value.search.options?.detailedView === true
|
||||
)
|
||||
|
||||
const disableDetailedView = computed(() => {
|
||||
return (
|
||||
theme.value.search?.provider === 'local' &&
|
||||
(theme.value.search.options?.disableDetailedView === true ||
|
||||
theme.value.search.options?.detailedView === false)
|
||||
)
|
||||
})
|
||||
|
||||
const buttonText = computed(() => {
|
||||
const options = theme.value.search?.options ?? theme.value.algolia
|
||||
|
||||
return (
|
||||
options?.locales?.[localeIndex.value]?.translations?.button?.buttonText ||
|
||||
options?.translations?.button?.buttonText ||
|
||||
'Search'
|
||||
)
|
||||
})
|
||||
|
||||
watchEffect(() => {
|
||||
if (disableDetailedView.value) {
|
||||
showDetailedList.value = false
|
||||
}
|
||||
})
|
||||
|
||||
const results: Ref<(SearchResult & Result)[]> = shallowRef([])
|
||||
|
||||
const enableNoResults = ref(false)
|
||||
|
||||
watch(filterText, () => {
|
||||
enableNoResults.value = false
|
||||
})
|
||||
|
||||
const mark = computedAsync(async () => {
|
||||
if (!resultsEl.value) return
|
||||
return markRaw(new Mark(resultsEl.value))
|
||||
}, null)
|
||||
|
||||
const cache = new LRUCache<string, Map<string, string>>(16) // 16 files
|
||||
|
||||
debouncedWatch(
|
||||
() => [searchIndex.value, filterText.value, showDetailedList.value] as const,
|
||||
async ([index, filterTextValue, showDetailedListValue], old, onCleanup) => {
|
||||
if (old?.[0] !== index) {
|
||||
// in case of hmr
|
||||
cache.clear()
|
||||
}
|
||||
|
||||
let canceled = false
|
||||
onCleanup(() => {
|
||||
canceled = true
|
||||
})
|
||||
|
||||
if (!index) return
|
||||
|
||||
// Search
|
||||
results.value = index
|
||||
.search(filterTextValue)
|
||||
.slice(0, 16) as (SearchResult & Result)[]
|
||||
enableNoResults.value = true
|
||||
|
||||
// Highlighting
|
||||
const mods = showDetailedListValue
|
||||
? await Promise.all(results.value.map((r) => fetchExcerpt(r.id)))
|
||||
: []
|
||||
if (canceled) return
|
||||
for (const { id, mod } of mods) {
|
||||
const mapId = id.slice(0, id.indexOf('#'))
|
||||
let map = cache.get(mapId)
|
||||
if (map) continue
|
||||
map = new Map()
|
||||
cache.set(mapId, map)
|
||||
const comp = mod.default ?? mod
|
||||
if (comp?.render || comp?.setup) {
|
||||
const app = createApp(comp)
|
||||
// Silence warnings about missing components
|
||||
app.config.warnHandler = () => {}
|
||||
app.provide(dataSymbol, vitePressData)
|
||||
Object.defineProperties(app.config.globalProperties, {
|
||||
$frontmatter: {
|
||||
get() {
|
||||
return vitePressData.frontmatter.value
|
||||
}
|
||||
},
|
||||
$params: {
|
||||
get() {
|
||||
return vitePressData.page.value.params
|
||||
}
|
||||
}
|
||||
})
|
||||
const div = document.createElement('div')
|
||||
app.mount(div)
|
||||
const headings = div.querySelectorAll('h1, h2, h3, h4, h5, h6')
|
||||
headings.forEach((el) => {
|
||||
const href = el.querySelector('a')?.getAttribute('href')
|
||||
const anchor = href?.startsWith('#') && href.slice(1)
|
||||
if (!anchor) return
|
||||
let html = ''
|
||||
while ((el = el.nextElementSibling!) && !/^h[1-6]$/i.test(el.tagName))
|
||||
html += el.outerHTML
|
||||
map!.set(anchor, html)
|
||||
})
|
||||
app.unmount()
|
||||
}
|
||||
if (canceled) return
|
||||
}
|
||||
|
||||
const terms = new Set<string>()
|
||||
|
||||
results.value = results.value.map((r) => {
|
||||
const [id, anchor] = r.id.split('#')
|
||||
const map = cache.get(id)
|
||||
const text = map?.get(anchor) ?? ''
|
||||
for (const term in r.match) {
|
||||
terms.add(term)
|
||||
}
|
||||
return { ...r, text }
|
||||
})
|
||||
|
||||
await nextTick()
|
||||
if (canceled) return
|
||||
|
||||
await new Promise((r) => {
|
||||
mark.value?.unmark({
|
||||
done: () => {
|
||||
mark.value?.markRegExp(formMarkRegex(terms), { done: r })
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const excerpts = el.value?.querySelectorAll('.result .excerpt') ?? []
|
||||
for (const excerpt of excerpts) {
|
||||
excerpt
|
||||
.querySelector('mark[data-markjs="true"]')
|
||||
?.scrollIntoView({ block: 'center' })
|
||||
}
|
||||
// FIXME: without this whole page scrolls to the bottom
|
||||
resultsEl.value?.firstElementChild?.scrollIntoView({ block: 'start' })
|
||||
},
|
||||
{ debounce: 200, immediate: true }
|
||||
)
|
||||
|
||||
async function fetchExcerpt(id: string) {
|
||||
const file = pathToFile(id.slice(0, id.indexOf('#')))
|
||||
try {
|
||||
if (!file) throw new Error(`Cannot find file for id: ${id}`)
|
||||
return { id, mod: await import(/*@vite-ignore*/ file) }
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return { id, mod: {} }
|
||||
}
|
||||
}
|
||||
|
||||
/* Search input focus */
|
||||
|
||||
const searchInput = ref<HTMLInputElement>()
|
||||
const disableReset = computed(() => {
|
||||
return filterText.value?.length <= 0
|
||||
})
|
||||
function focusSearchInput(select = true) {
|
||||
searchInput.value?.focus()
|
||||
select && searchInput.value?.select()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
focusSearchInput()
|
||||
})
|
||||
|
||||
function onSearchBarClick(event: PointerEvent) {
|
||||
if (event.pointerType === 'mouse') {
|
||||
focusSearchInput()
|
||||
}
|
||||
}
|
||||
|
||||
/* Search keyboard selection */
|
||||
|
||||
const selectedIndex = ref(-1)
|
||||
const disableMouseOver = ref(false)
|
||||
|
||||
watch(results, (r) => {
|
||||
selectedIndex.value = r.length ? 0 : -1
|
||||
scrollToSelectedResult()
|
||||
})
|
||||
|
||||
function scrollToSelectedResult() {
|
||||
nextTick(() => {
|
||||
const selectedEl = document.querySelector('.result.selected')
|
||||
if (selectedEl) {
|
||||
selectedEl.scrollIntoView({
|
||||
block: 'nearest'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onKeyStroke('ArrowUp', (event) => {
|
||||
event.preventDefault()
|
||||
selectedIndex.value--
|
||||
if (selectedIndex.value < 0) {
|
||||
selectedIndex.value = results.value.length - 1
|
||||
}
|
||||
disableMouseOver.value = true
|
||||
scrollToSelectedResult()
|
||||
})
|
||||
|
||||
onKeyStroke('ArrowDown', (event) => {
|
||||
event.preventDefault()
|
||||
selectedIndex.value++
|
||||
if (selectedIndex.value >= results.value.length) {
|
||||
selectedIndex.value = 0
|
||||
}
|
||||
disableMouseOver.value = true
|
||||
scrollToSelectedResult()
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
onKeyStroke('Enter', (e) => {
|
||||
if (e.isComposing) return
|
||||
|
||||
if (e.target instanceof HTMLButtonElement && e.target.type !== 'submit')
|
||||
return
|
||||
|
||||
const selectedPackage = results.value[selectedIndex.value]
|
||||
if (e.target instanceof HTMLInputElement && !selectedPackage) {
|
||||
e.preventDefault()
|
||||
return
|
||||
}
|
||||
|
||||
if (selectedPackage) {
|
||||
router.go(selectedPackage.id)
|
||||
emit('close')
|
||||
}
|
||||
})
|
||||
|
||||
onKeyStroke('Escape', () => {
|
||||
emit('close')
|
||||
})
|
||||
|
||||
// Translations
|
||||
const defaultTranslations: { modal: ModalTranslations } = {
|
||||
modal: {
|
||||
displayDetails: 'Display detailed list',
|
||||
resetButtonTitle: 'Reset search',
|
||||
backButtonTitle: 'Close search',
|
||||
noResultsText: 'No results for',
|
||||
footer: {
|
||||
selectText: 'to select',
|
||||
selectKeyAriaLabel: 'enter',
|
||||
navigateText: 'to navigate',
|
||||
navigateUpKeyAriaLabel: 'up arrow',
|
||||
navigateDownKeyAriaLabel: 'down arrow',
|
||||
closeText: 'to close',
|
||||
closeKeyAriaLabel: 'escape'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const $t = createSearchTranslate(defaultTranslations)
|
||||
|
||||
// Back
|
||||
|
||||
onMounted(() => {
|
||||
// Prevents going to previous site
|
||||
window.history.pushState(null, '', null)
|
||||
})
|
||||
|
||||
useEventListener('popstate', (event) => {
|
||||
event.preventDefault()
|
||||
emit('close')
|
||||
})
|
||||
|
||||
/** Lock body */
|
||||
const isLocked = useScrollLock(inBrowser ? document.body : null)
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
isLocked.value = true
|
||||
nextTick().then(() => activate())
|
||||
})
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
isLocked.value = false
|
||||
})
|
||||
|
||||
function resetSearch() {
|
||||
filterText.value = ''
|
||||
nextTick().then(() => focusSearchInput(false))
|
||||
}
|
||||
|
||||
function formMarkRegex(terms: Set<string>) {
|
||||
return new RegExp(
|
||||
[...terms]
|
||||
.sort((a, b) => b.length - a.length)
|
||||
.map((term) => `(${escapeRegExp(term)})`)
|
||||
.join('|'),
|
||||
'gi'
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<div
|
||||
ref="el"
|
||||
role="button"
|
||||
:aria-owns="results?.length ? 'localsearch-list' : undefined"
|
||||
aria-expanded="true"
|
||||
aria-haspopup="listbox"
|
||||
aria-labelledby="localsearch-label"
|
||||
class="VPLocalSearchBox CustomLocalSearchBox"
|
||||
>
|
||||
<div class="backdrop" @click="$emit('close')" />
|
||||
|
||||
<div class="shell">
|
||||
<form
|
||||
class="search-bar"
|
||||
@pointerup="onSearchBarClick($event)"
|
||||
@submit.prevent=""
|
||||
>
|
||||
<label
|
||||
:title="buttonText"
|
||||
id="localsearch-label"
|
||||
for="localsearch-input"
|
||||
>
|
||||
<svg
|
||||
class="search-icon"
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<g
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
>
|
||||
<circle cx="11" cy="11" r="8" />
|
||||
<path d="m21 21l-4.35-4.35" />
|
||||
</g>
|
||||
</svg>
|
||||
</label>
|
||||
<div class="search-actions before">
|
||||
<button
|
||||
class="back-button"
|
||||
:title="$t('modal.backButtonTitle')"
|
||||
@click="$emit('close')"
|
||||
>
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M19 12H5m7 7l-7-7l7-7"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<input
|
||||
ref="searchInput"
|
||||
v-model="filterText"
|
||||
:placeholder="buttonText"
|
||||
id="localsearch-input"
|
||||
aria-labelledby="localsearch-label"
|
||||
class="search-input"
|
||||
/>
|
||||
<div class="search-actions">
|
||||
<button
|
||||
v-if="!disableDetailedView"
|
||||
class="toggle-layout-button"
|
||||
type="button"
|
||||
:class="{ 'detailed-list': showDetailedList }"
|
||||
:title="$t('modal.displayDetails')"
|
||||
@click="
|
||||
selectedIndex > -1 && (showDetailedList = !showDetailedList)
|
||||
"
|
||||
>
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M3 14h7v7H3zM3 3h7v7H3zm11 1h7m-7 5h7m-7 6h7m-7 5h7"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="clear-button"
|
||||
type="reset"
|
||||
:disabled="disableReset"
|
||||
:title="$t('modal.resetButtonTitle')"
|
||||
@click="resetSearch"
|
||||
>
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M20 5H9l-7 7l7 7h11a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2Zm-2 4l-6 6m0-6l6 6"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<ul
|
||||
ref="resultsEl"
|
||||
:id="results?.length ? 'localsearch-list' : undefined"
|
||||
:role="results?.length ? 'listbox' : undefined"
|
||||
:aria-labelledby="results?.length ? 'localsearch-label' : undefined"
|
||||
class="results"
|
||||
@mousemove="disableMouseOver = false"
|
||||
>
|
||||
<li
|
||||
v-for="(p, index) in results"
|
||||
:key="p.id"
|
||||
role="option"
|
||||
:aria-selected="selectedIndex === index ? 'true' : 'false'"
|
||||
>
|
||||
<a
|
||||
:href="p.id"
|
||||
class="result"
|
||||
:class="{
|
||||
selected: selectedIndex === index
|
||||
}"
|
||||
:aria-label="[...p.titles, p.title].join(' > ')"
|
||||
@mouseenter="!disableMouseOver && (selectedIndex = index)"
|
||||
@focusin="selectedIndex = index"
|
||||
@click="$emit('close')"
|
||||
>
|
||||
<div>
|
||||
<div class="titles">
|
||||
<span class="title-icon">#</span>
|
||||
<span
|
||||
v-for="(t, index) in p.titles"
|
||||
:key="index"
|
||||
class="title"
|
||||
>
|
||||
<span class="text" v-html="t" />
|
||||
<svg width="18" height="18" viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="m9 18l6-6l-6-6"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span class="title main">
|
||||
<span class="text" v-html="p.title" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div v-if="showDetailedList" class="excerpt-wrapper">
|
||||
<div v-if="p.text" class="excerpt" inert>
|
||||
<div class="vp-doc" v-html="p.text" />
|
||||
</div>
|
||||
<div class="excerpt-gradient-bottom" />
|
||||
<div class="excerpt-gradient-top" />
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
v-if="filterText && !results.length && enableNoResults"
|
||||
class="no-results"
|
||||
>
|
||||
{{ $t('modal.noResultsText') }} "<strong>{{ filterText }}</strong
|
||||
>"
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="search-keyboard-shortcuts">
|
||||
<span>
|
||||
<kbd :aria-label="$t('modal.footer.navigateUpKeyAriaLabel')">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 19V5m-7 7l7-7l7 7"
|
||||
/>
|
||||
</svg>
|
||||
</kbd>
|
||||
<kbd :aria-label="$t('modal.footer.navigateDownKeyAriaLabel')">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 5v14m7-7l-7 7l-7-7"
|
||||
/>
|
||||
</svg>
|
||||
</kbd>
|
||||
{{ $t('modal.footer.navigateText') }}
|
||||
</span>
|
||||
<span>
|
||||
<kbd :aria-label="$t('modal.footer.selectKeyAriaLabel')">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24">
|
||||
<g
|
||||
fill="none"
|
||||
stroke="currentcolor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path d="m9 10l-5 5l5 5" />
|
||||
<path d="M20 4v7a4 4 0 0 1-4 4H4" />
|
||||
</g>
|
||||
</svg>
|
||||
</kbd>
|
||||
{{ $t('modal.footer.selectText') }}
|
||||
</span>
|
||||
<span>
|
||||
<kbd :aria-label="$t('modal.footer.closeKeyAriaLabel')">esc</kbd>
|
||||
{{ $t('modal.footer.closeText') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.VPLocalSearchBox {
|
||||
position: fixed;
|
||||
z-index: 100;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.backdrop {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: var(--vp-backdrop-bg-color);
|
||||
transition: opacity 0.5s;
|
||||
}
|
||||
|
||||
.shell {
|
||||
position: relative;
|
||||
padding: 12px;
|
||||
margin: 64px auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
background: var(--vp-local-search-bg);
|
||||
width: min(100vw - 60px, 900px);
|
||||
height: min-content;
|
||||
max-height: min(100vh - 128px, 900px);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.shell {
|
||||
margin: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
max-height: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.search-bar {
|
||||
padding: 0 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.search-bar:focus-within {
|
||||
border-color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.search-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.search-input {
|
||||
padding: 6px 12px;
|
||||
font-size: inherit;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.search-input {
|
||||
padding: 6px 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.search-actions {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
@media (any-pointer: coarse) {
|
||||
.search-actions {
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 769px) {
|
||||
.search-actions.before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.search-actions button {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.search-actions button:not([disabled]):hover,
|
||||
.toggle-layout-button.detailed-list {
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.search-actions button.clear-button:disabled {
|
||||
opacity: 0.37;
|
||||
}
|
||||
|
||||
.search-keyboard-shortcuts {
|
||||
font-size: 0.8rem;
|
||||
opacity: 75%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
line-height: 14px;
|
||||
}
|
||||
|
||||
.search-keyboard-shortcuts span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.search-keyboard-shortcuts {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.search-keyboard-shortcuts kbd {
|
||||
background: rgba(128, 128, 128, 0.1);
|
||||
border-radius: 4px;
|
||||
padding: 3px 6px;
|
||||
min-width: 24px;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
border: 1px solid rgba(128, 128, 128, 0.15);
|
||||
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.results {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
|
||||
.result {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
border-radius: 12px;
|
||||
transition: none;
|
||||
line-height: 1rem;
|
||||
border: solid 2px var(--vp-local-search-result-border);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.result > div {
|
||||
margin: 12px;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.result > div {
|
||||
margin: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.titles {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
position: relative;
|
||||
z-index: 1001;
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.title.main {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.title-icon {
|
||||
opacity: 0.5;
|
||||
font-weight: 500;
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.title svg {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.result.selected {
|
||||
--vp-local-search-result-bg: var(--vp-local-search-result-selected-bg);
|
||||
border-color: var(--vp-local-search-result-selected-border);
|
||||
}
|
||||
|
||||
.excerpt-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.excerpt {
|
||||
opacity: 75%;
|
||||
pointer-events: none;
|
||||
max-height: 140px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
opacity: 0.5;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.result.selected .excerpt {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.excerpt :deep(*) {
|
||||
font-size: 0.8rem !important;
|
||||
line-height: 130% !important;
|
||||
}
|
||||
|
||||
.titles :deep(mark),
|
||||
.excerpt :deep(mark) {
|
||||
background-color: var(--vp-local-search-highlight-bg);
|
||||
color: var(--vp-local-search-highlight-text);
|
||||
border-radius: 2px;
|
||||
padding: 0 2px;
|
||||
}
|
||||
|
||||
.excerpt :deep(.vp-code-group) .tabs {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.excerpt :deep(.vp-code-group) div[class*='language-'] {
|
||||
border-radius: 8px !important;
|
||||
}
|
||||
|
||||
.excerpt-gradient-bottom {
|
||||
position: absolute;
|
||||
bottom: -1px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background: linear-gradient(transparent, var(--vp-local-search-result-bg));
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.excerpt-gradient-top {
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background: linear-gradient(var(--vp-local-search-result-bg), transparent);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.result.selected .titles,
|
||||
.result.selected .title-icon {
|
||||
color: var(--vp-c-brand-1) !important;
|
||||
}
|
||||
|
||||
.no-results {
|
||||
font-size: 0.9rem;
|
||||
text-align: center;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
svg {
|
||||
flex: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,254 @@
|
||||
<script lang="ts" setup>
|
||||
import { useWindowScroll } from '@vueuse/core'
|
||||
import { ref, watchPostEffect } from 'vue'
|
||||
import { useData } from 'vitepress/dist/client/theme-default/composables/data'
|
||||
import { useLocalNav } from 'vitepress/dist/client/theme-default/composables/local-nav'
|
||||
import { useSidebar } from 'vitepress/dist/client/theme-default/composables/sidebar'
|
||||
import VPNavBarAppearance from 'vitepress/dist/client/theme-default/components/VPNavBarAppearance.vue'
|
||||
import VPNavBarExtra from 'vitepress/dist/client/theme-default/components/VPNavBarExtra.vue'
|
||||
import VPNavBarHamburger from 'vitepress/dist/client/theme-default/components/VPNavBarHamburger.vue'
|
||||
import VPNavBarMenu from 'vitepress/dist/client/theme-default/components/VPNavBarMenu.vue'
|
||||
import VPNavBarSearch from 'vitepress/dist/client/theme-default/components/VPNavBarSearch.vue'
|
||||
import VPNavBarSocialLinks from 'vitepress/dist/client/theme-default/components/VPNavBarSocialLinks.vue'
|
||||
import CustomNavBarTitle from './CustomNavBarTitle.vue'
|
||||
import VPNavBarTranslations from 'vitepress/dist/client/theme-default/components/VPNavBarTranslations.vue'
|
||||
|
||||
defineProps<{
|
||||
isScreenOpen: boolean
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
(e: 'toggle-screen'): void
|
||||
}>()
|
||||
|
||||
const { y } = useWindowScroll()
|
||||
const { hasSidebar } = useSidebar()
|
||||
const { hasLocalNav } = useLocalNav()
|
||||
const { frontmatter } = useData()
|
||||
|
||||
const classes = ref<Record<string, boolean>>({})
|
||||
|
||||
watchPostEffect(() => {
|
||||
classes.value = {
|
||||
'has-sidebar': hasSidebar.value,
|
||||
'has-local-nav': hasLocalNav.value,
|
||||
top: frontmatter.value.layout === 'home' && y.value === 0,
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="VPNavBar CustomNavbar" :class="classes">
|
||||
<div class="wrapper">
|
||||
<div class="container">
|
||||
<div class="title">
|
||||
<CustomNavBarTitle>
|
||||
<template #nav-bar-title-before><slot name="nav-bar-title-before" /></template>
|
||||
<template #nav-bar-title-after><slot name="nav-bar-title-after" /></template>
|
||||
</CustomNavBarTitle>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div class="content-body">
|
||||
<slot name="nav-bar-content-before" />
|
||||
<VPNavBarSearch class="search" />
|
||||
<VPNavBarMenu class="menu" />
|
||||
<VPNavBarTranslations class="translations" />
|
||||
<VPNavBarAppearance class="appearance" />
|
||||
<VPNavBarSocialLinks class="social-links" />
|
||||
<VPNavBarExtra class="extra" />
|
||||
<slot name="nav-bar-content-after" />
|
||||
<VPNavBarHamburger class="hamburger" :active="isScreenOpen" @click="$emit('toggle-screen')" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="divider">
|
||||
<div class="divider-line" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.VPNavBar {
|
||||
position: relative;
|
||||
height: var(--vp-nav-height);
|
||||
pointer-events: none;
|
||||
white-space: nowrap;
|
||||
transition: background-color 0.5s;
|
||||
background-color: #ffffff;
|
||||
|
||||
.dark & {
|
||||
background-color: #141414;
|
||||
}
|
||||
}
|
||||
|
||||
.VPNavBar.has-local-nav {
|
||||
background-color: var(--vp-nav-bg-color);
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.VPNavBar.has-local-nav {
|
||||
// background-color: transparent;
|
||||
background-color: var(--vp-nav-bg-color);
|
||||
}
|
||||
|
||||
.VPNavBar:not(.has-sidebar):not(.top) {
|
||||
background-color: var(--vp-nav-bg-color);
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
padding: 0 8px 0 24px;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.wrapper {
|
||||
padding: 0 32px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.VPNavBar.has-sidebar .wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: 0 auto;
|
||||
// max-width: calc(var(--vp-layout-max-width) - 64px);
|
||||
height: var(--vp-nav-height);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.container > .title,
|
||||
.container > .content {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.container :deep(*) {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.VPNavBar.has-sidebar .container {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
flex-shrink: 0;
|
||||
height: calc(var(--vp-nav-height) - 1px);
|
||||
transition: background-color 0.5s;
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.VPNavBar.has-sidebar .title {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 2;
|
||||
padding: 0 32px;
|
||||
// width: var(--vp-sidebar-width);
|
||||
height: var(--vp-nav-height);
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1440px) {
|
||||
.VPNavBar.has-sidebar .title {
|
||||
padding-left: 24px;
|
||||
// width: calc((100% - (var(--vp-layout-max-width) - 64px)) / 2 + var(--vp-sidebar-width) - 32px);
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.VPNavBar.has-sidebar .content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding-right: 32px;
|
||||
padding-left: var(--vp-sidebar-width);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1440px) {
|
||||
.VPNavBar.has-sidebar .content {
|
||||
// padding-right: calc((100vw - var(--vp-layout-max-width)) / 2 + 32px);
|
||||
// padding-left: calc((100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width));
|
||||
}
|
||||
}
|
||||
|
||||
.content-body {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
height: var(--vp-nav-height);
|
||||
transition: background-color 0.5s;
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.VPNavBar:not(.top) .content-body {
|
||||
position: relative;
|
||||
background-color: var(--vp-nav-bg-color);
|
||||
}
|
||||
|
||||
.VPNavBar:not(.has-sidebar):not(.top) .content-body {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.content-body {
|
||||
column-gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.menu + .translations::before,
|
||||
.menu + .appearance::before,
|
||||
.menu + .social-links::before,
|
||||
.translations + .appearance::before,
|
||||
.appearance + .social-links::before {
|
||||
margin-right: 8px;
|
||||
margin-left: 8px;
|
||||
width: 1px;
|
||||
height: 24px;
|
||||
background-color: var(--vp-c-divider);
|
||||
content: "";
|
||||
}
|
||||
|
||||
.menu + .appearance::before,
|
||||
.translations + .appearance::before {
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.appearance + .social-links::before {
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.social-links {
|
||||
margin-right: -8px;
|
||||
}
|
||||
|
||||
.divider {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.divider-line {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
transition: background-color 0.5s;
|
||||
background-color: rgba(25, 28, 52, 0.12);
|
||||
|
||||
.dark & {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,53 @@
|
||||
<script lang="ts" setup>
|
||||
import type { DefaultTheme } from 'vitepress/theme'
|
||||
import { useData } from 'vitepress/dist/client/theme-default/composables/data'
|
||||
import { isActive } from 'vitepress/dist/client/shared'
|
||||
import VPLink from 'vitepress/dist/client/theme-default/components/VPLink.vue'
|
||||
|
||||
defineProps<{
|
||||
item: DefaultTheme.NavItemWithLink
|
||||
}>()
|
||||
|
||||
const { page } = useData()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VPLink
|
||||
:class="{
|
||||
VPNavBarMenuLink: true,
|
||||
CustomNavBarMenuLink: true,
|
||||
active: isActive(
|
||||
page.relativePath,
|
||||
item.activeMatch || item.link,
|
||||
!!item.activeMatch
|
||||
)
|
||||
}"
|
||||
:href="item.link"
|
||||
:target="item.target"
|
||||
:rel="item.rel"
|
||||
tabindex="0"
|
||||
>
|
||||
<span v-html="item.text"></span>
|
||||
</VPLink>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.VPNavBarMenuLink {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
line-height: var(--vp-nav-height);
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: var(--color-text-inactive);
|
||||
transition: color 0.25s;
|
||||
}
|
||||
|
||||
.VPNavBarMenuLink.active {
|
||||
color: var(--color-text-active);
|
||||
}
|
||||
|
||||
.VPNavBarMenuLink:hover {
|
||||
color: var(--color-text-active);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,220 @@
|
||||
<script lang="ts" setup>
|
||||
import type { ButtonTranslations } from 'vitepress/types/local-search'
|
||||
import { createSearchTranslate } from 'vitepress/dist/client/theme-default/support/translation'
|
||||
|
||||
// Button-Translations
|
||||
const defaultTranslations: { button: ButtonTranslations } = {
|
||||
button: {
|
||||
buttonText: 'Search',
|
||||
buttonAriaLabel: 'Search'
|
||||
}
|
||||
}
|
||||
|
||||
const $t = createSearchTranslate(defaultTranslations)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button type="button" class="DocSearch DocSearch-Button CustomDocSearch-Button" :aria-label="$t('button.buttonAriaLabel')">
|
||||
<span class="DocSearch-Button-Container">
|
||||
<svg
|
||||
class="DocSearch-Search-Icon"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
aria-label="search icon"
|
||||
>
|
||||
<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('button.buttonText') }}</span>
|
||||
</span>
|
||||
<span class="DocSearch-Button-Keys">
|
||||
<kbd class="DocSearch-Button-Key"></kbd>
|
||||
<kbd class="DocSearch-Button-Key">K</kbd>
|
||||
</span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
[class*='DocSearch'] {
|
||||
--docsearch-primary-color: var(--vp-c-brand-1);
|
||||
--docsearch-highlight-color: var(--docsearch-primary-color);
|
||||
--docsearch-text-color: var(--vp-c-text-1);
|
||||
--docsearch-muted-color: var(--vp-c-text-2);
|
||||
--docsearch-searchbox-shadow: none;
|
||||
--docsearch-searchbox-background: transparent;
|
||||
--docsearch-searchbox-focus-background: transparent;
|
||||
--docsearch-key-gradient: transparent;
|
||||
--docsearch-key-shadow: none;
|
||||
--docsearch-modal-background: var(--vp-c-bg-soft);
|
||||
--docsearch-footer-background: var(--vp-c-bg);
|
||||
}
|
||||
|
||||
.dark [class*='DocSearch'] {
|
||||
--docsearch-modal-shadow: none;
|
||||
--docsearch-footer-shadow: none;
|
||||
--docsearch-logo-color: var(--vp-c-text-2);
|
||||
--docsearch-hit-background: var(--vp-c-default-soft);
|
||||
--docsearch-hit-color: var(--vp-c-text-2);
|
||||
--docsearch-hit-shadow: none;
|
||||
}
|
||||
|
||||
.DocSearch-Button {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 12px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 48px;
|
||||
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: 12px;
|
||||
padding: 0 10px 0 12px;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
background-color: var(--vp-c-bg-alt);
|
||||
}
|
||||
|
||||
.DocSearch-Button:hover {
|
||||
border-color: var(--vp-c-brand-1);
|
||||
background: var(--vp-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(--vp-c-text-1);
|
||||
fill: currentColor;
|
||||
transition: color 0.5s;
|
||||
}
|
||||
|
||||
.DocSearch-Button:hover .DocSearch-Search-Icon {
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.DocSearch-Button .DocSearch-Search-Icon {
|
||||
top: 1px;
|
||||
margin-right: 8px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
color: var(--vp-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(--vp-c-text-2);
|
||||
transition: color 0.5s;
|
||||
}
|
||||
|
||||
.DocSearch-Button:hover .DocSearch-Button-Placeholder {
|
||||
color: var(--vp-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(--vp-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;
|
||||
line-height: 22px;
|
||||
font-family: var(--vp-font-family-base);
|
||||
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(--vp-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: 0 !important;
|
||||
}
|
||||
|
||||
.DocSearch-Button .DocSearch-Button-Key:first-child:after {
|
||||
content: 'Ctrl';
|
||||
font-size: 12px;
|
||||
letter-spacing: normal;
|
||||
color: var(--docsearch-muted-color);
|
||||
}
|
||||
|
||||
.mac .DocSearch-Button .DocSearch-Button-Key:first-child:after {
|
||||
content: '\2318';
|
||||
}
|
||||
|
||||
.DocSearch-Button .DocSearch-Button-Key:first-child > * {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,78 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useData } from 'vitepress/dist/client/theme-default/composables/data'
|
||||
import { useLangs } from 'vitepress/dist/client/theme-default/composables/langs'
|
||||
import { useSidebar } from 'vitepress/dist/client/theme-default/composables/sidebar'
|
||||
import { normalizeLink } from 'vitepress/dist/client/theme-default/support/utils'
|
||||
import VPImage from 'vitepress/dist/client/theme-default/components/VPImage.vue'
|
||||
|
||||
const { site, theme } = useData()
|
||||
const { hasSidebar } = useSidebar()
|
||||
const { currentLang } = useLangs()
|
||||
|
||||
const link = computed(() =>
|
||||
typeof theme.value.logoLink === 'string'
|
||||
? theme.value.logoLink
|
||||
: theme.value.logoLink?.link
|
||||
)
|
||||
|
||||
const rel = computed(() =>
|
||||
typeof theme.value.logoLink === 'string'
|
||||
? undefined
|
||||
: theme.value.logoLink?.rel
|
||||
)
|
||||
|
||||
const target = computed(() =>
|
||||
typeof theme.value.logoLink === 'string'
|
||||
? undefined
|
||||
: theme.value.logoLink?.target
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="VPNavBarTitle CustomNavBarTitle" :class="{ 'has-sidebar': hasSidebar }">
|
||||
<a
|
||||
class="title"
|
||||
:href="link ?? normalizeLink(currentLang.link)"
|
||||
:rel="rel"
|
||||
:target="target"
|
||||
>
|
||||
<slot name="nav-bar-title-before" />
|
||||
<VPImage v-if="theme.logo" class="logo" :image="theme.logo" />
|
||||
<template v-if="theme.siteTitle">{{ theme.siteTitle }}</template>
|
||||
<template v-else-if="theme.siteTitle === undefined">{{ site.title }}</template>
|
||||
<slot name="nav-bar-title-after" />
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid transparent;
|
||||
width: 100%;
|
||||
height: var(--vp-nav-height);
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-1);
|
||||
transition: opacity 0.25s;
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.title {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
.VPNavBarTitle.has-sidebar .title {
|
||||
border-bottom-color: var(--vp-c-divider);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
:deep(.logo) {
|
||||
margin-right: 8px;
|
||||
height: var(--vp-nav-logo-height);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,137 @@
|
||||
<script lang="ts" setup>
|
||||
import { useScrollLock } from '@vueuse/core'
|
||||
import { inBrowser } from 'vitepress'
|
||||
import { ref, watch } from 'vue'
|
||||
import { useSidebar } from 'vitepress/dist/client/theme-default/composables/sidebar'
|
||||
import VPSidebarItem from 'vitepress/dist/client/theme-default/components/VPSidebarItem.vue'
|
||||
|
||||
const { sidebarGroups, hasSidebar } = useSidebar()
|
||||
|
||||
const props = defineProps<{
|
||||
open: boolean
|
||||
}>()
|
||||
|
||||
// a11y: focus Nav element when menu has opened
|
||||
const navEl = ref<HTMLElement | null>(null)
|
||||
const isLocked = useScrollLock(inBrowser ? document.body : null)
|
||||
|
||||
watch(
|
||||
[props, navEl],
|
||||
() => {
|
||||
if (props.open) {
|
||||
isLocked.value = true
|
||||
navEl.value?.focus()
|
||||
} else isLocked.value = false
|
||||
},
|
||||
{ immediate: true, flush: 'post' }
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<aside
|
||||
v-if="hasSidebar"
|
||||
class="VPSidebar CustomSidebar"
|
||||
:class="{ open }"
|
||||
ref="navEl"
|
||||
@click.stop
|
||||
>
|
||||
<div class="curtain" />
|
||||
|
||||
<nav class="nav" id="VPSidebarNav" aria-labelledby="sidebar-aria-label" tabindex="-1">
|
||||
<span class="visually-hidden" id="sidebar-aria-label">
|
||||
Sidebar Navigation
|
||||
</span>
|
||||
|
||||
<slot name="sidebar-nav-before" />
|
||||
|
||||
<div v-for="item in sidebarGroups" :key="item.text" class="group">
|
||||
<VPSidebarItem :item="item" :depth="0" />
|
||||
</div>
|
||||
|
||||
<slot name="sidebar-nav-after" />
|
||||
</nav>
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.VPSidebar {
|
||||
position: fixed;
|
||||
top: var(--vp-layout-top-height, 0px);
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: var(--vp-z-index-sidebar);
|
||||
padding: 32px 32px 96px 32px;
|
||||
width: 256px;
|
||||
max-width: 320px;
|
||||
background-color: var(--vp-sidebar-bg-color);
|
||||
opacity: 0;
|
||||
box-shadow: var(--vp-c-shadow-3);
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
transform: translateX(-100%);
|
||||
transition: opacity 0.5s, transform 0.25s ease;
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
|
||||
.VPSidebar.open {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transform: translateX(0);
|
||||
transition: opacity 0.25s,
|
||||
transform 0.5s cubic-bezier(0.19, 1, 0.22, 1);
|
||||
}
|
||||
|
||||
.dark .VPSidebar {
|
||||
box-shadow: var(--vp-shadow-1);
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.VPSidebar {
|
||||
padding-top: var(--vp-nav-height);
|
||||
// width: var(--vp-sidebar-width);
|
||||
// max-width: 100%;
|
||||
background-color: var(--vp-sidebar-bg-color);
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
box-shadow: none;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1440px) {
|
||||
.VPSidebar {
|
||||
// padding-left: max(32px, calc((100% - (var(--vp-layout-max-width) - 64px)) / 2));
|
||||
// width: calc((100% - (var(--vp-layout-max-width) - 64px)) / 2 + var(--vp-sidebar-width) - 32px);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.curtain {
|
||||
position: sticky;
|
||||
top: -64px;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
margin-top: calc(var(--vp-nav-height) * -1);
|
||||
margin-right: -32px;
|
||||
margin-left: -32px;
|
||||
height: var(--vp-nav-height);
|
||||
background-color: var(--vp-sidebar-bg-color);
|
||||
}
|
||||
}
|
||||
|
||||
.nav {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.group + .group {
|
||||
border-top: 1px solid var(--vp-c-divider);
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.group {
|
||||
padding-top: 10px;
|
||||
width: calc(var(--vp-sidebar-width) - 64px);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,30 @@
|
||||
// https://vitepress.dev/guide/custom-theme
|
||||
import { h } from 'vue'
|
||||
import type { Theme, EnhanceAppContext } from 'vitepress'
|
||||
import DefaultTheme from 'vitepress/theme-without-fonts'
|
||||
import { enhanceAppWithTabs } from 'vitepress-plugin-tabs/client'
|
||||
import './scss/style.scss'
|
||||
import matomo from '../matomo';
|
||||
|
||||
const matomoHost = import.meta.env.VITE_MATOMO_HOST;
|
||||
const matomoSiteId = import.meta.env.VITE_MATOMO_SITE_ID;
|
||||
console.log({matomo: typeof matomoHost !== 'undefined' && matomoHost})
|
||||
|
||||
export default {
|
||||
extends: DefaultTheme,
|
||||
Layout: () => {
|
||||
return h(DefaultTheme.Layout, null, {
|
||||
// https://vitepress.dev/guide/extending-default-theme#layout-slots
|
||||
})
|
||||
},
|
||||
enhanceApp({ app, router }: EnhanceAppContext) {
|
||||
enhanceAppWithTabs(app)
|
||||
if (typeof matomoHost !== 'undefined' && matomoHost) {
|
||||
matomo({
|
||||
router: router,
|
||||
siteID: matomoSiteId,
|
||||
trackerUrl: matomoHost
|
||||
})
|
||||
}
|
||||
}
|
||||
} satisfies Theme
|
||||
@@ -0,0 +1,4 @@
|
||||
@use "vp-doc.scss";
|
||||
@use "vp-custom-block.scss";
|
||||
@use "vp-doc-aside.scss";
|
||||
@use "vp-sidebar.scss";
|
||||
@@ -0,0 +1,11 @@
|
||||
.custom-block {
|
||||
padding: 24px;
|
||||
|
||||
&-title {
|
||||
font-size: 18px;
|
||||
line-height: 22px;
|
||||
font-weight: 500;
|
||||
letter-spacing: .2px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
.VPDocAside {
|
||||
.outline-link {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.outline-title {
|
||||
font-size: 17px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
@mixin font_style($fontSize, $fontWeight, $lineHeight, $letterSpacing) {
|
||||
font-size: $fontSize;
|
||||
font-weight: $fontWeight;
|
||||
line-height: $lineHeight;
|
||||
letter-spacing: $letterSpacing;
|
||||
}
|
||||
|
||||
@mixin generate-numbered-list-styles($start, $end) {
|
||||
@for $counter from $start through $end {
|
||||
$counter-name: list + ' ' + ($counter - 1);
|
||||
ol[start*="#{$counter}"] {
|
||||
list-style-type: none;
|
||||
counter-reset: $counter-name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vp-doc {
|
||||
font-size: 17px;
|
||||
|
||||
// Titles
|
||||
h1 {
|
||||
@include font_style(44px, 500, 46px, 0.3px);
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@include font_style(34px, 400, 36px, 0.3px);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
@include font_style(26px, 500, 32px, 0.2px);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
@include font_style(20px, 700, 28px, 0.2px);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
h5 {
|
||||
@include font_style(17px, 500, 22px, 0.2px);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
// Text
|
||||
ol {
|
||||
list-style-type: none;
|
||||
counter-reset: list;
|
||||
margin: 0 0 0 50px;
|
||||
padding: 0 0 5px 0;
|
||||
font-size: 16px;
|
||||
|
||||
& > * + * {
|
||||
margin-top: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
@include generate-numbered-list-styles(2, 50);
|
||||
|
||||
ol li {
|
||||
position: relative;
|
||||
padding: 5px 0 0 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
li + li {
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
ul li {
|
||||
padding-bottom: 0;
|
||||
padding-top: 0;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
ol li:nth-last-of-type(n+2)::after {
|
||||
content: '';
|
||||
border-left: 1px solid rgb(201, 197, 197);
|
||||
position: absolute;
|
||||
line-height: 100%;
|
||||
left: -30px;
|
||||
top: 43px;
|
||||
bottom: -30px;
|
||||
}
|
||||
|
||||
ol li::before {
|
||||
content: counter(list);
|
||||
counter-increment: list;
|
||||
display: inline-flex;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -48px;
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
background-color: #7e00ed;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
line-height: 25px;
|
||||
font-size: 16px;
|
||||
border-radius: 50%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
ul li:nth-last-of-type(n):after {
|
||||
content: none;
|
||||
}
|
||||
|
||||
ol ul li::before {
|
||||
counter-increment: list;
|
||||
content: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ol li p {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
// Links
|
||||
a {
|
||||
color: #1a73e8;
|
||||
|
||||
&:hover, &:focus-visible, &:focus, &:active {
|
||||
color: #1a73e8;
|
||||
}
|
||||
|
||||
&:visited {
|
||||
color: #7e00ed;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
.VPSidebar {
|
||||
&Item {
|
||||
.text {
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
:root {
|
||||
--color-text-active: rgba(9, 11, 22, 0.94);
|
||||
--color-text-inactive: rgba(25, 28, 52, 0.7);
|
||||
--color-gradient-magma: linear-gradient(-46deg, #1a73e8 0%, #bc00b8 49.44%, #f55555 100%);
|
||||
|
||||
// Button
|
||||
--color-button-contained-background-color: #fdd835;
|
||||
--color-button-contained-hover-background-color: #fdc435;
|
||||
--color-button-contained-text-color: rgba(9, 11, 22, 0.94);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--color-text-active: rgba(255, 255, 255, 0.87);
|
||||
--color-text-inactive: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
$font-path-beeline-sans: '/fonts/beeline-sans' !default;
|
||||
|
||||
@mixin beeline-sans-font($type, $weight, $style: normal) {
|
||||
@font-face {
|
||||
font-family: "Beeline Sans";
|
||||
src:url('#{$font-path-beeline-sans}/BeelineSans-#{$type}.woff2') format('woff2'),
|
||||
url('#{$font-path-beeline-sans}/BeelineSans-#{$type}.woff') format('woff'),
|
||||
url('#{$font-path-beeline-sans}/BeelineSans-#{$type}.ttf') format('truetype');
|
||||
font-weight: $weight;
|
||||
font-style: $style;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin beeline-sans-font-pair($type, $weight) {
|
||||
@include beeline-sans-font($type, $weight);
|
||||
|
||||
}
|
||||
|
||||
@include beeline-sans-font-pair(Regular, 400);
|
||||
|
||||
@include beeline-sans-font-pair(Medium, 500);
|
||||
|
||||
@include beeline-sans-font-pair(Bold, 700);
|
||||
|
||||
@include beeline-sans-font-pair(Black, 900);
|
||||
|
||||
$font-path-roboto-mono: '/fonts/roboto-mono' !default;
|
||||
|
||||
@mixin roboto-mono-font($type, $weight, $style: normal) {
|
||||
@font-face {
|
||||
font-family: "Roboto Mono";
|
||||
src:url('#{$font-path-roboto-mono}/RobotoMono-#{$type}.woff2') format('woff2'),
|
||||
url('#{$font-path-roboto-mono}/RobotoMono-#{$type}.woff') format('woff'),
|
||||
url('#{$font-path-roboto-mono}/RobotoMono-#{$type}.ttf') format('truetype');
|
||||
font-weight: $weight;
|
||||
font-style: $style;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin roboto-mono-font-pair($type, $weight) {
|
||||
@include roboto-mono-font($type, $weight);
|
||||
|
||||
}
|
||||
|
||||
@include roboto-mono-font-pair(Light, 300);
|
||||
|
||||
@include roboto-mono-font-pair(Regular, 400);
|
||||
|
||||
@include roboto-mono-font-pair(Medium, 500);
|
||||
|
||||
@include roboto-mono-font-pair(Bold, 700);
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
@use "fonts.scss";
|
||||
@use "design-system.scss";
|
||||
@use "vars.scss";
|
||||
@use "components";
|
||||
@@ -0,0 +1,205 @@
|
||||
/**
|
||||
* Customize default theme styling by overriding CSS variables:
|
||||
* https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css
|
||||
*/
|
||||
|
||||
/**
|
||||
* Colors
|
||||
*
|
||||
* Each colors have exact same color scale system with 3 levels of solid
|
||||
* colors with different brightness, and 1 soft color.
|
||||
*
|
||||
* - `XXX-1`: The most solid color used mainly for colored text. It must
|
||||
* satisfy the contrast ratio against when used on top of `XXX-soft`.
|
||||
*
|
||||
* - `XXX-2`: The color used mainly for hover state of the button.
|
||||
*
|
||||
* - `XXX-3`: The color for solid background, such as bg color of the button.
|
||||
* It must satisfy the contrast ratio with pure white (#ffffff) text on
|
||||
* top of it.
|
||||
*
|
||||
* - `XXX-soft`: The color used for subtle background such as custom container
|
||||
* or badges. It must satisfy the contrast ratio when putting `XXX-1` colors
|
||||
* on top of it.
|
||||
*
|
||||
* The soft color must be semi transparent alpha channel. This is crucial
|
||||
* because it allows adding multiple "soft" colors on top of each other
|
||||
* to create a accent, such as when having inline code block inside
|
||||
* custom containers.
|
||||
*
|
||||
* - `default`: The color used purely for subtle indication without any
|
||||
* special meanings attched to it such as bg color for menu hover state.
|
||||
*
|
||||
* - `brand`: Used for primary brand colors, such as link text, button with
|
||||
* brand theme, etc.
|
||||
*
|
||||
* - `tip`: Used to indicate useful information. The default theme uses the
|
||||
* brand color for this by default.
|
||||
*
|
||||
* - `warning`: Used to indicate warning to the users. Used in custom
|
||||
* container, badges, etc.
|
||||
*
|
||||
* - `danger`: Used to show error, or dangerous message to the users. Used
|
||||
* in custom container, badges, etc.
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
:root {
|
||||
--vp-c-default-1: var(--vp-c-gray-1);
|
||||
--vp-c-default-2: var(--vp-c-gray-2);
|
||||
--vp-c-default-3: var(--vp-c-gray-3);
|
||||
--vp-c-default-soft: var(--vp-c-gray-soft);
|
||||
|
||||
--vp-c-brand-1: #fdc435; // var(--vp-c-indigo-1);
|
||||
--vp-c-brand-2: #fdc435; // var(--vp-c-indigo-2);
|
||||
--vp-c-brand-3: #fdd835; // var(--vp-c-indigo-3);
|
||||
--vp-c-brand-soft: #fff7d7; // var(--vp-c-indigo-soft);
|
||||
|
||||
--vp-c-tip-1: var(--vp-c-brand-1);
|
||||
--vp-c-tip-2: var(--vp-c-brand-2);
|
||||
--vp-c-tip-3: var(--vp-c-brand-3);
|
||||
--vp-c-tip-soft: var(--vp-c-brand-soft);
|
||||
|
||||
--vp-c-warning-1: #ff9419; // var(--vp-c-yellow-1);
|
||||
--vp-c-warning-2: var(--vp-c-yellow-2);
|
||||
--vp-c-warning-3: var(--vp-c-yellow-3);
|
||||
--vp-c-warning-soft: var(--vp-c-yellow-soft);
|
||||
|
||||
--vp-c-danger-1: var(--vp-c-red-1);
|
||||
--vp-c-danger-2: var(--vp-c-red-2);
|
||||
--vp-c-danger-3: var(--vp-c-red-3);
|
||||
--vp-c-danger-soft: var(--vp-c-red-soft);
|
||||
--vp-c-divider: rgba(25, 28, 52, 0.12);
|
||||
// --vp-sidebar-width: 272;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--vp-c-divider: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Colors: Background
|
||||
*
|
||||
* - `bg`: The bg color used for main screen.
|
||||
*
|
||||
* - `bg-alt`: The alternative bg color used in places such as "sidebar",
|
||||
* or "code block".
|
||||
*
|
||||
* - `bg-elv`: The elevated bg color. This is used at parts where it "floats",
|
||||
* such as "dialog".
|
||||
*
|
||||
* - `bg-soft`: The bg color to slightly distinguish some components from
|
||||
* the page. Used for things like "carbon ads" or "table".
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
:root {
|
||||
--vp-c-bg: #ffffff;
|
||||
--vp-c-bg-alt: #f9f9f9;
|
||||
--vp-c-bg-elv: #ffffff;
|
||||
--vp-c-bg-soft: #f9f9f9;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--vp-c-bg: #141414;
|
||||
--vp-c-bg-alt: #212121;
|
||||
--vp-c-bg-elv: #2e2f33;
|
||||
--vp-c-bg-soft: #36383c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Typography
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
:root {
|
||||
--vp-font-family-base: 'Beeline Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
--vp-font-family-mono: 'Roboto Mono', monospace;
|
||||
|
||||
// Code
|
||||
// --vp-code-font-size: ;
|
||||
--vp-code-color: rgba(9, 11, 22, 0.94);
|
||||
--vp-code-link-color: rgba(9, 11, 22, 0.94);
|
||||
--vp-code-link-hover-color: rgba(9, 11, 22, 0.94);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--vp-code-color: rgba(255, 255, 255, 0.87);
|
||||
--vp-code-link-color: rgba(255, 255, 255, 0.87);
|
||||
--vp-code-link-hover-color: rgba(255, 255, 255, 0.87);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component: Button
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
:root {
|
||||
--vp-button-brand-border: transparent;
|
||||
--vp-button-brand-text: var(--vp-c-white);
|
||||
--vp-button-brand-bg: var(--vp-c-brand-3);
|
||||
--vp-button-brand-hover-border: transparent;
|
||||
--vp-button-brand-hover-text: var(--vp-c-white);
|
||||
--vp-button-brand-hover-bg: var(--vp-c-brand-2);
|
||||
--vp-button-brand-active-border: transparent;
|
||||
--vp-button-brand-active-text: var(--vp-c-white);
|
||||
--vp-button-brand-active-bg: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component: Home
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
:root {
|
||||
--vp-home-hero-name-color: transparent;
|
||||
--vp-home-hero-name-background: -webkit-linear-gradient(
|
||||
120deg,
|
||||
#bd34fe 30%,
|
||||
#41d1ff
|
||||
);
|
||||
|
||||
--vp-home-hero-image-background-image: linear-gradient(
|
||||
-45deg,
|
||||
#bd34fe 50%,
|
||||
#47caff 50%
|
||||
);
|
||||
--vp-home-hero-image-filter: blur(44px);
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
:root {
|
||||
--vp-home-hero-image-filter: blur(56px);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
:root {
|
||||
--vp-home-hero-image-filter: blur(68px);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Component: Custom Block
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
:root {
|
||||
--vp-custom-block-font-size: 17px;
|
||||
--vp-custom-block-code-font-size: 17px;
|
||||
--vp-custom-block-tip-border: transparent;
|
||||
--vp-custom-block-tip-text: var(--vp-c-text-1);
|
||||
--vp-custom-block-tip-bg: #f1f1f3;
|
||||
--vp-custom-block-tip-code-bg: var(--vp-c-brand-soft);
|
||||
--vp-custom-block-warning-bg: #fff4e1;
|
||||
--vp-custom-block-danger-bg: #ffecef;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--vp-custom-block-warning-bg: #3d392a;
|
||||
--vp-custom-block-tip-bg: #36383c;
|
||||
--vp-custom-block-danger-bg: #371313;
|
||||
}
|
||||
|
||||
/**
|
||||
* Component: Algolia
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
.DocSearch {
|
||||
--docsearch-primary-color: var(--vp-c-brand-1) !important;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user