docs-please 0.2.0-beta.0 → 0.2.1-beta.0

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 (49) hide show
  1. package/app/app.config.ts +9 -1
  2. package/app/assets/css/main.css +2 -2
  3. package/app/components/app/AppHeader.vue +92 -37
  4. package/app/components/app/AppHeaderCenter.vue +3 -0
  5. package/app/components/app/AppHeaderLogo.vue +43 -0
  6. package/app/components/app/AppHeaderSearch.vue +3 -0
  7. package/app/components/app/AppSearch.vue +189 -0
  8. package/app/components/app/AppSearchButton.vue +51 -0
  9. package/app/components/content/Accordion.vue +117 -0
  10. package/app/components/content/AccordionItem.vue +27 -0
  11. package/app/components/content/Badge.vue +42 -0
  12. package/app/components/content/Collapsible.vue +56 -0
  13. package/app/components/content/ProseKbd.vue +9 -0
  14. package/app/components/content/ProseTable.vue +2 -2
  15. package/app/components/content/ProseTh.vue +1 -1
  16. package/app/components/content/ProseTr.vue +1 -1
  17. package/app/components/ui/command/Command.vue +86 -0
  18. package/app/components/ui/command/CommandDialog.vue +21 -0
  19. package/app/components/ui/command/CommandEmpty.vue +23 -0
  20. package/app/components/ui/command/CommandGroup.vue +44 -0
  21. package/app/components/ui/command/CommandInput.vue +35 -0
  22. package/app/components/ui/command/CommandItem.vue +75 -0
  23. package/app/components/ui/command/CommandList.vue +21 -0
  24. package/app/components/ui/command/CommandSeparator.vue +20 -0
  25. package/app/components/ui/command/CommandShortcut.vue +14 -0
  26. package/app/components/ui/command/index.ts +25 -0
  27. package/app/components/ui/dialog/Dialog.vue +19 -0
  28. package/app/components/ui/dialog/DialogClose.vue +15 -0
  29. package/app/components/ui/dialog/DialogContent.vue +53 -0
  30. package/app/components/ui/dialog/DialogDescription.vue +23 -0
  31. package/app/components/ui/dialog/DialogFooter.vue +15 -0
  32. package/app/components/ui/dialog/DialogHeader.vue +17 -0
  33. package/app/components/ui/dialog/DialogOverlay.vue +21 -0
  34. package/app/components/ui/dialog/DialogScrollContent.vue +59 -0
  35. package/app/components/ui/dialog/DialogTitle.vue +23 -0
  36. package/app/components/ui/dialog/DialogTrigger.vue +15 -0
  37. package/app/components/ui/dialog/index.ts +10 -0
  38. package/app/components/ui/kbd/Kbd.vue +21 -0
  39. package/app/components/ui/kbd/KbdGroup.vue +17 -0
  40. package/app/components/ui/kbd/index.ts +2 -0
  41. package/app/composables/useContentSearch.ts +52 -0
  42. package/app/layouts/default.vue +1 -0
  43. package/app/layouts/docs.vue +1 -0
  44. package/app/utils/navigation.ts +7 -0
  45. package/app/utils/prerender.ts +12 -0
  46. package/nuxt.config.ts +4 -0
  47. package/nuxt.schema.ts +44 -0
  48. package/package.json +7 -2
  49. package/server/routes/raw/[...slug].md.get.ts +45 -0
