mirror of
https://github.com/KazooTTT/kazoottt-blog.git
synced 2025-06-17 07:51:22 +08:00
Refactor Calendar component
This commit is contained in:
@ -72,183 +72,195 @@ const weekDays = ['一', '二', '三', '四', '五', '六', '日']
|
|||||||
day?: 'numeric' | '2-digit'
|
day?: 'numeric' | '2-digit'
|
||||||
}
|
}
|
||||||
|
|
||||||
const allPosts = JSON.parse(document.getElementById('posts-data')?.textContent || '[]') as Post[]
|
function initCalendar() {
|
||||||
const currentMonthDisplay = document.getElementById('currentMonthDisplay')
|
const allPosts = JSON.parse(
|
||||||
const prevMonthBtn = document.getElementById('prevMonth')
|
document.getElementById('posts-data')?.textContent || '[]'
|
||||||
const nextMonthBtn = document.getElementById('nextMonth')
|
) as Post[]
|
||||||
const calendarGrid = document.getElementById('calendarGrid')
|
const currentMonthDisplay = document.getElementById('currentMonthDisplay')
|
||||||
|
const prevMonthBtn = document.getElementById('prevMonth')
|
||||||
|
const nextMonthBtn = document.getElementById('nextMonth')
|
||||||
|
const calendarGrid = document.getElementById('calendarGrid')
|
||||||
|
|
||||||
// 使用传入的 currentDate 作为初始日期
|
// 使用传入的 currentDate 作为初始日期
|
||||||
let currentDate = new Date(
|
let currentDate = new Date(
|
||||||
document.querySelector('[data-current-date]')?.getAttribute('data-current-date') || new Date()
|
document.querySelector('[data-current-date]')?.getAttribute('data-current-date') || new Date()
|
||||||
)
|
)
|
||||||
|
|
||||||
// 获取所有有日记的月份
|
// 获取所有有日记的月份
|
||||||
const months = allPosts.map((post) => {
|
const months = allPosts.map((post) => {
|
||||||
const date = new Date(post.data.date)
|
const date = new Date(post.data.date)
|
||||||
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`
|
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`
|
||||||
})
|
})
|
||||||
const uniqueMonths = Array.from(new Set(months)).sort()
|
const uniqueMonths = Array.from(new Set(months)).sort()
|
||||||
|
|
||||||
function getFormattedDate(date: Date, options: DateTimeFormatOptions): string {
|
function getFormattedDate(date: Date, options: DateTimeFormatOptions): string {
|
||||||
return new Intl.DateTimeFormat('zh-CN', options).format(date)
|
return new Intl.DateTimeFormat('zh-CN', options).format(date)
|
||||||
}
|
|
||||||
|
|
||||||
function getCurrentMonthStr(): string {
|
|
||||||
return `${currentDate.getFullYear()}-${String(currentDate.getMonth() + 1).padStart(2, '0')}`
|
|
||||||
}
|
|
||||||
|
|
||||||
function canNavigateToMonth(direction: 'prev' | 'next'): boolean {
|
|
||||||
const currentMonthStr = getCurrentMonthStr()
|
|
||||||
if (direction === 'prev') {
|
|
||||||
return uniqueMonths.some((m) => m < currentMonthStr)
|
|
||||||
} else {
|
|
||||||
const now = new Date()
|
|
||||||
const currentMonth = new Date(currentDate.getFullYear(), currentDate.getMonth())
|
|
||||||
const thisMonth = new Date(now.getFullYear(), now.getMonth())
|
|
||||||
return currentMonth < thisMonth
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateNavigationButtons() {
|
|
||||||
if (prevMonthBtn && nextMonthBtn) {
|
|
||||||
const canGoPrev = canNavigateToMonth('prev')
|
|
||||||
const canGoNext = canNavigateToMonth('next')
|
|
||||||
|
|
||||||
prevMonthBtn.classList.toggle('cursor-not-allowed', !canGoPrev)
|
|
||||||
prevMonthBtn.classList.toggle('opacity-50', !canGoPrev)
|
|
||||||
;(prevMonthBtn as HTMLButtonElement).disabled = !canGoPrev
|
|
||||||
|
|
||||||
nextMonthBtn.classList.toggle('cursor-not-allowed', !canGoNext)
|
|
||||||
nextMonthBtn.classList.toggle('opacity-50', !canGoNext)
|
|
||||||
;(nextMonthBtn as HTMLButtonElement).disabled = !canGoNext
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderCalendar() {
|
|
||||||
// 获取当月的第一天和最后一天
|
|
||||||
const firstDayOfMonth = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1)
|
|
||||||
const lastDayOfMonth = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0)
|
|
||||||
|
|
||||||
// 获取当月第一天是星期几(0是周日,1是周一,以此类推)
|
|
||||||
const firstDayWeekday = firstDayOfMonth.getDay()
|
|
||||||
const adjustedFirstDayWeekday = firstDayWeekday === 0 ? 7 : firstDayWeekday
|
|
||||||
|
|
||||||
// 计算日历表格需要显示的天数
|
|
||||||
const daysInPrevMonth = adjustedFirstDayWeekday - 1
|
|
||||||
const daysInCurrentMonth = lastDayOfMonth.getDate()
|
|
||||||
const totalDays = Math.ceil((daysInPrevMonth + daysInCurrentMonth) / 7) * 7
|
|
||||||
|
|
||||||
// 获取上个月的最后几天
|
|
||||||
const lastDayOfPrevMonth = new Date(
|
|
||||||
currentDate.getFullYear(),
|
|
||||||
currentDate.getMonth(),
|
|
||||||
0
|
|
||||||
).getDate()
|
|
||||||
|
|
||||||
// 更新月份显示
|
|
||||||
if (currentMonthDisplay) {
|
|
||||||
currentMonthDisplay.textContent = `${currentDate.getFullYear()}年${currentDate.getMonth() + 1}月`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新导航按钮状态
|
function getCurrentMonthStr(): string {
|
||||||
updateNavigationButtons()
|
return `${currentDate.getFullYear()}-${String(currentDate.getMonth() + 1).padStart(2, '0')}`
|
||||||
|
}
|
||||||
|
|
||||||
// 清除现有的日历内容(保留星期标题)
|
function canNavigateToMonth(direction: 'prev' | 'next'): boolean {
|
||||||
const existingDays = calendarGrid?.querySelectorAll('.calendar-day')
|
const currentMonthStr = getCurrentMonthStr()
|
||||||
existingDays?.forEach((day) => day.remove())
|
if (direction === 'prev') {
|
||||||
|
return uniqueMonths.some((m) => m < currentMonthStr)
|
||||||
|
} else {
|
||||||
|
const now = new Date()
|
||||||
|
const currentMonth = new Date(currentDate.getFullYear(), currentDate.getMonth())
|
||||||
|
const thisMonth = new Date(now.getFullYear(), now.getMonth())
|
||||||
|
return currentMonth < thisMonth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 生成日历内容
|
function updateNavigationButtons() {
|
||||||
for (let i = 0; i < totalDays; i++) {
|
if (prevMonthBtn && nextMonthBtn) {
|
||||||
const dayNumber = i - daysInPrevMonth + 1
|
const canGoPrev = canNavigateToMonth('prev')
|
||||||
const isCurrentMonth = dayNumber > 0 && dayNumber <= daysInCurrentMonth
|
const canGoNext = canNavigateToMonth('next')
|
||||||
const displayDay = isCurrentMonth
|
|
||||||
? dayNumber
|
|
||||||
: dayNumber <= 0
|
|
||||||
? lastDayOfPrevMonth + dayNumber
|
|
||||||
: dayNumber - daysInCurrentMonth
|
|
||||||
|
|
||||||
const date = new Date(
|
prevMonthBtn.classList.toggle('cursor-not-allowed', !canGoPrev)
|
||||||
|
prevMonthBtn.classList.toggle('opacity-50', !canGoPrev)
|
||||||
|
;(prevMonthBtn as HTMLButtonElement).disabled = !canGoPrev
|
||||||
|
|
||||||
|
nextMonthBtn.classList.toggle('cursor-not-allowed', !canGoNext)
|
||||||
|
nextMonthBtn.classList.toggle('opacity-50', !canGoNext)
|
||||||
|
;(nextMonthBtn as HTMLButtonElement).disabled = !canGoNext
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderCalendar() {
|
||||||
|
// 获取当月的第一天和最后一天
|
||||||
|
const firstDayOfMonth = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1)
|
||||||
|
const lastDayOfMonth = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0)
|
||||||
|
|
||||||
|
// 获取当月第一天是星期几(0是周日,1是周一,以此类推)
|
||||||
|
const firstDayWeekday = firstDayOfMonth.getDay()
|
||||||
|
const adjustedFirstDayWeekday = firstDayWeekday === 0 ? 7 : firstDayWeekday
|
||||||
|
|
||||||
|
// 计算日历表格需要显示的天数
|
||||||
|
const daysInPrevMonth = adjustedFirstDayWeekday - 1
|
||||||
|
const daysInCurrentMonth = lastDayOfMonth.getDate()
|
||||||
|
const totalDays = Math.ceil((daysInPrevMonth + daysInCurrentMonth) / 7) * 7
|
||||||
|
|
||||||
|
// 获取上个月的最后几天
|
||||||
|
const lastDayOfPrevMonth = new Date(
|
||||||
currentDate.getFullYear(),
|
currentDate.getFullYear(),
|
||||||
isCurrentMonth
|
currentDate.getMonth(),
|
||||||
? currentDate.getMonth()
|
0
|
||||||
: dayNumber <= 0
|
).getDate()
|
||||||
? currentDate.getMonth() - 1
|
|
||||||
: currentDate.getMonth() + 1,
|
|
||||||
displayDay
|
|
||||||
)
|
|
||||||
|
|
||||||
const formattedDate = getFormattedDate(date, {
|
// 更新月份显示
|
||||||
year: 'numeric',
|
if (currentMonthDisplay) {
|
||||||
month: '2-digit',
|
currentMonthDisplay.textContent = `${currentDate.getFullYear()}年${currentDate.getMonth() + 1}月`
|
||||||
day: '2-digit'
|
|
||||||
})
|
|
||||||
|
|
||||||
// 查找当天的文章
|
|
||||||
const postsForDay = allPosts.filter((post) => {
|
|
||||||
const postDate = new Date(post.data.date)
|
|
||||||
return (
|
|
||||||
getFormattedDate(postDate, {
|
|
||||||
year: 'numeric',
|
|
||||||
month: '2-digit',
|
|
||||||
day: '2-digit'
|
|
||||||
}) === formattedDate
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const hasPost = postsForDay.length > 0
|
|
||||||
|
|
||||||
// 创建日历单元格
|
|
||||||
const dayElement = document.createElement('div')
|
|
||||||
dayElement.className = `calendar-day min-h-[100px] rounded-lg border p-2 ${
|
|
||||||
isCurrentMonth ? 'bg-primary-foreground' : 'bg-muted/50'
|
|
||||||
} ${hasPost ? 'border-green-400/50' : 'border-border'}`
|
|
||||||
|
|
||||||
// 添加日期显示
|
|
||||||
const dateDisplay = document.createElement('div')
|
|
||||||
dateDisplay.className = 'mb-1 text-right'
|
|
||||||
const dateSpan = document.createElement('span')
|
|
||||||
dateSpan.className = `inline-block h-7 w-7 rounded-full text-center leading-7 ${
|
|
||||||
!isCurrentMonth ? 'text-muted-foreground' : ''
|
|
||||||
} ${hasPost ? 'bg-green-400/20' : ''}`
|
|
||||||
dateSpan.textContent = displayDay.toString()
|
|
||||||
dateDisplay.appendChild(dateSpan)
|
|
||||||
dayElement.appendChild(dateDisplay)
|
|
||||||
|
|
||||||
// 如果有文章,添加文章链接
|
|
||||||
if (hasPost) {
|
|
||||||
const postsContainer = document.createElement('div')
|
|
||||||
postsContainer.className = 'space-y-1 text-xs'
|
|
||||||
postsForDay.forEach((post) => {
|
|
||||||
const link = document.createElement('a')
|
|
||||||
link.href = `/diary/${post.slug}/`
|
|
||||||
link.className = 'block line-clamp-2 transition-colors hover:text-green-400'
|
|
||||||
link.title = post.data.title
|
|
||||||
link.textContent = post.data.title
|
|
||||||
postsContainer.appendChild(link)
|
|
||||||
})
|
|
||||||
dayElement.appendChild(postsContainer)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
calendarGrid?.appendChild(dayElement)
|
// 更新导航按钮状态
|
||||||
|
updateNavigationButtons()
|
||||||
|
|
||||||
|
// 清除现有的日历内容(保留星期标题)
|
||||||
|
const existingDays = calendarGrid?.querySelectorAll('.calendar-day')
|
||||||
|
existingDays?.forEach((day) => day.remove())
|
||||||
|
|
||||||
|
// 生成日历内容
|
||||||
|
for (let i = 0; i < totalDays; i++) {
|
||||||
|
const dayNumber = i - daysInPrevMonth + 1
|
||||||
|
const isCurrentMonth = dayNumber > 0 && dayNumber <= daysInCurrentMonth
|
||||||
|
const displayDay = isCurrentMonth
|
||||||
|
? dayNumber
|
||||||
|
: dayNumber <= 0
|
||||||
|
? lastDayOfPrevMonth + dayNumber
|
||||||
|
: dayNumber - daysInCurrentMonth
|
||||||
|
|
||||||
|
const date = new Date(
|
||||||
|
currentDate.getFullYear(),
|
||||||
|
isCurrentMonth
|
||||||
|
? currentDate.getMonth()
|
||||||
|
: dayNumber <= 0
|
||||||
|
? currentDate.getMonth() - 1
|
||||||
|
: currentDate.getMonth() + 1,
|
||||||
|
displayDay
|
||||||
|
)
|
||||||
|
|
||||||
|
const formattedDate = getFormattedDate(date, {
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit'
|
||||||
|
})
|
||||||
|
|
||||||
|
// 查找当天的文章
|
||||||
|
const postsForDay = allPosts.filter((post) => {
|
||||||
|
const postDate = new Date(post.data.date)
|
||||||
|
return (
|
||||||
|
getFormattedDate(postDate, {
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit'
|
||||||
|
}) === formattedDate
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const hasPost = postsForDay.length > 0
|
||||||
|
|
||||||
|
// 创建日历单元格
|
||||||
|
const dayElement = document.createElement('div')
|
||||||
|
dayElement.className = `calendar-day min-h-[100px] rounded-lg border p-2 ${
|
||||||
|
isCurrentMonth ? 'bg-primary-foreground' : 'bg-muted/50'
|
||||||
|
} ${hasPost ? 'border-green-400/50' : 'border-border'}`
|
||||||
|
|
||||||
|
// 添加日期显示
|
||||||
|
const dateDisplay = document.createElement('div')
|
||||||
|
dateDisplay.className = 'mb-1 text-right'
|
||||||
|
const dateSpan = document.createElement('span')
|
||||||
|
dateSpan.className = `inline-block h-7 w-7 rounded-full text-center leading-7 ${
|
||||||
|
!isCurrentMonth ? 'text-muted-foreground' : ''
|
||||||
|
} ${hasPost ? 'bg-green-400/20' : ''}`
|
||||||
|
dateSpan.textContent = displayDay.toString()
|
||||||
|
dateDisplay.appendChild(dateSpan)
|
||||||
|
dayElement.appendChild(dateDisplay)
|
||||||
|
|
||||||
|
// 如果有文章,添加文章链接
|
||||||
|
if (hasPost) {
|
||||||
|
const postsContainer = document.createElement('div')
|
||||||
|
postsContainer.className = 'space-y-1 text-xs'
|
||||||
|
postsForDay.forEach((post) => {
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = `/diary/${post.slug}/`
|
||||||
|
link.className = 'block line-clamp-2 transition-colors hover:text-green-400'
|
||||||
|
link.title = post.data.title
|
||||||
|
link.textContent = post.data.title
|
||||||
|
postsContainer.appendChild(link)
|
||||||
|
})
|
||||||
|
dayElement.appendChild(postsContainer)
|
||||||
|
}
|
||||||
|
|
||||||
|
calendarGrid?.appendChild(dayElement)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 初始渲染
|
||||||
|
renderCalendar()
|
||||||
|
|
||||||
|
// 添加事件监听器
|
||||||
|
prevMonthBtn?.addEventListener('click', () => {
|
||||||
|
if (canNavigateToMonth('prev')) {
|
||||||
|
currentDate.setMonth(currentDate.getMonth() - 1)
|
||||||
|
renderCalendar()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
nextMonthBtn?.addEventListener('click', () => {
|
||||||
|
if (canNavigateToMonth('next')) {
|
||||||
|
currentDate.setMonth(currentDate.getMonth() + 1)
|
||||||
|
renderCalendar()
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始渲染
|
// 页面加载时初始化
|
||||||
renderCalendar()
|
initCalendar()
|
||||||
|
|
||||||
// 添加事件监听器
|
// 在视图转换后重新初始化
|
||||||
prevMonthBtn?.addEventListener('click', () => {
|
document.addEventListener('astro:after-swap', () => {
|
||||||
if (canNavigateToMonth('prev')) {
|
initCalendar()
|
||||||
currentDate.setMonth(currentDate.getMonth() - 1)
|
|
||||||
renderCalendar()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
nextMonthBtn?.addEventListener('click', () => {
|
|
||||||
if (canNavigateToMonth('next')) {
|
|
||||||
currentDate.setMonth(currentDate.getMonth() + 1)
|
|
||||||
renderCalendar()
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@ -5,23 +5,23 @@ import { Image } from 'astro:assets'
|
|||||||
|
|
||||||
<header class='fixed left-0 right-0 top-0 z-50 bg-white shadow-sm dark:bg-gray-800'>
|
<header class='fixed left-0 right-0 top-0 z-50 bg-white shadow-sm dark:bg-gray-800'>
|
||||||
<nav
|
<nav
|
||||||
class='mx-auto flex w-full items-center justify-between px-4 py-3 sm:flex sm:w-3/5 sm:items-center lg:w-4/5'
|
class='mx-auto flex w-full items-center justify-between px-4 py-3 lg:flex lg:w-3/5 lg:w-4/5 lg:items-center'
|
||||||
aria-label='global'
|
aria-label='global'
|
||||||
>
|
>
|
||||||
<a class='flex items-center' href='/'>
|
<a class='flex items-center' href='/'>
|
||||||
<Image
|
<Image
|
||||||
src={kazootttAvatar}
|
src={kazootttAvatar}
|
||||||
alt='profile photo'
|
alt='profile photo'
|
||||||
class='mr-2 h-8 w-auto rounded-full sm:hidden'
|
class='mr-2 h-8 w-auto rounded-full lg:hidden'
|
||||||
loading='eager'
|
loading='eager'
|
||||||
/>
|
/>
|
||||||
<div class='hidden flex-none text-xl font-semibold sm:block' aria-label='Brand'>声控烤箱</div>
|
<div class='hidden flex-none text-xl font-semibold lg:block' aria-label='Brand'>声控烤箱</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<!-- Mobile menu button -->
|
<!-- Mobile menu button -->
|
||||||
<button
|
<button
|
||||||
id='mobileMenuButton'
|
id='mobileMenuButton'
|
||||||
class='rounded-md p-2 hover:bg-border sm:hidden'
|
class='rounded-md p-2 hover:bg-border lg:hidden'
|
||||||
aria-label='Toggle mobile menu'
|
aria-label='Toggle mobile menu'
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
@ -36,7 +36,7 @@ import { Image } from 'astro:assets'
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Desktop Navigation -->
|
<!-- Desktop Navigation -->
|
||||||
<div class='hidden flex-row items-center justify-center gap-x-7 sm:flex'>
|
<div class='hidden flex-row items-center justify-center gap-x-7 lg:flex'>
|
||||||
<div class='relative'>
|
<div class='relative'>
|
||||||
<a
|
<a
|
||||||
href='/blog'
|
href='/blog'
|
||||||
@ -145,7 +145,7 @@ import { Image } from 'astro:assets'
|
|||||||
<!-- Mobile Navigation -->
|
<!-- Mobile Navigation -->
|
||||||
<div
|
<div
|
||||||
id='mobileMenu'
|
id='mobileMenu'
|
||||||
class='fixed inset-x-0 top-[72px] z-50 hidden max-h-[calc(100vh-72px)] overflow-y-auto rounded-b-lg bg-white shadow-lg dark:bg-gray-800 sm:hidden'
|
class='fixed inset-x-0 top-[72px] z-50 hidden max-h-[calc(100vh-72px)] overflow-y-auto rounded-b-lg bg-white shadow-lg dark:bg-gray-800 lg:hidden'
|
||||||
>
|
>
|
||||||
<div class='space-y-2 px-4 py-2'>
|
<div class='space-y-2 px-4 py-2'>
|
||||||
<a
|
<a
|
||||||
|
@ -25,7 +25,7 @@ const {
|
|||||||
<body class='flex justify-center bg-background'>
|
<body class='flex justify-center bg-background'>
|
||||||
<ThemeProvider />
|
<ThemeProvider />
|
||||||
<main
|
<main
|
||||||
class='flex min-h-screen w-screen max-w-[60rem] flex-col items-center px-6 pb-10 pt-24 font-satoshi text-[0.92rem] leading-relaxed sm:px-10 lg:px-10'
|
class='flex min-h-screen w-screen max-w-[64rem] flex-col items-center px-6 pb-10 pt-24 font-satoshi text-[0.92rem] leading-relaxed sm:px-10 lg:px-10'
|
||||||
>
|
>
|
||||||
<Header />
|
<Header />
|
||||||
<slot />
|
<slot />
|
||||||
|
@ -5,6 +5,7 @@ import BlogHero from '@/components/blog/Hero.astro'
|
|||||||
import TOC from '@/components/blog/TOC.astro'
|
import TOC from '@/components/blog/TOC.astro'
|
||||||
import Button from '@/components/Button.astro'
|
import Button from '@/components/Button.astro'
|
||||||
import PageLayout from './BaseLayout.astro'
|
import PageLayout from './BaseLayout.astro'
|
||||||
|
import GiscusComment from '@/components/GiscusComment'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
post: CollectionEntry<'post'>
|
post: CollectionEntry<'post'>
|
||||||
@ -55,10 +56,11 @@ const { headings } = await post.render()
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
id='blog-gallery'
|
id='blog-gallery'
|
||||||
class='prose prose-base prose-zinc mt-12 text-muted-foreground dark:prose-invert prose-headings:font-medium prose-headings:text-foreground prose-headings:before:absolute prose-headings:before:-ms-4 prose-th:before:content-none prose-img:shadow'
|
class='prose-base prose-zinc mt-12 w-full text-muted-foreground dark:prose-invert prose-headings:font-medium prose-headings:text-foreground prose-headings:before:absolute prose-headings:before:-ms-4 prose-th:before:content-none prose-img:shadow'
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
<GiscusComment client:load />
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
@ -80,38 +82,6 @@ const { headings } = await post.render()
|
|||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Image Preview Modal -->
|
|
||||||
<div
|
|
||||||
id='image-modal'
|
|
||||||
class='pointer-events-none fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50 opacity-0 backdrop-blur-md transition-opacity duration-300 ease-in-out'
|
|
||||||
>
|
|
||||||
<div class='relative flex h-full w-full items-center justify-center p-4' id='modal-container'>
|
|
||||||
<img
|
|
||||||
id='modal-image'
|
|
||||||
alt='modal-image'
|
|
||||||
class='h-auto max-h-full w-auto max-w-full object-contain transition-transform duration-300 ease-in-out'
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
id='close-modal'
|
|
||||||
class='absolute right-4 top-4 text-white transition-colors duration-300 hover:text-gray-300'
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns='http://www.w3.org/2000/svg'
|
|
||||||
class='h-6 w-6'
|
|
||||||
fill='none'
|
|
||||||
viewBox='0 0 24 24'
|
|
||||||
stroke='currentColor'
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
stroke-linecap='round'
|
|
||||||
stroke-linejoin='round'
|
|
||||||
stroke-width='2'
|
|
||||||
d='M6 18L18 6M6 6l12 12'></path>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</PageLayout>
|
</PageLayout>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@ -131,79 +101,6 @@ const { headings } = await post.render()
|
|||||||
|
|
||||||
const observer = new IntersectionObserver(callback)
|
const observer = new IntersectionObserver(callback)
|
||||||
observer.observe(targetHeader)
|
observer.observe(targetHeader)
|
||||||
|
|
||||||
// Image preview functionality
|
|
||||||
const imageModal = document.getElementById('image-modal')
|
|
||||||
const modalImage = document.getElementById('modal-image') as HTMLImageElement
|
|
||||||
const modalContainer = document.getElementById('modal-container')
|
|
||||||
|
|
||||||
function openModal() {
|
|
||||||
if (imageModal) {
|
|
||||||
imageModal.classList.remove('opacity-0', 'pointer-events-none')
|
|
||||||
modalImage.style.transform = 'scale(1)'
|
|
||||||
isZoomed = false
|
|
||||||
document.body.style.overflow = 'hidden'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeModal() {
|
|
||||||
if (imageModal) {
|
|
||||||
imageModal.classList.add('opacity-0', 'pointer-events-none')
|
|
||||||
modalImage.src = ''
|
|
||||||
modalImage.alt = ''
|
|
||||||
modalImage.style.transform = 'scale(1)'
|
|
||||||
isZoomed = false
|
|
||||||
document.body.style.overflow = ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('click', (e) => {
|
|
||||||
const target = e.target as HTMLElement
|
|
||||||
if (target.tagName === 'IMG' && target.closest('.prose')) {
|
|
||||||
const img = target as HTMLImageElement
|
|
||||||
modalImage.src = img.src
|
|
||||||
modalImage.alt = img.alt
|
|
||||||
openModal()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 处理点击事件
|
|
||||||
if (imageModal) {
|
|
||||||
imageModal.addEventListener('click', (e) => {
|
|
||||||
const clickedElement = e.target as HTMLElement
|
|
||||||
// 如果点击的是 modal 背景或 modal-container(不是图片和关闭按钮)
|
|
||||||
if (clickedElement === imageModal || clickedElement === modalContainer) {
|
|
||||||
closeModal()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 关闭按钮事件
|
|
||||||
const closeButton = document.getElementById('close-modal')
|
|
||||||
if (closeButton) {
|
|
||||||
closeButton.addEventListener('click', closeModal)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ESC 键关闭
|
|
||||||
document.addEventListener('keydown', (e) => {
|
|
||||||
if (e.key === 'Escape' && imageModal && !imageModal.classList.contains('opacity-0')) {
|
|
||||||
closeModal()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 图片缩放功能
|
|
||||||
let isZoomed = false
|
|
||||||
modalImage.addEventListener('click', (e) => {
|
|
||||||
e.stopPropagation() // 阻止事件冒泡到 modal
|
|
||||||
if (isZoomed) {
|
|
||||||
modalImage.style.transform = 'scale(1)'
|
|
||||||
modalImage.classList.remove('zoomed')
|
|
||||||
} else {
|
|
||||||
modalImage.style.transform = 'scale(1.5)'
|
|
||||||
modalImage.classList.add('zoomed')
|
|
||||||
}
|
|
||||||
isZoomed = !isZoomed
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -81,7 +81,7 @@ const serializedPosts = allPosts.map((post) => ({
|
|||||||
type='application/json'
|
type='application/json'
|
||||||
set:html={JSON.stringify(serializedPosts)}
|
set:html={JSON.stringify(serializedPosts)}
|
||||||
/>
|
/>
|
||||||
<div data-current-date={currentDate.toISOString()}>
|
<div id='calendar-container' data-current-date={currentDate.toISOString()}>
|
||||||
<Calendar posts={allPosts} currentDate={currentDate} />
|
<Calendar posts={allPosts} currentDate={currentDate} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -98,3 +98,28 @@ const serializedPosts = allPosts.map((post) => ({
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</PageLayout>
|
</PageLayout>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function initializeCalendar() {
|
||||||
|
const postsDataScript = document.getElementById('posts-data')
|
||||||
|
if (!postsDataScript) return
|
||||||
|
|
||||||
|
// 触发一个自定义事件,通知日历组件重新初始化
|
||||||
|
window.dispatchEvent(
|
||||||
|
new CustomEvent('calendar-init', {
|
||||||
|
detail: {
|
||||||
|
posts: JSON.parse(postsDataScript.textContent || '[]'),
|
||||||
|
currentDate: document.getElementById('calendar-container')?.dataset.currentDate
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面首次加载时初始化
|
||||||
|
initializeCalendar()
|
||||||
|
|
||||||
|
// 在视图转换后重新初始化
|
||||||
|
document.addEventListener('astro:after-swap', () => {
|
||||||
|
initializeCalendar()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
Reference in New Issue
Block a user