undocs 0.3.1 → 0.3.3

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/app/app.config.ts CHANGED
@@ -3,9 +3,7 @@ export default defineAppConfig({
3
3
  socialBackground: 'https://github.com/unjs/docs/blob/main/assets/ellipse.png?raw=true',
4
4
  logo: '/icon.svg',
5
5
  github: undefined,
6
- socials: {
7
- x: 'unjsio',
8
- },
6
+ socials: {},
9
7
  },
10
8
  ui: {
11
9
  colors: {
@@ -1,7 +1,5 @@
1
1
  <script lang="ts" setup>
2
2
  const appConfig = useAppConfig()
3
-
4
- console.log(JSON.stringify(appConfig.docs, null, 2))
5
3
  </script>
6
4
 
7
5
  <template>
@@ -19,7 +17,7 @@ console.log(JSON.stringify(appConfig.docs, null, 2))
19
17
  &middot; Generated with
20
18
  <NuxtLink class="font-medium hover:text-primary" to="https://undocs.unjs.io" target="_blank">UnDocs </NuxtLink>
21
19
  and
22
- <NuxtLink class="font-medium hover:text-primary" to="https://ui.nuxt.com/pro" target="_blank">Nuxt UI Pro</NuxtLink
23
- >.
20
+ <NuxtLink class="font-medium hover:text-primary" to="https://ui.nuxt.com/pro" target="_blank">Nuxt UI Pro</NuxtLink>
21
+ .
24
22
  </p>
25
23
  </template>
@@ -35,7 +35,6 @@ const headerLinks = computed(() => {
35
35
  <UTooltip text="Search" :kbds="['meta', 'K']">
36
36
  <UContentSearchButton />
37
37
  </UTooltip>
38
- <!-- <div class="flex items-center border-l border-slate-200 ml-6 pl-6 dark:border-slate-800"> -->
39
38
  <UColorModeButton />
40
39
  <SocialButtons />
41
40
  </template>
@@ -1,33 +1,35 @@
1
1
  <script setup lang="ts">
2
2
  const appConfig = useAppConfig()
3
3
 
4
- const props = defineProps({
5
- socials: {
6
- type: Array,
7
- required: false,
8
- default: () => ['github', 'twitter', 'discord'],
9
- },
10
- })
4
+ // const props = defineProps({
5
+ // socials: {
6
+ // type: Array,
7
+ // required: false,
8
+ // default: () => ['github', 'twitter', 'discord'],
9
+ // },
10
+ // })
11
11
 
12
12
  const socialLinks = computed(() => {
13
- return Object.entries({ github: appConfig.docs.github, ...appConfig.docs.socials })
14
- .reverse() // x<>github
15
- .filter(([key]) => props.socials?.includes(key) || !props.socials)
16
- .map(([key, value]) => {
17
- if (typeof value === 'object') {
18
- return value
19
- }
20
- if (typeof value === 'string' && value) {
21
- return {
22
- // Workaround: i-simple-icons-x i-simple-icons-github
23
- icon: `i-simple-icons-${key}`,
24
- label: value,
25
- to: /^https?:\/\//.test(value) ? value : `https://${key}.com/${value}`,
13
+ return (
14
+ Object.entries({ github: appConfig.docs.github, ...appConfig.docs.socials })
15
+ .reverse() // x<>github
16
+ // .filter(([key]) => props.socials?.includes(key) || !props.socials)
17
+ .map(([key, value]) => {
18
+ if (typeof value === 'object') {
19
+ return value
20
+ }
21
+ if (typeof value === 'string' && value) {
22
+ return {
23
+ // Workaround: i-simple-icons-x i-simple-icons-github
24
+ icon: `i-simple-icons-${key}`,
25
+ label: value,
26
+ to: /^https?:\/\//.test(value) ? value : `https://${key}.com/${value}`,
27
+ }
26
28
  }
27
- }
28
- return undefined
29
- })
30
- .filter(Boolean)
29
+ return undefined
30
+ })
31
+ .filter(Boolean)
32
+ )
31
33
  })
32
34
  </script>
33
35
  <template>
@@ -0,0 +1,16 @@
1
+ export interface Sponsors {
2
+ username: string
3
+ sponsors: {
4
+ name: string
5
+ image: string
6
+ website: string
7
+ }[][]
8
+ }
9
+
10
+ export async function useSponsors(): Promise<Sponsors | undefined> {
11
+ const appConfig = useAppConfig()
12
+ const sponsorsAPI = appConfig.docs.sponsors.api
13
+ if (sponsorsAPI) {
14
+ return (await $fetch<Sponsors>(sponsorsAPI)) || undefined
15
+ }
16
+ }
@@ -0,0 +1,9 @@
1
+ <script setup lang="ts"></script>
2
+
3
+ <template>
4
+ <UContainer>
5
+ <UPage>
6
+ <slot />
7
+ </UPage>
8
+ </UContainer>
9
+ </template>
@@ -7,7 +7,7 @@ const docsNav = useDocsNav()
7
7
  <UPage :ui="{ left: 'lg:col-span-2 pr-2 border-r border-default' }">
8
8
  <template #left>
9
9
  <UPageAside>
10
- <UPageAnchors :links="docsNav.links" />
10
+ <UPageAnchors :links="docsNav.links.filter((l) => l.title !== 'Blog')" />
11
11
  <USeparator v-if="docsNav.activeLinks?.length" type="dashed" class="py-6" />
12
12
  <UContentNavigation
13
13
  :navigation="docsNav.activeLinks"
@@ -129,14 +129,15 @@ function removeFirstH1AndBlockquote(body: MarkdownRoot, content: ParsedContentFi
129
129
 
130
130
  // Use the first blockquote as the description
131
131
  const firstEl = body.value[0]
132
- const bloquoteText = _getTextContent(firstEl)
133
-
134
- if (firstEl[0] === 'blockquote' && content.description === '' && !bloquoteText.startsWith('!')) {
135
- content.description = bloquoteText
136
- if (content.seo) {
137
- ;(content.seo as any).description = bloquoteText
132
+ if (firstEl) {
133
+ const bloquoteText = _getTextContent(firstEl)
134
+ if (firstEl[0] === 'blockquote' && content.description === '' && !bloquoteText.startsWith('!')) {
135
+ content.description = bloquoteText
136
+ if (content.seo) {
137
+ ;(content.seo as any).description = bloquoteText
138
+ }
139
+ body.value.shift()
138
140
  }
139
- body.value.shift()
140
141
  }
141
142
  }
142
143
 
@@ -23,4 +23,8 @@ export const CommonIcons = [
23
23
  pattern: 'utils',
24
24
  icon: 'i-lucide-square-function',
25
25
  },
26
+ {
27
+ pattern: 'blog',
28
+ icon: 'i-lucide-file-text',
29
+ },
26
30
  ]
@@ -33,71 +33,28 @@ usePageSEO({
33
33
  description: page.value?.description,
34
34
  })
35
35
 
36
- const headline = computed(() => findPageHeadline(page.value))
37
-
38
- const tocLinks = computed(() => {
39
- return (page.value.body?.toc?.links || []).map((link) => ({
40
- ...link,
41
- children: undefined,
42
- }))
43
- })
44
-
45
- const tocMobileOpen = ref(false)
46
-
47
- const tocMobileLinks = computed(() => {
48
- return [
49
- [
50
- {
51
- label: 'Return to top',
52
- click: () => {
53
- window.scrollTo({ top: 0, left: 0, behavior: 'smooth' })
54
- },
55
- },
56
- ],
57
- (page.value.body?.toc?.links || []).map((link) => ({
58
- label: link.text,
59
- // href: `#${link.id}`,
60
- click: () => {
61
- tocMobileOpen.value = false
62
- document.getElementById(link.id)?.scrollIntoView({ behavior: 'smooth' })
63
- },
64
- })),
65
- ]
66
- })
67
-
68
- const isMobile = ref(false)
69
-
70
36
  onMounted(() => {
71
- isMobile.value = 'ontouchstart' in document.documentElement
37
+ const hash = window.location.hash
38
+ if (hash) {
39
+ document.querySelector(hash)?.scrollIntoView()
40
+ }
72
41
  })
73
42
  </script>
74
43
 
75
44
  <template>
76
45
  <UPage v-if="page">
77
- <UPageHeader :title="page.title" :description="page.description" :links="page.links" :headline="headline" />
46
+ <UPageHeader
47
+ :title="page.title"
48
+ :description="page.description"
49
+ :links="page.links"
50
+ :headline="findPageHeadline(page)"
51
+ />
78
52
 
79
- <!-- TOC -->
80
- <!-- large screen -->
81
- <template v-if="tocLinks.length > 0" #right>
82
- <UContentToc title="On this page" :links="tocLinks" class="hidden lg:block" />
53
+ <template #right>
54
+ <UContentToc title="On this page" :links="page.body?.toc?.links || []" highlight />
83
55
  </template>
84
- <!-- mobile -->
85
- <div
86
- v-if="tocMobileLinks.length > 1"
87
- class="float-right mt-4 top-[calc(var(--header-height)_+_0.5rem)] z-10 flex justify-end sticky mb-2 lg:hidden"
88
- >
89
- <UDropdownMenu v-model:open="tocMobileOpen" :items="tocMobileLinks" :mode="isMobile ? 'click' : 'hover'">
90
- <UButton
91
- color="neutral"
92
- label="On this page"
93
- :trailing="false"
94
- :icon="`i-heroicons-chevron-${tocMobileOpen ? 'down' : 'left'}-20-solid`"
95
- />
96
- </UDropdownMenu>
97
- </div>
98
56
 
99
57
  <UPageBody prose class="break-words">
100
- <br v-if="tocMobileLinks.length > 1" class="lg:hidden mb-2" />
101
58
  <ContentRenderer v-if="page.body" :value="page" />
102
59
 
103
60
  <div class="space-y-6">
@@ -0,0 +1,63 @@
1
+ <script setup lang="ts">
2
+ import { kebabCase } from 'scule'
3
+
4
+ definePageMeta({
5
+ layout: 'blog',
6
+ })
7
+
8
+ const route = useRoute()
9
+
10
+ const { data: page } = await useAsyncData(kebabCase(route.path), () =>
11
+ queryCollection('content').path(route.path).first(),
12
+ )
13
+
14
+ if (!page.value) {
15
+ throw createError({
16
+ statusCode: 404,
17
+ statusMessage: 'Page not found',
18
+ message: `${route.path} does not exist`,
19
+ fatal: true,
20
+ })
21
+ }
22
+
23
+ const appConfig = useAppConfig()
24
+
25
+ usePageSEO({
26
+ title: `${page.value?.title} - ${appConfig.site.name}`,
27
+ ogTitle: page.value?.title,
28
+ description: page.value?.description,
29
+ })
30
+ </script>
31
+
32
+ <template>
33
+ <UPage v-if="page">
34
+ <UPageHeader v-bind="page" :ui="{ headline: 'flex flex-col gap-y-8 items-start' }">
35
+ <template #headline>
36
+ <UBreadcrumb
37
+ :items="[{ label: 'Blog', icon: 'i-lucide-newspaper', to: '/blog' }, { label: page.title }]"
38
+ class="max-w-full"
39
+ />
40
+ <div class="flex items-center space-x-2">
41
+ <span>
42
+ {{ page.meta.category }}
43
+ </span>
44
+ <span class="text-muted"
45
+ >&nbsp;&middot;&nbsp;<time>{{ page.meta.date }}</time></span
46
+ >
47
+ </div>
48
+ </template>
49
+ <div class="mt-4 flex flex-wrap items-center gap-6">
50
+ <UUser
51
+ v-for="(author, index) in page.meta.authors || []"
52
+ :key="index"
53
+ :name="author.name"
54
+ :avatar="{ src: `https://github.com/${author.github}.png?size=64` }"
55
+ :to="`https://github.com/${author.github}`"
56
+ target="_blank"
57
+ :description="author.to ? `@${author.to.split('/').pop()}` : undefined"
58
+ />
59
+ </div>
60
+ </UPageHeader>
61
+ <ContentRenderer v-if="page.body" :value="page" />
62
+ </UPage>
63
+ </template>
@@ -0,0 +1,57 @@
1
+ <script setup lang="ts">
2
+ const { data: page } = await useAsyncData('/blog', () => queryCollection('content').path('/blog').first())
3
+
4
+ if (!page.value) {
5
+ throw createError({
6
+ statusCode: 404,
7
+ statusMessage: 'Page not found',
8
+ message: '/blog does not exist',
9
+ fatal: true,
10
+ })
11
+ }
12
+
13
+ const { data: articles } = await useAsyncData(() =>
14
+ queryCollection('content')
15
+ .where('path', 'LIKE', '/blog/%')
16
+ .order('id', 'DESC')
17
+ .all()
18
+ .then((res) => res),
19
+ )
20
+
21
+ const appConfig = useAppConfig()
22
+
23
+ usePageSEO({
24
+ title: `${page.value?.title} - ${appConfig.site.name}`,
25
+ ogTitle: page.value?.title,
26
+ description: page.value?.description,
27
+ })
28
+ </script>
29
+
30
+ <template>
31
+ <UContainer v-if="page">
32
+ <UPageHero :title="page.title" orientation="horizontal">
33
+ <template #description>{{ page.description }}</template>
34
+ </UPageHero>
35
+
36
+ <UPageBody>
37
+ <UContainer>
38
+ <UBlogPosts class="mb-12 md:grid-cols-2 lg:grid-cols-3">
39
+ <UBlogPost
40
+ v-for="(article, index) in articles"
41
+ :key="article.path"
42
+ :to="article.path"
43
+ :title="article.title"
44
+ :description="article.description"
45
+ :date="article.meta?.date"
46
+ :badge="
47
+ article.meta?.category ? { label: article.meta.category, color: 'primary', variant: 'subtle' } : undefined
48
+ "
49
+ :variant="index > 0 ? 'outline' : 'subtle'"
50
+ :orientation2="index === 0 ? 'horizontal' : 'vertical'"
51
+ :class="[index === 0 && 'col-span-full']"
52
+ />
53
+ </UBlogPosts>
54
+ </UContainer>
55
+ </UPageBody>
56
+ </UContainer>
57
+ </template>
@@ -8,8 +8,6 @@ const appConfig = useAppConfig()
8
8
 
9
9
  const docsConfig = appConfig.docs as DocsConfig
10
10
 
11
- // console.log('docsConfig', JSON.stringify(docsConfig, null, 2))
12
-
13
11
  const landing: LandingConfig & { _github: string } = defu(docsConfig.landing || {}, {
14
12
  // Meta
15
13
  navigation: false,
@@ -90,6 +88,16 @@ const hero = computed(() => {
90
88
  code: landing!.heroCode,
91
89
  } as const
92
90
  })
91
+
92
+ const { data: latest } = await useAsyncData(() =>
93
+ queryCollection('content')
94
+ .where('path', 'LIKE', '/blog/%')
95
+ .order('id', 'DESC')
96
+ .first()
97
+ .then((res) => res),
98
+ )
99
+
100
+ const { data: sponsors } = await useAsyncData(() => useSponsors())
93
101
  </script>
94
102
 
95
103
  <template>
@@ -109,6 +117,18 @@ const hero = computed(() => {
109
117
  <LandingBackground />
110
118
  </template>
111
119
 
120
+ <template #headline>
121
+ <NuxtLink v-if="latest" :to="latest.path">
122
+ <UBadge
123
+ variant="subtle"
124
+ size="lg"
125
+ class="px-3 relative rounded-full font-semibold dark:hover:bg-primary-400/15 dark:hover:ring-primary-700"
126
+ >
127
+ {{ latest.title }}
128
+ </UBadge>
129
+ </NuxtLink>
130
+ </template>
131
+
112
132
  <template #title>
113
133
  {{ landing.heroTitle }}<br /><span class="text-primary text-4xl">{{ landing.heroSubtitle }}</span>
114
134
  </template>
@@ -170,12 +190,50 @@ const hero = computed(() => {
170
190
  </template>
171
191
  </UPageSection>
172
192
 
173
- <UPageSection v-if="landing.contributors && landing._github" title="Made by community">
193
+ <UPageSection v-if="landing.contributors && landing._github" title="💛 Contributors">
174
194
  <div class="flex justify-center">
175
195
  <a :href="`https://github.com/${landing._github}/graphs/contributors`" target="_blank">
176
196
  <img :src="`https://contrib.rocks/image?repo=${landing._github}`" />
177
197
  </a>
178
198
  </div>
179
199
  </UPageSection>
200
+
201
+ <UPageSection v-if="sponsors?.sponsors.length" title="💜 Sponsors">
202
+ <div class="flex flex-col items-center">
203
+ <div
204
+ v-for="(tier, i) of sponsors.sponsors"
205
+ :key="i"
206
+ class="flex flex-wrap justify-center gap-4 mb-6 mt-6 max-w-4xl"
207
+ >
208
+ <div v-for="s in tier" :key="s.name" class="flex items-center gap-2 max-w-[300px]">
209
+ <a
210
+ :href="s.website"
211
+ target="_blank"
212
+ class="flex items-center gap-2"
213
+ :class="`font-size-${i === 0 ? '4xl' : i === 1 ? '3xl' : 'lg'}`"
214
+ >
215
+ <img
216
+ v-if="s.image"
217
+ :src="s.image"
218
+ :alt="s.name"
219
+ class="object-contain rounded-lg"
220
+ :class="`w-${i === 0 ? 16 : 8} h-${i === 0 ? 16 : 8}`"
221
+ />
222
+ <span v-if="i < 2" class="text-lg font-semibold">{{ s.name }}</span>
223
+ </a>
224
+ </div>
225
+ </div>
226
+ </div>
227
+ <div class="text-center">
228
+ <UButton
229
+ v-if="sponsors.username"
230
+ :to="`https://github.com/sponsors/${sponsors.username}`"
231
+ target="_blank"
232
+ color="neutral"
233
+ >
234
+ Become a Sponsor
235
+ </UButton>
236
+ </div>
237
+ </UPageSection>
180
238
  </div>
181
239
  </template>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "undocs",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "repository": "unjs/undocs",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -5,11 +5,13 @@ export interface DocsConfig {
5
5
  shortDescription?: string
6
6
  url?: string
7
7
  github?: string
8
+ socials?: Record<string, string>
8
9
  branch?: string
9
10
  themeColor?: string
10
11
  redirects?: Record<string, string>
11
12
  automd?: unknown
12
13
  buildCache?: boolean
14
+ sponsors?: { api: string }
13
15
  landing?:
14
16
  | false
15
17
  | {
@@ -30,6 +30,22 @@
30
30
  "type": "string",
31
31
  "description": "The GitHub repository for the documentation site."
32
32
  },
33
+ "socials": {
34
+ "type": "object",
35
+ "description": "Social media links for the documentation site.",
36
+ "additionalProperties": {
37
+ "type": "string"
38
+ }
39
+ },
40
+ "sponsors": {
41
+ "type": "object",
42
+ "properties": {
43
+ "api": {
44
+ "type": "string",
45
+ "description": "The URL to the sponsors JSON API."
46
+ }
47
+ }
48
+ },
33
49
  "branch": {
34
50
  "type": "string",
35
51
  "description": "The branch of the GitHub repository for the documentation site."