undocs 0.3.7 → 0.3.9

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.
@@ -1,14 +1,6 @@
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
- // })
11
-
12
4
  const socialLinks = computed(() => {
13
5
  return (
14
6
  Object.entries({ github: appConfig.docs.github, ...appConfig.docs.socials })
@@ -22,7 +14,7 @@ const socialLinks = computed(() => {
22
14
  return {
23
15
  // Workaround: i-simple-icons-x i-simple-icons-github
24
16
  icon: `i-simple-icons-${key}`,
25
- label: value,
17
+ label: titleCase(key),
26
18
  to: /^https?:\/\//.test(value) ? value : `https://${key}.com/${value}`,
27
19
  }
28
20
  }
@@ -33,15 +25,15 @@ const socialLinks = computed(() => {
33
25
  })
34
26
  </script>
35
27
  <template>
36
- <UButton
37
- v-for="link of socialLinks"
38
- :key="link.label"
39
- :aria-label="link.label"
40
- :icon="link.icon"
41
- :to="link.to"
42
- target="_blank"
43
- rel="noopener noreferrer"
44
- color="neutral"
45
- variant="ghost"
46
- />
28
+ <UTooltip v-for="link of socialLinks" :key="link.label" :text="link.label">
29
+ <UButton
30
+ :aria-label="link.label"
31
+ :icon="link.icon"
32
+ :to="link.to"
33
+ target="_blank"
34
+ rel="noopener noreferrer"
35
+ color="neutral"
36
+ variant="ghost"
37
+ />
38
+ </UTooltip>
47
39
  </template>
@@ -6,7 +6,7 @@ export default defineContentConfig({
6
6
  type: 'page',
7
7
  source: {
8
8
  cwd: globalThis.__DOCS_CWD__,
9
- include: '**/*.md',
9
+ include: '**/*.{md,yml}',
10
10
  exclude: ['**/.**/**', '**/node_modules/**', '**/dist/**', '**/.docs/**'],
11
11
  },
12
12
  }),
@@ -9,13 +9,7 @@ const docsNav = useDocsNav()
9
9
  <UPageAside>
10
10
  <UPageAnchors :links="docsNav.links.filter((l) => l.title !== 'Blog')" />
11
11
  <USeparator v-if="docsNav.activeLinks?.length" type="dashed" class="py-6" />
12
- <UContentNavigation
13
- :navigation="docsNav.activeLinks"
14
- default-open
15
- trailing-icon="i-lucide-chevron-right"
16
- :ui="{ linkTrailingIcon: 'group-data-[state=open]:rotate-90' }"
17
- highlight
18
- />
12
+ <UContentNavigation :navigation="docsNav.activeLinks" :collapsible="false" />
19
13
  </UPageAside>
20
14
  </template>
21
15
  <slot />
@@ -1,4 +1,4 @@
1
- import type { MarkdownRoot, MinimalNode, MinimalElement } from '@nuxt/content'
1
+ import type { MarkdownRoot, MinimarkNode, MinimarkElement } from '@nuxt/content'
2
2
  import type { Nuxt } from 'nuxt/schema'
3
3
  import { CommonIcons } from './icons'
4
4
  import type { DocsConfig } from '../../../schema/config'
@@ -13,6 +13,9 @@ interface ParsedContentFile extends Record<string, unknown> {
13
13
  meta?: {
14
14
  icon?: string
15
15
  }
16
+ body?: {
17
+ icon?: string
18
+ }
16
19
  }
17
20
 
18
21
  export async function setupContentHooks(nuxt: Nuxt, docsConfig: DocsConfig) {
@@ -59,13 +62,14 @@ export async function setupContentHooks(nuxt: Nuxt, docsConfig: DocsConfig) {
59
62
  })
60
63
 
61
64
  nuxt.hooks.hook('content:file:afterParse', ({ content }) => {
65
+ // Infer icon for top level navigation
66
+ inferIcons(content)
67
+
62
68
  // Filter out non-markdown files
63
69
  if (content.extension !== 'md') {
64
70
  return
65
71
  }
66
72
 
67
- resolveFileIcon(content)
68
-
69
73
  const body = content.body as MarkdownRoot
70
74
  removeFirstH1AndBlockquote(body, content)
71
75
  body.value = mergeCodeGroups(body.value)
@@ -84,7 +88,7 @@ export async function setupContentHooks(nuxt: Nuxt, docsConfig: DocsConfig) {
84
88
 
85
89
  // Handle GitHub flavoured markdown blockquotes
86
90
  // https://github.com/orgs/community/discussions/16925
87
- function transformGithubAlert(node: MinimalNode) {
91
+ function transformGithubAlert(node: MinimarkNode) {
88
92
  if (
89
93
  node[0] === 'blockquote' &&
90
94
  Array.isArray(node[2]) &&
@@ -97,17 +101,17 @@ function transformGithubAlert(node: MinimalNode) {
97
101
  // @ts-expect-error read-only
98
102
  node[0] = node[2][2][2].slice(1).toLowerCase()
99
103
  // @ts-expect-error read-only
100
- node[2] = ['p', {}, ...node[2].slice(3)] as MinimalElement
104
+ node[2] = ['p', {}, ...node[2].slice(3)] as MinimarkElement
101
105
  }
102
106
  }
103
107
 
104
108
  // --- transform steps list ---
105
109
 
106
- function transformStepsList(node: MinimalNode) {
110
+ function transformStepsList(node: MinimarkNode) {
107
111
  // CONVERT OL->LI to Steps
108
112
  if (Array.isArray(node) && node[0] === 'ol' && node.length > 3 && Array.isArray(node[2]) && node[2][0] === 'li') {
109
- const stepsChildren = node.slice(2).map((li: MinimalNode) => {
110
- return ['h4', {}, ...li.slice(2)] as MinimalElement
113
+ const stepsChildren = node.slice(2).map((li: MinimarkNode) => {
114
+ return ['h4', {}, ...li.slice(2)] as MinimarkElement
111
115
  })
112
116
  node.splice(0, Infinity, 'steps', { level: '4' }, ...stepsChildren)
113
117
  }
@@ -143,17 +147,31 @@ function removeFirstH1AndBlockquote(body: MarkdownRoot, content: ParsedContentFi
143
147
 
144
148
  // --- resolve icon ---
145
149
 
146
- function resolveFileIcon(content: ParsedContentFile) {
147
- if (content.navigation?.icon) {
148
- return
149
- }
150
- content.navigation ||= {}
151
- content.navigation.icon = content.meta.icon || _resolveIcon(content.path)
150
+ function inferIcons(content: ParsedContentFile) {
151
+ const icon = content.meta?.icon || content.navigation?.icon || content.body?.icon || _resolveIcon(content.path)
152
+
153
+ content.body ??= {}
154
+ content.body.icon ??= icon
155
+
156
+ content.meta ??= {}
157
+ content.meta.icon ??= icon
158
+
159
+ content.navigation ??= {}
160
+ content.navigation.icon ??= icon
152
161
  }
153
162
 
154
163
  function _resolveIcon(path: string = '') {
155
164
  // Split the path into parts and reverse it
156
- const paths = path.slice(1).split('/').reverse()
165
+ const paths = path
166
+ .slice(1)
167
+ .split('/')
168
+ .reverse()
169
+ .filter((p) => p && !p.startsWith('.'))
170
+
171
+ // Skip 2+ levels of directories
172
+ if (paths.length > 2) {
173
+ return
174
+ }
157
175
 
158
176
  // Search for icons in reverse order
159
177
  for (const p of paths) {
@@ -167,10 +185,10 @@ function _resolveIcon(path: string = '') {
167
185
 
168
186
  // --- Merge code groups ---
169
187
 
170
- function mergeCodeGroups(children: MinimalNode[] = []): MinimalNode[] {
171
- const newChildren: MinimalNode[] = []
188
+ function mergeCodeGroups(children: MinimarkNode[] = []): MinimarkNode[] {
189
+ const newChildren: MinimarkNode[] = []
172
190
 
173
- let codeBlocks: MinimalNode[] = []
191
+ let codeBlocks: MinimarkNode[] = []
174
192
 
175
193
  for (let i = 0; i < children.length; i++) {
176
194
  if (_isNamedCodeBlock(children[i])) {
@@ -187,20 +205,20 @@ function mergeCodeGroups(children: MinimalNode[] = []): MinimalNode[] {
187
205
  return newChildren
188
206
  }
189
207
 
190
- function _isNamedCodeBlock(node: MinimalNode): boolean {
208
+ function _isNamedCodeBlock(node: MinimarkNode): boolean {
191
209
  return node[0] === 'pre' && (node[1] as any)?.filename && node[2]?.[0] === 'code'
192
210
  }
193
211
 
194
212
  // --- transform automd jsdocs ---
195
213
 
196
- export function transformJSDocs(currChildIdx: number, children: MinimalNode[] = []) {
214
+ export function transformJSDocs(currChildIdx: number, children: MinimarkNode[] = []) {
197
215
  if (!children?.length || !_isJSDocBlock(children[currChildIdx])) {
198
216
  return
199
217
  }
200
218
 
201
- const fields: MinimalNode[] = []
219
+ const fields: MinimarkNode[] = []
202
220
 
203
- const generateFields = (i: number): MinimalNode => {
221
+ const generateFields = (i: number): MinimarkNode => {
204
222
  const name = _parseJSDocName(children[i])
205
223
  const type = _parseJSDocType(children[i + 1])
206
224
 
@@ -212,7 +230,7 @@ export function transformJSDocs(currChildIdx: number, children: MinimalNode[] =
212
230
  type,
213
231
  }
214
232
 
215
- const content: MinimalNode[] = []
233
+ const content: MinimarkNode[] = []
216
234
 
217
235
  i++
218
236
 
@@ -263,17 +281,17 @@ export function transformJSDocs(currChildIdx: number, children: MinimalNode[] =
263
281
  }
264
282
  }
265
283
 
266
- function _isJSDocBlock(children: MinimalNode): boolean {
284
+ function _isJSDocBlock(children: MinimarkNode): boolean {
267
285
  return (
268
286
  children?.tag === 'h3' && children?.children?.[0]?.tag === 'code' && children?.children?.[0]?.type === 'element'
269
287
  )
270
288
  }
271
289
 
272
- function _parseJSDocName(node: MinimalNode): string {
290
+ function _parseJSDocName(node: MinimarkNode): string {
273
291
  // Code block || id prop || empty string
274
292
  return node.children?.[0]?.children?.[0]?.value || node?.props?.id || ''
275
293
  }
276
- function _parseJSDocType(node: MinimalNode): string {
294
+ function _parseJSDocType(node: MinimarkNode): string {
277
295
  const hasType = !!node?.children?.[0]?.children?.[0]?.children?.[0]?.value
278
296
  if (!hasType) {
279
297
  return ''
@@ -284,7 +302,7 @@ function _parseJSDocType(node: MinimalNode): string {
284
302
 
285
303
  // --- internal utils ---
286
304
 
287
- function _getTextContent(node: MinimalNode): string {
305
+ function _getTextContent(node: MinimarkNode): string {
288
306
  if (!node) {
289
307
  return ''
290
308
  }
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { kebabCase } from 'scule'
3
- import { findPageHeadline } from '#ui-pro/utils/content'
3
+ import type { ContentNavigationItem } from '@nuxt/content'
4
4
 
5
5
  definePageMeta({
6
6
  layout: 'docs',
@@ -27,6 +27,30 @@ const { data: surround } = await useAsyncData(`${kebabCase(route.path)}-surround
27
27
  })
28
28
  })
29
29
 
30
+ const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
31
+
32
+ // console.log(JSON.stringify(navigation?.value, null, 2))
33
+
34
+ function makeBreadcrumb(items: ContentNavigationItem[], path: string, level = 0) {
35
+ const parent = [...items].find((i) => path.startsWith(i.path) && i.children?.length > 0)
36
+ if (!parent) {
37
+ return []
38
+ }
39
+ if (level === 0) {
40
+ return makeBreadcrumb(parent.children, path, level + 1)
41
+ }
42
+ return [
43
+ {
44
+ label: parent.title,
45
+ icon: parent.icon as string,
46
+ to: parent.page !== false ? parent.path : '',
47
+ },
48
+ ...makeBreadcrumb(parent.children, path, level + 1),
49
+ ]
50
+ }
51
+
52
+ const breadcrumb = computed(() => makeBreadcrumb(navigation?.value || [], page.value.path))
53
+
30
54
  usePageSEO({
31
55
  title: `${page.value?.title} - ${appConfig.site.name}`,
32
56
  ogTitle: page.value?.title,
@@ -36,12 +60,11 @@ usePageSEO({
36
60
 
37
61
  <template>
38
62
  <UPage v-if="page">
39
- <UPageHeader
40
- :title="page.title"
41
- :description="page.description"
42
- :links="page.links"
43
- :headline="findPageHeadline(page)"
44
- />
63
+ <UPageHeader :title="page.title" :description="page.description" :links="page.links">
64
+ <template #headline>
65
+ <UBreadcrumb :items="breadcrumb" />
66
+ </template>
67
+ </UPageHeader>
45
68
 
46
69
  <template #right>
47
70
  <UContentToc title="On this page" :links="page.body?.toc?.links || []" highlight />
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "undocs",
3
- "version": "0.3.7",
3
+ "version": "0.3.9",
4
4
  "repository": "unjs/undocs",
5
5
  "license": "MIT",
6
6
  "type": "module",