Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/danielroe/roe.dev. This is the code and content for my personal website, built in Nuxt. roe.dev
Something went wrong. Try again.
4.7 kB · 154 lines
Vue
at feat/nuxt-v5
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155<template> <div> <section v-for="category in sortedCategories" :key="category.title" class="mb-12" > <h2 class="text-lg"> {{ category.title }} </h2>
<div v-if="category.displayAsGrid" class="w-screen -mx-4 md:-mx-12 overflow-visible mt-4" > <ul class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6 px-4 md:px-12" > <li v-for="item in sortedItems(category)" :key="item.name" > <div class="bg-accent flex flex-col"> <div v-if="item.image" class="relative flex flex-col justify-end bg-gray-100 dark:bg-gray-900 aspect-video overflow-hidden" > <img :src="item.image.url" :alt="item.image.alt" :width="item.image.width ?? 400" :height="item.image.height ?? 225" class="w-full h-full object-cover" loading="lazy" > </div>
<div class="px-2 py-2 flex flex-col"> <div class="flex justify-between items-start"> <h3 class="font-medium"> {{ item.name }} </h3> </div>
<p v-if="item.description" class="text-sm text-gray-600 dark:text-gray-400 mt-1" > {{ item.description }} </p>
<div class="mt-3 space-y-2"> <div v-if="item.links && item.links.length" class="flex flex-wrap gap-2" > <a v-for="(link, i) in item.links" :key="i" :href="link.uri" target="_blank" rel="noopener noreferrer" class="text-sm px-3 py-1 bg-gray-100 dark:bg-gray-800 rounded-full flex items-center gap-2 f-ring hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors" > <img :src="`https://www.google.com/s2/favicons?domain=${getHost(link.uri)}&sz=256`" height="16" width="16" class="rounded-full" alt="" role="presentation" > {{ link.label || 'Visit' }} </a> </div> </div> </div> </div> </li> </ul> </div>
<ul v-else class="mt-4 pl-4 list-none" > <li v-for="item in sortedItems(category)" :key="item.name" class="mb-2" > <div class="flex items-center gap-1"> <span v-if="item.links && item.links.length"> <a :href="item.links?.[0]?.uri" target="_blank" rel="noopener noreferrer" class="underlined-link" >{{ item.name }}</a> </span> <span v-else>{{ item.name }}</span> </div>
<div v-if="item.description" class="text-sm text-gray-600 dark:text-gray-400 mt-1" > {{ item.description }} </div>
<div v-if="item.links && item.links.length > 1" class="flex flex-wrap gap-2 mt-2" > <a v-for="(link, i) in item.links.slice(1)" :key="i" :href="link.uri" target="_blank" rel="noopener noreferrer" class="text-xs px-2 py-0.5 bg-gray-100 dark:bg-gray-800 rounded f-ring hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors" > {{ link.label || 'Link' }} </a> </div> </li> </ul> </section> </div></template>
<script setup lang="ts">import type { UsesCategory } from '#shared/cms/uses'
const { data } = await useFetch<UsesCategory[]>('/api/uses')
function getHost (url: string): string { try { const urlObj = new URL(url) return urlObj.origin } catch { return '' }}
const sortedCategories = computed(() => { return [...(data.value || [])].sort((a, b) => (a.order || 100) - (b.order || 100))})
const sortedItems = (category: UsesCategory) => { return [...(category.items || [])].sort((a, b) => (a.order || 100) - (b.order || 100))}</script>