-
-
Notifications
You must be signed in to change notification settings - Fork 75
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
112 changed files
with
4,910 additions
and
258 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
shamefully-hoist=true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
export default defineAppConfig({ | ||
toaster: { | ||
position: 'bottom-right' as const, | ||
expand: true, | ||
duration: 5000, | ||
}, | ||
ui: { | ||
colors: { | ||
primary: 'green', | ||
gray: 'slate', | ||
}, | ||
}, | ||
uiPro: { | ||
prose: { | ||
codeIcon: { | ||
'robots.txt': 'vscode-icons:file-type-robots', | ||
}, | ||
}, | ||
}, | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
<script setup lang="ts"> | ||
import fetchStats from '~/composables/stats' | ||
import { modules } from '../../src/const' | ||
const { data: navigation } = await useAsyncData('navigation', () => fetchContentNavigation()) | ||
const { data: stats } = await useAsyncData('stats', () => fetchStats()) | ||
const route = useRoute() | ||
const publicRuntimeConfig = useRuntimeConfig().public | ||
const segment = computed(() => route.path.split('/')[1]) | ||
const children = computed(() => { | ||
return navigation!.value!.find(i => i._path === `/${segment.value}`)?.children || [] | ||
}) | ||
provide('stats', stats) | ||
provide('navigation', navigation) | ||
provide('docsAsideLinks', children) | ||
provide('modules', modules) | ||
provide('module', computed(() => { | ||
const m = SeoModules.find(l => l?.slug === segment.value) | ||
const stats = (publicRuntimeConfig.moduleStats || []).find(m2 => m2.id === m?.id)?.stats || {} | ||
return m | ||
})) | ||
useSeoMeta({ | ||
ogTitle: 'Nuxt SEO · All the boring SEO work for Nuxt done.', | ||
// twitterTitle: 'Nuxt SEO - All the boring SEO work for Nuxt done.', | ||
}) | ||
</script> | ||
|
||
<template> | ||
<div> | ||
<Header /> | ||
|
||
<NuxtLayout> | ||
<NuxtPage /> | ||
</NuxtLayout> | ||
|
||
<ClientOnly /> | ||
|
||
<footer class="relative z-10 antialiased font-sans bg-white dark:bg-gray-900 text-sm text-gray-700 dark:text-gray-200 mt-20"> | ||
<div class="border-t border-gray-200 dark:border-gray-800"> | ||
<UContainer> | ||
<div class="py-10 grid xl:grid-cols-3 lg:gap-20 gap-10"> | ||
<div> | ||
<div class="mb-5"> | ||
<NuxtLink to="/" title="Home" class="flex items-end gap-1.5 font-bold text-xl text-gray-900 dark:text-white font-title"> | ||
<Logo /> | ||
</NuxtLink> | ||
</div> | ||
<nav> | ||
<ul class="space-y-6"> | ||
<li> | ||
<NuxtLink to="/docs/nuxt-seo/getting-started/what-is-nuxt-seo"> | ||
What is Nuxt SEO? | ||
</NuxtLink> | ||
</li> | ||
<li> | ||
<NuxtLink to="/docs/nuxt-seo/getting-started/installation"> | ||
Install Nuxt SEO | ||
</NuxtLink> | ||
</li> | ||
</ul> | ||
</nav> | ||
</div> | ||
<div> | ||
<h3 class="font-bold mb-5"> | ||
Modules | ||
</h3> | ||
<nav> | ||
<ul class="grid grid-cols-2 gap-6"> | ||
<li v-for="(module, key) in modules" :key="key"> | ||
<NuxtLink :to="module.to"> | ||
<UIcon dynamic :name="module.icon" /> | ||
{{ module.label }} | ||
</NuxtLink> | ||
</li> | ||
</ul> | ||
</nav> | ||
</div> | ||
<div> | ||
<div class="bg-gray-50 dark:bg-gray-800 flex rounded-xl shadow p-5"> | ||
<div> | ||
<div class="mb-2"> | ||
Hey <UIcon name="i-noto-waving-hand" /> My name is <a href="https://harlanzw.com" target="_blank" class="underline">Harlan</a> and I'm the author and maintainer of Nuxt SEO. | ||
</div> | ||
<div> | ||
I'd love to have your <a href="https://github.com/sponsors/harlan-zw" class="underline">support</a>! | ||
</div> | ||
</div> | ||
<div class="gap-3"> | ||
<img alt="Harlan Wilton" loading="lazy" src="https://avatars.githubusercontent.com/u/5326365?v=4" class="mx-auto rounded-full w-10 h-10 mb-3"> | ||
<div class="flex justify-center items-center opacity-70"> | ||
<UButton color="white" title="Twitter" variant="ghost" to="https://twitter.com/harlan_zw" target="_blank"> | ||
<UIcon name="i-logos-twitter" class="text-xl" /> | ||
</UButton> | ||
<UButton color="white" title="GitHub" aria-label="GitHub" variant="ghost" to="https://github.com/harlan-zw" target="_blank"> | ||
<UIcon name="i-logos-github-icon" class="text-xl" /> | ||
</UButton> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</UContainer> | ||
</div> | ||
<div class="border-t border-gray-200 dark:border-gray-800"> | ||
<UContainer> | ||
<div class="py-10"> | ||
Copyright © 2023-{{ new Date().getFullYear() }} Harlan Wilton - <a href="https://github.com/harlan-zw/nuxt-seo/blob/main/LICENSE">MIT License</a> | ||
</div> | ||
</UContainer> | ||
</div> | ||
</footer> | ||
<NuxtLoadingIndicator /> | ||
</div> | ||
</template> | ||
|
||
<style> | ||
@import "tailwindcss"; | ||
@import "@nuxt/ui-pro"; | ||
@source "../content/**/*.md"; | ||
@theme { | ||
--font-family-sans: 'Public Sans', sans-serif; | ||
--font-family-mono: 'Fira Code', monospace; | ||
--color-green-50: #EFFDF5; | ||
--color-green-100: #D9FBE8; | ||
--color-green-200: #B3F5D1; | ||
--color-green-300: #75EDAE; | ||
--color-green-400: #00DC82; | ||
--color-green-500: #00C16A; | ||
--color-green-600: #00A155; | ||
--color-green-700: #007F45; | ||
--color-green-800: #016538; | ||
--color-green-900: #0A5331; | ||
--color-green-950: #052E16; | ||
} | ||
:root { | ||
--container-width: 90rem; | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
<template> | ||
<UBanner icon="i-heroicons-wrench-screwdriver" :actions="[{ label: 'Go to Nuxt UI v2', to: 'https://ui.nuxt.com', trailingIcon: 'i-heroicons-arrow-right-20-solid', class: 'rounded-full' }]" :close="false"> | ||
<template #title> | ||
You're looking at the documentation for <span class="font-semibold">Nuxt SEO</span>! | ||
</template> | ||
</UBanner> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
<script lang="ts" setup> | ||
import { onMounted, ref, useElementHover, useIntervalFn, watch } from '#imports' | ||
import { useMouseInElement } from '@vueuse/core' | ||
defineProps<{ | ||
icon: string | ||
interval: number | ||
}>() | ||
const robotsInject = inject('robots') | ||
const container = ref() | ||
// we're not moving anywhere | ||
const direction = { | ||
x: Math.random() > 0.5 ? 1 : -1, | ||
y: Math.random() > 0.5 ? 1 : -1, | ||
} | ||
const pos = { | ||
x: 0, | ||
y: 0, | ||
} | ||
const size = { | ||
// tiny buffer | ||
width: 33, | ||
height: 33, | ||
} | ||
const styles = ref({ | ||
opacity: 0, | ||
transform: '', | ||
}) | ||
let rotation = 0 | ||
let speed = 0 | ||
const isHovered = ref(robotsInject.value.hover) | ||
onMounted(() => { | ||
// parent node for bounding box | ||
const parentNode = container.value.parentNode | ||
// hover requires a class | ||
const parentHover = useElementHover(container.value.closest('.showcase-card')) | ||
watch(parentHover, () => { | ||
isHovered.value = parentHover.value | ||
}) | ||
const { width, height } = parentNode.getBoundingClientRect() | ||
const { elementX: mouseX, elementY: mouseY } = useMouseInElement(parentNode) | ||
// set facing rotation, should be 0, 90, 180, 270 | ||
rotation = 0 | ||
// start in a random spot | ||
pos.x = Math.min(Math.random() * width, width - size.width) | ||
pos.y = Math.min(Math.random() * height, height - size.height) | ||
// start at a speed between 1-3 | ||
speed = 0 | ||
const maxSpeed = Math.random() * 2 + 3 | ||
// start by assigning a reandom diagonal direction to head towards | ||
direction.x = Math.random() > 0.5 ? -1 : -1 | ||
direction.y = Math.random() > 0.5 ? 1 : -1 | ||
let isEvadingMouse = false | ||
const { pause, resume } = useIntervalFn(() => { | ||
// increase speed if it's not at max | ||
if (speed < maxSpeed) | ||
speed += 0.1 | ||
// if it's too fast, slow it down | ||
if (speed > maxSpeed) | ||
speed -= 0.1 | ||
// do the movement | ||
pos.x += Math.round(direction.x * speed) | ||
pos.y += Math.round(direction.y * speed) | ||
// we want to create a DVD screensaver effect, we move diagonally until we hit a wall then we bounce off of it | ||
// travel diagonally, each time we hit the corner, add a carriage | ||
// if we hit the top or bottom, reverse the y direction | ||
// if we hit the left or right, reverse the x direction | ||
// only if the direction is still heading out of bounds | ||
if (pos.x + size.width > width && direction.x === 1) { | ||
rotation = 90 | ||
direction.x *= -1 | ||
// only if we're not going 1.5x the max speed, increase the speed for a bounce effect | ||
speed = Math.min(speed + maxSpeed * 1.5, maxSpeed * 1.5) | ||
} | ||
if (pos.x < 0 && direction.x === -1) { | ||
rotation = 270 | ||
direction.x *= -1 | ||
speed = Math.min(speed + maxSpeed * 1.5, maxSpeed * 1.5) | ||
} | ||
if (pos.y + size.height > height && direction.y === 1) { | ||
rotation = 0 | ||
direction.y *= -1 | ||
speed = Math.min(speed + maxSpeed * 1.5, maxSpeed * 1.5) | ||
} | ||
if (pos.y < 0 && direction.y === -1) { | ||
rotation = 180 | ||
direction.y *= -1 | ||
speed = Math.min(speed + maxSpeed * 1.5, maxSpeed * 1.5) | ||
} | ||
// also collide with the mouse | ||
if (pos.x < mouseX.value && pos.x + size.width > mouseX.value && pos.y < mouseY.value && pos.y + size.height > mouseY.value) { | ||
if (isEvadingMouse) | ||
return | ||
// we're colliding with the mouse, so we need to bounce off of it | ||
// we need to figure out which side we're colliding with | ||
// we can do this by figuring out which side is closer | ||
const xDist = Math.min(Math.abs(pos.x - mouseX.value), Math.abs(pos.x + size.width - mouseX.value)) | ||
const yDist = Math.min(Math.abs(pos.y - mouseY.value), Math.abs(pos.y + size.height - mouseY.value)) | ||
if (xDist < yDist) { | ||
// we're colliding with the left or right side | ||
direction.x *= -1 | ||
rotation = 90 | ||
} | ||
else { | ||
// we're colliding with the top or bottom side | ||
direction.y *= -1 | ||
rotation = 0 | ||
} | ||
isEvadingMouse = true | ||
robotsInject.value.collisions += 1 | ||
speed = Math.min(speed + maxSpeed * 1.5, maxSpeed * 1.5) | ||
} | ||
else { | ||
isEvadingMouse = false | ||
} | ||
const stylesTmp: Record<string, any> = {} | ||
// apply transform style to container | ||
if (rotation === 180) { | ||
// we need to flip instread | ||
stylesTmp.transform = `translate(${pos.x}px, ${pos.y}px) rotateX(${rotation}deg)` | ||
} | ||
else { | ||
stylesTmp.transform = `translate(${pos.x}px, ${pos.y}px) rotate(${rotation}deg)` | ||
} | ||
stylesTmp.opacity = Math.min(styles.value.opacity + 0.01 + Math.random() * 0.01, 1) | ||
styles.value = stylesTmp | ||
}, 1000 / 30 /* 30 fps */, { | ||
immediate: isHovered.value, | ||
}) | ||
watch(parentHover, (hovered) => { | ||
if (hovered) { | ||
resume() | ||
robotsInject.value.hover = true | ||
} | ||
else { | ||
robotsInject.value.hover = false | ||
pause() | ||
rotation = 0 | ||
speed = 0 | ||
styles.value = { | ||
opacity: 0, | ||
transform: styles.value.transform, | ||
} | ||
} | ||
}) | ||
}) | ||
</script> | ||
|
||
<template> | ||
<div ref="container" class="absolute top-0 left-0"> | ||
<UIcon dynamic :name="icon" size="30" class="transition-opacity" :style="styles" /> | ||
</div> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<script lang="ts" setup> | ||
defineProps<{ username: string, image: string }>() | ||
</script> | ||
|
||
<template> | ||
<div class="tweet max-w-[550px] mx-auto my-15 dark:bg-black bg-white p-4 rounded-xl border not-prose dark:border-gray-700 px-5 py-5 text-black dark:text-white relative"> | ||
<UIcon name="i-logos-discord-icon" class="opacity-60 absolute top-3 right-4 w-6 h-6" /> | ||
<div class="flex gap-3"> | ||
<div><UAvatar class="mt-1" :src="image" :alt="`Discord user ${username}`" /></div> | ||
<div class="opacity-90"> | ||
<div class="font-semibold"> | ||
{{ username }} | ||
</div> | ||
<slot /> | ||
</div> | ||
</div> | ||
</div> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
<script setup lang="ts"> | ||
// import { Dialog, DialogPanel, TransitionRoot } from '@headlessui/vue' | ||
// | ||
const isDialogOpen = ref(false) | ||
</script> | ||
|
||
<template> | ||
<header class="sticky top-0 z-50 w-full backdrop-blur flex-none dark:border-gray-800"> | ||
<HeaderLinks v-model="isDialogOpen" /> | ||
|
||
<!-- <TransitionRoot :show="isDialogOpen" as="template">--> | ||
<!-- <Dialog as="div" @close="isDialogOpen = false">--> | ||
<!-- <DialogPanel class="fixed inset-0 z-50 overflow-y-auto bg-white dark:bg-gray-900 lg:hidden">--> | ||
<!-- <div class="px-4 sm:px-6 sticky top-0 border-b border-gray-200 dark:border-gray-800 bg-white/75 dark:bg-gray-900/75 backdrop-blur z-10">--> | ||
<!-- <HeaderLinks v-model="isDialogOpen" :links="links" />--> | ||
<!-- </div>--> | ||
<!-- <div class="px-4 sm:px-6 py-4 sm:py-6">--> | ||
<!-- <!– <UDocsAsideLinks @click="isDialogOpen = false" /> –>--> | ||
<!-- </div>--> | ||
<!-- </DialogPanel>--> | ||
<!-- </Dialog>--> | ||
<!-- </TransitionRoot>--> | ||
</header> | ||
</template> |
Oops, something went wrong.