feat(theme): add print options

userquin/feat-add-print-options
userquin 2 years ago
parent 46758a507c
commit dc2f98c443

@ -3,10 +3,13 @@ import icon from './documate.vue'
</script> </script>
<template> <template>
<div class="ad-component"> <div class="ad-component screen-only">
<a target="_blank" href="https://github.com/aircodelabs/documate"> <a target="_blank" href="https://github.com/aircodelabs/documate">
<icon class="icon"/> <icon class="icon" />
<span>Embed AI chat in your VitePress using your own content. Open-source.</span> <span
>Embed AI chat in your VitePress using your own content.
Open-source.</span
>
<span class="cta">Try now </span> <span class="cta">Try now </span>
</a> </a>
</div> </div>
@ -15,7 +18,7 @@ import icon from './documate.vue'
<style scoped> <style scoped>
.ad-component { .ad-component {
margin-bottom: 2rem; margin-bottom: 2rem;
padding: .5rem .85rem; padding: 0.5rem 0.85rem;
border: 1px solid var(--vp-c-divider); border: 1px solid var(--vp-c-divider);
border-radius: 4px; border-radius: 4px;
text-decoration: none; text-decoration: none;
@ -30,8 +33,8 @@ import icon from './documate.vue'
.ad-component .icon { .ad-component .icon {
display: inline-block; display: inline-block;
width: .75rem; width: 0.75rem;
height: .75rem; height: 0.75rem;
margin-right: 0.3rem; margin-right: 0.3rem;
} }

@ -106,4 +106,10 @@ if (carbonOptions) {
.VPCarbonAds :deep(> div:first-of-type) { .VPCarbonAds :deep(> div:first-of-type) {
display: block; display: block;
} }
@media print {
.VPCarbonAds {
display: none;
}
}
</style> </style>

@ -5,9 +5,12 @@ import { useSidebar } from '../composables/sidebar'
import VPDoc from './VPDoc.vue' import VPDoc from './VPDoc.vue'
import VPHome from './VPHome.vue' import VPHome from './VPHome.vue'
import VPPage from './VPPage.vue' import VPPage from './VPPage.vue'
import { useScreenOnly } from '../composables/scree-only'
const { page, frontmatter } = useData() const { page, frontmatter } = useData()
const { hasSidebar } = useSidebar() const { hasSidebar } = useSidebar()
const screenOnly = useScreenOnly('navbar')
const screenOnlySidebar = useScreenOnly('sidebar')
</script> </script>
<template> <template>
@ -16,7 +19,9 @@ const { hasSidebar } = useSidebar()
id="VPContent" id="VPContent"
:class="{ :class="{
'has-sidebar': hasSidebar, 'has-sidebar': hasSidebar,
'is-home': frontmatter.layout === 'home' 'is-home': frontmatter.layout === 'home',
'with-screen-only': screenOnly,
'no-print-sidebar': screenOnlySidebar
}" }"
> >
<slot name="not-found" v-if="page.isNotFound"><NotFound /></slot> <slot name="not-found" v-if="page.isNotFound"><NotFound /></slot>
@ -92,4 +97,14 @@ const { hasSidebar } = useSidebar()
padding-left: calc((100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width)); padding-left: calc((100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width));
} }
} }
@media print {
.VPContent.with-screen-only:not(.is-home) {
--vp-nav-height: 0;
}
.VPContent.with-screen-only.no-print-sidebar {
padding-left: 0;
width: 100%;
}
}
</style> </style>

