@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,21 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<Titles title="Subscribe to our Newsletter" description="Get new Feathers content as it becomes available." />
|
|
4
|
+
|
|
5
|
+
<form
|
|
6
|
+
action="https://buttondown.email/api/emails/embed-subscribe/feathers"
|
|
7
|
+
method="post"
|
|
8
|
+
target="popupwindow"
|
|
9
|
+
onsubmit="window.open('https://buttondown.email/feathers', 'popupwindow')"
|
|
10
|
+
class="embeddable-buttondown-form flex flex-col"
|
|
11
|
+
>
|
|
12
|
+
<Join class="join-vertical md:join-horizontal mt-8">
|
|
13
|
+
<Label xl join floating input class="bg-white/10 py-3 px-3 w-full">
|
|
14
|
+
<Text>Enter your email</Text>
|
|
15
|
+
<Input id="bd-email" type="email" name="email" placeholder="Enter your email" />
|
|
16
|
+
</Label>
|
|
17
|
+
<Button join primary type="submit" value="Subscribe" name="Submit" size="xl"> Subscribe </Button>
|
|
18
|
+
</Join>
|
|
19
|
+
</form>
|
|
20
|
+
</div>
|
|
21
|
+
</template>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { MenuItem } from '../../content.config.schema'
|
|
3
|
+
|
|
4
|
+
defineProps<{
|
|
5
|
+
section?: MenuItem
|
|
6
|
+
}>()
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<template>
|
|
10
|
+
<Menu class="w-full">
|
|
11
|
+
<li v-if="!section?.noDivider" class="mx-0" />
|
|
12
|
+
<MenuTitle v-if="section && !section.path" class="flex flex-row items-center gap-3">
|
|
13
|
+
<Icon v-if="section.icon" :name="section.icon" :class="section.iconClasses" />
|
|
14
|
+
{{ section.title }}
|
|
15
|
+
</MenuTitle>
|
|
16
|
+
|
|
17
|
+
<template v-for="link in section?.children" :key="link.title">
|
|
18
|
+
<!-- Menu item with children - render submenu -->
|
|
19
|
+
<MenuItem v-if="link.children">
|
|
20
|
+
<MenuExpand :close-on-click-outside="false">
|
|
21
|
+
<MenuExpandToggle class="flex flex-row items-center">
|
|
22
|
+
<Icon v-if="link.icon" :name="link.icon" class="w-5 h-5 mr-1" />
|
|
23
|
+
<Text class="grow">
|
|
24
|
+
{{ link.title }}
|
|
25
|
+
</Text>
|
|
26
|
+
<Badge v-if="link.meta?.new" sm accent class="ml-2"> new </Badge>
|
|
27
|
+
</MenuExpandToggle>
|
|
28
|
+
<!-- Recursively render child menu -->
|
|
29
|
+
<SidebarMenuSection :children="link.children" :no-divider="true" />
|
|
30
|
+
</MenuExpand>
|
|
31
|
+
</MenuItem>
|
|
32
|
+
|
|
33
|
+
<!-- Regular menu item without children -->
|
|
34
|
+
<MenuItem v-else>
|
|
35
|
+
<NuxtLink :to="link.path" exact-active-class="menu-active" class="flex flex-row items-center">
|
|
36
|
+
<Icon v-if="link.icon" :name="link.icon" class="w-5 h-5 mr-1" />
|
|
37
|
+
<Text class="grow">
|
|
38
|
+
{{ link.title }}
|
|
39
|
+
</Text>
|
|
40
|
+
<Badge v-if="link.meta?.new" sm accent class="ml-2"> new </Badge>
|
|
41
|
+
</NuxtLink>
|
|
42
|
+
</MenuItem>
|
|
43
|
+
</template>
|
|
44
|
+
</Menu>
|
|
45
|
+
</template>
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import TocTree from './TocTree.vue'
|
|
3
|
+
|
|
4
|
+
const pageRoute = useRoute()
|
|
5
|
+
|
|
6
|
+
// Interface for our TOC structure
|
|
7
|
+
interface TocLink {
|
|
8
|
+
id: string
|
|
9
|
+
text: string
|
|
10
|
+
level: number
|
|
11
|
+
children: TocLink[]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Create a reactive reference for our TOC links
|
|
15
|
+
const tocLinks = ref<TocLink[]>([])
|
|
16
|
+
const pageTitle = ref('Table of Contents')
|
|
17
|
+
|
|
18
|
+
// Function to scan the page for headings and build the TOC
|
|
19
|
+
function scanHeadings() {
|
|
20
|
+
// Wait for the DOM to be ready
|
|
21
|
+
if (typeof document === 'undefined') {
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Find all headings (h1-h6) with IDs
|
|
26
|
+
const headings = document.querySelectorAll('h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]')
|
|
27
|
+
const links: TocLink[] = []
|
|
28
|
+
|
|
29
|
+
// Get the page title from the first h1
|
|
30
|
+
const h1 = document.querySelector('h1')
|
|
31
|
+
if (h1) {
|
|
32
|
+
pageTitle.value = h1.textContent || 'Table of Contents'
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// If no headings found, it might mean content hasn't loaded yet
|
|
36
|
+
// But also check if we have at least an h1 to ensure page content exists
|
|
37
|
+
if (headings.length === 0 && !h1) {
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Process each heading
|
|
42
|
+
headings.forEach((heading) => {
|
|
43
|
+
// Skip the first h1 as it's the page title
|
|
44
|
+
if (heading.tagName === 'H1' && heading === h1) {
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const id = heading.id
|
|
49
|
+
const level = Number.parseInt(heading.tagName.substring(1), 10)
|
|
50
|
+
|
|
51
|
+
// Create a TOC link
|
|
52
|
+
const link: TocLink = { id, text: heading.textContent || '', level, children: [] }
|
|
53
|
+
links.push(link)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
// Build a robust hierarchical structure in a single pass
|
|
57
|
+
const hierarchicalLinks: TocLink[] = []
|
|
58
|
+
const parents: TocLink[] = []
|
|
59
|
+
|
|
60
|
+
for (const link of links) {
|
|
61
|
+
// Always ensure children is initialized
|
|
62
|
+
if (!('children' in link) || !Array.isArray(link.children)) {
|
|
63
|
+
(link as TocLink).children = []
|
|
64
|
+
}
|
|
65
|
+
// Find the last parent of lower level
|
|
66
|
+
while (parents.length > 0 && parents[parents.length - 1]!.level >= link.level) {
|
|
67
|
+
parents.pop()
|
|
68
|
+
}
|
|
69
|
+
if (parents.length === 0) {
|
|
70
|
+
hierarchicalLinks.push(link)
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
parents[parents.length - 1]!.children.push(link)
|
|
74
|
+
}
|
|
75
|
+
parents.push(link)
|
|
76
|
+
}
|
|
77
|
+
tocLinks.value = hierarchicalLinks
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Check if we're in the browser
|
|
81
|
+
let observer: MutationObserver | null = null
|
|
82
|
+
|
|
83
|
+
onMounted(() => {
|
|
84
|
+
// Initial scan with a small delay to ensure content is rendered
|
|
85
|
+
setTimeout(() => {
|
|
86
|
+
scanHeadings()
|
|
87
|
+
}, 100)
|
|
88
|
+
|
|
89
|
+
// Re-scan when route changes (for SPA navigation)
|
|
90
|
+
watch(() => pageRoute.path, () => {
|
|
91
|
+
// Clear current TOC immediately when route changes
|
|
92
|
+
tocLinks.value = []
|
|
93
|
+
pageTitle.value = 'Table of Contents'
|
|
94
|
+
|
|
95
|
+
// Use multiple strategies to ensure content is fully loaded
|
|
96
|
+
nextTick(() => {
|
|
97
|
+
// First attempt after nextTick
|
|
98
|
+
scanHeadings()
|
|
99
|
+
|
|
100
|
+
// Second attempt with a delay to handle slow rendering
|
|
101
|
+
setTimeout(() => {
|
|
102
|
+
scanHeadings()
|
|
103
|
+
}, 200)
|
|
104
|
+
|
|
105
|
+
// Third attempt with a longer delay as fallback
|
|
106
|
+
setTimeout(() => {
|
|
107
|
+
scanHeadings()
|
|
108
|
+
}, 500)
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
// Also watch for hash changes to update active states
|
|
113
|
+
watch(() => pageRoute.hash, () => {
|
|
114
|
+
// No need to rescan, just update active states
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
// Watch for DOM mutations affecting headings
|
|
118
|
+
observer = new MutationObserver((mutations) => {
|
|
119
|
+
let shouldRescan = false
|
|
120
|
+
for (const mutation of mutations) {
|
|
121
|
+
if (
|
|
122
|
+
Array.from(mutation.addedNodes).some(isHeadingNode)
|
|
123
|
+
|| Array.from(mutation.removedNodes).some(isHeadingNode)
|
|
124
|
+
|| (mutation.type === 'attributes' && isHeadingNode(mutation.target))
|
|
125
|
+
) {
|
|
126
|
+
shouldRescan = true
|
|
127
|
+
break
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (shouldRescan) {
|
|
131
|
+
// Add a small delay to allow DOM to stabilize
|
|
132
|
+
setTimeout(() => {
|
|
133
|
+
scanHeadings()
|
|
134
|
+
}, 50)
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
observer.observe(document.body, {
|
|
138
|
+
childList: true,
|
|
139
|
+
subtree: true,
|
|
140
|
+
attributes: true,
|
|
141
|
+
attributeFilter: ['id'],
|
|
142
|
+
})
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
onBeforeUnmount(() => {
|
|
146
|
+
if (observer) {
|
|
147
|
+
observer.disconnect()
|
|
148
|
+
observer = null
|
|
149
|
+
}
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
function isHeadingNode(node: Node | EventTarget): boolean {
|
|
153
|
+
if (!(node instanceof HTMLElement)) {
|
|
154
|
+
return false
|
|
155
|
+
}
|
|
156
|
+
return /^H[1-6]$/.test(node.tagName)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function checkActive(hash: string) {
|
|
160
|
+
return pageRoute.hash === hash ? 'active' : null
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function toTop() {
|
|
164
|
+
window.scrollTo({
|
|
165
|
+
top: 0,
|
|
166
|
+
behavior: 'smooth',
|
|
167
|
+
})
|
|
168
|
+
setTimeout(() => {
|
|
169
|
+
window.location.hash = ''
|
|
170
|
+
}, 600)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function toBottom() {
|
|
174
|
+
window.scrollTo({
|
|
175
|
+
top: document.body.scrollHeight,
|
|
176
|
+
behavior: 'smooth',
|
|
177
|
+
})
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function scrollToHeading(id: string) {
|
|
181
|
+
const el = document.getElementById(id)
|
|
182
|
+
if (el) {
|
|
183
|
+
el.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
|
184
|
+
// Update the hash after the scroll
|
|
185
|
+
history.replaceState(null, '', `#${id}`)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
</script>
|
|
189
|
+
|
|
190
|
+
<template>
|
|
191
|
+
<Flex col>
|
|
192
|
+
<Menu v-if="tocLinks.length" class="md:menu-sm xl:menu-md w-full">
|
|
193
|
+
<NuxtLink href="#" custom>
|
|
194
|
+
<template #default="{ route }">
|
|
195
|
+
<MenuItem class="text-base-content cursor-pointer">
|
|
196
|
+
<a
|
|
197
|
+
class="flex flex-row items-center justify-between font-semibold bg-neutral/30"
|
|
198
|
+
:class="checkActive(route?.hash || '')" @click="toTop"
|
|
199
|
+
>
|
|
200
|
+
{{ pageTitle }}
|
|
201
|
+
<Icon name="feather:arrow-up" class="text-base" />
|
|
202
|
+
</a>
|
|
203
|
+
</MenuItem>
|
|
204
|
+
</template>
|
|
205
|
+
</NuxtLink>
|
|
206
|
+
|
|
207
|
+
<TocTree
|
|
208
|
+
:links="tocLinks"
|
|
209
|
+
:check-active="checkActive"
|
|
210
|
+
:scroll-to-heading="scrollToHeading"
|
|
211
|
+
/>
|
|
212
|
+
|
|
213
|
+
<MenuItem>
|
|
214
|
+
<a class="flex flex-row items-center justify-between hover:bg-accent/25 mt-6" @click="toBottom">
|
|
215
|
+
scroll to bottom
|
|
216
|
+
<Icon name="feather:arrow-down" class="text-base" />
|
|
217
|
+
</a>
|
|
218
|
+
</MenuItem>
|
|
219
|
+
</Menu>
|
|
220
|
+
</Flex>
|
|
221
|
+
</template>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const { isDark, toggleTheme } = useTheme()
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<template>
|
|
6
|
+
<Button ghost circle aria-label="Toggle theme" @click="toggleTheme">
|
|
7
|
+
<Icon v-if="isDark" name="heroicons:moon" size="24" />
|
|
8
|
+
<Icon v-else name="heroicons:sun" size="24" />
|
|
9
|
+
</Button>
|
|
10
|
+
</template>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
defineProps<{
|
|
3
|
+
superTitle?: string
|
|
4
|
+
title: string,
|
|
5
|
+
subTitle?: string
|
|
6
|
+
description?: string
|
|
7
|
+
}>()
|
|
8
|
+
|
|
9
|
+
const slots = defineSlots<{
|
|
10
|
+
superTitle?: string
|
|
11
|
+
title: string,
|
|
12
|
+
subTitle?: string
|
|
13
|
+
description?: string
|
|
14
|
+
}>()
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<template>
|
|
18
|
+
<Flex col items-center class="gap-4">
|
|
19
|
+
<slot v-if="slots.superTitle" name="superTitle" />
|
|
20
|
+
<Text v-if="superTitle && !slots.superTitle" center size="md" class="text-balance md:text-lg">
|
|
21
|
+
{{ superTitle }}
|
|
22
|
+
</Text>
|
|
23
|
+
|
|
24
|
+
<slot v-if="slots.title" name="title" />
|
|
25
|
+
<Text v-if="title && !slots.title" center size="4xl" class="text-balance md:text-5xl">
|
|
26
|
+
{{ title }}
|
|
27
|
+
</Text>
|
|
28
|
+
|
|
29
|
+
<slot v-if="slots.subTitle" name="subTitle" />
|
|
30
|
+
<Text v-if="subTitle && !slots.subTitle" center size="md" class="text-balance md:text-lg">
|
|
31
|
+
{{ subTitle }}
|
|
32
|
+
</Text>
|
|
33
|
+
|
|
34
|
+
<slot v-if="slots.description" name="description" />
|
|
35
|
+
<Text v-if="description && !slots.description" center size="md" class="text-balance md:text-lg">
|
|
36
|
+
{{ description }}
|
|
37
|
+
</Text>
|
|
38
|
+
</Flex>
|
|
39
|
+
</template>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { PropType } from 'vue'
|
|
3
|
+
|
|
4
|
+
interface TocLink {
|
|
5
|
+
id: string
|
|
6
|
+
text: string
|
|
7
|
+
level: number
|
|
8
|
+
children: TocLink[]
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
defineProps({
|
|
12
|
+
links: {
|
|
13
|
+
type: Array as PropType<TocLink[]>,
|
|
14
|
+
required: true
|
|
15
|
+
},
|
|
16
|
+
checkActive: {
|
|
17
|
+
type: Function as PropType<(hash: string) => string | null>,
|
|
18
|
+
required: true
|
|
19
|
+
},
|
|
20
|
+
scrollToHeading: {
|
|
21
|
+
type: Function as PropType<(id: string) => void>,
|
|
22
|
+
required: true
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<template>
|
|
28
|
+
<Menu v-if="links && links.length" class="w-full">
|
|
29
|
+
<MenuItem v-for="link in links" :key="link.id">
|
|
30
|
+
<NuxtLink :href="`#${link.id}`" custom>
|
|
31
|
+
<template #default="{ route }">
|
|
32
|
+
<a
|
|
33
|
+
class="block px-2 py-1 text-base-content hover:bg-accent/10 rounded cursor-pointer"
|
|
34
|
+
:class="checkActive(route?.hash || '')"
|
|
35
|
+
@click.prevent="scrollToHeading(link.id)"
|
|
36
|
+
>
|
|
37
|
+
{{ link.text }}
|
|
38
|
+
</a>
|
|
39
|
+
</template>
|
|
40
|
+
</NuxtLink>
|
|
41
|
+
<TocTree
|
|
42
|
+
v-if="link.children && link.children.length"
|
|
43
|
+
:links="link.children"
|
|
44
|
+
:check-active="checkActive"
|
|
45
|
+
:scroll-to-heading="scrollToHeading"
|
|
46
|
+
/>
|
|
47
|
+
</MenuItem>
|
|
48
|
+
</Menu>
|
|
49
|
+
</template>
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
uri: string
|
|
4
|
+
cid: string
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const props = defineProps<Props>()
|
|
8
|
+
const embedContainer = ref<HTMLElement | null>(null)
|
|
9
|
+
|
|
10
|
+
// Extract the post URL from the AT URI
|
|
11
|
+
// Format: at://did:plc:xxx/app.bsky.feed.post/postId
|
|
12
|
+
const getPostUrl = () => {
|
|
13
|
+
const parts = props.uri.split('/')
|
|
14
|
+
const did = parts[2]
|
|
15
|
+
const postId = parts[parts.length - 1]
|
|
16
|
+
return `https://bsky.app/profile/${did}/post/${postId}`
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const postUrl = getPostUrl()
|
|
20
|
+
|
|
21
|
+
onMounted(() => {
|
|
22
|
+
// Load the Bluesky embed script
|
|
23
|
+
const scriptId = 'bluesky-embed-script'
|
|
24
|
+
|
|
25
|
+
const initializeEmbed = () => {
|
|
26
|
+
// Wait a bit longer and check multiple times
|
|
27
|
+
let attempts = 0
|
|
28
|
+
const maxAttempts = 10
|
|
29
|
+
|
|
30
|
+
const checkAndScan = () => {
|
|
31
|
+
attempts++
|
|
32
|
+
|
|
33
|
+
if ((window as any).bluesky?.scan) {
|
|
34
|
+
console.log('Bluesky scan function found, triggering...')
|
|
35
|
+
;(window as any).bluesky.scan()
|
|
36
|
+
} else if (attempts < maxAttempts) {
|
|
37
|
+
console.log(`Waiting for Bluesky script... attempt ${attempts}`)
|
|
38
|
+
setTimeout(checkAndScan, 200)
|
|
39
|
+
} else {
|
|
40
|
+
console.error('Bluesky embed script did not load properly')
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
setTimeout(checkAndScan, 100)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!document.getElementById(scriptId)) {
|
|
48
|
+
const script = document.createElement('script')
|
|
49
|
+
script.id = scriptId
|
|
50
|
+
script.src = 'https://embed.bsky.app/static/embed.js'
|
|
51
|
+
script.async = true
|
|
52
|
+
script.charset = 'utf-8'
|
|
53
|
+
|
|
54
|
+
script.onload = () => {
|
|
55
|
+
console.log('Bluesky embed script loaded')
|
|
56
|
+
initializeEmbed()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
script.onerror = () => {
|
|
60
|
+
console.error('Failed to load Bluesky embed script')
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
document.head.appendChild(script)
|
|
64
|
+
} else {
|
|
65
|
+
initializeEmbed()
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
</script>
|
|
69
|
+
|
|
70
|
+
<template>
|
|
71
|
+
<ClientOnly>
|
|
72
|
+
<div class="bluesky-embed-wrapper my-6 rounded-lg" ref="embedContainer">
|
|
73
|
+
<blockquote
|
|
74
|
+
class="bluesky-embed"
|
|
75
|
+
:data-bluesky-uri="uri"
|
|
76
|
+
:data-bluesky-cid="cid"
|
|
77
|
+
data-bluesky-embed-color-mode="dark"
|
|
78
|
+
>
|
|
79
|
+
<p lang="en">
|
|
80
|
+
Loading Bluesky post...
|
|
81
|
+
<a :href="postUrl" target="_blank" rel="noopener noreferrer">View on Bluesky</a>
|
|
82
|
+
</p>
|
|
83
|
+
</blockquote>
|
|
84
|
+
</div>
|
|
85
|
+
</ClientOnly>
|
|
86
|
+
</template>
|
|
87
|
+
|
|
88
|
+
<style scoped>
|
|
89
|
+
.bluesky-embed-wrapper :deep(iframe) {
|
|
90
|
+
border-radius: 0.7rem !important;
|
|
91
|
+
}
|
|
92
|
+
</style>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Collapse arrow toggle class="mb-8 rounded-b-xl rounded-t-none bg-base-200 p-0">
|
|
3
|
+
<CollapseTitle class="text-lg font-medium">
|
|
4
|
+
Code Example
|
|
5
|
+
</CollapseTitle>
|
|
6
|
+
<CollapseContent class="overflow-hidden">
|
|
7
|
+
<slot />
|
|
8
|
+
</CollapseContent>
|
|
9
|
+
</Collapse>
|
|
10
|
+
</template>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
defineProps({
|
|
3
|
+
href: {
|
|
4
|
+
type: String,
|
|
5
|
+
default: '',
|
|
6
|
+
},
|
|
7
|
+
target: {
|
|
8
|
+
type: String,
|
|
9
|
+
default: undefined,
|
|
10
|
+
required: false,
|
|
11
|
+
},
|
|
12
|
+
})
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<template>
|
|
16
|
+
<NuxtLink :href="href" :target="target" class="daisy-link link-primary">
|
|
17
|
+
<slot />
|
|
18
|
+
</NuxtLink>
|
|
19
|
+
</template>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, useRuntimeConfig } from '#imports';
|
|
3
|
+
|
|
4
|
+
const props = defineProps<{ id?: string }>()
|
|
5
|
+
|
|
6
|
+
const { headings } = useRuntimeConfig().public.mdc
|
|
7
|
+
const generate = computed(() => props.id && ((typeof headings?.anchorLinks === 'boolean' && headings?.anchorLinks === true) || (typeof headings?.anchorLinks === 'object' && headings?.anchorLinks?.h1)))
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<h1 :id="props.id">
|
|
12
|
+
<a v-if="id || generate" :href="`#${props.id}`">
|
|
13
|
+
<slot />
|
|
14
|
+
</a>
|
|
15
|
+
<slot v-else />
|
|
16
|
+
</h1>
|
|
17
|
+
</template>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, useRuntimeConfig } from '#imports';
|
|
3
|
+
|
|
4
|
+
const props = defineProps<{ id?: string }>()
|
|
5
|
+
|
|
6
|
+
const { headings } = useRuntimeConfig().public.mdc
|
|
7
|
+
const generate = computed(() => props.id && ((typeof headings?.anchorLinks === 'boolean' && headings?.anchorLinks === true) || (typeof headings?.anchorLinks === 'object' && headings?.anchorLinks?.h2)))
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<h2 :id="props.id">
|
|
12
|
+
<a v-if="id || generate" :href="`#${props.id}`">
|
|
13
|
+
<slot />
|
|
14
|
+
</a>
|
|
15
|
+
<slot v-else />
|
|
16
|
+
</h2>
|
|
17
|
+
</template>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, useRuntimeConfig } from '#imports';
|
|
3
|
+
|
|
4
|
+
const props = defineProps<{ id?: string }>()
|
|
5
|
+
|
|
6
|
+
const { headings } = useRuntimeConfig().public.mdc
|
|
7
|
+
const generate = computed(() => props.id && ((typeof headings?.anchorLinks === 'boolean' && headings?.anchorLinks === true) || (typeof headings?.anchorLinks === 'object' && headings?.anchorLinks?.h3)))
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<h3 :id="props.id">
|
|
12
|
+
<a v-if="id || generate" :href="`#${props.id}`">
|
|
13
|
+
<slot />
|
|
14
|
+
</a>
|
|
15
|
+
<slot v-else />
|
|
16
|
+
</h3>
|
|
17
|
+
</template>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, useRuntimeConfig } from '#imports';
|
|
3
|
+
|
|
4
|
+
const props = defineProps<{ id?: string }>()
|
|
5
|
+
|
|
6
|
+
const { headings } = useRuntimeConfig().public.mdc
|
|
7
|
+
const generate = computed(() => props.id && ((typeof headings?.anchorLinks === 'boolean' && headings?.anchorLinks === true) || (typeof headings?.anchorLinks === 'object' && headings?.anchorLinks?.h4)))
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<h4 :id="props.id">
|
|
12
|
+
<a v-if="id || generate" :href="`#${props.id}`">
|
|
13
|
+
<slot />
|
|
14
|
+
</a>
|
|
15
|
+
<slot v-else />
|
|
16
|
+
</h4>
|
|
17
|
+
</template>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, useRuntimeConfig } from '#imports';
|
|
3
|
+
|
|
4
|
+
const props = defineProps<{ id?: string }>()
|
|
5
|
+
|
|
6
|
+
const { headings } = useRuntimeConfig().public.mdc
|
|
7
|
+
const generate = computed(() => props.id && ((typeof headings?.anchorLinks === 'boolean' && headings?.anchorLinks === true) || (typeof headings?.anchorLinks === 'object' && headings?.anchorLinks?.h5)))
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<h5 :id="props.id">
|
|
12
|
+
<a v-if="id || generate" :href="`#${props.id}`">
|
|
13
|
+
<slot />
|
|
14
|
+
</a>
|
|
15
|
+
<slot v-else />
|
|
16
|
+
</h5>
|
|
17
|
+
</template>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, useRuntimeConfig } from '#imports';
|
|
3
|
+
|
|
4
|
+
const props = defineProps<{ id?: string }>()
|
|
5
|
+
|
|
6
|
+
const { headings } = useRuntimeConfig().public.mdc
|
|
7
|
+
const generate = computed(() => props.id && ((typeof headings?.anchorLinks === 'boolean' && headings?.anchorLinks === true) || (typeof headings?.anchorLinks === 'object' && headings?.anchorLinks?.h6)))
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<h6 :id="props.id">
|
|
12
|
+
<a v-if="id || generate" :href="`#${props.id}`">
|
|
13
|
+
<slot />
|
|
14
|
+
</a>
|
|
15
|
+
<slot v-else />
|
|
16
|
+
</h6>
|
|
17
|
+
</template>
|