refactor About, Blog, Contact, and Desktop components for improved responsiveness and structure; add BlogArticle component for article display

This commit is contained in:
2026-01-30 21:11:20 -05:00
parent 7f56539831
commit 2dbf53db63
5 changed files with 146 additions and 154 deletions

View File

@@ -65,74 +65,17 @@
<p class="text-yellow-600 text-sm">Check back later for updates!</p>
</div>
<!-- Blog Post Modal -->
<Teleport to="body">
<div v-if="selectedPost" ref="modalRef" class="fixed z-60" :style="modalStyle">
<div
class="bg-[#c0c0c0] border-2 border-white border-r-[#808080] border-b-[#808080] shadow-2xl max-w-4xl w-full max-h-[85vh] flex flex-col"
>
<!-- Modal Title Bar -->
<div
class="bg-linear-to-r from-[#000080] to-[#1084d0] px-2 py-1 flex items-center justify-between cursor-move select-none"
@mousedown="startDrag"
>
<span class="text-white font-bold text-sm">{{ selectedPost.title }}</span>
<button
class="w-5 h-5 bg-[#c0c0c0] border border-white border-b-[#808080] border-r-[#808080] flex items-center justify-center text-xs font-bold hover:bg-[#dfdfdf]"
@click="selectedPost = null"
>
<X :size="16" />
</button>
</div>
<!-- Modal Content -->
<div class="bg-white border-2 border-[#808080] border-t-white border-l-white overflow-auto flex-1">
<div class="max-w-3xl mx-auto px-8 py-8">
<div class="mb-6 pb-6 border-b-2 border-[#808080]">
<h1 class="text-4xl font-bold mb-4 text-[#000080]">{{ selectedPost.title }}</h1>
<div class="flex items-center gap-4 text-sm text-gray-600">
<span class="flex items-center gap-1.5 bg-[#f0f0f0] px-3 py-1.5 border border-[#808080]">
<Calendar :size="16" />
{{ formatDate(selectedPost.date) }}
</span>
<span
v-if="selectedPost.tags && selectedPost.tags.length > 0"
class="flex items-center gap-1.5 bg-[#f0f0f0] px-3 py-1.5 border border-[#808080]"
>
<Tag :size="16" />
{{ selectedPost.tags.join(', ') }}
</span>
</div>
</div>
<!-- key change: a single reusable class -->
<ContentRenderer :value="selectedPost" class="blog-prose">
<template #empty>
<p class="text-gray-500 text-center py-8">No content available.</p>
</template>
</ContentRenderer>
</div>
</div>
</div>
</div>
</Teleport>
</div>
</template>
<script setup lang="ts">
import { X, Calendar, Tag, ArrowRight, FileText, Loader2, AlertCircle, Inbox } from 'lucide-vue-next'
import { Calendar, Tag, ArrowRight, FileText, Loader2, AlertCircle, Inbox } from 'lucide-vue-next'
import type { BlogCollectionItem } from '@nuxt/content'
import { ref, computed, onMounted, onUnmounted } from 'vue'
const selectedPost = ref<BlogCollectionItem | null>(null)
const selectedPostPath = ref<string | null>(null)
const modalRef = ref<HTMLElement | null>(null)
const isDragging = ref(false)
const dragStartX = ref(0)
const dragStartY = ref(0)
const modalX = ref(0)
const modalY = ref(0)
const emit = defineEmits<{
'article-opened': [title: string, content: BlogCollectionItem]
'article-closed': []
}>()
const { data: posts, pending, error } = await useAsyncData('blog-posts', () => {
return queryCollection('blog').all()
@@ -143,54 +86,13 @@ const sortedPosts = computed(() => {
return [...posts.value].sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
})
const modalStyle = computed(() => {
if (modalX.value === 0 && modalY.value === 0) {
return {
left: '50%',
top: '50%',
transform: 'translate(-50%, -50%)',
width: '100%',
maxWidth: '56rem',
padding: '1rem',
}
}
return {
left: `${modalX.value}px`,
top: `${modalY.value}px`,
width: '100%',
maxWidth: '56rem',
padding: '1rem',
}
})
const openPost = async (path: string) => {
selectedPostPath.value = path
selectedPost.value = await queryCollection('blog').path(path).first()
modalX.value = 0
modalY.value = 0
}
const post = await queryCollection('blog').path(path).first()
const startDrag = (e: MouseEvent) => {
isDragging.value = true
if (modalX.value === 0 && modalY.value === 0) {
const rect = modalRef.value?.getBoundingClientRect()
if (rect) {
modalX.value = rect.left
modalY.value = rect.top
}
// Emit to parent to open in separate window
if (post) {
emit('article-opened', post.title, post)
}
dragStartX.value = e.clientX - modalX.value
dragStartY.value = e.clientY - modalY.value
}
const onDrag = (e: MouseEvent) => {
if (!isDragging.value) return
modalX.value = e.clientX - dragStartX.value
modalY.value = e.clientY - dragStartY.value
}
const stopDrag = () => {
isDragging.value = false
}
const formatDate = (dateString: string) => {
@@ -198,15 +100,6 @@ const formatDate = (dateString: string) => {
const date = new Date(dateString)
return date.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' })
}
onMounted(() => {
document.addEventListener('mousemove', onDrag)
document.addEventListener('mouseup', stopDrag)
})
onUnmounted(() => {
document.removeEventListener('mousemove', onDrag)
document.removeEventListener('mouseup', stopDrag)
})
</script>
<style scoped>