@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.
Files changed (76) hide show
  1. package/app/assets/icons/feathers.svg +12 -0
  2. package/app/assets/icons/pinion.svg +3 -0
  3. package/app/assets/icons/talon.svg +3 -0
  4. package/app/components/CodePreview.vue +139 -0
  5. package/app/components/CodeSnippet.vue +119 -0
  6. package/app/components/Discord.vue +33 -0
  7. package/app/components/DocsPage.vue +11 -0
  8. package/app/components/DocsSearch.vue +62 -0
  9. package/app/components/DocsSearchModal.vue +831 -0
  10. package/app/components/DocsSidebar.vue +43 -0
  11. package/app/components/DocsTiles.vue +121 -0
  12. package/app/components/FooterMain.vue +46 -0
  13. package/app/components/HeroProduct.vue +148 -0
  14. package/app/components/MoonSurface.vue +1599 -0
  15. package/app/components/NewsletterSubscribe.vue +21 -0
  16. package/app/components/SidebarMenuSection.vue +45 -0
  17. package/app/components/TableOfContents.vue +221 -0
  18. package/app/components/ThemeToggle.vue +10 -0
  19. package/app/components/Titles.vue +39 -0
  20. package/app/components/TocTree.vue +49 -0
  21. package/app/components/content/BlueskyEmbed.vue +92 -0
  22. package/app/components/content/CodeWrapper.vue +10 -0
  23. package/app/components/content/ProseA.vue +19 -0
  24. package/app/components/content/ProseAlert.vue +11 -0
  25. package/app/components/content/ProseBlockquote.vue +11 -0
  26. package/app/components/content/ProseEm.vue +5 -0
  27. package/app/components/content/ProseH1.vue +17 -0
  28. package/app/components/content/ProseH2.vue +17 -0
  29. package/app/components/content/ProseH3.vue +17 -0
  30. package/app/components/content/ProseH4.vue +17 -0
  31. package/app/components/content/ProseH5.vue +17 -0
  32. package/app/components/content/ProseH6.vue +17 -0
  33. package/app/components/content/ProseHr.vue +3 -0
  34. package/app/components/content/ProseImg.vue +37 -0
  35. package/app/components/content/ProseLi.vue +3 -0
  36. package/app/components/content/ProseOl.vue +5 -0
  37. package/app/components/content/ProseP.vue +3 -0
  38. package/app/components/content/ProsePre.vue +49 -0
  39. package/app/components/content/ProsePre2.vue +69 -0
  40. package/app/components/content/ProseScript.vue +37 -0
  41. package/app/components/content/ProseStrong.vue +5 -0
  42. package/app/components/content/ProseTable.vue +7 -0
  43. package/app/components/content/ProseTbody.vue +5 -0
  44. package/app/components/content/ProseTd.vue +5 -0
  45. package/app/components/content/ProseTh.vue +5 -0
  46. package/app/components/content/ProseThead.vue +5 -0
  47. package/app/components/content/ProseTr.vue +5 -0
  48. package/app/components/content/ProseUl.vue +5 -0
  49. package/app/composables/useGlobalSearch.ts +22 -0
  50. package/app/composables/useTheme.ts +17 -0
  51. package/app/layouts/default.vue +46 -0
  52. package/app/layouts/docs.vue +62 -0
  53. package/app/layouts/page.vue +11 -0
  54. package/app/pages/help.vue +19 -0
  55. package/app/pages/privacy.vue +30 -0
  56. package/app/pages/tos.vue +28 -0
  57. package/content/pages/privacy.md +152 -0
  58. package/content/pages/tos.md +133 -0
  59. package/content/products/1-auth.yaml +13 -0
  60. package/content/products/2-feathers.yaml +13 -0
  61. package/content/products/3-pinion.yaml +13 -0
  62. package/content/products/4-daisyui-kit.yaml +14 -0
  63. package/content/products/lofi.yaml +14 -0
  64. package/content.config.ts +36 -0
  65. package/nuxt.config.ts +102 -0
  66. package/package.json +33 -0
  67. package/public/img/bird-comms.png +0 -0
  68. package/public/img/bird-yellow.svg +125 -0
  69. package/public/img/logo-auth-white.svg +22 -0
  70. package/public/img/logos/feathersdev-white.svg +24 -0
  71. package/public/img/logos/talon-auth-white.svg +19 -0
  72. package/public/img/planet-yellow.svg +31 -0
  73. package/public/img/rock-lg.svg +6 -0
  74. package/public/img/rock-md.svg +6 -0
  75. package/public/img/top_background.svg +56 -0
  76. package/vitest.config.ts +7 -0