@@ -0,0 +1,59 @@
1
+ <script setup lang="ts">
2
+ import type { DialogContentEmits, DialogContentProps } from "reka-ui"
3
+ import type { HTMLAttributes } from "vue"
4
+ import { reactiveOmit } from "@vueuse/core"
5
+ import { X } from "lucide-vue-next"
6
+ import {
7
+ DialogClose,
8
+ DialogContent,
9
+ DialogOverlay,
10
+ DialogPortal,
11
+ useForwardPropsEmits,
12
+ } from "reka-ui"
13
+ import { cn } from '~/lib/utils'
14
+
15
+ defineOptions({
16
+ inheritAttrs: false,
17
+ })
18
+
19
+ const props = defineProps<DialogContentProps & { class?: HTMLAttributes["class"] }>()
20
+ const emits = defineEmits<DialogContentEmits>()
21
+
22
+ const delegatedProps = reactiveOmit(props, "class")
23
+
24
+ const forwarded = useForwardPropsEmits(delegatedProps, emits)
25
+ </script>
26
+
27
+ <template>
28
+ <DialogPortal>
29
+ <DialogOverlay
30
+ class="fixed inset-0 z-50 grid place-items-center overflow-y-auto bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0"
31
+ >
32
+ <DialogContent
33
+ :class="
34
+ cn(
35
+ 'relative z-50 grid w-full max-w-lg my-8 gap-4 border border-border bg-background p-6 shadow-lg duration-200 sm:rounded-lg md:w-full',
36
+ props.class,
37
+ )
38
+ "
39
+ v-bind="{ ...$attrs, ...forwarded }"
40
+ @pointer-down-outside="(event) => {
41
+ const originalEvent = event.detail.originalEvent;
42
+ const target = originalEvent.target as HTMLElement;
43
+ if (originalEvent.offsetX > target.clientWidth || originalEvent.offsetY > target.clientHeight) {
44
+ event.preventDefault();
45
+ }
46
+ }"
47
+ >
48
+ <slot />
49
+
50
+ <DialogClose
51
+ class="absolute top-4 right-4 p-0.5 transition-colors rounded-md hover:bg-secondary"
52
+ >
53
+ <X class="w-4 h-4" />
54
+ <span class="sr-only">Close</span>
55
+ </DialogClose>
56
+ </DialogContent>
57
+ </DialogOverlay>
58
+ </DialogPortal>
59
+ </template>
@@ -0,0 +1,23 @@
1
+ <script setup lang="ts">
2
+ import type { DialogTitleProps } from "reka-ui"
3
+ import type { HTMLAttributes } from "vue"
4
+ import { reactiveOmit } from "@vueuse/core"
5
+ import { DialogTitle, useForwardProps } from "reka-ui"
6
+ import { cn } from '~/lib/utils'
7
+
8
+ const props = defineProps<DialogTitleProps & { class?: HTMLAttributes["class"] }>()
9
+
10
+ const delegatedProps = reactiveOmit(props, "class")
11
+
12
+ const forwardedProps = useForwardProps(delegatedProps)
13
+ </script>
14
+
15
+ <template>
16
+ <DialogTitle
17
+ data-slot="dialog-title"
18
+ v-bind="forwardedProps"
19
+ :class="cn('text-lg leading-none font-semibold', props.class)"
20
+ >
21
+ <slot />
22
+ </DialogTitle>
23
+ </template>
@@ -0,0 +1,15 @@
1
+ <script setup lang="ts">
2
+ import type { DialogTriggerProps } from "reka-ui"
3
+ import { DialogTrigger } from "reka-ui"
4
+
5
+ const props = defineProps<DialogTriggerProps>()
6
+ </script>
7
+
8
+ <template>
9
+ <DialogTrigger
10
+ data-slot="dialog-trigger"
11
+ v-bind="props"
12
+ >
13
+ <slot />
14
+ </DialogTrigger>
15
+ </template>
@@ -0,0 +1,10 @@
1
+ export { default as Dialog } from "./Dialog.vue"
2
+ export { default as DialogClose } from "./DialogClose.vue"
3
+ export { default as DialogContent } from "./DialogContent.vue"
4
+ export { default as DialogDescription } from "./DialogDescription.vue"
5
+ export { default as DialogFooter } from "./DialogFooter.vue"
6
+ export { default as DialogHeader } from "./DialogHeader.vue"
7
+ export { default as DialogOverlay } from "./DialogOverlay.vue"
8
+ export { default as DialogScrollContent } from "./DialogScrollContent.vue"
9
+ export { default as DialogTitle } from "./DialogTitle.vue"
10
+ export { default as DialogTrigger } from "./DialogTrigger.vue"
@@ -0,0 +1,21 @@
1
+ <script setup lang="ts">
2
+ import type { HTMLAttributes } from "vue"
3
+ import { cn } from '~/lib/utils'
4
+
5
+ const props = defineProps<{
6
+ class?: HTMLAttributes["class"]
7
+ }>()
8
+ </script>
9
+
10
+ <template>
11
+ <kbd
12
+ :class="cn(
13
+ 'bg-muted text-muted-foreground pointer-events-none inline-flex h-5 w-fit min-w-5 items-center justify-center gap-1 rounded-sm px-1 font-sans text-xs font-medium select-none',
14
+ '[&_svg:not([class*=\'size-\'])]:size-3',
15
+ '[[data-slot=tooltip-content]_&]:bg-background/20 [[data-slot=tooltip-content]_&]:text-background dark:[[data-slot=tooltip-content]_&]:bg-background/10',
16
+ props.class,
17
+ )"
18
+ >
19
+ <slot />
20
+ </kbd>
21
+ </template>
@@ -0,0 +1,17 @@
1
+ <script setup lang="ts">
2
+ import type { HTMLAttributes } from "vue"
3
+ import { cn } from '~/lib/utils'
4
+
5
+ const props = defineProps<{
6
+ class?: HTMLAttributes["class"]
7
+ }>()
8
+ </script>
9
+
10
+ <template>
11
+ <kbd
12
+ data-slot="kbd-group"
13
+ :class="cn('inline-flex items-center gap-1', props.class)"
14
+ >
15
+ <slot />
16
+ </kbd>
17
+ </template>
@@ -0,0 +1,2 @@
1
+ export { default as Kbd } from "./Kbd.vue"
2
+ export { default as KbdGroup } from "./KbdGroup.vue"
@@ -0,0 +1,52 @@
1
+ import type { NavigationItem } from './useNavigation'
2
+
3
+ const open = ref(false)
4
+
5
+ export interface SearchItem {
6
+ id: string
7
+ label: string
8
+ icon?: Component
9
+ to?: string
10
+ onSelect?: () => void
11
+ prefix?: string
12
+ suffix?: string
13
+ active?: boolean
14
+ disabled?: boolean
15
+ isDoc?: boolean
16
+ }
17
+
18
+ export interface SearchGroup {
19
+ id: string
20
+ label: string
21
+ items: SearchItem[]
22
+ }
23
+
24
+ export function useContentSearch() {
25
+ return {
26
+ open,
27
+ }
28
+ }
29
+
30
+ export function flattenNavigation(items: NavigationItem[], prefix = '', isDocSection = false): SearchItem[] {
31
+ return items.flatMap((item) => {
32
+ const result: SearchItem[] = []
33
+ // Determine if this item is a doc page based on parent context or path
34
+ const itemIsDoc = isDocSection || item.path?.startsWith('/docs')
35
+
36
+ if (item.type === 'page' && item.path) {
37
+ result.push({
38
+ id: item.path,
39
+ label: item.title,
40
+ to: item.path,
41
+ prefix: prefix || undefined,
42
+ isDoc: itemIsDoc,
43
+ })
44
+ }
45
+
46
+ if (item.children?.length) {
47
+ result.push(...flattenNavigation(item.children, item.title, itemIsDoc))
48
+ }
49
+
50
+ return result
51
+ })
52
+ }
@@ -8,5 +8,6 @@
8
8
  <slot />
