Layout.vue 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. <template>
  2. <div class="theme" :class="pageClasses">
  3. <NavBar v-if="showNavbar" @toggle="toggleSidebar">
  4. <template #search>
  5. <slot name="navbar-search">
  6. <AlgoliaSearchBox
  7. v-if="theme.algolia"
  8. :options="theme.algolia"
  9. :multilang="!!theme.locales"
  10. :key="siteRouteData.lang"
  11. />
  12. </slot>
  13. </template>
  14. </NavBar>
  15. <SideBar :open="openSideBar">
  16. <template #sidebar-top>
  17. <slot name="sidebar-top" />
  18. </template>
  19. <template #sidebar-bottom>
  20. <slot name="sidebar-bottom" />
  21. </template>
  22. </SideBar>
  23. <!-- TODO: make this button accessible -->
  24. <div class="sidebar-mask" @click="toggleSidebar(false)" />
  25. <Content v-if="isCustomLayout" />
  26. <Home v-else-if="enableHome">
  27. <template #hero>
  28. <slot name="home-hero" />
  29. </template>
  30. <template #features>
  31. <slot name="home-features" />
  32. </template>
  33. <template #footer>
  34. <slot name="home-footer" />
  35. </template>
  36. </Home>
  37. <Page v-else>
  38. <template #top>
  39. <slot name="page-top-ads">
  40. <div
  41. id="ads-container"
  42. v-if="theme.carbonAds && theme.carbonAds.carbon"
  43. >
  44. <CarbonAds
  45. :key="'carbon' + page.relativePath"
  46. :code="theme.carbonAds.carbon"
  47. :placement="theme.carbonAds.placement"
  48. />
  49. </div>
  50. </slot>
  51. <slot name="page-top" />
  52. </template>
  53. <template #bottom>
  54. <slot name="page-bottom" />
  55. <slot name="page-bottom-ads">
  56. <BuySellAds
  57. v-if="theme.carbonAds && theme.carbonAds.custom"
  58. :key="'custom' + page.relativePath"
  59. :code="theme.carbonAds.custom"
  60. :placement="theme.carbonAds.placement"
  61. />
  62. </slot>
  63. </template>
  64. </Page>
  65. </div>
  66. <Debug />
  67. </template>
  68. <script setup lang="ts">
  69. import { ref, computed, watch, defineAsyncComponent } from 'vue'
  70. import {
  71. useRoute,
  72. useSiteData,
  73. usePageData,
  74. useSiteDataByRoute
  75. } from 'vitepress'
  76. import { isSideBarEmpty, getSideBarConfig } from './support/sideBar'
  77. import type { DefaultTheme } from './config'
  78. // components
  79. import NavBar from './components/NavBar.vue'
  80. import SideBar from './components/SideBar.vue'
  81. import Page from './components/Page.vue'
  82. const Home = defineAsyncComponent(() => import('./components/Home.vue'))
  83. const NoopComponent = () => null
  84. const CarbonAds = __CARBON__
  85. ? defineAsyncComponent(() => import('./components/CarbonAds.vue'))
  86. : NoopComponent
  87. const BuySellAds = __BSA__
  88. ? defineAsyncComponent(() => import('./components/BuySellAds.vue'))
  89. : NoopComponent
  90. const AlgoliaSearchBox = __ALGOLIA__
  91. ? defineAsyncComponent(() => import('./components/AlgoliaSearchBox.vue'))
  92. : NoopComponent
  93. // generic state
  94. const route = useRoute()
  95. const siteData = useSiteData<DefaultTheme.Config>()
  96. const siteRouteData = useSiteDataByRoute()
  97. const theme = computed(() => siteData.value.themeConfig)
  98. const page = usePageData()
  99. // custom layout
  100. const isCustomLayout = computed(() => !!route.data.frontmatter.customLayout)
  101. // home
  102. const enableHome = computed(() => !!route.data.frontmatter.home)
  103. // navbar
  104. const showNavbar = computed(() => {
  105. const { themeConfig } = siteRouteData.value
  106. const { frontmatter } = route.data
  107. if (frontmatter.navbar === false || themeConfig.navbar === false) {
  108. return false
  109. }
  110. return (
  111. siteData.value.title ||
  112. themeConfig.logo ||
  113. themeConfig.repo ||
  114. themeConfig.nav
  115. )
  116. })
  117. // sidebar
  118. const openSideBar = ref(false)
  119. const showSidebar = computed(() => {
  120. const { frontmatter } = route.data
  121. if (frontmatter.home || frontmatter.sidebar === false) {
  122. return false
  123. }
  124. const { themeConfig } = siteRouteData.value
  125. return !isSideBarEmpty(
  126. getSideBarConfig(themeConfig.sidebar, route.data.relativePath)
  127. )
  128. })
  129. const toggleSidebar = (to?: boolean) => {
  130. openSideBar.value = typeof to === 'boolean' ? to : !openSideBar.value
  131. }
  132. const hideSidebar = toggleSidebar.bind(null, false)
  133. // close the sidebar when navigating to a different location
  134. watch(route, hideSidebar)
  135. // TODO: route only changes when the pathname changes
  136. // listening to hashchange does nothing because it's prevented in router
  137. // page classes
  138. const pageClasses = computed(() => {
  139. return [
  140. {
  141. 'no-navbar': !showNavbar.value,
  142. 'sidebar-open': openSideBar.value,
  143. 'no-sidebar': !showSidebar.value
  144. }
  145. ]
  146. })
  147. </script>
  148. <style>
  149. #ads-container {
  150. margin: 0 auto;
  151. }
  152. @media (min-width: 420px) {
  153. #ads-container {
  154. position: relative;
  155. right: 0;
  156. float: right;
  157. margin: -8px -8px 24px 24px;
  158. width: 146px;
  159. }
  160. }
  161. @media (max-width: 420px) {
  162. #ads-container {
  163. /* Avoid layout shift */
  164. height: 105px;
  165. margin: 1.75rem 0;
  166. }
  167. }
  168. @media (min-width: 1400px) {
  169. #ads-container {
  170. position: fixed;
  171. right: 8px;
  172. bottom: 8px;
  173. }
  174. }
  175. </style>