Fix: Replace all template placeholders globally

This commit is contained in:
2026-02-24 19:05:19 -08:00
parent 17a8a82483
commit a308bf7ab1

110
build.js
View File

@@ -1,68 +1,92 @@
import{readFileSync,writeFileSync,mkdirSync,cpSync,readdirSync,existsSync,statSync}from'fs' import { readFileSync, writeFileSync, mkdirSync, cpSync, readdirSync, existsSync, statSync } from 'fs'
import{join,extname}from'path' import { join } from 'path'
import matter from'gray-matter' import matter from 'gray-matter'
import{marked}from'marked' import { marked } from 'marked'
const ARTICLES='articles',DIST='dist',TEMPLATES='templates',STATIC='static' const ARTICLES = 'articles'
const DIST = 'dist'
const TEMPLATES = 'templates'
const STATIC = 'static'
const template=n=>readFileSync(join(TEMPLATES,n),'utf-8') const template = n => readFileSync(join(TEMPLATES, n), 'utf-8')
const articleTmpl=template('article.html') const articleTmpl = template('article.html')
const homeTmpl=template('home.html') const homeTmpl = template('home.html')
mkdirSync(DIST,{recursive:true}) const render = (tpl, data) =>
tpl.replace(/{{\s*([a-zA-Z0-9_]+)\s*}}/g, (_, key) => `${data[key] ?? ''}`)
if(existsSync(STATIC)) const fmtDateLong = d =>
cpSync(STATIC,DIST,{recursive:true}) d ? new Date(d).toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' }) : ''
const articles=[] const fmtDateIso = d => (d ? new Date(d).toISOString().split('T')[0] : '')
for(const slug of readdirSync(ARTICLES)){ mkdirSync(DIST, { recursive: true })
const dir=join(ARTICLES,slug)
if(!statSync(dir).isDirectory()) continue
const mdPath=join(dir,'index.md') if (existsSync(STATIC)) cpSync(STATIC, DIST, { recursive: true })
if(!existsSync(mdPath)) continue
const{data,content}=matter(readFileSync(mdPath,'utf-8')) const articles = []
const html=marked(content)
const outDir=join(DIST,slug) for (const slug of readdirSync(ARTICLES)) {
mkdirSync(outDir,{recursive:true}) const dir = join(ARTICLES, slug)
if (!statSync(dir).isDirectory()) continue
// copy co-located assets const mdPath = join(dir, 'index.md')
for(const f of readdirSync(dir)){ if (!existsSync(mdPath)) continue
if(f==='index.md') continue
cpSync(join(dir,f),join(outDir,f)) const { data, content } = matter(readFileSync(mdPath, 'utf-8'))
const html = marked(content)
const outDir = join(DIST, slug)
mkdirSync(outDir, { recursive: true })
for (const f of readdirSync(dir)) {
if (f === 'index.md') continue
cpSync(join(dir, f), join(outDir, f), { recursive: true })
} }
const page=articleTmpl const page = render(articleTmpl, {
.replace('{{title}}',data.title||slug) title: data.title || slug,
.replace('{{date}}',data.date?new Date(data.date).toLocaleDateString('en-US',{month:'long',day:'numeric',year:'numeric'}):'') date: fmtDateLong(data.date),
.replace('{{date_iso}}',data.date?new Date(data.date).toISOString().split('T')[0]:'') date_iso: fmtDateIso(data.date),
.replace('{{tags}}', (data.tags||[]).map(t=>`<span class="inline-flex items-center rounded-full border border-gray-200 bg-gray-50 px-2 py-0.5 text-xs font-medium text-gray-600 mono">${t}</span>`).join(' ')) tags: (data.tags || [])
.replace('{{excerpt}}',data.excerpt||'') .map(
.replace('{{slug}}',slug) t =>
.replace('{{body}}',html) `<span class="inline-flex items-center rounded-full border border-gray-200 bg-gray-50 px-2 py-0.5 text-xs font-medium text-gray-600 mono">${t}</span>`
)
.join(' '),
excerpt: data.excerpt || '',
slug,
body: html
})
writeFileSync(join(outDir,'index.html'),page) writeFileSync(join(outDir, 'index.html'), page)
articles.push({slug,...data}) articles.push({ slug, ...data })
console.log(`${slug}`) console.log(`${slug}`)
} }
articles.sort((a,b)=>new Date(b.date)-new Date(a.date)) articles.sort((a, b) => new Date(b.date) - new Date(a.date))
const articleListHtml=articles.map(a=>` const articleListHtml = articles
.map(
a => `
<a href="/${a.slug}/" class="block rounded-2xl border border-gray-200 bg-white shadow-sm hover:shadow-md transition-shadow overflow-hidden"> <a href="/${a.slug}/" class="block rounded-2xl border border-gray-200 bg-white shadow-sm hover:shadow-md transition-shadow overflow-hidden">
<div class="px-5 py-4"> <div class="px-5 py-4">
<div class="flex items-start justify-between gap-4"> <div class="flex items-start justify-between gap-4">
<h2 class="text-base font-semibold text-gray-900">${a.title}</h2> <h2 class="text-base font-semibold text-gray-900">${a.title}</h2>
<time datetime="${new Date(a.date).toISOString().split('T')[0]}" class="mono text-xs text-gray-400 whitespace-nowrap mt-0.5">${new Date(a.date).toLocaleDateString('en-US',{month:'short',day:'numeric'})}</time> <time datetime="${new Date(a.date).toISOString().split('T')[0]}" class="mono text-xs text-gray-400 whitespace-nowrap mt-0.5">${new Date(a.date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}</time>
</div> </div>
${a.excerpt?`<p class="text-sm text-gray-500 mt-1.5">${a.excerpt}</p>`:''} ${a.excerpt ? `<p class="text-sm text-gray-500 mt-1.5">${a.excerpt}</p>` : ''}
<div class="flex gap-1.5 mt-3">${(a.tags||[]).map(t=>`<span class="inline-flex items-center rounded-full border border-gray-200 bg-gray-50 px-2 py-0.5 text-xs font-medium text-gray-600 mono">${t}</span>`).join('')}</div> <div class="flex gap-1.5 mt-3">${(a.tags || [])
.map(
t =>
`<span class="inline-flex items-center rounded-full border border-gray-200 bg-gray-50 px-2 py-0.5 text-xs font-medium text-gray-600 mono">${t}</span>`
)
.join('')}</div>
</div> </div>
</a>`).join('\n') </a>`
)
.join('\n')
const home=homeTmpl.replace('{{articles}}',articleListHtml) const home = render(homeTmpl, { articles: articleListHtml })
writeFileSync(join(DIST,'index.html'),home) writeFileSync(join(DIST, 'index.html'), home)
console.log(`\n🏠 index.html → ${articles.length} article(s)`) console.log(`\n🏠 index.html → ${articles.length} article(s)`)