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.
- package/README.md +9 -22
- package/package.json +38 -63
- package/theme/app/router.options.ts +18 -0
- package/theme/assets/css/fonts.css +84 -0
- package/theme/assets/css/main.css +104 -0
- package/theme/components/app/Container.vue +25 -0
- package/theme/components/app/Footer.vue +40 -0
- package/theme/components/app/MobileNav.vue +85 -0
- package/theme/components/app/Navbar.vue +37 -0
- package/theme/components/app/NavbarLogo.vue +33 -0
- package/theme/components/app/Page.vue +7 -0
- package/theme/components/app/PoweredByDocus.vue +11 -0
- package/theme/components/content/Alert.vue +124 -0
- package/theme/components/content/BlockHero.vue +54 -0
- package/theme/components/content/ButtonLink.vue +45 -0
- package/theme/components/content/Card.vue +46 -0
- package/theme/components/content/CardGrid.vue +23 -0
- package/theme/components/content/CodeBlock.vue +47 -0
- package/theme/components/content/CodeGroup.vue +135 -0
- package/theme/components/content/CopyButton.vue +49 -0
- package/theme/components/content/List.vue +5 -0
- package/theme/components/content/NeedContribution.vue +23 -0
- package/theme/components/content/ReadMore.vue +25 -0
- package/theme/components/content/Sandbox.vue +102 -0
- package/theme/components/content/TabsHeader.vue +44 -0
- package/theme/components/content/Terminal.vue +64 -0
- package/theme/components/content/VideoPlayer.vue +115 -0
- package/theme/components/dev/Debug.vue +65 -0
- package/theme/components/docs/DocsAside.vue +21 -0
- package/theme/components/docs/DocsAsideTree.vue +104 -0
- package/theme/components/docs/DocsHero.vue +39 -0
- package/theme/components/docs/DocsPage.vue +21 -0
- package/theme/components/docs/DocsPageContent.vue +32 -0
- package/theme/components/docs/DocsToc.vue +77 -0
- package/theme/components/globals/Icon.vue +24 -0
- package/theme/components/globals/Logo.vue +3 -0
- package/theme/components/globals/NuxtImg.vue +45 -0
- package/theme/components/globals/SocialIcons.vue +45 -0
- package/theme/components/globals/ThemeSelect.vue +35 -0
- package/theme/components/icons/IconAlgolia.vue +8 -0
- package/theme/components/icons/IconArrowLeft.vue +10 -0
- package/theme/components/icons/IconArrowRight.vue +10 -0
- package/theme/components/icons/IconBadgeCheck.vue +14 -0
- package/theme/components/icons/IconCheck.vue +10 -0
- package/theme/components/icons/IconCheckCircle.vue +10 -0
- package/theme/components/icons/IconChevronRight.vue +12 -0
- package/theme/components/icons/IconClipboardCheck.vue +14 -0
- package/theme/components/icons/IconClipboardCopy.vue +14 -0
- package/theme/components/icons/IconCodeSandbox.vue +8 -0
- package/theme/components/icons/IconCopy.vue +17 -0
- package/theme/components/icons/IconDots.vue +10 -0
- package/theme/components/icons/IconEdit.vue +18 -0
- package/theme/components/icons/IconExclamationCircle.vue +12 -0
- package/theme/components/icons/IconExclamationTriangle.vue +10 -0
- package/theme/components/icons/IconExternalLink.vue +12 -0
- package/theme/components/icons/IconGit.vue +7 -0
- package/theme/components/icons/IconGitHub.vue +10 -0
- package/theme/components/icons/IconHeart.vue +9 -0
- package/theme/components/icons/IconInformationCircle.vue +10 -0
- package/theme/components/icons/IconLighthouse.vue +83 -0
- package/theme/components/icons/IconLine.vue +10 -0
- package/theme/components/icons/IconMarkdown.vue +13 -0
- package/theme/components/icons/IconMenu.vue +12 -0
- package/theme/components/icons/IconMenuAlt.vue +10 -0
- package/theme/components/icons/IconMinus.vue +10 -0
- package/theme/components/icons/IconMoon.vue +10 -0
- package/theme/components/icons/IconNuxt.vue +14 -0
- package/theme/components/icons/IconNuxtContent.vue +20 -0
- package/theme/components/icons/IconNuxtLabs.vue +21 -0
- package/theme/components/icons/IconPlus.vue +10 -0
- package/theme/components/icons/IconPuzzle.vue +8 -0
- package/theme/components/icons/IconSSG.vue +7 -0
- package/theme/components/icons/IconSearch.vue +12 -0
- package/theme/components/icons/IconSun.vue +10 -0
- package/theme/components/icons/IconTailwind.vue +3 -0
- package/theme/components/icons/IconTocBack.vue +21 -0
- package/theme/components/icons/IconTocCurrent.vue +21 -0
- package/theme/components/icons/IconTocNext.vue +8 -0
- package/theme/components/icons/IconTranslate.vue +14 -0
- package/theme/components/icons/IconTwitter.vue +8 -0
- package/theme/components/icons/IconVite.vue +30 -0
- package/theme/components/icons/IconVue.vue +6 -0
- package/theme/components/icons/IconVueTelescope.vue +11 -0
- package/theme/components/icons/IconWindi.vue +17 -0
- package/theme/components/icons/IconX.vue +12 -0
- package/theme/components/icons/IconXCircle.vue +10 -0
- package/theme/components/icons/IconZap.vue +8 -0
- package/theme/components/prose/ProseA.vue +66 -0
- package/theme/components/prose/ProseBlockquote.vue +21 -0
- package/theme/components/prose/ProseCode.vue +67 -0
- package/theme/components/prose/ProseCodeInline.vue +38 -0
- package/theme/components/prose/ProseEm.vue +11 -0
- package/theme/components/prose/ProseH1.vue +22 -0
- package/theme/components/prose/ProseH2.vue +22 -0
- package/theme/components/prose/ProseH3.vue +24 -0
- package/theme/components/prose/ProseH4.vue +24 -0
- package/theme/components/prose/ProseHr.vue +13 -0
- package/theme/components/prose/ProseImg.vue +30 -0
- package/theme/components/prose/ProseLi.vue +31 -0
- package/theme/components/prose/ProseOl.vue +16 -0
- package/theme/components/prose/ProseP.vue +14 -0
- package/theme/components/prose/ProseStrong.vue +14 -0
- package/theme/components/prose/ProseTable.vue +13 -0
- package/theme/components/prose/ProseTbody.vue +5 -0
- package/theme/components/prose/ProseTd.vue +11 -0
- package/theme/components/prose/ProseTh.vue +11 -0
- package/theme/components/prose/ProseThead.vue +11 -0
- package/theme/components/prose/ProseTr.vue +11 -0
- package/theme/components/prose/ProseUl.vue +15 -0
- package/theme/composables/useDocus.ts +43 -0
- package/theme/composables/useMenu.ts +7 -0
- package/theme/composables/useScrollToHeading.ts +35 -0
- package/theme/composables/useScrollspy.ts +46 -0
- package/theme/composables/useUserAgent.ts +7 -0
- package/theme/composables/utils.ts +4 -0
- package/theme/layouts/default.vue +29 -0
- package/theme/layouts/page.vue +19 -0
- package/theme/middleware/components.ts +26 -0
- package/theme/middleware/navigation.global.ts +12 -0
- package/theme/middleware/page.ts +8 -0
- package/theme/middleware/theme.global.ts +12 -0
- package/theme/nuxt.config.ts +171 -0
- package/theme/pages/[...slug].vue +64 -0
- package/theme/plugins/menu.ts +67 -0
- package/theme/plugins/user-agent.ts +27 -0
- package/theme/utils/components.ts +25 -0
- package/theme/utils/navigation.ts +49 -0
- package/theme/utils/plugin.ts +21 -0
- package/theme/utils/queries.ts +68 -0
- package/theme/utils/state.ts +33 -0
- package/theme/utils/theme.ts +66 -0
- package/dist/create-docus/create-docus.js +0 -7
- package/dist/create-docus/helpers.js +0 -244
- package/dist/create-docus/index.js +0 -87
- package/dist/index.js +0 -10
|
@@ -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,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,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,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
|
+
}
|