9
9
  </main>
10
10
  <AppFooter />
11
+ <AppSearch />
11
12
  </div>
12
13
  </template>
@@ -23,5 +23,6 @@
23
23
  </div>
24
24
  </div>
25
25
  <AppFooter />
26
+ <AppSearch />
26
27
  </div>
27
28
  </template>
@@ -0,0 +1,7 @@
1
+ import type { ContentNavigationItem } from '@nuxt/content'
2
+
3
+ export const flattenNavigation = (items?: ContentNavigationItem[]): ContentNavigationItem[] => items?.flatMap(
4
+ item => item.children
5
+ ? flattenNavigation(item.children)
6
+ : [item],
7
+ ) || []
@@ -0,0 +1,12 @@
1
+ export const addPrerenderPath = (path: string) => {
2
+ const event = useRequestEvent()
3
+ if (event) {
4
+ event.node.res.setHeader(
5
+ 'x-nitro-prerender',
6
+ [
7
+ event.node.res.getHeader('x-nitro-prerender'),
8
+ path,
9
+ ].filter(Boolean).join(','),
10
+ )
11
+ }
12
+ }
package/nuxt.config.ts CHANGED
@@ -13,6 +13,9 @@ export default defineNuxtConfig({
13
13
  '@nuxt/image',
14
14
  '@nuxt/icon',
15
15
  'nuxt-og-image',
16
+ '@nuxtjs/robots',
17
+ 'nuxt-og-image',
18
+ 'nuxt-llms',
16
19
  ],
17
20
  devtools: { enabled: true },
18
21
  css: [resolve('./app/assets/css/main.css')],
@@ -61,6 +64,7 @@ export default defineNuxtConfig({
61
64
  'tabs': 'Tabs',
62
65
  'tabs-item': 'TabsItem',
63
66
  'u-color-mode-image': 'UColorModeImage',
67
+ 'kbd': 'ProseKbd',
64
68
  },
65
69
  },
66
70
  },
package/nuxt.schema.ts CHANGED
@@ -29,6 +29,50 @@ export default defineNuxtSchema({
29
29
  type: 'string',
30
30
  },
31
31
  },