@@ -0,0 +1,37 @@
1
+ <script setup lang="ts">
2
+ import { withBase } from 'ufo'
3
+ import { computed, useRuntimeConfig } from '#imports'
4
+
5
+ const props = defineProps({
6
+ src: {
7
+ type: String,
8
+ default: '',
9
+ },
10
+ alt: {
11
+ type: String,
12
+ default: '',
13
+ },
14
+ width: {
15
+ type: [String, Number],
16
+ default: undefined,
17
+ },
18
+ height: {
19
+ type: [String, Number],
20
+ default: undefined,
21
+ },
22
+ })
23
+
24
+ const refinedSrc = computed(() => {
25
+ if (props.src?.startsWith('/') && !props.src.startsWith('//'))
26
+ return withBase(props.src, useRuntimeConfig().app.baseURL)
27
+
28
+ return props.src
29
+ })
30
+ </script>
31
+
32
+ <template>
33
+ <div class="prose-img text-center mb-4">
34
+ <img :src="refinedSrc" :alt="alt" :width="width" :height="height" class="mb-1 rounded-md" />
35
+ <small v-if="alt">{{ alt }}</small>
36
+ </div>
37
+ </template>
@@ -0,0 +1,3 @@
1
+ <template>
2
+ <li><slot /></li>
3
+ </template>
@@ -0,0 +1,5 @@
1
+ <template>
2
+ <ol>
3
+ <slot />
4
+ </ol>
5
+ </template>
@@ -0,0 +1,3 @@
1
+ <template>
2
+ <p><slot /></p>
3
+ </template>
@@ -0,0 +1,49 @@
1
+ <script setup lang="ts">
2
+ interface Props {
3
+ code?: string
4
+ language?: string | null
5
+ filename?: string | null
6
+ highlights?: Array<number>
7
+ meta?: string | null
8
+ class?: string
9
+ }
10
+
11
+ const props = withDefaults(defineProps<Props>(), {
12
+ code: '',
13
+ language: null,
14
+ filename: null,
15
+ highlights: () => [],
16
+ meta: null,
17
+ class: ''
18
+ })
19
+
20
+ const copied = ref(false)
21
+
22
+ const copyToClipboard = async () => {
23
+ if (!props.code) return
24
+
25
+ try {
26
+ await navigator.clipboard.writeText(props.code)
27
+ copied.value = true
28
+ setTimeout(() => {
29
+ copied.value = false
30
+ }, 1500)
31
+ } catch (err) {
32
+ console.error('Failed to copy text: ', err)
33
+ }
34
+ }
35
+ </script>
36
+
37
+ <template>
38
+ <div class="my-6">
39
+ <div class="relative">
40
+ <Badge v-if="filename" sm neutral class="absolute top-2.5 left-2 font-mono text-base-content/50">
41
+ {{ filename }}
42
+ </Badge>
43
+ <Button v-if="code" xs neutral class="absolute top-2 right-4 cursor-pointer" @click="copyToClipboard">
44
+ {{ copied ? 'Copied!' : 'Copy' }}
45
+ </Button>
46
+ <pre :class="['overflow-x-auto rounded-lg p-4', filename ? 'pt-12' : '', $props.class]"><slot /></pre>
47
+ </div>
48
+ </div>
49
+ </template>
@@ -0,0 +1,69 @@
1
+ <script setup lang="ts">
2
+ import Button from '@/components/Button.vue'
3
+ import { useClipboard } from '@vueuse/core'
4
+
5
+ const props = defineProps({
6
+ code: {
7
+ type: String,
8
+ default: '',
9
+ },
10
+ language: {
11
+ type: String,
12
+ default: null,
13
+ },
14
+ filename: {
15
+ type: String,
16
+ default: null,
17
+ },
18
+ highlights: {
19
+ type: Array as () => number[],
20
+ default: () => [],
21
+ },
22
+ meta: {
23
+ type: String,
24
+ default: null,
25
+ },
26
+ class: {
27
+ type: String,
28
+ default: null,
29
+ },
30
+ })
31
+
32
+ const { copy, copied } = useClipboard({ source: computed(() => props.code) })
33
+ </script>
34
+
35
+ <template>
36
+ <div>
37
+ <CodeWrapper v-if="meta?.includes('collapse=true')" class="code-wrapper">
38
+ <div class="relative -my-6">
39
+ <div class="absolute flex flex-row items-center top-0 right-0">
40
+ <div v-if="copied" class="pr-2 text-sm text-neutral-content">
41
+ copied
42
+ </div>
43
+ <Button
44
+ ghost sm
45
+ class="text-neutral-content hover:bg-secondary hover:text-secondary-content rounded-tl-none rounded-br-none rounded-tr-md rounded-bl-md"
46
+ @click="() => copy(code)"
47
+ >
48
+ copy
49
+ </Button>
50
+ </div>
51
+ <div class="lg:max-w-[calc(100vw-380px)]">
52
+ <slot />
53
+ </div>
54
+ </div>
55
+ </CodeWrapper>
56
+ <slot v-else />
57
+ </div>
58
+ </template>
59
+
60
+ <style>
61
+ .code-wrapper pre code .line {
62
+ display: block;
63
+ min-height: 1rem;
64
+ }
65
+
66
+ .code-wrapper pre {
67
+ max-width: calc(100vw - 3.5rem);
68
+ }
69
+ </style>
@@ -0,0 +1,37 @@
1
+ <script setup lang="ts">
2
+ interface Props {
3
+ src?: string
4
+ async?: boolean
5
+ defer?: boolean
6
+ type?: string
7
+ }
8
+
9
+ const props = defineProps<Props>()
10
+
11
+ // Only allow specific whitelisted scripts for security
12
+ const allowedScripts = [
13
+ 'https://embed.bsky.app/static/embed.js'
14
+ ]
15
+
16
+ const isAllowed = props.src && allowedScripts.includes(props.src)
17
+
18
+ if (isAllowed && props.src) {
19
+ useHead({
20
+ script: [
21
+ {
22
+ src: props.src,
23
+ async: props.async ?? true,
24
+ defer: props.defer,
25
+ type: props.type || 'text/javascript',
26
+ }
27
+ ]
28
+ })
29
+ }
30
+ </script>
31
+
32
+ <template>
33
+ <!-- Empty template - script is loaded via useHead -->
34
+ <div v-if="!isAllowed && src" class="border border-yellow-200 rounded-lg p-4 bg-yellow-50 text-yellow-800">
35
+ <p class="text-sm">Script from "{{ src }}" is not in the allowlist and cannot be loaded.</p>
36
+ </div>
37
+ </template>
@@ -0,0 +1,5 @@
1
+ <template>
2
+ <strong>
3
+ <slot />
4
+ </strong>
5
+ </template>
@@ -0,0 +1,7 @@
1
+ <template>
2
+ <div class="not-prose relative mb-10 mt-6 overflow-x-auto">
3
+ <table class="table table-sm">
4
+ <slot />
5
+ </table>
6
+ </div>
7
+ </template>
@@ -0,0 +1,5 @@
1
+ <template>
2
+ <tbody>
3
+ <slot />
4
+ </tbody>
5
+ </template>
@@ -0,0 +1,5 @@
1
+ <template>
2
+ <td>
3
+ <slot />
4
+ </td>
5
+ </template>
@@ -0,0 +1,5 @@
1
+ <template>
2
+ <th>
3
+ <slot />
4
+ </th>
5
+ </template>
@@ -0,0 +1,5 @@
1
+ <template>
2
+ <thead>
3
+ <slot />
4
+ </thead>
5
+ </template>
@@ -0,0 +1,5 @@
1
+ <template>
2
+ <tr class="hover">
3
+ <slot />
4
+ </tr>
5
+ </template>
@@ -0,0 +1,5 @@
1
+ <template>
2
+ <ul>
3
+ <slot />
4
+ </ul>
5
+ </template>
@@ -0,0 +1,22 @@
1
+ const isSearchOpen = ref(false)
2
+
3
+ export function useGlobalSearch() {
4
+ function openSearch() {
5
+ isSearchOpen.value = true
6
+ }
7
+
8
+ function closeSearch() {
9
+ isSearchOpen.value = false
10
+ }
11
+
12
+ function toggleSearch() {
13
+ isSearchOpen.value = !isSearchOpen.value
14
+ }
15
+
16
+ return {
17
+ isSearchOpen,
18
+ openSearch,
19
+ closeSearch,
20
+ toggleSearch,
21
+ }
22
+ }
@@ -0,0 +1,17 @@
1
+ export function useTheme() {
2
+ const colorMode = useCookie<'light' | 'dark'>('color-mode', {
3
+ default: () => 'light',
4
+ })
5
+
6
+ const isDark = computed(() => colorMode.value === 'dark')
7
+
8
+ function toggleTheme() {
9
+ colorMode.value = isDark.value ? 'light' : 'dark'
10
+ }
11
+
12
+ return {
13
+ isDark,
14
+ toggleTheme,
15
+ colorMode,
16
+ }
17
+ }
@@ -0,0 +1,46 @@
1
+ <template>
2
+ <Drawer v-slot="{ openDrawer }" name="docs" class="lg:drawer-open">
3
+ <!-- Static sidebar for desktop -->
4
+ <Sidebar />
5
+
6
+ <DrawerContent name="docs">
7
+ <Flex class="sticky top-0 h-16 p-2 md:px-4 z-10 w-full items-center">
8
+ <div class="grow">
9
+ <Button square class="lg:hidden" @click="() => openDrawer()">
10
+ <span class="sr-only">Open sidebar</span>
11
+ <Icon name="heroicons-outline:menu-alt-2" aria-hidden="true" class="w-5 h-5" />
12
+ </Button>
13
+
14
+ <Search />
15
+ </div>
16
+
17
+ <div class="flex items-center ml-2 md:ml-6">
18
+ <ThemePicker />
19
+ </div>
20
+ </Flex>
21
+
22
+ <div class="grid grid-cols-4 pb-32">
23
+ <div class="flex flex-col col-span-4 md:col-span-3 order-2 md:order-1">
24
+ <Prose class="px-3 lg:px-6 pt-6">
25
+ <slot />
26
+ </Prose>
27
+ </div>
28
+ <div id="sidebar" class="col-span-4 md:col-span-1 md:pr-6 order-1 md:order-2">
29
+ <div class="hidden md:block md:sticky top-16">
30
+ <TableOfContents class="overflow-hidden" />
31
+ </div>
32
+
33
+ <Dropdown end class="z-20 md:hidden fixed right-2" close-on-click-outside>
34
+ <DropdownButton square>
35
+ <!-- Table of contents icon -->
36
+ <Icon name="heroicons-outline:menu-alt-3" class="w-5 h-5 pointer-events-none" aria-hidden="true" />
37
+ </DropdownButton>
38
+ <DropdownContent class="w-80 z-10 mt-1 max-h-[80vh] overflow-y-auto rounded-lg shadow-xl">
39
+ <TableOfContents class="bg-base-200" />
40
+ </DropdownContent>
41
+ </Dropdown>
42
+ </div>
43
+ </div>
44
+ </DrawerContent>
45
+ </Drawer>
46
+ </template>
@@ -0,0 +1,62 @@
1
+ <script lang="ts" setup>
2
+ const dropdownId = useId()
3
+ </script>
4
+
5
+ <template>
6
+ <div class="bg-base-200">
7
+ <div class="relative mx-auto max-w-328">
8
+ <TopNav class="mx-auto max-w-328 border-b border-b-base-100/20" />
9
+
10
+ <Drawer v-slot="{ toggleDrawer }" name="docs" class="lg:drawer-open">
11
+ <DrawerSide name="docs" class="z-40">
12
+ <DocsSidebar @close="toggleDrawer()" />
13
+ </DrawerSide>
14
+
15
+ <DrawerContent name="docs">
16
+ <Flex class="sticky top-0 h-16 p-2 md:px-4 z-10 w-full items-center lg:hidden">
17
+ <div class="grow flex items-center gap-2">
18
+ <Button square class="lg:hidden" @click="() => toggleDrawer()">
19
+ <span class="sr-only">Open sidebar</span>
20
+ <Icon name="heroicons-outline:menu-alt-2" aria-hidden="true" class="w-5 h-5" />
21
+ </Button>
22
+
23
+ <DocsSearch class="grow" />
24
+ </div>
25
+
26
+ <div class="flex items-center ml-2 md:ml-6">
27
+ <!-- <ThemePicker /> -->
28
+
29
+ <Dropdown
30
+ strategy="fixed"
31
+ placement="bottom-end"
32
+ class="z-20 md:hidden"
33
+ :random-id="dropdownId"
34
+ close-on-click-outside
35
+ >
36
+ <DropdownButton square>
37
+ <Icon name="heroicons-outline:menu-alt-3" class="w-5 h-5 pointer-events-none" aria-hidden="true" />
38
+ </DropdownButton>
39
+ <DropdownContent class="w-80 z-10 mt-1 max-h-[80vh] overflow-y-auto rounded-lg shadow-xl">
40
+ <TableOfContents class="bg-base-300" />
41
+ </DropdownContent>
42
+ </Dropdown>
43
+ </div>
44
+ </Flex>
45
+
46
+ <div class="grid grid-cols-4 pb-32">
47
+ <div class="flex flex-col col-span-4 md:col-span-3 overflow-hidden">
48
+ <NuxtPage />
49
+ </div>
50
+ <div id="right-sidebar" class="col-span-1 md:pr-6">
51
+ <div class="hidden md:block md:sticky top-16">
52
+ <TableOfContents class="overflow-hidden" />
53
+ </div>
54
+ </div>
55
+ </div>
56
+ </DrawerContent>
57
+ </Drawer>
58
+ </div>
59
+ </div>
60
+ <div class="mx-auto max-w-378 bg-base-200 rounded-b-xl h-12 mb-24" />
61
+ <FooterMain />
62
+ </template>
@@ -0,0 +1,11 @@
1
+ <template>
2
+ <main class="relative w-full min-h-full bg-base-200">
3
+ <div class="absolute w-full z-50">
4
+ <TopNav class="max-w-[82rem] mx-auto" />
5
+ </div>
6
+ <div class="relative pt-0">
7
+ <NuxtPage />
8
+ </div>
9
+ <FooterMain />
10
+ </main>
11
+ </template>
@@ -0,0 +1,19 @@
1
+ <script lang="ts" setup>
2
+ definePageMeta({
3
+ layout: 'page',
4
+ })
5
+ </script>
6
+
7
+ <template>
8
+ <div
9
+ class="bg-[url('/img/top_background.svg')] bg-no-repeat bg-cover bg-center text-base-content max-w-screen overflow-x-hidden"
10
+ >
11
+ <div class="relative mx-auto max-w-[82rem] lg:drawer-open pt-16 px-4">
12
+ <Titles title="Help Center" sub-title="Find answers to your questions" class="py-24" />
13
+ </div>
14
+ <div class="h-64"></div>
15
+ </div>
16
+ <div class="bg-base-200 min-h-screen max-w-[82rem] mx-auto -mt-64 rounded-4xl p-6 pt-12 lg:p-12">
17
+ <Discord />
18
+ </div>
19
+ </template>
@@ -0,0 +1,30 @@
1
+ <script setup lang="ts">
2
+ definePageMeta({
3
+ layout: 'page',
4
+ })
5
+
6
+ const { data: page } = await useAsyncData(() =>
7
+ queryCollection('sharedPages').where('path', '=', '/pages/privacy').first()
8
+ )
9
+
10
+ useSeoMeta({
11
+ title: page.value?.title,
12
+ description: page.value?.description,
13
+ })
14
+ </script>
15
+
16
+ <template>
17
+ <div
18
+ class="bg-[url('/img/top_background.svg')] bg-no-repeat bg-cover bg-center text-base-content max-w-screen overflow-x-hidden"
19
+ >
20
+ <div class="relative mx-auto max-w-[82rem] lg:drawer-open pt-16 px-4">
21
+ <Titles v-if="page" :title="page.title" class="py-24" />
22
+ </div>
23
+ <div class="h-64"></div>
24
+ </div>
25
+ <div class="bg-base-200 min-h-screen max-w-[82rem] mx-auto -mt-64 rounded-4xl p-6 pt-12 lg:p-12">
26
+ <div v-if="page" class="prose mx-auto">
27
+ <ContentRenderer :value="page" />
28
+ </div>
29
+ </div>
30
+ </template>
@@ -0,0 +1,28 @@
1
+ <script setup lang="ts">
2
+ definePageMeta({
3
+ layout: 'page',
4
+ })
5
+
6
+ const { data: page } = await useAsyncData(() => queryCollection('sharedPages').where('path', '=', '/pages/tos').first())
7
+
8
+ useSeoMeta({
9
+ title: page.value?.title,
10
+ description: page.value?.description,
11
+ })
12
+ </script>
13
+
14
+ <template>
15
+ <div
16
+ class="bg-[url('/img/top_background.svg')] bg-no-repeat bg-cover bg-center text-base-content max-w-screen overflow-x-hidden"
17
+ >
18
+ <div class="relative mx-auto max-w-[82rem] lg:drawer-open pt-16 px-4">
19
+ <Titles v-if="page" :title="page.title" class="py-24" />
20
+ </div>
21
+ <div class="h-64"></div>
22
+ </div>
23
+ <div class="bg-base-200 min-h-screen max-w-[82rem] mx-auto -mt-64 rounded-4xl p-6 pt-12 lg:p-12">
24
+ <div v-if="page" class="prose mx-auto">
25
+ <ContentRenderer :value="page" />
26
+ </div>
27
+ </div>
28
+ </template>