docmk 1.0.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.
- package/.claude/skills/pdf/SKILL.md +89 -0
- package/.claude/skills/web-scraping/SKILL.md +78 -0
- package/CLAUDE.md +90 -0
- package/bin/docmk.js +3 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +636 -0
- package/dist/index.js.map +1 -0
- package/final-site/assets/main-B4orIFxK.css +1 -0
- package/final-site/assets/main-CSoKXua6.js +25 -0
- package/final-site/favicon.svg +4 -0
- package/final-site/index.html +26 -0
- package/final-site/robots.txt +4 -0
- package/final-site/sitemap.xml +14 -0
- package/my-docs/api/README.md +152 -0
- package/my-docs/api/advanced.md +260 -0
- package/my-docs/getting-started/README.md +24 -0
- package/my-docs/tutorials/README.md +272 -0
- package/my-docs/tutorials/customization.md +492 -0
- package/package.json +59 -0
- package/postcss.config.js +6 -0
- package/site/assets/main-BZUsYUCF.css +1 -0
- package/site/assets/main-q6laQtCD.js +114 -0
- package/site/favicon.svg +4 -0
- package/site/index.html +23 -0
- package/site/robots.txt +4 -0
- package/site/sitemap.xml +34 -0
- package/site-output/assets/main-B4orIFxK.css +1 -0
- package/site-output/assets/main-CSoKXua6.js +25 -0
- package/site-output/favicon.svg +4 -0
- package/site-output/index.html +26 -0
- package/site-output/robots.txt +4 -0
- package/site-output/sitemap.xml +14 -0
- package/src/builder/index.ts +189 -0
- package/src/builder/vite-dev.ts +117 -0
- package/src/cli/commands/build.ts +48 -0
- package/src/cli/commands/dev.ts +53 -0
- package/src/cli/commands/preview.ts +57 -0
- package/src/cli/index.ts +42 -0
- package/src/client/App.vue +15 -0
- package/src/client/components/SearchBox.vue +204 -0
- package/src/client/components/Sidebar.vue +18 -0
- package/src/client/components/SidebarItem.vue +108 -0
- package/src/client/index.html +21 -0
- package/src/client/layouts/AppLayout.vue +99 -0
- package/src/client/lib/utils.ts +6 -0
- package/src/client/main.ts +42 -0
- package/src/client/pages/Home.vue +279 -0
- package/src/client/pages/SkillPage.vue +565 -0
- package/src/client/router.ts +16 -0
- package/src/client/styles/global.css +92 -0
- package/src/client/utils/routes.ts +69 -0
- package/src/parser/index.ts +253 -0
- package/src/scanner/index.ts +127 -0
- package/src/types/index.ts +45 -0
- package/tailwind.config.js +65 -0
- package/test-build/assets/main-C2ARPC0e.css +1 -0
- package/test-build/assets/main-CHIQpV3B.js +25 -0
- package/test-build/favicon.svg +4 -0
- package/test-build/index.html +47 -0
- package/test-build/robots.txt +4 -0
- package/test-build/sitemap.xml +19 -0
- package/test-dist/assets/main-B4orIFxK.css +1 -0
- package/test-dist/assets/main-CSoKXua6.js +25 -0
- package/test-dist/favicon.svg +4 -0
- package/test-dist/index.html +26 -0
- package/test-dist/robots.txt +4 -0
- package/test-dist/sitemap.xml +14 -0
- package/tsconfig.json +30 -0
- package/tsup.config.ts +13 -0
- package/vite.config.ts +21 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="search-box">
|
|
3
|
+
<div class="relative w-full">
|
|
4
|
+
<svg class="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
5
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
|
6
|
+
</svg>
|
|
7
|
+
<input
|
|
8
|
+
v-model="searchQuery"
|
|
9
|
+
type="text"
|
|
10
|
+
placeholder="Search documentation..."
|
|
11
|
+
class="w-full h-9 pl-9 pr-4 text-sm bg-secondary border border-border rounded-md placeholder-muted-foreground focus-outline"
|
|
12
|
+
@input="handleSearch"
|
|
13
|
+
@focus="showResults = true"
|
|
14
|
+
@blur="hideResults"
|
|
15
|
+
/>
|
|
16
|
+
<kbd class="absolute right-3 top-1/2 -translate-y-1/2 hidden sm:inline-flex h-5 items-center gap-1 rounded border border-border bg-muted px-1.5 font-mono text-xs text-muted-foreground">
|
|
17
|
+
<span>⌘</span>K
|
|
18
|
+
</kbd>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<!-- Search Results -->
|
|
22
|
+
<div v-if="showResults && (searchResults.length > 0 || searchQuery)" class="absolute top-full left-0 right-0 mt-2 bg-background border border-border rounded-lg shadow-lg z-50 max-h-96 overflow-y-auto">
|
|
23
|
+
<div v-if="searchResults.length === 0 && searchQuery" class="p-4 text-center text-muted-foreground text-sm">
|
|
24
|
+
No results found for "{{ searchQuery }}"
|
|
25
|
+
</div>
|
|
26
|
+
<div v-else>
|
|
27
|
+
<router-link
|
|
28
|
+
v-for="result in searchResults.slice(0, 8)"
|
|
29
|
+
:key="result.path"
|
|
30
|
+
:to="result.route"
|
|
31
|
+
class="block p-3 hover:bg-accent transition-colors border-b border-border last:border-b-0"
|
|
32
|
+
@click="hideResults"
|
|
33
|
+
>
|
|
34
|
+
<div class="font-medium text-foreground text-sm mb-1">{{ result.title }}</div>
|
|
35
|
+
<div v-if="result.description" class="text-xs text-muted-foreground mb-1 line-clamp-2">
|
|
36
|
+
{{ result.description }}
|
|
37
|
+
</div>
|
|
38
|
+
<div class="text-xs text-muted-foreground font-mono">{{ result.pathDisplay }}</div>
|
|
39
|
+
</router-link>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</template>
|
|
44
|
+
|
|
45
|
+
<script setup lang="ts">
|
|
46
|
+
import { ref, computed } from 'vue'
|
|
47
|
+
import { useRouter } from 'vue-router'
|
|
48
|
+
import { SkillFile } from '../../types/index.js'
|
|
49
|
+
import { getFileRoute, getPathDisplay } from '../utils/routes.js'
|
|
50
|
+
|
|
51
|
+
const props = defineProps<{
|
|
52
|
+
files: SkillFile[]
|
|
53
|
+
}>()
|
|
54
|
+
|
|
55
|
+
const router = useRouter()
|
|
56
|
+
const searchQuery = ref('')
|
|
57
|
+
const showResults = ref(false)
|
|
58
|
+
|
|
59
|
+
interface SearchResult {
|
|
60
|
+
path: string
|
|
61
|
+
route: string
|
|
62
|
+
title: string
|
|
63
|
+
description?: string
|
|
64
|
+
pathDisplay: string
|
|
65
|
+
score: number
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const searchResults = computed(() => {
|
|
69
|
+
if (!searchQuery.value.trim()) return []
|
|
70
|
+
|
|
71
|
+
const query = searchQuery.value.toLowerCase().trim()
|
|
72
|
+
const results: SearchResult[] = []
|
|
73
|
+
|
|
74
|
+
for (const file of props.files) {
|
|
75
|
+
const title = file.title || file.name
|
|
76
|
+
const description = file.description || ''
|
|
77
|
+
const content = file.content || ''
|
|
78
|
+
const path = file.path
|
|
79
|
+
|
|
80
|
+
let score = 0
|
|
81
|
+
|
|
82
|
+
// Title match (highest priority)
|
|
83
|
+
if (title.toLowerCase().includes(query)) {
|
|
84
|
+
score += 10
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Description match
|
|
88
|
+
if (description.toLowerCase().includes(query)) {
|
|
89
|
+
score += 5
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Content match
|
|
93
|
+
if (content.toLowerCase().includes(query)) {
|
|
94
|
+
score += 1
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Path match
|
|
98
|
+
if (path.toLowerCase().includes(query)) {
|
|
99
|
+
score += 2
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (score > 0) {
|
|
103
|
+
results.push({
|
|
104
|
+
path,
|
|
105
|
+
route: getFileRoute(file),
|
|
106
|
+
title,
|
|
107
|
+
description,
|
|
108
|
+
pathDisplay: getPathDisplay(path),
|
|
109
|
+
score
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return results.sort((a, b) => b.score - a.score)
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
// Route generation functions moved to utils/routes.ts
|
|
118
|
+
|
|
119
|
+
function handleSearch() {
|
|
120
|
+
showResults.value = true
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function hideResults() {
|
|
124
|
+
setTimeout(() => {
|
|
125
|
+
showResults.value = false
|
|
126
|
+
}, 200) // Delay to allow clicks
|
|
127
|
+
}
|
|
128
|
+
</script>
|
|
129
|
+
|
|
130
|
+
<style scoped>
|
|
131
|
+
.search-box {
|
|
132
|
+
position: relative;
|
|
133
|
+
width: 100%;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.relative { position: relative; }
|
|
137
|
+
.absolute { position: absolute; }
|
|
138
|
+
.w-full { width: 100%; }
|
|
139
|
+
.h-4 { height: 1rem; }
|
|
140
|
+
.w-4 { width: 1rem; }
|
|
141
|
+
.h-5 { height: 1.25rem; }
|
|
142
|
+
.h-9 { height: 2.25rem; }
|
|
143
|
+
.left-3 { left: 0.75rem; }
|
|
144
|
+
.right-3 { right: 0.75rem; }
|
|
145
|
+
.top-1\/2 { top: 50%; }
|
|
146
|
+
.top-full { top: 100%; }
|
|
147
|
+
.left-0 { left: 0; }
|
|
148
|
+
.right-0 { right: 0; }
|
|
149
|
+
.-translate-y-1\/2 { transform: translateY(-50%); }
|
|
150
|
+
.pl-9 { padding-left: 2.25rem; }
|
|
151
|
+
.pr-4 { padding-right: 1rem; }
|
|
152
|
+
.px-1\.5 { padding-left: 0.375rem; padding-right: 0.375rem; }
|
|
153
|
+
.p-3 { padding: 0.75rem; }
|
|
154
|
+
.p-4 { padding: 1rem; }
|
|
155
|
+
.mt-2 { margin-top: 0.5rem; }
|
|
156
|
+
.mb-1 { margin-bottom: 0.25rem; }
|
|
157
|
+
.gap-1 { gap: 0.25rem; }
|
|
158
|
+
.text-sm { font-size: 0.875rem; }
|
|
159
|
+
.text-xs { font-size: 0.75rem; }
|
|
160
|
+
.font-medium { font-weight: 500; }
|
|
161
|
+
.font-mono { font-family: var(--font-mono); }
|
|
162
|
+
.text-muted-foreground { color: var(--color-muted-foreground); }
|
|
163
|
+
.text-foreground { color: var(--color-foreground); }
|
|
164
|
+
.placeholder-muted-foreground::placeholder { color: var(--color-muted-foreground); }
|
|
165
|
+
.bg-secondary { background-color: var(--color-secondary); }
|
|
166
|
+
.bg-muted { background-color: var(--color-muted); }
|
|
167
|
+
.bg-card { background-color: var(--color-card); }
|
|
168
|
+
.bg-background { background-color: hsl(var(--background)); }
|
|
169
|
+
.bg-accent { background-color: var(--color-accent); }
|
|
170
|
+
.border { border-width: 1px; }
|
|
171
|
+
.border-b { border-bottom-width: 1px; }
|
|
172
|
+
.border-border { border-color: var(--color-border); }
|
|
173
|
+
.rounded-md { border-radius: var(--radius-md); }
|
|
174
|
+
.rounded-lg { border-radius: var(--radius-lg); }
|
|
175
|
+
.rounded { border-radius: var(--radius-sm); }
|
|
176
|
+
.shadow-lg { box-shadow: var(--shadow-lg); }
|
|
177
|
+
.z-50 { z-index: 50; }
|
|
178
|
+
.max-h-96 { max-height: 24rem; }
|
|
179
|
+
.overflow-y-auto { overflow-y: auto; }
|
|
180
|
+
.items-center { align-items: center; }
|
|
181
|
+
.text-center { text-align: center; }
|
|
182
|
+
.block { display: block; }
|
|
183
|
+
.hidden { display: none; }
|
|
184
|
+
.inline-flex { display: inline-flex; }
|
|
185
|
+
.transition-colors { transition: color 0.2s ease, background-color 0.2s ease; }
|
|
186
|
+
.hover\:bg-accent:hover { background-color: var(--color-accent); }
|
|
187
|
+
.last\:border-b-0:last-child { border-bottom-width: 0; }
|
|
188
|
+
.line-clamp-2 {
|
|
189
|
+
display: -webkit-box;
|
|
190
|
+
-webkit-line-clamp: 2;
|
|
191
|
+
-webkit-box-orient: vertical;
|
|
192
|
+
overflow: hidden;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.focus-outline:focus {
|
|
196
|
+
outline: none;
|
|
197
|
+
border-color: var(--color-ring);
|
|
198
|
+
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
@media (min-width: 640px) {
|
|
202
|
+
.sm\:inline-flex { display: inline-flex; }
|
|
203
|
+
}
|
|
204
|
+
</style>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<nav class="space-y-1">
|
|
3
|
+
<SidebarItem
|
|
4
|
+
v-for="item in navigation"
|
|
5
|
+
:key="item.text"
|
|
6
|
+
:item="item"
|
|
7
|
+
/>
|
|
8
|
+
</nav>
|
|
9
|
+
</template>
|
|
10
|
+
|
|
11
|
+
<script setup lang="ts">
|
|
12
|
+
import { Navigation } from '../../types/index.js'
|
|
13
|
+
import SidebarItem from './SidebarItem.vue'
|
|
14
|
+
|
|
15
|
+
defineProps<{
|
|
16
|
+
navigation: Navigation[]
|
|
17
|
+
}>()
|
|
18
|
+
</script>
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="sidebar-item">
|
|
3
|
+
<!-- Main Item (Parent with Children) -->
|
|
4
|
+
<div v-if="item.children && item.children.length > 0">
|
|
5
|
+
<button
|
|
6
|
+
@click="toggleExpanded"
|
|
7
|
+
class="flex items-center justify-between w-full px-3 py-2 text-sm font-medium text-foreground hover:bg-accent rounded-md transition-colors"
|
|
8
|
+
>
|
|
9
|
+
<div class="flex items-center gap-2">
|
|
10
|
+
<!-- Fallback icon logic: check item text or use generic -->
|
|
11
|
+
<component :is="getIcon(item.text)" class="h-4 w-4 text-muted-foreground" />
|
|
12
|
+
{{ item.text }}
|
|
13
|
+
</div>
|
|
14
|
+
<component
|
|
15
|
+
:is="isExpanded ? ChevronDown : ChevronRight"
|
|
16
|
+
class="h-4 w-4 text-muted-foreground"
|
|
17
|
+
/>
|
|
18
|
+
</button>
|
|
19
|
+
|
|
20
|
+
<!-- Children -->
|
|
21
|
+
<div
|
|
22
|
+
v-if="isExpanded"
|
|
23
|
+
class="ml-6 mt-1 space-y-1 border-l border-border pl-3"
|
|
24
|
+
>
|
|
25
|
+
<SidebarItem
|
|
26
|
+
v-for="child in item.children"
|
|
27
|
+
:key="child.text"
|
|
28
|
+
:item="child"
|
|
29
|
+
/>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<!-- Leaf Item (Link) -->
|
|
34
|
+
<router-link
|
|
35
|
+
v-else-if="item.link"
|
|
36
|
+
:to="item.link"
|
|
37
|
+
class="block px-3 py-1.5 text-sm rounded-md transition-colors"
|
|
38
|
+
:class="[
|
|
39
|
+
isActive(item.link)
|
|
40
|
+
? 'text-primary font-medium bg-primary/5'
|
|
41
|
+
: 'text-muted-foreground hover:text-foreground hover:bg-accent'
|
|
42
|
+
]"
|
|
43
|
+
>
|
|
44
|
+
{{ item.text }}
|
|
45
|
+
</router-link>
|
|
46
|
+
|
|
47
|
+
<!-- Leaf Item (Text only, rare case) -->
|
|
48
|
+
<span v-else class="block px-3 py-1.5 text-sm text-muted-foreground">
|
|
49
|
+
{{ item.text }}
|
|
50
|
+
</span>
|
|
51
|
+
</div>
|
|
52
|
+
</template>
|
|
53
|
+
|
|
54
|
+
<script setup lang="ts">
|
|
55
|
+
import { ref, computed } from 'vue'
|
|
56
|
+
import { useRoute } from 'vue-router'
|
|
57
|
+
import { Navigation } from '../../types/index.js'
|
|
58
|
+
import {
|
|
59
|
+
ChevronDown,
|
|
60
|
+
ChevronRight,
|
|
61
|
+
BookOpen,
|
|
62
|
+
Layers,
|
|
63
|
+
Code,
|
|
64
|
+
FileText,
|
|
65
|
+
Folder
|
|
66
|
+
} from 'lucide-vue-next'
|
|
67
|
+
|
|
68
|
+
const props = defineProps<{
|
|
69
|
+
item: Navigation
|
|
70
|
+
}>()
|
|
71
|
+
|
|
72
|
+
const route = useRoute()
|
|
73
|
+
const isExpanded = ref(false)
|
|
74
|
+
|
|
75
|
+
// Simple icon mapping based on text (Acme Docs style)
|
|
76
|
+
const getIcon = (text: string) => {
|
|
77
|
+
const lower = text.toLowerCase()
|
|
78
|
+
if (lower.includes('getting started') || lower.includes('introduction')) return BookOpen
|
|
79
|
+
if (lower.includes('core') || lower.includes('concept')) return Layers
|
|
80
|
+
if (lower.includes('api') || lower.includes('reference')) return Code
|
|
81
|
+
if (lower.includes('guide') || lower.includes('tutorial')) return FileText
|
|
82
|
+
return Folder // Default
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Check if active
|
|
86
|
+
const isActive = (link?: string) => {
|
|
87
|
+
if (!link) return false
|
|
88
|
+
return route.path === link
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Auto-expand if current route is within this section
|
|
92
|
+
const shouldAutoExpand = computed(() => {
|
|
93
|
+
if (!props.item.children) return false
|
|
94
|
+
return props.item.children.some(child =>
|
|
95
|
+
(child.link && isActive(child.link)) ||
|
|
96
|
+
(child.children && child.children.some(c => c.link && isActive(c.link)))
|
|
97
|
+
)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
// Initialize expansion state
|
|
101
|
+
if (shouldAutoExpand.value) {
|
|
102
|
+
isExpanded.value = true
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function toggleExpanded() {
|
|
106
|
+
isExpanded.value = !isExpanded.value
|
|
107
|
+
}
|
|
108
|
+
</script>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<meta name="description" content="Modern documentation site">
|
|
8
|
+
<title>Documentation</title>
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<div id="app">
|
|
12
|
+
<div class="loading">
|
|
13
|
+
<div style="text-align: center;">
|
|
14
|
+
<div style="font-size: 1.5rem; margin-bottom: 0.5rem;">📚</div>
|
|
15
|
+
<div>Loading Documentation...</div>
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
<script type="module" src="/main.ts"></script>
|
|
20
|
+
</body>
|
|
21
|
+
</html>
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="min-h-screen bg-background font-sans antialiased">
|
|
3
|
+
<!-- Header -->
|
|
4
|
+
<header class="sticky top-0 z-50 w-full border-b border-border bg-background/95 backdrop-blur">
|
|
5
|
+
<div class="flex h-14 items-center px-4 lg:px-6">
|
|
6
|
+
<!-- Mobile Menu Toggle -->
|
|
7
|
+
<button
|
|
8
|
+
class="inline-flex items-center justify-center rounded-md p-2 mr-2 md:hidden hover:bg-accent"
|
|
9
|
+
@click="toggleMobileMenu"
|
|
10
|
+
>
|
|
11
|
+
<Menu v-if="!mobileMenuOpen" class="h-5 w-5" />
|
|
12
|
+
<X v-else class="h-5 w-5" />
|
|
13
|
+
</button>
|
|
14
|
+
|
|
15
|
+
<!-- Logo -->
|
|
16
|
+
<router-link to="/" class="flex items-center space-x-2">
|
|
17
|
+
<Zap class="h-6 w-6" />
|
|
18
|
+
<span class="font-bold">{{ config?.siteConfig.title || 'Docs' }}</span>
|
|
19
|
+
</router-link>
|
|
20
|
+
|
|
21
|
+
<!-- Search -->
|
|
22
|
+
<div class="flex-1 mx-4 lg:mx-8 max-w-md">
|
|
23
|
+
<SearchBox v-if="config" :files="config.files" />
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<!-- GitHub -->
|
|
27
|
+
<a
|
|
28
|
+
href="https://github.com"
|
|
29
|
+
target="_blank"
|
|
30
|
+
rel="noopener noreferrer"
|
|
31
|
+
class="p-2 hover:bg-accent rounded-md"
|
|
32
|
+
>
|
|
33
|
+
<Github class="h-5 w-5" />
|
|
34
|
+
</a>
|
|
35
|
+
</div>
|
|
36
|
+
</header>
|
|
37
|
+
|
|
38
|
+
<div class="flex">
|
|
39
|
+
<!-- Mobile Sidebar Overlay -->
|
|
40
|
+
<div
|
|
41
|
+
v-if="mobileMenuOpen"
|
|
42
|
+
class="fixed inset-0 z-40 bg-background/80 backdrop-blur-sm md:hidden"
|
|
43
|
+
@click="toggleMobileMenu"
|
|
44
|
+
/>
|
|
45
|
+
|
|
46
|
+
<!-- Sidebar -->
|
|
47
|
+
<aside
|
|
48
|
+
:class="[
|
|
49
|
+
'fixed md:sticky top-14 z-40 h-[calc(100vh-3.5rem)] w-64 border-r border-border bg-background overflow-y-auto transition-transform md:translate-x-0',
|
|
50
|
+
mobileMenuOpen ? 'translate-x-0' : '-translate-x-full'
|
|
51
|
+
]"
|
|
52
|
+
>
|
|
53
|
+
<div class="p-4">
|
|
54
|
+
<Sidebar v-if="config" :navigation="config.navigation" />
|
|
55
|
+
</div>
|
|
56
|
+
</aside>
|
|
57
|
+
|
|
58
|
+
<!-- Main Content -->
|
|
59
|
+
<main class="flex-1 min-w-0">
|
|
60
|
+
<div class="mx-auto px-6 py-10 lg:px-12 xl:px-16 max-w-[1400px]">
|
|
61
|
+
<router-view v-if="config" :config="config" />
|
|
62
|
+
<div v-else class="flex h-[50vh] items-center justify-center text-muted-foreground">
|
|
63
|
+
Loading...
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</main>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
</template>
|
|
70
|
+
|
|
71
|
+
<script setup lang="ts">
|
|
72
|
+
import { ref, onMounted } from 'vue'
|
|
73
|
+
import { DocGenConfig } from '../../types/index.js'
|
|
74
|
+
import Sidebar from '../components/Sidebar.vue'
|
|
75
|
+
import SearchBox from '../components/SearchBox.vue'
|
|
76
|
+
import { Menu, X, Github, Zap } from 'lucide-vue-next'
|
|
77
|
+
|
|
78
|
+
const config = ref<DocGenConfig | null>(null)
|
|
79
|
+
const mobileMenuOpen = ref(false)
|
|
80
|
+
|
|
81
|
+
const toggleMobileMenu = () => {
|
|
82
|
+
mobileMenuOpen.value = !mobileMenuOpen.value
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
onMounted(async () => {
|
|
86
|
+
try {
|
|
87
|
+
if (typeof globalThis.__DOCGEN_CONFIG__ !== 'undefined' && globalThis.__DOCGEN_CONFIG__) {
|
|
88
|
+
config.value = globalThis.__DOCGEN_CONFIG__
|
|
89
|
+
} else {
|
|
90
|
+
const response = await fetch('/api/config')
|
|
91
|
+
if (response.ok) {
|
|
92
|
+
config.value = await response.json()
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.error('Failed to load configuration:', error)
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
</script>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { createApp } from 'vue'
|
|
2
|
+
import { createRouter, createWebHistory } from 'vue-router'
|
|
3
|
+
import App from './App.vue'
|
|
4
|
+
import { routes } from './router.js'
|
|
5
|
+
import './styles/global.css'
|
|
6
|
+
|
|
7
|
+
// Load config from API in dev mode
|
|
8
|
+
async function loadConfig() {
|
|
9
|
+
if (!globalThis.__DOCGEN_CONFIG__) {
|
|
10
|
+
try {
|
|
11
|
+
const response = await fetch('/api/config')
|
|
12
|
+
if (response.ok) {
|
|
13
|
+
globalThis.__DOCGEN_CONFIG__ = await response.json()
|
|
14
|
+
}
|
|
15
|
+
} catch (error) {
|
|
16
|
+
console.error('Failed to load config from API:', error)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Initialize app
|
|
22
|
+
async function init() {
|
|
23
|
+
await loadConfig()
|
|
24
|
+
|
|
25
|
+
// Create router
|
|
26
|
+
const router = createRouter({
|
|
27
|
+
history: createWebHistory(),
|
|
28
|
+
routes
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
// Create and mount app
|
|
32
|
+
const app = createApp(App)
|
|
33
|
+
app.use(router)
|
|
34
|
+
app.mount('#app')
|
|
35
|
+
|
|
36
|
+
// Global error handler
|
|
37
|
+
app.config.errorHandler = (err, instance, info) => {
|
|
38
|
+
console.error('Vue Error:', err, info)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
init()
|