32
+ header: {
33
+ $schema: {
34
+ title: 'Header Configuration',
35
+ description: 'Header configuration options',
36
+ },
37
+ title: {
38
+ $default: '',
39
+ $schema: {
40
+ title: 'Header Title',
41
+ description: 'Title displayed in the header (defaults to site title)',
42
+ type: 'string',
43
+ },
44
+ },
45
+ logo: {
46
+ $schema: {
47
+ title: 'Logo Configuration',
48
+ description: 'Logo images for light and dark modes',
49
+ },
50
+ light: {
51
+ $default: '',
52
+ $schema: {
53
+ title: 'Light Mode Logo',
54
+ description: 'Logo image for light mode',
55
+ type: 'string',
56
+ },
57
+ },
58
+ dark: {
59
+ $default: '',
60
+ $schema: {
61
+ title: 'Dark Mode Logo',
62
+ description: 'Logo image for dark mode',
63
+ type: 'string',
64
+ },
65
+ },
66
+ alt: {
67
+ $default: '',
68
+ $schema: {
69
+ title: 'Logo Alt Text',
70
+ description: 'Alt text for the logo image',
71
+ type: 'string',
72
+ },
73
+ },
74
+ },
75
+ },
32
76
  github: {
33
77
  $schema: {
34
78
  title: 'GitHub Configuration',
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "docs-please",
3
3
  "type": "module",
4
- "version": "0.2.0-beta.0",
4
+ "version": "0.2.1-beta.0",
5
5
  "description": "Nuxt layer for documentation sites using shadcn-vue",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -29,7 +29,8 @@
29
29
  "typecheck": "vue-tsc -b"
30
30
  },
31
31
  "peerDependencies": {
32
- "nuxt": "^4.0.0"
32
+ "better-sqlite3": "12.x",
33
+ "nuxt": "4.x"
33
34
  },
34
35
  "dependencies": {
35
36
  "@iconify-json/lucide": "^1.2.75",
@@ -42,12 +43,14 @@
42
43
  "@nuxtjs/mdc": "^0.18.4",
43
44
  "@tabler/icons-vue": "^3.35.0",
44
45
  "@vueuse/core": "^14.1.0",
46
+ "@vueuse/integrations": "^14.1.0",
45
47
  "class-variance-authority": "^0.7.1",
46
48
  "clsx": "^2.1.1",
47
49
  "debug": "^4.4.1",
48
50
  "defu": "^6.1.4",
49
51
  "eslint": "^9.39.1",
50
52
  "exsolve": "^1.0.8",
53
+ "fuse.js": "^7.1.0",
51
54
  "lucide-vue-next": "^0.555.0",
52
55
  "nuxt-og-image": "^5.1.12",
53
56
  "oxc-parser": "^0.99.0",
@@ -70,8 +73,10 @@
70
73
  "zod-to-json-schema": "^3.25.0"
71
74
  },
72
75
  "devDependencies": {
76
+ "@nuxtjs/robots": "^5.6.1",
73
77
  "@tailwindcss/vite": "^4.1.17",
74
78
  "better-sqlite3": "^11.9.1",
79
+ "nuxt-llms": "^0.1.3",
75
80
  "typescript": "^5.9.3",
76
81
  "vue-tsc": "^2.0.0"
77
82
  },
@@ -0,0 +1,45 @@
1
+ import { withLeadingSlash } from 'ufo'
2
+ import { stringify } from 'minimark/stringify'
3
+ import { queryCollection } from '@nuxt/content/nitro'
4
+ import type { Collections } from '@nuxt/content'
5
+
6
+ export default eventHandler(async (event) => {
7
+ const slug = getRouterParams(event)['slug.md']
8
+ if (!slug?.endsWith('.md')) {
9
+ throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
10
+ }
11
+
12
+ const path = withLeadingSlash(slug.replace('.md', ''))
13
+ const config = useRuntimeConfig(event).public
14
+
15
+ let collectionName = 'docs'
16
+ if (config.i18n?.locales) {
17
+ const pathSegments = path.split('/').filter(Boolean)
18
+ const firstSegment = pathSegments[0]
19
+
20
+ const availableLocales = config.i18n.locales.map((locale: string | { code: string }) =>
21
+ typeof locale === 'string' ? locale : locale.code,
22
+ )
23
+
24
+ if (firstSegment && availableLocales.includes(firstSegment)) {
25
+ collectionName = `docs_${firstSegment}`
26
+ }
27
+ else if (config.i18n.defaultLocale) {
28
+ collectionName = `docs_${config.i18n.defaultLocale}`
29
+ }
30
+ }
31
+
32
+ const page = await queryCollection(event, collectionName as keyof Collections).path(path).first()
33
+ if (!page) {
34
+ throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
35
+ }
36
+
37
+ // Add title and description to the top of the page if missing
38
+ if (page.body.value[0]?.[0] !== 'h1') {
39
+ page.body.value.unshift(['blockquote', {}, page.description])
40
+ page.body.value.unshift(['h1', {}, page.title])
41
+ }
42
+
43
+ setHeader(event, 'Content-Type', 'text/markdown; charset=utf-8')
44
+ return stringify({ ...page.body, type: 'minimark' }, { format: 'markdown/html' })
45
+ })