122 lines
3.7 KiB
JavaScript
122 lines
3.7 KiB
JavaScript
const collectFrontmatter = (raw) => {
|
|
const lines = raw.split(/\r?\n/);
|
|
const meta = {};
|
|
let cursor = 0;
|
|
|
|
if (lines[0]?.trim() === '---') {
|
|
cursor = 1;
|
|
for (; cursor < lines.length; cursor += 1) {
|
|
const rawLine = lines[cursor];
|
|
const line = rawLine.trim();
|
|
if (line === '---') {
|
|
cursor += 1;
|
|
break;
|
|
}
|
|
if (!line) continue;
|
|
if (/^\s+/.test(rawLine)) continue;
|
|
if (!line.includes(':')) continue;
|
|
const [key, ...rest] = line.split(':');
|
|
meta[key.trim()] = rest.join(':').trim();
|
|
}
|
|
}
|
|
|
|
const body = lines.slice(cursor).join('\n').trim();
|
|
return { meta, body };
|
|
};
|
|
|
|
const extractTitle = (body) => {
|
|
const titleLine = body.split(/\r?\n/).find((line) => line.startsWith('# '));
|
|
return titleLine ? titleLine.replace(/^#\s+/, '').trim() : '';
|
|
};
|
|
|
|
const extractExcerpt = (body) => {
|
|
const lines = body.split(/\r?\n/);
|
|
for (const line of lines) {
|
|
if (!line.trim()) continue;
|
|
if (line.startsWith('#')) continue;
|
|
return line.trim();
|
|
}
|
|
return '';
|
|
};
|
|
|
|
const allowedTags = new Set(['일상', '개발', '공부', '아이디어', '기타']);
|
|
|
|
const normalizeTags = (value) => {
|
|
if (!value) return [];
|
|
const mapped = value
|
|
.split(',')
|
|
.map((tag) => tag.trim())
|
|
.filter(Boolean)
|
|
.map((tag) => {
|
|
const lower = tag.toLowerCase();
|
|
if (lower === 'diary' || lower === 'dailylog') return '일상';
|
|
if (lower === 'dev' || lower === 'development') return '개발';
|
|
if (lower === 'study') return '공부';
|
|
if (lower === 'idea' || lower === 'ideas') return '아이디어';
|
|
return tag;
|
|
});
|
|
|
|
const normalized = mapped.map((tag) => (allowedTags.has(tag) ? tag : '기타'));
|
|
return Array.from(new Set(normalized));
|
|
};
|
|
|
|
const normalizeTitle = (slug) =>
|
|
slug
|
|
.replace(/-/g, ' ')
|
|
.replace(/\b\w/g, (letter) => letter.toUpperCase());
|
|
|
|
const inferTagFromPath = (path) => {
|
|
const match = path.match(/\/blog\/([^/]+)\//);
|
|
if (!match) return '';
|
|
const folder = match[1];
|
|
if (folder === 'daily') return '일상';
|
|
if (folder === 'dev') return '개발';
|
|
if (folder === 'study') return '공부';
|
|
if (folder === 'ideas') return '아이디어';
|
|
return '기타';
|
|
};
|
|
|
|
const inferDateFromSlug = (slug) => {
|
|
const match = slug.match(/\d{4}-\d{2}-\d{2}/);
|
|
return match ? match[0] : '';
|
|
};
|
|
|
|
export const getBlogPosts = () => {
|
|
const modules = import.meta.glob('/src/content/blog/**/*.md', {
|
|
as: 'raw',
|
|
eager: true,
|
|
});
|
|
|
|
const posts = Object.entries(modules).map(([path, raw]) => {
|
|
const slug = path.split('/').pop().replace(/\.md$/, '');
|
|
const { meta, body } = collectFrontmatter(raw);
|
|
const inferredTag = inferTagFromPath(path);
|
|
const title = meta.title || extractTitle(body) || normalizeTitle(slug);
|
|
const excerpt = meta.excerpt || meta.description || extractExcerpt(body);
|
|
const date = meta.date || inferDateFromSlug(slug);
|
|
const baseTags = normalizeTags(meta.tags);
|
|
const categoryTags = normalizeTags(meta.category);
|
|
const tags = Array.from(
|
|
new Set([
|
|
...baseTags,
|
|
...categoryTags,
|
|
...(inferredTag ? [inferredTag] : []),
|
|
])
|
|
);
|
|
return {
|
|
slug,
|
|
title,
|
|
excerpt,
|
|
date,
|
|
tags,
|
|
body,
|
|
};
|
|
});
|
|
|
|
return posts.sort((a, b) => {
|
|
const aDate = Date.parse(a.date || '') || 0;
|
|
const bDate = Date.parse(b.date || '') || 0;
|
|
return bDate - aDate;
|
|
});
|
|
};
|