@ -6,11 +6,14 @@ import { useSidebar } from '../composables/sidebar'
import VPDocAside from './VPDocAside.vue' import VPDocAside from './VPDocAside.vue'
import VPDocFooter from './VPDocFooter.vue' import VPDocFooter from './VPDocFooter.vue'
import VPDocOutlineDropdown from './VPDocOutlineDropdown.vue' import VPDocOutlineDropdown from './VPDocOutlineDropdown.vue'
import { useScreenOnly } from '../composables/scree-only'
const { theme } = useData() const { theme } = useData()
const route = useRoute() const route = useRoute()
const { hasSidebar, hasAside, leftAside } = useSidebar() const { hasSidebar, hasAside, leftAside } = useSidebar()
const screenOnly = useScreenOnly('navbar')
const screenOnlyOutline = useScreenOnly('outline')
const pageName = computed(() => const pageName = computed(() =>
route.path.replace(/[./]+/g, '_').replace(/_html$/, '') route.path.replace(/[./]+/g, '_').replace(/_html$/, '')
@ -20,11 +23,11 @@ const pageName = computed(() =>
<template> <template>
<div <div
class="VPDoc" class="VPDoc"
:class="{ 'has-sidebar': hasSidebar, 'has-aside': hasAside }" :class="{ 'has-sidebar': hasSidebar, 'has-aside': hasAside, 'with-screen-only': screenOnly }"
> >
<slot name="doc-top" /> <slot name="doc-top" />
<div class="container"> <div class="container">
<div v-if="hasAside" class="aside" :class="{'left-aside': leftAside}"> <div v-if="hasAside" class="aside" :class="{'left-aside': leftAside,'screen-only': screenOnlyOutline}">
<div class="aside-curtain" /> <div class="aside-curtain" />
<div class="aside-container"> <div class="aside-container">
<div class="aside-content"> <div class="aside-content">
@ -207,4 +210,11 @@ const pageName = computed(() =>
content: ''; content: '';
color: currentColor; color: currentColor;
} }
@media print {
/* will be moved to the left? */
.VPDoc.with-screen-only {
padding-top: 24px !important;
}
}
</style> </style>

@ -5,8 +5,11 @@ import { getHeaders, resolveTitle, type MenuItem } from '../composables/outline'
import VPDocOutlineItem from './VPDocOutlineItem.vue' import VPDocOutlineItem from './VPDocOutlineItem.vue'
import { onContentUpdated } from 'vitepress' import { onContentUpdated } from 'vitepress'
import VPIconChevronRight from './icons/VPIconChevronRight.vue' import VPIconChevronRight from './icons/VPIconChevronRight.vue'
import { useScreenOnly } from '../composables/scree-only'
const { frontmatter, theme } = useData() const { frontmatter, theme } = useData()
const screenOnly = useScreenOnly('navbar')
const open = ref(false) const open = ref(false)
onContentUpdated(() => { onContentUpdated(() => {
@ -14,7 +17,6 @@ onContentUpdated(() => {
}) })
const headers = shallowRef<MenuItem[]>([]) const headers = shallowRef<MenuItem[]>([])
onContentUpdated(() => { onContentUpdated(() => {
headers.value = getHeaders( headers.value = getHeaders(
frontmatter.value.outline ?? theme.value.outline frontmatter.value.outline ?? theme.value.outline
@ -23,7 +25,7 @@ onContentUpdated(() => {
</script> </script>
<template> <template>
<div v-if="headers.length > 0" class="VPDocOutlineDropdown" :class="{ 'screen-only': frontmatter.navbar === false }"> <div v-if="headers.length > 0" class="VPDocOutlineDropdown" :class="{ 'screen-only': screenOnly }">
<button @click="open = !open" :class="{ open }"> <button @click="open = !open" :class="{ open }">
{{ resolveTitle(theme) }} {{ resolveTitle(theme) }}
<VPIconChevronRight class="icon" /> <VPIconChevronRight class="icon" />

@ -1,13 +1,19 @@
<script setup lang="ts"> <script setup lang="ts">
import { useData } from '../composables/data' import { useData } from '../composables/data'
import { useSidebar } from '../composables/sidebar' import { useSidebar } from '../composables/sidebar'
import { useScreenOnly } from '../composables/scree-only'
const { theme, frontmatter } = useData() const { theme, frontmatter } = useData()
const { hasSidebar } = useSidebar() const { hasSidebar } = useSidebar()
const screenOnly = useScreenOnly('footer')
</script> </script>
<template> <template>
<footer v-if="theme.footer && frontmatter.footer !== false" class="VPFooter" :class="{ 'has-sidebar': hasSidebar }"> <footer
v-if="theme.footer && frontmatter.footer !== false"
class="VPFooter"
:class="{ 'has-sidebar': hasSidebar, 'screen-only': screenOnly }"
>
<div class="container"> <div class="container">
<p v-if="theme.footer.message" class="message" v-html="theme.footer.message"></p> <p v-if="theme.footer.message" class="message" v-html="theme.footer.message"></p>
<p v-if="theme.footer.copyright" class="copyright" v-html="theme.footer.copyright"></p> <p v-if="theme.footer.copyright" class="copyright" v-html="theme.footer.copyright"></p>

@ -7,6 +7,7 @@ import { getHeaders, type MenuItem } from '../composables/outline'
import { useSidebar } from '../composables/sidebar' import { useSidebar } from '../composables/sidebar'
import VPLocalNavOutlineDropdown from './VPLocalNavOutlineDropdown.vue' import VPLocalNavOutlineDropdown from './VPLocalNavOutlineDropdown.vue'
import VPIconAlignLeft from './icons/VPIconAlignLeft.vue' import VPIconAlignLeft from './icons/VPIconAlignLeft.vue'
import { useScreenOnly } from '../composables/scree-only'
defineProps<{ defineProps<{
open: boolean open: boolean
@ -40,12 +41,14 @@ const empty = computed(() => {
return headers.value.length === 0 && !hasSidebar.value return headers.value.length === 0 && !hasSidebar.value
}) })
const screenOnly = useScreenOnly('navbar')
const classes = computed(() => { const classes = computed(() => {
return { return {
VPLocalNav: true, VPLocalNav: true,
fixed: empty.value, fixed: empty.value,
'reached-top': y.value >= navHeight.value, 'reached-top': y.value >= navHeight.value,
'screen-only': frontmatter.value.navbar === false 'screen-only': screenOnly.value
} }
}) })
</script> </script>

@ -5,9 +5,11 @@ import { useData } from '../composables/data'
import { useNav } from '../composables/nav' import { useNav } from '../composables/nav'
import VPNavBar from './VPNavBar.vue' import VPNavBar from './VPNavBar.vue'
import VPNavScreen from './VPNavScreen.vue' import VPNavScreen from './VPNavScreen.vue'
import {useScreenOnly} from "../composables/scree-only";
const { isScreenOpen, closeScreen, toggleScreen } = useNav() const { isScreenOpen, closeScreen, toggleScreen } = useNav()
const { frontmatter } = useData() const { frontmatter } = useData()
const screenOnly = useScreenOnly('navbar')
const hasNavbar = computed(() => { const hasNavbar = computed(() => {
return frontmatter.value.navbar !== false return frontmatter.value.navbar !== false
@ -23,7 +25,7 @@ watchEffect(() => {
</script> </script>
<template> <template>
<header v-if="hasNavbar" class="VPNav"> <header v-if="hasNavbar" class="VPNav" :class="{ 'screen-only': screenOnly }">
<VPNavBar :is-screen-open="isScreenOpen" @toggle-screen="toggleScreen"> <VPNavBar :is-screen-open="isScreenOpen" @toggle-screen="toggleScreen">
<template #nav-bar-title-before><slot name="nav-bar-title-before" /></template> <template #nav-bar-title-before><slot name="nav-bar-title-before" /></template>
<template #nav-bar-title-after><slot name="nav-bar-title-after" /></template> <template #nav-bar-title-after><slot name="nav-bar-title-after" /></template>

@ -4,8 +4,10 @@ import { inBrowser } from 'vitepress'
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
import { useSidebar } from '../composables/sidebar' import { useSidebar } from '../composables/sidebar'
import VPSidebarItem from './VPSidebarItem.vue' import VPSidebarItem from './VPSidebarItem.vue'
import { useScreenOnly } from '../composables/scree-only'
const { sidebarGroups, hasSidebar } = useSidebar() const { sidebarGroups, hasSidebar } = useSidebar()
const screenOnly = useScreenOnly('sidebar')
const props = defineProps<{ const props = defineProps<{
open: boolean open: boolean
@ -31,7 +33,7 @@ watch(
<aside <aside
v-if="hasSidebar" v-if="hasSidebar"
class="VPSidebar" class="VPSidebar"
:class="{ open }" :class="{ open, 'screen-only': screenOnly }"
ref="navEl" ref="navEl"
@click.stop @click.stop
> >

@ -0,0 +1,15 @@
import { useData } from './data'
import type { DefaultTheme } from 'vitepress/theme'
import { computed } from 'vue'
export function useScreenOnly(entry: keyof DefaultTheme.PrintOptions) {
const { theme, frontmatter } = useData()
return computed(
() =>
frontmatter.value[entry] === false ||
theme.value.print === false ||
(typeof theme.value.print === 'object' &&
theme.value.print[entry] === false)
)
}

@ -223,6 +223,24 @@ export async function resolveSiteData(
): Promise<SiteData> { ): Promise<SiteData> {
userConfig = userConfig || (await resolveUserConfig(root, command, mode))[0] userConfig = userConfig || (await resolveUserConfig(root, command, mode))[0]
const themeConfig = userConfig.themeConfig || {}
if (typeof themeConfig.print === 'boolean') {
themeConfig.print = {
outline: themeConfig.print,
navbar: themeConfig.print,
sidebar: themeConfig.print,
footer: themeConfig.print
}
}
else {
themeConfig.print ??= {}
themeConfig.print.outline ??= false
themeConfig.print.navbar ??= false
themeConfig.print.sidebar ??= false
themeConfig.print.footer ??= true
}
return { return {
lang: userConfig.lang || 'en-US', lang: userConfig.lang || 'en-US',
dir: userConfig.dir || 'ltr', dir: userConfig.dir || 'ltr',
@ -232,7 +250,7 @@ export async function resolveSiteData(
base: userConfig.base ? userConfig.base.replace(/([^/])$/, '$1/') : '/', base: userConfig.base ? userConfig.base.replace(/([^/])$/, '$1/') : '/',
head: resolveSiteDataHead(userConfig), head: resolveSiteDataHead(userConfig),
appearance: userConfig.appearance ?? true, appearance: userConfig.appearance ?? true,
themeConfig: userConfig.themeConfig || {}, themeConfig,
locales: userConfig.locales || {}, locales: userConfig.locales || {},
scrollOffset: userConfig.scrollOffset ?? 90, scrollOffset: userConfig.scrollOffset ?? 90,
cleanUrls: !!userConfig.cleanUrls, cleanUrls: !!userConfig.cleanUrls,

@ -145,6 +145,19 @@ export namespace DefaultTheme {
* Customize text of 404 page. * Customize text of 404 page.
*/ */
notFound?: NotFoundOptions notFound?: NotFoundOptions
/**
* Customize how pages will be printed.
*
* To exclude `outline`, `navbar`, `sidebar` and `footer` from being printed, configure `print: false`.
*
* To print all of them, configure `print: true`.
*
* Frontmatter configuration will override the global configuration, for example, a page containing `navbar: false`, the navbar will not be printed.
*
* @default { outline: false, navbar: false, sidebar: false, footer: true }
*/
print?: boolean | PrintOptions
} }
// nav ----------------------------------------------------------------------- // nav -----------------------------------------------------------------------
@ -474,4 +487,33 @@ export namespace DefaultTheme {
*/ */
code?: string code?: string
} }
// print options -----------------------------------------------------------------
export interface PrintOptions {
/**
* Should the outline be printed?
*
* @default false
*/
outline?: boolean
/**
* Should the navbar be printed?
*
* @default false
*/
navbar?: boolean
/**
* Should the sidebar be printed?
*
* @default false
*/
sidebar?: boolean
/**
* Should the sidebar be printed?
*
* @default true
*/
footer?: boolean
}
} }

Loading…
Cancel
Save