docus 1.0.7 → 3.0.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/README.md +9 -22
  2. package/package.json +38 -63
  3. package/theme/app/router.options.ts +18 -0
  4. package/theme/assets/css/fonts.css +84 -0
  5. package/theme/assets/css/main.css +104 -0
  6. package/theme/components/app/Container.vue +25 -0
  7. package/theme/components/app/Footer.vue +40 -0
  8. package/theme/components/app/MobileNav.vue +85 -0
  9. package/theme/components/app/Navbar.vue +37 -0
  10. package/theme/components/app/NavbarLogo.vue +33 -0
  11. package/theme/components/app/Page.vue +7 -0
  12. package/theme/components/app/PoweredByDocus.vue +11 -0
  13. package/theme/components/content/Alert.vue +124 -0
  14. package/theme/components/content/BlockHero.vue +54 -0
  15. package/theme/components/content/ButtonLink.vue +45 -0
  16. package/theme/components/content/Card.vue +46 -0
  17. package/theme/components/content/CardGrid.vue +23 -0
  18. package/theme/components/content/CodeBlock.vue +47 -0
  19. package/theme/components/content/CodeGroup.vue +135 -0
  20. package/theme/components/content/CopyButton.vue +49 -0
  21. package/theme/components/content/List.vue +5 -0
  22. package/theme/components/content/NeedContribution.vue +23 -0
  23. package/theme/components/content/ReadMore.vue +25 -0
  24. package/theme/components/content/Sandbox.vue +102 -0
  25. package/theme/components/content/TabsHeader.vue +44 -0
  26. package/theme/components/content/Terminal.vue +64 -0
  27. package/theme/components/content/VideoPlayer.vue +115 -0
  28. package/theme/components/dev/Debug.vue +65 -0
  29. package/theme/components/docs/DocsAside.vue +21 -0
  30. package/theme/components/docs/DocsAsideTree.vue +104 -0
  31. package/theme/components/docs/DocsHero.vue +39 -0
  32. package/theme/components/docs/DocsPage.vue +21 -0
  33. package/theme/components/docs/DocsPageContent.vue +32 -0
  34. package/theme/components/docs/DocsToc.vue +77 -0
  35. package/theme/components/globals/Icon.vue +24 -0
  36. package/theme/components/globals/Logo.vue +3 -0
  37. package/theme/components/globals/NuxtImg.vue +45 -0
  38. package/theme/components/globals/SocialIcons.vue +45 -0
  39. package/theme/components/globals/ThemeSelect.vue +35 -0
  40. package/theme/components/icons/IconAlgolia.vue +8 -0
  41. package/theme/components/icons/IconArrowLeft.vue +10 -0
  42. package/theme/components/icons/IconArrowRight.vue +10 -0
  43. package/theme/components/icons/IconBadgeCheck.vue +14 -0
  44. package/theme/components/icons/IconCheck.vue +10 -0
  45. package/theme/components/icons/IconCheckCircle.vue +10 -0
  46. package/theme/components/icons/IconChevronRight.vue +12 -0
  47. package/theme/components/icons/IconClipboardCheck.vue +14 -0
  48. package/theme/components/icons/IconClipboardCopy.vue +14 -0
  49. package/theme/components/icons/IconCodeSandbox.vue +8 -0
  50. package/theme/components/icons/IconCopy.vue +17 -0
  51. package/theme/components/icons/IconDots.vue +10 -0
  52. package/theme/components/icons/IconEdit.vue +18 -0
  53. package/theme/components/icons/IconExclamationCircle.vue +12 -0
  54. package/theme/components/icons/IconExclamationTriangle.vue +10 -0
  55. package/theme/components/icons/IconExternalLink.vue +12 -0
  56. package/theme/components/icons/IconGit.vue +7 -0
  57. package/theme/components/icons/IconGitHub.vue +10 -0
  58. package/theme/components/icons/IconHeart.vue +9 -0
  59. package/theme/components/icons/IconInformationCircle.vue +10 -0
  60. package/theme/components/icons/IconLighthouse.vue +83 -0
  61. package/theme/components/icons/IconLine.vue +10 -0
  62. package/theme/components/icons/IconMarkdown.vue +13 -0
  63. package/theme/components/icons/IconMenu.vue +12 -0
  64. package/theme/components/icons/IconMenuAlt.vue +10 -0
  65. package/theme/components/icons/IconMinus.vue +10 -0
  66. package/theme/components/icons/IconMoon.vue +10 -0
  67. package/theme/components/icons/IconNuxt.vue +14 -0
  68. package/theme/components/icons/IconNuxtContent.vue +20 -0
  69. package/theme/components/icons/IconNuxtLabs.vue +21 -0
  70. package/theme/components/icons/IconPlus.vue +10 -0
  71. package/theme/components/icons/IconPuzzle.vue +8 -0
  72. package/theme/components/icons/IconSSG.vue +7 -0
  73. package/theme/components/icons/IconSearch.vue +12 -0
  74. package/theme/components/icons/IconSun.vue +10 -0
  75. package/theme/components/icons/IconTailwind.vue +3 -0
  76. package/theme/components/icons/IconTocBack.vue +21 -0
  77. package/theme/components/icons/IconTocCurrent.vue +21 -0
  78. package/theme/components/icons/IconTocNext.vue +8 -0
  79. package/theme/components/icons/IconTranslate.vue +14 -0
  80. package/theme/components/icons/IconTwitter.vue +8 -0
  81. package/theme/components/icons/IconVite.vue +30 -0
  82. package/theme/components/icons/IconVue.vue +6 -0
  83. package/theme/components/icons/IconVueTelescope.vue +11 -0
  84. package/theme/components/icons/IconWindi.vue +17 -0
  85. package/theme/components/icons/IconX.vue +12 -0
  86. package/theme/components/icons/IconXCircle.vue +10 -0
  87. package/theme/components/icons/IconZap.vue +8 -0
  88. package/theme/components/prose/ProseA.vue +66 -0
  89. package/theme/components/prose/ProseBlockquote.vue +21 -0
  90. package/theme/components/prose/ProseCode.vue +67 -0
  91. package/theme/components/prose/ProseCodeInline.vue +38 -0
  92. package/theme/components/prose/ProseEm.vue +11 -0
  93. package/theme/components/prose/ProseH1.vue +22 -0
  94. package/theme/components/prose/ProseH2.vue +22 -0
  95. package/theme/components/prose/ProseH3.vue +24 -0
  96. package/theme/components/prose/ProseH4.vue +24 -0
  97. package/theme/components/prose/ProseHr.vue +13 -0
  98. package/theme/components/prose/ProseImg.vue +30 -0
  99. package/theme/components/prose/ProseLi.vue +31 -0
  100. package/theme/components/prose/ProseOl.vue +16 -0
  101. package/theme/components/prose/ProseP.vue +14 -0
  102. package/theme/components/prose/ProseStrong.vue +14 -0
  103. package/theme/components/prose/ProseTable.vue +13 -0
  104. package/theme/components/prose/ProseTbody.vue +5 -0
  105. package/theme/components/prose/ProseTd.vue +11 -0
  106. package/theme/components/prose/ProseTh.vue +11 -0
  107. package/theme/components/prose/ProseThead.vue +11 -0
  108. package/theme/components/prose/ProseTr.vue +11 -0
  109. package/theme/components/prose/ProseUl.vue +15 -0
  110. package/theme/composables/useDocus.ts +43 -0
  111. package/theme/composables/useMenu.ts +7 -0
  112. package/theme/composables/useScrollToHeading.ts +35 -0
  113. package/theme/composables/useScrollspy.ts +46 -0
  114. package/theme/composables/useUserAgent.ts +7 -0
  115. package/theme/composables/utils.ts +4 -0
  116. package/theme/layouts/default.vue +29 -0
  117. package/theme/layouts/page.vue +19 -0
  118. package/theme/middleware/components.ts +26 -0
  119. package/theme/middleware/navigation.global.ts +12 -0
  120. package/theme/middleware/page.ts +8 -0
  121. package/theme/middleware/theme.global.ts +12 -0
  122. package/theme/nuxt.config.ts +171 -0
  123. package/theme/pages/[...slug].vue +64 -0
  124. package/theme/plugins/menu.ts +67 -0
  125. package/theme/plugins/user-agent.ts +27 -0
  126. package/theme/utils/components.ts +25 -0
  127. package/theme/utils/navigation.ts +49 -0
  128. package/theme/utils/plugin.ts +21 -0
  129. package/theme/utils/queries.ts +68 -0
  130. package/theme/utils/state.ts +33 -0
  131. package/theme/utils/theme.ts +66 -0
  132. package/dist/create-docus/create-docus.js +0 -7
  133. package/dist/create-docus/helpers.js +0 -244
  134. package/dist/create-docus/index.js +0 -87
  135. package/dist/index.js +0 -10
