Layout.vue 4.7 KB

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