--- import type { PostEntry } from '@/lib/content'; interface Props { posts: PostEntry[]; } interface WeekCell { week: number; words: number; level: number; } const { posts } = Astro.props; const currentYear = new Date().getFullYear(); const years = Array.from({ length: 3 }, (_, index) => currentYear - 2 + index); function countWords(source: string): number { const text = source .replace(/```[\s\S]*?```/g, '') .replace(/`[^`]+`/g, '') .replace(/!\[[^\]]*\]\([^)]*\)/g, '') .replace(/\[([^\]]*)\]\([^)]*\)/g, '$1') .replace(/<[^>]+>/g, '') .replace(/https?:\/\/\S+/g, '') .replace(/[#{*_~>`|+-]/g, ' ') .replace(/\s+/g, ' ') .trim(); const chineseCharacters = text.match(/[\u3400-\u4dbf\u4e00-\u9fff]/g)?.length ?? 0; const otherWords = text .replace(/[\u3400-\u4dbf\u4e00-\u9fff]/g, ' ') .split(/\s+/) .filter(Boolean).length; return chineseCharacters + otherWords; } function getIsoWeek(date: Date): { year: number; week: number } { const utcDate = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())); const day = utcDate.getUTCDay() || 7; utcDate.setUTCDate(utcDate.getUTCDate() + 4 - day); const year = utcDate.getUTCFullYear(); const yearStart = new Date(Date.UTC(year, 0, 1)); const week = Math.ceil(((utcDate.getTime() - yearStart.getTime()) / 86_400_000 + 1) / 7); return { year, week }; } function getLevel(words: number): number { if (words === 0) return 0; if (words < 1_000) return 1; if (words < 3_000) return 2; if (words < 8_000) return 3; return 4; } const wordsByWeek = new Map(); let totalWords = 0; for (const post of posts) { const words = countWords(post.body ?? ''); totalWords += words; const { year, week } = getIsoWeek(post.data.pubDate); if (!years.includes(year)) continue; const key = `${year}-${week}`; wordsByWeek.set(key, (wordsByWeek.get(key) ?? 0) + words); } const yearRows = years.map((year) => ({ year, weeks: Array.from({ length: 53 }, (_, index): WeekCell => { const week = index + 1; const words = wordsByWeek.get(`${year}-${week}`) ?? 0; return { week, words, level: getLevel(words) }; }) })); const totalWordsLabel = totalWords >= 10_000 ? `${(totalWords / 10_000).toFixed(2)}W` : totalWords.toLocaleString('zh-CN'); ---

写作热力图

自博客建立以来,已输出 {totalWordsLabel}

{[0, 1, 2, 3, 4].map((level) => )}
{yearRows.map((row) =>
{row.year} {row.weeks.map((cell) => {row.year} W{String(cell.week).padStart(2, '0')} · {cell.words.toLocaleString('zh-CN')} )}
)}