@@ -0,0 +1,11 @@
1
+ <template>
2
+ <th>
3
+ <slot />
4
+ </th>
5
+ </template>
6
+
7
+ <style lang="postcss" scoped>
8
+ thead th {
9
+ @apply font-semibold text-secondary p-2 pt-0 align-bottom first:pl-0 last:pr-0;
10
+ }
11
+ </style>
@@ -0,0 +1,11 @@
1
+ <template>
2
+ <thead>
3
+ <slot />
4
+ </thead>
5
+ </template>
6
+
7
+ <style lang="postcss" scoped>
8
+ thead {
9
+ @apply border-b border-gray-200 dark:border-gray-700;
10
+ }
11
+ </style>
@@ -0,0 +1,11 @@
1
+ <template>
2
+ <tr>
3
+ <slot />
4
+ </tr>
5
+ </template>
6
+
7
+ <style lang="postcss" scoped>
8
+ tbody tr {
9
+ @apply border-b border-gray-100 dark:border-gray-800;
10
+ }
11
+ </style>
@@ -0,0 +1,15 @@
1
+ <template>
2
+ <ul>
3
+ <slot />
4
+ </ul>
5
+ </template>
6
+
7
+ <style lang="postcss" scoped>
8
+ ul {
9
+ @apply list-none mx-0 p-0 my-[1.25em];
10
+ :deep(ul),
11
+ :deep(ol) {
12
+ @apply !my-[0.5em];
13
+ }
14
+ }
15
+ </style>
@@ -0,0 +1,43 @@
1
+ import { useDocusState } from '../utils/state'
2
+ import { computed } from '#imports'
3
+
4
+ export const useDocus = () => {
5
+ const { theme, navigation, page, surround } = useDocusState()
6
+
7
+ /**
8
+ * Table of contents from parsed page.
9
+ */
10
+ const toc = computed(() => page?.value?.body?.toc || [])
11
+
12
+ /**
13
+ * Content type from parsed page.
14
+ */
15
+ const type = computed(() => page.value?.meta?.type)
16
+
17
+ /**
18
+ * Layout type from parsed page.
19
+ */
20
+ const layout = computed(() => page.value?.meta?.layout)
21
+
22
+ /**
23
+ * Next page from `surround`.
24
+ */
25
+ const next = computed(() => surround.value?.[1] || null)
26
+
27
+ /**
28
+ * Previous page from `surround`.
29
+ */
30
+ const prev = computed(() => surround.value?.[0] || null)
31
+
32
+ return {
33
+ theme,
34
+ navigation,
35
+ surround,
36
+ page,
37
+ toc,
38
+ type,
39
+ layout,
40
+ next,
41
+ prev,
42
+ }
43
+ }
@@ -0,0 +1,7 @@
1
+ import { useNuxtApp } from '#imports'
2
+
3
+ export const useMenu = () => {
4
+ const { $menu } = useNuxtApp()
5
+
6
+ return $menu
7
+ }
@@ -0,0 +1,35 @@
1
+ export const useConvertPropToPixels = (prop: string): number => {
2
+ const tempDiv = document.createElement('div')
3
+
4
+ tempDiv.style.position = 'absolute'
5
+ tempDiv.style.opacity = '0'
6
+ tempDiv.style.height = getComputedStyle(document.documentElement).getPropertyValue(prop)
7
+
8
+ document.body.appendChild(tempDiv)
9
+
10
+ const pixels = parseInt(getComputedStyle(tempDiv).height)
11
+
12
+ document.body.removeChild(tempDiv)
13
+
14
+ return pixels
15
+ }
16
+
17
+ export const useScrollToHeading = (id: string, scrollMarginCssVar: string) => {
18
+ // Use replaceState to prevent page jump when adding hash
19
+ history.replaceState({}, '', `#${id}`)
20
+
21
+ // Do not remove setTimeout (does not work in Safari)
22
+ setTimeout(() => {
23
+ const escapedId = id.replace(/\./g, '\\.')
24
+
25
+ const heading = document.querySelector(`#${escapedId}`) as any
26
+
27
+ const offset = heading.offsetTop - useConvertPropToPixels(scrollMarginCssVar)
28
+
29
+ window.scrollTo({
30
+ top: offset,
31
+ left: 0,
32
+ behavior: 'smooth',
33
+ })
34
+ })
35
+ }
@@ -0,0 +1,46 @@
1
+ import type { Ref } from 'vue'
2
+ import { onBeforeMount, onBeforeUnmount, ref, watch } from '#imports'
3
+
4
+ /**
5
+ * Scrollspy allows you to watch visible headings in a specific page.
6
+ * Useful for table of contents live style updates.
7
+ */
8
+ export const useScrollspy = () => {
9
+ const observer = ref() as Ref<IntersectionObserver>
10
+ const visibleHeadings = ref([]) as Ref<string[]>
11
+ const activeHeadings = ref([]) as Ref<string[]>
12
+
13
+ const observerCallback = (entries: IntersectionObserverEntry[]) =>
14
+ entries.forEach((entry) => {
15
+ const id = entry.target.id
16
+
17
+ if (entry.isIntersecting)
18
+ visibleHeadings.value.push(id)
19
+ else
20
+ visibleHeadings.value = visibleHeadings.value.filter(t => t !== id)
21
+ })
22
+
23
+ const updateHeadings = (headings: Element[]) =>
24
+ headings.forEach((heading) => {
25
+ observer.value.observe(heading)
26
+ })
27
+
28
+ watch(visibleHeadings, (val, oldVal) => {
29
+ if (val.length === 0)
30
+ activeHeadings.value = oldVal
31
+ else
32
+ activeHeadings.value = val
33
+ })
34
+
35
+ // Create intersection observer
36
+ onBeforeMount(() => (observer.value = new IntersectionObserver(observerCallback)))
37
+
38
+ // Destroy it
39
+ onBeforeUnmount(() => observer.value?.disconnect())
40
+
41
+ return {
42
+ visibleHeadings,
43
+ activeHeadings,
44
+ updateHeadings,
45
+ }
46
+ }
@@ -0,0 +1,7 @@
1
+ import { useNuxtApp } from '#imports'
2
+
3
+ export const useUserAgent = () => {
4
+ const { $userAgent } = useNuxtApp()
5
+
6
+ return $userAgent
7
+ }
@@ -0,0 +1,4 @@
1
+ export const classNames = (...args: any[]) => {
2
+ const classes = args.filter(Boolean).join(' ')
3
+ return classes.length ? classes : undefined
4
+ }
@@ -0,0 +1,29 @@
1
+ <script setup lang="ts">
2
+ import { useDocus } from '#imports'
3
+
4
+ const { theme } = useDocus()
5
+ </script>
6
+
7
+ <template>
8
+ <div class="w-full flex min-h-screen flex-col">
9
+ <Debug v-if="theme?.debug" :config="theme?.debug" />
10
+
11
+ <div class="min-h-[calc(100vh-12rem)] sm:min-h-[calc(100vh-8rem)] flex flex-col">
12
+ <Navbar />
13
+
14
+ <DocsPage>
15
+ <template #aside>
16
+ <DocsAside />
17
+ </template>
18
+
19
+ <DocsPageContent>
20
+ <div class="max-w-none">
21
+ <NuxtPage />
22
+ </div>
23
+ </DocsPageContent>
24
+ </DocsPage>
25
+ </div>
26
+
27
+ <Footer />
28
+ </div>
29
+ </template>
@@ -0,0 +1,19 @@
1
+ <script setup lang="ts">
2
+ import { useDocus } from '#imports'
3
+
4
+ const { theme } = useDocus()
5
+ </script>
6
+
7
+ <template>
8
+ <div class="w-full flex min-h-screen flex-col">
9
+ <Debug v-if="theme?.debug" :config="theme?.debug" />
10
+
11
+ <Navbar />
12
+
13
+ <div class="min-h-[calc(100vh-12rem)] sm:min-h-[calc(100vh-8rem)] flex flex-col">
14
+ <NuxtPage />
15
+ </div>
16
+
17
+ <Footer />
18
+ </div>
19
+ </template>
@@ -0,0 +1,26 @@
1
+ import { flattenComponents } from '../utils/components'
2
+ import { useDocusState } from '../utils/state'
3
+ import * as Components from '#components'
4
+ import { useNuxtApp } from '#imports'
5
+
6
+ export default defineNuxtRouteMiddleware(
7
+ async () => {
8
+ const { page } = useDocusState()
9
+ const nuxtApp = useNuxtApp()
10
+
11
+ if (page.value) {
12
+ // Components used in page (prose + Vue components)
13
+ const components: string[] = flattenComponents(page.value.body.children)
14
+
15
+ // Preload components before rendering
16
+ for (const name of components) {
17
+ if (!nuxtApp.vueApp.component(name)) {
18
+ // eslint-disable-next-line no-console
19
+ console.log({ name, component: (Components as any)[name] })
20
+
21
+ nuxtApp.vueApp.component(name, (Components as any)[name])
22
+ }
23
+ }
24
+ }
25
+ },
26
+ )
@@ -0,0 +1,12 @@
1
+ import { useDocusState } from '../utils/state'
2
+ import { queryNavigation } from '../utils/queries'
3
+ import { defineNuxtRouteMiddleware } from '#imports'
4
+
5
+ export default defineNuxtRouteMiddleware(
6
+ async () => {
7
+ const { navigation } = useDocusState()
8
+
9
+ if (!navigation.value)
10
+ await queryNavigation()
11
+ },
12
+ )
@@ -0,0 +1,8 @@
1
+ import { queryPage } from '../utils/queries'
2
+ import { defineNuxtRouteMiddleware } from '#imports'
3
+
4
+ export default defineNuxtRouteMiddleware(
5
+ async (to) => {
6
+ await queryPage(to)
7
+ },
8
+ )
@@ -0,0 +1,12 @@
1
+ import { useDocusState } from '../utils/state'
2
+ import { queryTheme } from '../utils/queries'
3
+ import { defineNuxtRouteMiddleware } from '#imports'
4
+
5
+ export default defineNuxtRouteMiddleware(
6
+ async () => {
7
+ const { theme } = useDocusState()
8
+
9
+ if (!theme.value)
10
+ await queryTheme()
11
+ },
12
+ )
@@ -0,0 +1,171 @@
1
+ import { fileURLToPath } from 'url'
2
+ import { defineNuxtConfig } from 'nuxt'
3
+ import colors from 'tailwindcss/colors.js'
4
+ import { resolve } from 'pathe'
5
+
6
+ const themeDir = fileURLToPath(new URL('./', import.meta.url))
7
+ const resolveThemeDir = (path: string) => resolve(themeDir, path)
8
+
9
+ const plugins = []
10
+
11
+ const components = [
12
+ {
13
+ prefix: '',
14
+ path: './components/app',
15
+ global: true,
16
+ },
17
+ {
18
+ prefix: '',
19
+ path: './components/docs',
20
+ global: true,
21
+ },
22
+ {
23
+ prefix: '',
24
+ path: './components/prose',
25
+ global: true,
26
+ },
27
+ {
28
+ prefix: '',
29
+ path: './components/globals',
30
+ global: true,
31
+ },
32
+ {
33
+ prefix: '',
34
+ path: './components/content',
35
+ global: true,
36
+ },
37
+ {
38
+ prefix: '',
39
+ path: './components/icons',
40
+ global: true,
41
+ },
42
+ {
43
+ prefix: '',
44
+ path: './components/icons',
45
+ global: true,
46
+ },
47
+ ]
48
+
49
+ // Only register the plugin in development as it's not needed in production
50
+ if (process.env.NODE_ENV === 'development') {
51
+ // Dev plugin
52
+ plugins.push({
53
+ src: resolveThemeDir('utils/plugin.ts'),
54
+ })
55
+ }
56
+
57
+ // Dev components
58
+ components.push({
59
+ prefix: '',
60
+ path: './components/dev',
61
+ global: true,
62
+ })
63
+
64
+ export default defineNuxtConfig({
65
+ /*
66
+ runtimeConfig: {
67
+ public: {
68
+ plausible: {
69
+ domain: process.env.PLAUSIBLE_DOMAIN,
70
+ },
71
+ },
72
+ },
73
+ */
74
+ plugins,
75
+ head: {
76
+ title: 'Docus',
77
+ link: [
78
+ {
79
+ rel: 'stylesheet',
80
+ href: 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap',
81
+ },
82
+ { rel: 'preconnect', href: 'https://fonts.gstatic.com' },
83
+ ],
84
+ meta: [
85
+ { hid: 'og:site_name', property: 'og:site_name', content: 'Nuxt 3' },
86
+ { hid: 'og:type', property: 'og:type', content: 'website' },
87
+ ],
88
+ },
89
+ loading: {
90
+ color: '#00DC82',
91
+ },
92
+ /**
93
+ * Components
94
+ */
95
+ // To enable for `components` middleware
96
+ //
97
+ // components: [
98
+ // './components/app',
99
+ // './components/docs',
100
+ // './components/prose',
101
+ // './components/globals',
102
+ // './components/content',
103
+ // './components/dev',
104
+ // {
105
+ // prefix: '',
106
+ // path: './components/icons',
107
+ // global: true,
108
+ // },
109
+ // ],
110
+ // To enable for working build
111
+ components,
112
+ css: [
113
+ resolveThemeDir('assets/css/fonts.css'),
114
+ ],
115
+ tailwindcss: {
116
+ viewer: false,
117
+ cssPath: resolveThemeDir('assets/css/main.css'),
118
+ config: {
119
+ darkMode: 'class',
120
+ theme: {
121
+ extend: {
122
+ colors: {
123
+ gray: colors.gray,
124
+ primary: colors.indigo,
125
+ },
126
+ fontFamily: {
127
+ sans: 'Inter, sans-serif',
128
+ },
129
+ spacing: {
130
+ base: '320px',
131
+ header: 'var(--header-height)',
132
+ },
133
+ },
134
+ },
135
+ plugins: [
136
+ require('@tailwindcss/typography'),
137
+ require('@tailwindcss/forms'),
138
+ require('@tailwindcss/line-clamp'),
139
+ require('@tailwindcss/aspect-ratio'),
140
+ ],
141
+ content: [
142
+ '~/content/**/*.{md,yml,json,json5,csv}',
143
+ resolveThemeDir('assets/**/*.{mjs,vue,js,ts}'),
144
+ resolveThemeDir('components/**/*.{mjs,vue,js,ts}'),
145
+ resolveThemeDir('layouts/**/*.{mjs,vue,js,ts}'),
146
+ resolveThemeDir('pages/**/*.{mjs,vue,js,ts}'),
147
+ ],
148
+ safelist: [24, 36, 48, 60, 72, 84, 96, 108, 120].map(number => `pl-[${number}px]`),
149
+ },
150
+ },
151
+ content: {
152
+ highlight: {
153
+ theme: 'one-dark-pro',
154
+ preload: ['json', 'js', 'ts', 'html', 'css', 'vue', 'diff', 'shell', 'markdown', 'yaml', 'bash'],
155
+ },
156
+ },
157
+ colorMode: {
158
+ classSuffix: '',
159
+ },
160
+ /**
161
+ * Modules
162
+ */
163
+ modules: [
164
+ '@nuxt/content',
165
+ '@nuxtjs/tailwindcss',
166
+ '@nuxtjs/color-mode',
167
+ '@nuxthq/admin',
168
+ // 'vue-plausible',
169
+ '@vueuse/nuxt',
170
+ ],
171
+ })
@@ -0,0 +1,64 @@
1
+ <script setup lang="ts">
2
+ import { useDocus, useHead } from '#imports'
3
+
4
+ definePageMeta({
5
+ middleware: [
6
+ 'page',
7
+ /* 'components' */
8
+ ],
9
+ })
10
+
11
+ const { page, theme } = useDocus()
12
+
13
+ useHead({
14
+ title: `${theme.value?.title} | ${page.value?.title}`,
15
+ description: page.value?.description || theme.value?.description || '',
16
+ meta: [
17
+ { hid: 'og:site_name', property: 'og:site_name', content: 'Nuxt' },
18
+ { hid: 'og:type', property: 'og:type', content: 'website' },
19
+ { hid: 'twitter:site', name: 'twitter:site', content: theme.value?.url || theme.value?.twitter || '' },
20
+ {
21
+ hid: 'twitter:card',
22
+ name: 'twitter:card',
23
+ content: 'summary_large_image',
24
+ },
25
+ {
26
+ hid: 'og:image',
27
+ property: 'og:image',
28
+ content: theme.value?.cover || '',
29
+ },
30
+ {
31
+ hid: 'og:image:secure_url',
32
+ property: 'og:image:secure_url',
33
+ content: theme.value?.cover || '',
34
+ },
35
+ {
36
+ hid: 'og:image:alt',
37
+ property: 'og:image:alt',
38
+ content: theme.value?.coverAlt || '',
39
+ },
40
+ {
41
+ hid: 'twitter:image',
42
+ name: 'twitter:image',
43
+ content: theme.value?.cover || '',
44
+ },
45
+ ],
46
+ })
47
+ </script>
48
+
49
+ <template>
50
+ <Content v-if="page" class="content" :document="page" />
51
+ <p v-else>
52
+ <Alert type="warning">
53
+ Page not found!
54
+ </Alert>
55
+ </p>
56
+ </template>
57
+
58
+ <style scoped>
59
+ .content {
60
+ & > :first-child {
61
+ margin-top: 0 !important;
62
+ }
63
+ }
64
+ </style>
@@ -0,0 +1,67 @@
1
+ import { defineNuxtPlugin, ref } from '#imports'
2
+
3
+ export default defineNuxtPlugin((ctx: any) => {
4
+ // Menu visible reference
5
+ const visible = ref(false)
6
+
7
+ // Current tab visible reference
8
+ const currentTab = ref()
9
+
10
+ // Scrollbar gap (used for responsive menu)
11
+ const scrollBarGap = ref()
12
+
13
+ // Open the menu
14
+ const open = () => (visible.value = true)
15
+
16
+ // Close the menu
17
+ const close = () => (visible.value = false)
18
+
19
+ // Toggle the menu (useful for one-off buttons)
20
+ const toggle = () => (visible.value = !visible.value)
21
+
22
+ // Toggle a tab from its id
23
+ const toggleTab = (tab: string) =>
24
+ currentTab.value === tab ? (currentTab.value = undefined) : (currentTab.value = tab)
25
+
26
+ // Watch route change, close on change
27
+ ctx.$router.afterEach(() => setTimeout(close, 50))
28
+
29
+ // Watch visible and remove overflow so the scrollbar disappears when menu is opened
30
+ if (process.client) {
31
+ watch(
32
+ visible,
33
+ (isVisible) => {
34
+ const html = document.querySelector('html')
35
+
36
+ if (isVisible) {
37
+ scrollBarGap.value = window.innerWidth - document.documentElement.clientWidth
38
+ html.style.overflow = 'hidden'
39
+ html.style.paddingRight = `${scrollBarGap.value}px`
40
+ }
41
+ else {
42
+ setTimeout(() => {
43
+ html.style.overflow = ''
44
+ html.style.paddingRight = ''
45
+ }, 100) /* had to put it, because of layout shift on leave transition */
46
+ }
47
+ },
48
+ {
49
+ immediate: true,
50
+ },
51
+ )
52
+ }
53
+
54
+ return {
55
+ provide: {
56
+ menu: {
57
+ scrollBarGap,
58
+ visible,
59
+ close,
60
+ open,
61
+ toggle,
62
+ currentTab,
63
+ toggleTab,
64
+ },
65
+ },
66
+ }
67
+ })
@@ -0,0 +1,27 @@
1
+ import { defineNuxtPlugin, ref } from '#imports'
2
+
3
+ export default defineNuxtPlugin(() => {
4
+ const isDesktopSafari = ref(false)
5
+ const isDesktopFirefox = ref(false)
6
+
7
+ const refresh = () => {
8
+ isDesktopSafari.value
9
+ = !/Mobi|Android/i.test(navigator.userAgent)
10
+ && /Safari/i.test(navigator.userAgent)
11
+ && !/Chrome|Chromium/i.test(navigator.userAgent)
12
+
13
+ isDesktopFirefox.value = !/Mobi|Android/i.test(navigator.userAgent) && /Firefox/i.test(navigator.userAgent)
14
+ }
15
+
16
+ if (process.client)
17
+ refresh()
18
+
19
+ return {
20
+ provide: {
21
+ userAgent: {
22
+ isDesktopSafari,
23
+ isDesktopFirefox,
24
+ },
25
+ },
26
+ }
27
+ })
@@ -0,0 +1,25 @@
1
+ import { isHTMLTag } from '@vue/shared'
2
+ import { pascalCase } from 'scule'
3
+ import { useRuntimeConfig } from '#imports'
4
+
5
+ export const flattenComponents = (body, flattened = []) => {
6
+ // Grab tags list from content config
7
+ const { content: { tags = {} } } = useRuntimeConfig().public
8
+
9
+ for (const node of body) {
10
+ if (node?.tag) {
11
+ let tag = node.tag
12
+
13
+ if (Object.keys(tags).includes(tag))
14
+ tag = pascalCase(`prose-${tag}`)
15
+
16
+ if (!isHTMLTag(tag) && !flattened.includes(tag))
17
+ flattened.push(pascalCase(tag))
18
+ }
19
+
20
+ if (node.children)
21
+ flattenComponents(node.children, flattened)
22
+ }
23
+
24
+ return flattened
25
+ }