@feathersdev/websites 0.0.1
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/assets/icons/feathers.svg +12 -0
- package/app/assets/icons/pinion.svg +3 -0
- package/app/assets/icons/talon.svg +3 -0
- package/app/components/CodePreview.vue +139 -0
- package/app/components/CodeSnippet.vue +119 -0
- package/app/components/Discord.vue +33 -0
- package/app/components/DocsPage.vue +11 -0
- package/app/components/DocsSearch.vue +62 -0
- package/app/components/DocsSearchModal.vue +831 -0
- package/app/components/DocsSidebar.vue +43 -0
- package/app/components/DocsTiles.vue +121 -0
- package/app/components/FooterMain.vue +46 -0
- package/app/components/HeroProduct.vue +148 -0
- package/app/components/MoonSurface.vue +1599 -0
- package/app/components/NewsletterSubscribe.vue +21 -0
- package/app/components/SidebarMenuSection.vue +45 -0
- package/app/components/TableOfContents.vue +221 -0
- package/app/components/ThemeToggle.vue +10 -0
- package/app/components/Titles.vue +39 -0
- package/app/components/TocTree.vue +49 -0
- package/app/components/content/BlueskyEmbed.vue +92 -0
- package/app/components/content/CodeWrapper.vue +10 -0
- package/app/components/content/ProseA.vue +19 -0
- package/app/components/content/ProseAlert.vue +11 -0
- package/app/components/content/ProseBlockquote.vue +11 -0
- package/app/components/content/ProseEm.vue +5 -0
- package/app/components/content/ProseH1.vue +17 -0
- package/app/components/content/ProseH2.vue +17 -0
- package/app/components/content/ProseH3.vue +17 -0
- package/app/components/content/ProseH4.vue +17 -0
- package/app/components/content/ProseH5.vue +17 -0
- package/app/components/content/ProseH6.vue +17 -0
- package/app/components/content/ProseHr.vue +3 -0
- package/app/components/content/ProseImg.vue +37 -0
- package/app/components/content/ProseLi.vue +3 -0
- package/app/components/content/ProseOl.vue +5 -0
- package/app/components/content/ProseP.vue +3 -0
- package/app/components/content/ProsePre.vue +49 -0
- package/app/components/content/ProsePre2.vue +69 -0
- package/app/components/content/ProseScript.vue +37 -0
- package/app/components/content/ProseStrong.vue +5 -0
- package/app/components/content/ProseTable.vue +7 -0
- package/app/components/content/ProseTbody.vue +5 -0
- package/app/components/content/ProseTd.vue +5 -0
- package/app/components/content/ProseTh.vue +5 -0
- package/app/components/content/ProseThead.vue +5 -0
- package/app/components/content/ProseTr.vue +5 -0
- package/app/components/content/ProseUl.vue +5 -0
- package/app/composables/useGlobalSearch.ts +22 -0
- package/app/composables/useTheme.ts +17 -0
- package/app/layouts/default.vue +46 -0
- package/app/layouts/docs.vue +62 -0
- package/app/layouts/page.vue +11 -0
- package/app/pages/help.vue +19 -0
- package/app/pages/privacy.vue +30 -0
- package/app/pages/tos.vue +28 -0
- package/content/pages/privacy.md +152 -0
- package/content/pages/tos.md +133 -0
- package/content/products/1-auth.yaml +13 -0
- package/content/products/2-feathers.yaml +13 -0
- package/content/products/3-pinion.yaml +13 -0
- package/content/products/4-daisyui-kit.yaml +14 -0
- package/content/products/lofi.yaml +14 -0
- package/content.config.ts +36 -0
- package/nuxt.config.ts +102 -0
- package/package.json +33 -0
- package/public/img/bird-comms.png +0 -0
- package/public/img/bird-yellow.svg +125 -0
- package/public/img/logo-auth-white.svg +22 -0
- package/public/img/logos/feathersdev-white.svg +24 -0
- package/public/img/logos/talon-auth-white.svg +19 -0
- package/public/img/planet-yellow.svg +31 -0
- package/public/img/rock-lg.svg +6 -0
- package/public/img/rock-md.svg +6 -0
- package/public/img/top_background.svg +56 -0
- package/vitest.config.ts +7 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<svg width="114" height="134" viewBox="0 0 114 134" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<g clip-path="url(#clip0_3_343)">
|
|
3
|
+
<path d="M85.5 0C79.8 0 74.3 2.2 70.2 6.3L68.7 7.8C68 18.6 64.4 28.4 58.7 37.5C60.7 28.8 62.9 20 61.4 15.1L47.5 29C46.8 39.7 43.2 49.5 37.5 58.5C39.8 49.9 41.3 42 40.4 36.1L26.3 50.1C21.5 54.9 18.8 61.4 18.7 68.2V106.8C18.6 106.8 5.2 120 5.2 120C1.9 123.2 0 127.7 0 132.3V133.4L99.2 34.9C108.5 25.7 113.7 13.1 113.7 0.1H85.5V0Z" fill="black"/>
|
|
4
|
+
<path d="M77.8 83L105.1 55.7999C110.5 50.5 113.5 43.2 113.5 35.7V19.2C112.6 22.8 111.4 26.2 109.5 28.9C105.9 33.9 101.9 38.6 97.5 42.9L50.6 89.3999L68.7 87.3C72.1 86.9 75.3 85.3999 77.7 83H77.8Z" fill="black"/>
|
|
5
|
+
<path d="M45.5 94.6L23.5 116.5L35.9 116.7C40.9 116.7 45.6 114.9 49.2 111.4L70.4 90.4C69 91.3 67.7 91.8 66.3 92.2L45.6 94.6H45.5Z" fill="black"/>
|
|
6
|
+
</g>
|
|
7
|
+
<defs>
|
|
8
|
+
<clipPath id="clip0_3_343">
|
|
9
|
+
<rect width="114" height="134" fill="white"/>
|
|
10
|
+
</clipPath>
|
|
11
|
+
</defs>
|
|
12
|
+
</svg>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg width="114" height="115" viewBox="0 0 114 115" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M74.1 0.1V15.1C69.2 5.1 61.8 1.8 51.8 0H0C3.4 15.8 10.7 27.5 21.6 35C30.3 40.9 41.5 44.3 55.1 45.1H69.5V49C64.5 49.3 59.6 49.3 55 49H19.1C28.3 78.6 50.7 84.6 83.7 84.6V88.5H38C50 114.8 71.8 114.8 85.6 114.8H113.7V41.4C113.7 19.2 96.1 1 74.1 0.1Z" fill="black"/>
|
|
3
|
+
</svg>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg width="149" height="152" viewBox="0 0 149 152" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M85.165 0.849446C110.203 4.58736 132.512 21.7936 144.26 46.5944C148.532 55.6129 149.778 60.3595 148.176 61.3681C146.811 62.2581 142.895 61.5461 137.199 59.4695C127.528 55.9689 124.799 55.1976 119.696 54.4856C111.271 53.3583 111.627 53.5956 102.965 43.6278C98.5147 38.466 89.2589 31.9394 84.2751 30.4561C82.9698 30.0408 82.9104 30.1001 83.6817 31.2274C84.1564 31.8801 85.9957 33.9567 87.7756 35.9147C102.312 51.5783 103.321 52.8836 107.474 61.6055C113.704 74.6585 116.552 87.0589 116.552 101.061C116.552 110.792 115.899 112.631 112.517 112.631C110.796 112.631 109.966 111.919 105.338 106.638C99.5827 100.171 96.3788 97.3233 91.3356 94.2381C86.2923 91.1528 85.521 90.2035 84.1564 85.6943C79.4098 69.9713 78.8758 68.7253 73.5953 60.7155C70.0947 55.3756 62.8562 48.1371 58.8216 46.0011C57.101 45.0518 55.5584 44.3992 55.4397 44.5178C55.321 44.6365 56.567 46.2978 58.169 48.2557C68.4927 60.5375 74.4853 72.5225 77.4519 86.5249C78.8758 93.4668 78.8758 108.062 77.4519 114.945C75.3753 124.675 72.3493 132.745 68.1961 139.627C64.2802 146.035 62.8562 148.824 62.8562 150.01C62.8562 150.9 62.4409 151.197 61.3729 151.197C59.5929 151.197 58.8809 149.892 56.745 142.89C55.9143 140.102 54.3717 135.059 53.3037 131.617C50.3371 121.827 46.2432 114.47 40.6067 108.715C35.9194 104.028 34.3175 102.901 20.4338 94.7721C12.6613 90.2628 5.89743 83.9143 3.34615 78.7524C-2.34973 67.3013 -0.688428 54.2483 7.67739 44.0432C10.288 40.8392 13.2546 38.1693 25.3583 27.8455C27.7316 25.7689 32.0035 21.2597 34.9108 17.8184C37.8181 14.3178 41.3187 10.6392 42.802 9.57126C48.0232 5.83334 57.3976 2.33274 65.2888 1.1461C67.7214 0.790109 70.332 0.374785 71.1627 0.256121C74.6039 -0.218536 79.2318 -0.0405349 85.165 0.849446Z" fill="black"/>
|
|
3
|
+
</svg>
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { codeToHtml } from 'shiki'
|
|
3
|
+
import { computed, onMounted, ref, watch } from 'vue'
|
|
4
|
+
|
|
5
|
+
const props = defineProps<{
|
|
6
|
+
path: string
|
|
7
|
+
minHeight?: string
|
|
8
|
+
previewClasses?: string
|
|
9
|
+
prose?: boolean
|
|
10
|
+
}>()
|
|
11
|
+
|
|
12
|
+
const code = ref('')
|
|
13
|
+
const highlightedCode = ref('')
|
|
14
|
+
|
|
15
|
+
// Extract filename from path for display
|
|
16
|
+
const filename = computed(() => {
|
|
17
|
+
return props.path.split('/').pop() || 'code.txt'
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
// Determine language from file extension
|
|
21
|
+
const language = computed(() => {
|
|
22
|
+
const extension = props.path.split('.').pop() || 'text'
|
|
23
|
+
const languageMap: Record<string, string> = {
|
|
24
|
+
ts: 'typescript',
|
|
25
|
+
js: 'javascript',
|
|
26
|
+
vue: 'vue',
|
|
27
|
+
svelte: 'svelte', // Add this line
|
|
28
|
+
html: 'html',
|
|
29
|
+
css: 'css',
|
|
30
|
+
json: 'json',
|
|
31
|
+
md: 'markdown',
|
|
32
|
+
tsx: 'tsx',
|
|
33
|
+
jsx: 'jsx'
|
|
34
|
+
}
|
|
35
|
+
return languageMap[extension] || 'text'
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
async function loadCodeAndComponent() {
|
|
39
|
+
try {
|
|
40
|
+
// Look in the app/.cache/examples directory
|
|
41
|
+
const files = import.meta.glob(
|
|
42
|
+
[
|
|
43
|
+
'../code/examples/**/*.ts',
|
|
44
|
+
'../code/examples/**/*.tsx',
|
|
45
|
+
'../code/examples/**/*.js',
|
|
46
|
+
'../code/examples/**/*.jsx',
|
|
47
|
+
'../code/examples/**/*.vue',
|
|
48
|
+
'../code/examples/**/*.svelte',
|
|
49
|
+
'../code/examples/**/*.md'
|
|
50
|
+
],
|
|
51
|
+
{
|
|
52
|
+
as: 'raw',
|
|
53
|
+
eager: false
|
|
54
|
+
}
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
// Normalize the path by removing prefixes and cleaning up
|
|
58
|
+
let normalizedPath = props.path
|
|
59
|
+
.replace(/^@\/examples\//, '') // Remove @/examples/ prefix
|
|
60
|
+
.replace(/^\.\.\/\.\.\/\.\.\/\.\.\/examples\//, '') // Remove ../../../../examples/ prefix
|
|
61
|
+
.replace(/^examples\//, '') // Remove examples/ prefix if present
|
|
62
|
+
.replace(/^\/*|\/*$/g, '') // Remove leading/trailing slashes
|
|
63
|
+
|
|
64
|
+
// Try to find the file with the normalized path
|
|
65
|
+
let fileKey = Object.keys(files).find((key) => key.includes(`/${normalizedPath}`))
|
|
66
|
+
|
|
67
|
+
// If not found, try to find it by just the filename
|
|
68
|
+
if (!fileKey) {
|
|
69
|
+
const filename = normalizedPath.split('/').pop()
|
|
70
|
+
fileKey = Object.keys(files).find((key) => key.endsWith(`/${filename}`))
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!fileKey) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
`File not found: ${normalizedPath}. Make sure the file exists in the examples directory and has been copied to the cache.`
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const loadRaw = files[fileKey]
|
|
80
|
+
if (!loadRaw) {
|
|
81
|
+
throw new Error(
|
|
82
|
+
`File not found: ${normalizedPath}. Make sure the file exists in the examples directory and has been copied to the cache.`
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
code.value = await loadRaw()
|
|
86
|
+
|
|
87
|
+
highlightedCode.value = await codeToHtml(code.value, {
|
|
88
|
+
lang: language.value,
|
|
89
|
+
theme: 'github-dark'
|
|
90
|
+
})
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.error(`Error loading file: ${props.path}`, error)
|
|
93
|
+
code.value = `// Error loading file: ${props.path}\n// ${error instanceof Error ? error.message : String(error)}`
|
|
94
|
+
highlightedCode.value = await codeToHtml(code.value, {
|
|
95
|
+
lang: 'javascript',
|
|
96
|
+
theme: 'github-dark'
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
onMounted(loadCodeAndComponent)
|
|
102
|
+
|
|
103
|
+
watch(
|
|
104
|
+
() => props.path,
|
|
105
|
+
() => {
|
|
106
|
+
loadCodeAndComponent()
|
|
107
|
+
}
|
|
108
|
+
)
|
|
109
|
+
</script>
|
|
110
|
+
|
|
111
|
+
<template>
|
|
112
|
+
<ProsePre :code="code" :filename="filename" class="code-preview">
|
|
113
|
+
<div v-html="highlightedCode" />
|
|
114
|
+
</ProsePre>
|
|
115
|
+
</template>
|
|
116
|
+
|
|
117
|
+
<style>
|
|
118
|
+
.code-preview > div > pre {
|
|
119
|
+
padding-left: 0;
|
|
120
|
+
}
|
|
121
|
+
.code-preview {
|
|
122
|
+
margin-top: 0;
|
|
123
|
+
padding-top: 0;
|
|
124
|
+
padding-bottom: 0;
|
|
125
|
+
}
|
|
126
|
+
.code-preview pre code span.line:not(:last-of-type):empty {
|
|
127
|
+
display: block;
|
|
128
|
+
margin-bottom: 1.5rem;
|
|
129
|
+
}
|
|
130
|
+
pre.shiki {
|
|
131
|
+
background-color: var(--tw-prose-pre-bg) !important;
|
|
132
|
+
color: var(--tw-prose-pre-code) !important;
|
|
133
|
+
font-size: 16px;
|
|
134
|
+
}
|
|
135
|
+
pre.shiki code {
|
|
136
|
+
display: flex;
|
|
137
|
+
flex-direction: column;
|
|
138
|
+
}
|
|
139
|
+
</style>
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { codeToHtml } from 'shiki'
|
|
3
|
+
import { computed, onMounted, ref, resolveComponent, watch } from 'vue'
|
|
4
|
+
|
|
5
|
+
const props = defineProps<{
|
|
6
|
+
slug: string
|
|
7
|
+
name: string
|
|
8
|
+
minHeight?: string
|
|
9
|
+
previewClasses?: string
|
|
10
|
+
prose?: boolean
|
|
11
|
+
}>()
|
|
12
|
+
|
|
13
|
+
const code = ref('')
|
|
14
|
+
const highlightedCode = ref('')
|
|
15
|
+
const copied = ref(false)
|
|
16
|
+
|
|
17
|
+
function pascalCase(str: string): string {
|
|
18
|
+
return str
|
|
19
|
+
.replace(/(^|[-_/])(\w)/g, (_: string, __: string, c: string) => c ? c.toUpperCase() : '')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const componentName = computed(() => {
|
|
23
|
+
// e.g., DemoSwapFlip for slug="swap", name="Flip"
|
|
24
|
+
return `Demo${pascalCase(props.slug)}${pascalCase(props.name)}`
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const resolvedComponent = computed(() => {
|
|
28
|
+
return resolveComponent(componentName.value)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const filename = computed(() => `${props.name}.vue`)
|
|
32
|
+
|
|
33
|
+
async function loadCodeAndComponent() {
|
|
34
|
+
const files = import.meta.glob('~/components-docs/demo/**/*.vue', { as: 'raw' })
|
|
35
|
+
const key = `/components-docs/demo/${props.slug}/${props.name}.vue`
|
|
36
|
+
|
|
37
|
+
const loadRaw = files[key]
|
|
38
|
+
if (!loadRaw) {
|
|
39
|
+
console.error(`Missing file: ${key}`)
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
code.value = await loadRaw()
|
|
44
|
+
|
|
45
|
+
highlightedCode.value = await codeToHtml(code.value, {
|
|
46
|
+
lang: 'vue',
|
|
47
|
+
theme: 'github-dark',
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function copyCode() {
|
|
52
|
+
await navigator.clipboard.writeText(code.value)
|
|
53
|
+
copied.value = true
|
|
54
|
+
setTimeout(() => (copied.value = false), 1500)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
onMounted(loadCodeAndComponent)
|
|
58
|
+
|
|
59
|
+
watch([() => props.slug, () => props.name], () => {
|
|
60
|
+
loadCodeAndComponent()
|
|
61
|
+
})
|
|
62
|
+
</script>
|
|
63
|
+
|
|
64
|
+
<template>
|
|
65
|
+
<div class="code-preview my-6">
|
|
66
|
+
<div
|
|
67
|
+
:style="minHeight ? { minHeight } : undefined"
|
|
68
|
+
class="flex overflow-x-auto p-6 lg:p-10 flex-nowrap items-start mt-4 border-4 rounded-t-xl border-base-300 gap-2 code-preview-bg"
|
|
69
|
+
:class="[previewClasses, prose ? 'prose' : 'not-prose']"
|
|
70
|
+
>
|
|
71
|
+
<component :is="resolvedComponent" />
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<Collapse arrow toggle class="mb-8 rounded-b-xl rounded-t-none bg-base-300 p-0">
|
|
75
|
+
<CollapseTitle class="text-lg font-medium bg-base-300">
|
|
76
|
+
Code Example
|
|
77
|
+
</CollapseTitle>
|
|
78
|
+
<CollapseContent class="overflow-hidden !pb-0 -mt-4">
|
|
79
|
+
<div class="relative">
|
|
80
|
+
<Badge sm neutral class="absolute top-2.5 left-2 font-mono text-base-content/50">
|
|
81
|
+
{{ filename }}
|
|
82
|
+
</Badge>
|
|
83
|
+
<Button xs neutral class="copy-button" @click="copyCode">
|
|
84
|
+
{{ copied ? 'Copied!' : 'Copy' }}
|
|
85
|
+
</Button>
|
|
86
|
+
<pre class="overflow-x-auto rounded-lg p-4" v-html="highlightedCode" />
|
|
87
|
+
</div>
|
|
88
|
+
</CollapseContent>
|
|
89
|
+
</Collapse>
|
|
90
|
+
</div>
|
|
91
|
+
</template>
|
|
92
|
+
|
|
93
|
+
<style scoped>
|
|
94
|
+
.copy-button {
|
|
95
|
+
position: absolute;
|
|
96
|
+
top: 0.5rem;
|
|
97
|
+
right: 1rem;
|
|
98
|
+
font-size: 0.75rem;
|
|
99
|
+
color: white;
|
|
100
|
+
cursor: pointer;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.code-preview-bg {
|
|
104
|
+
/* Diagonal stripes, subtle effect */
|
|
105
|
+
background-image: repeating-linear-gradient(
|
|
106
|
+
-45deg,
|
|
107
|
+
rgba(0,0,0,0.06) 0,
|
|
108
|
+
rgba(0,0,0,0.06) 8px,
|
|
109
|
+
transparent 8px,
|
|
110
|
+
transparent 16px
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
</style>
|
|
114
|
+
|
|
115
|
+
<style>
|
|
116
|
+
.shiki code {
|
|
117
|
+
@apply flex flex-col;
|
|
118
|
+
}
|
|
119
|
+
</style>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Flex col items-center justify-center class="md:flex-row py-8 md:py-24 bg-base-300/25 rounded-4xl">
|
|
3
|
+
<Flex col class="md:flex-row-reverse items-center gap-6">
|
|
4
|
+
<Flex class="px-6 md:pl-24">
|
|
5
|
+
<img src="/img/bird-comms.png" alt="Discord" />
|
|
6
|
+
</Flex>
|
|
7
|
+
<Flex col items-center class="gap-4 md:items-start py-5 md:pl-8">
|
|
8
|
+
<!-- <Text size="md" class="text-center md:text-left text-base-content/70">Need more support?</Text> -->
|
|
9
|
+
<Text is="h3" size="3xl" bold class="text-center md:text-left">Join our community</Text>
|
|
10
|
+
<Text size="md" class="text-center md:text-left text-base-content/70 text-balance line-clamp-2"
|
|
11
|
+
>Get help and support <br />
|
|
12
|
+
from the Feathers community</Text
|
|
13
|
+
>
|
|
14
|
+
<Flex>
|
|
15
|
+
<NuxtLink
|
|
16
|
+
primary
|
|
17
|
+
href="https://discord.gg/qa8kez8QBx"
|
|
18
|
+
target="_blank"
|
|
19
|
+
rel="noopener noreferrer"
|
|
20
|
+
class="btn btn-primary btn-xl gap-2 mt-4"
|
|
21
|
+
>
|
|
22
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="currentColor" viewBox="0 0 24 24">
|
|
23
|
+
<path
|
|
24
|
+
d="M20.317 4.37a19.791 19.791 0 00-4.885-1.515.074.074 0 00-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 00-5.487 0 12.64 12.64 0 00-.617-1.25.077.077 0 00-.079-.037A19.736 19.736 0 003.677 4.37a.07.07 0 00-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 00.031.057 19.9 19.9 0 005.993 3.03.078.078 0 00.084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 00-.041-.105 13.107 13.107 0 01-1.872-.892.077.077 0 01-.008-.128 10.2 10.2 0 00.372-.292.074.074 0 01.077-.01c3.928 1.8 8.18 1.8 12.062 0a.074.074 0 01.078.01c.12.098.246.198.373.293a.077.077 0 01-.008.127 12.27 12.27 0 01-1.873.892.077.077 0 00-.041.105c.36.698.774 1.362 1.225 1.993a.076.076 0 00.084.028 19.84 19.84 0 005.99-3.03.077.077 0 00.032-.054c.5-5.18-.838-9.674-3.549-13.66a.061.061 0 00-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.418 0-1.333.946-2.415 2.157-2.415 1.21 0 2.175 1.08 2.157 2.415 0 1.333-.946 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.418 0-1.333.954-2.415 2.157-2.415 1.203 0 2.175 1.08 2.157 2.415 0 1.333-.955 2.418-2.157 2.418z"
|
|
25
|
+
/>
|
|
26
|
+
</svg>
|
|
27
|
+
Join our Discord
|
|
28
|
+
</NuxtLink>
|
|
29
|
+
</Flex>
|
|
30
|
+
</Flex>
|
|
31
|
+
</Flex>
|
|
32
|
+
</Flex>
|
|
33
|
+
</template>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { AuthDocsCollectionItem, FeathersDocsCollectionItem, PinionDocsCollectionItem } from '@nuxt/content'
|
|
3
|
+
|
|
4
|
+
defineProps<{ page: PinionDocsCollectionItem | AuthDocsCollectionItem | FeathersDocsCollectionItem }>()
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<template>
|
|
8
|
+
<div v-if="page" class="pt-6 px-3 lg:px-6 prose mx-auto max-w-full w-full">
|
|
9
|
+
<ContentRenderer v-if="page" :value="page" />
|
|
10
|
+
</div>
|
|
11
|
+
</template>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, onMounted, onUnmounted } from 'vue'
|
|
3
|
+
|
|
4
|
+
const props = defineProps<{
|
|
5
|
+
/** Array of Nuxt Content collection names to search. If not provided, auto-detects from route. */
|
|
6
|
+
collections?: string[]
|
|
7
|
+
/** Display name for the search (e.g., "Feathers Docs"). If not provided, auto-generates. */
|
|
8
|
+
searchLabel?: string
|
|
9
|
+
/** Array of doc paths to show as popular/featured when search is empty */
|
|
10
|
+
popularPaths?: string[]
|
|
11
|
+
}>()
|
|
12
|
+
|
|
13
|
+
const isModalOpen = ref(false)
|
|
14
|
+
const isMac = ref(false)
|
|
15
|
+
|
|
16
|
+
onMounted(() => {
|
|
17
|
+
// Detect platform for keyboard shortcut display
|
|
18
|
+
isMac.value = navigator.platform.toLowerCase().includes('mac')
|
|
19
|
+
|
|
20
|
+
// Register global keyboard shortcut
|
|
21
|
+
const handleGlobalKeydown = (event: KeyboardEvent) => {
|
|
22
|
+
// Cmd+K on Mac, Ctrl+K on Windows/Linux
|
|
23
|
+
if ((event.metaKey || event.ctrlKey) && event.key === 'k') {
|
|
24
|
+
event.preventDefault()
|
|
25
|
+
isModalOpen.value = true
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
document.addEventListener('keydown', handleGlobalKeydown)
|
|
30
|
+
|
|
31
|
+
onUnmounted(() => {
|
|
32
|
+
document.removeEventListener('keydown', handleGlobalKeydown)
|
|
33
|
+
})
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
function openModal() {
|
|
37
|
+
isModalOpen.value = true
|
|
38
|
+
}
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<template>
|
|
42
|
+
<div class="w-full">
|
|
43
|
+
<!-- Search Trigger Button -->
|
|
44
|
+
<Button
|
|
45
|
+
ghost
|
|
46
|
+
class="bg-base-100 border border-base-300 w-full justify-start gap-2 font-normal text-base-content/60 hover:text-base-content hover:bg-base-100 hover:border-base-content/20 whitespace-nowrap"
|
|
47
|
+
@click="openModal"
|
|
48
|
+
>
|
|
49
|
+
<Icon name="heroicons:magnifying-glass" class="w-4 h-4" />
|
|
50
|
+
<Text class="grow text-left">Search docs...</Text>
|
|
51
|
+
<Kbd sm class="hidden sm:inline-flex">{{ isMac ? '⌘' : 'Ctrl' }}k</Kbd>
|
|
52
|
+
</Button>
|
|
53
|
+
|
|
54
|
+
<!-- Search Modal -->
|
|
55
|
+
<DocsSearchModal
|
|
56
|
+
v-model="isModalOpen"
|
|
57
|
+
:collections="props.collections"
|
|
58
|
+
:search-label="props.searchLabel"
|
|
59
|
+
:popular-paths="props.popularPaths"
|
|
60
|
+
/>
|
|
61
|
+
</div>
|
|
62
|
+
</template>
|