TheSource.
이 블로그를 구성하는 실제 소스 코드(Raw Code)의 로직을 탐색합니다.
src / pagesReadOnly
* 파일을 클릭하면 우측 에디터에
실제 로직(Frontmatter)이 로드됩니다.
ASTROindex.astro
---
// src/pages/index.astro
import BaseLayout from '../layouts/BaseLayout.astro';
import { getCollection } from 'astro:content';
import Hero from '../components/home/Hero.astro';
import CourseCarousel from '../components/home/CourseCarousel.astro';
import LatestBrewing from '../components/home/LatestBrewing.astro';
import SystemLogs from '../components/home/SystemLogs.astro';
import MatchaGuide from '../components/home/MatchaGuide.astro';
import ApiPlayground from '../components/home/ApiPlayground.astro';
// 데이터 가져오기 및 가공
const allPosts = await getCollection('blog');
const sortedPosts = allPosts.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
const latestPosts = sortedPosts.slice(0, 3);
const allUpdates = await getCollection('updates');
const recentUpdates = allUpdates
.sort((a, b) => {
const dateDiff = b.data.date.valueOf() - a.data.date.valueOf();
if (dateDiff !== 0) return dateDiff;
return b.data.version.localeCompare(a.data.version, undefined, { numeric: true });
})
.slice(0, 5);
------
// src/pages/about.astro
import BaseLayout from "../layouts/BaseLayout.astro";
import PageTitle from "../components/PageTitle.astro";
import Badge from "../components/Badge.astro";
import Button from "../components/Button.astro";
// Imported Data & Icons
import { team, story } from "../data/aboutData";
import { Icons } from "../components/icons/AboutIcons";
------
// src/pages/ingredients.astro
import BaseLayout from "../layouts/BaseLayout.astro";
import PageTitle from "../components/PageTitle.astro";
import Button from "../components/Button.astro";
import IngredientLayers from "../components/ingredients/IngredientLayers.astro";
------
// src/pages/kitchen.astro
import BaseLayout from '../layouts/BaseLayout.astro';
import KitchenHeader from '../components/KitchenHeader.astro';
import KitchenFooter from '../components/KitchenFooter.astro';
import KitchenTitle from '../components/KitchenTitle.astro';
import { siteConfig } from '../data/siteConfig';
// 파일 트리 데이터
const fileTree = `
matcha-logic/
├── public/
│ ├── admin/ # Decap CMS (config.yml)
│ ├── images/ # Assets
│ └── _redirects # Routing Rules
├── src/
│ ├── components/ # UI Components
│ ├── content/ # Markdown Data
│ ├── layouts/ # Page Shells
│ └── pages/ # Routing Points
└── astro.config.mjs # Configuration
`;
------
// src/pages/changelog.astro
import BaseLayout from '../layouts/BaseLayout.astro';
import PageTitle from '../components/PageTitle.astro';
import Button from '../components/Button.astro';
import { getCollection, render } from 'astro:content';
import { manualBlogMapping, manualTastingMapping } from '../data/manualRelations';
import { typeLabels, changeTypeThemes, parseVersion } from '../data/changelogConfig';
const allUpdates = await getCollection('updates');
const allBlogs = await getCollection('blog');
const allTastings = await getCollection('tasting');
// 파일 이름 기준으로 내림차순(최근 v2.4.0이 맨 위) 정렬
const sortedUpdates = allUpdates.sort((a, b) => {
const versionA = parseVersion(a.id);
const versionB = parseVersion(b.id);
if (versionB[0] !== versionA[0]) return versionB[0] - versionA[0];
if (versionB[1] !== versionA[1]) return versionB[1] - versionA[1];
return versionB[2] - versionA[2];
});
const totalReleases = allUpdates.length;
const existingTypes = Object.keys(typeLabels).filter(t => t !== 'all' && sortedUpdates.some(u => u.data.type === t));
const countByType = sortedUpdates.reduce((acc, u) => {
acc[u.data.type] = (acc[u.data.type] || 0) + 1;
return acc;
}, {} as Record<string, number>);
// 연동된 블로그 및 테이스팅 노트 매핑 데이터 생성 (1:1 정규화 튜플)
const matrix = await Promise.all(
sortedUpdates.map(async (update) => {
const versionStr = update.data.version;
const { Content } = await render(update);
// 블로그 포스트 연동 탐색
let matchedBlog = allBlogs.find(blog =>
(update.data.relatedBlog && blog.id === update.data.relatedBlog) ||
(blog.data.relatedUpdate && blog.data.relatedUpdate === versionStr) ||
(manualBlogMapping[versionStr] && blog.id === manualBlogMapping[versionStr])
);
// 테이스팅 노트 연동 탐색 (버전 명세 비교 시 'v' 접두사 정규화)
const cleanVersion = versionStr.replace(/^v/, '');
let matchedTasting = allTastings.find(tasting => {
const cleanTastingVersion = tasting.data.version.replace(/^v/, '');
return cleanTastingVersion === cleanVersion ||
(manualTastingMapping[versionStr] && tasting.id === manualTastingMapping[versionStr]);
});
return {
update,
Content,
blog: matchedBlog,
tasting: matchedTasting
};
})
);
const getTypeBadge = (type: string) => {
return changeTypeThemes[type]?.badge || 'bg-gray-100 text-gray-600';
};
------
// src/pages/blog/[...slug].astro
import { getCollection, render } from "astro:content";
import BaseLayout from "../../layouts/BaseLayout.astro";
import RelatedPosts from "../../components/RelatedPosts.astro";
export async function getStaticPaths() {
const blogEntries = await getCollection("blog");
return blogEntries.map((entry) => ({
params: { slug: entry.id },
props: { entry },
}));
}
const { entry } = Astro.props;
const { Content } = await render(entry);
// Find related update if it exists
let relatedUpdateEntry = null;
if (entry.data.relatedUpdate) {
const updates = await getCollection("updates");
relatedUpdateEntry = updates.find((u) => u.data.version === entry.data.relatedUpdate);
}
let titleParts = [entry.data.title];
if (entry.data.title.includes("\n")) {
titleParts = entry.data.title.split("\n");
} else if (entry.data.title.includes(":")) {
const index = entry.data.title.indexOf(":");
titleParts = [
entry.data.title.substring(0, index + 1).trim(),
entry.data.title.substring(index + 1).trim()
];
}
const yyyy = entry.data.pubDate.getFullYear();
const mm = String(entry.data.pubDate.getMonth() + 1).padStart(2, "0");
const dd = String(entry.data.pubDate.getDate()).padStart(2, "0");
const formattedDate = `${yyyy}.${mm}.${dd}`;
---main*Logic Only
UTF-8Astro Core