feat: add blog system with static site generator
- Add blog posts in Markdown (_posts/) - Build script converts MD to HTML at container build time - First posts: building with AI lessons, quick start guide - AGENTS.md documents blog writing style (unixsheikh-inspired)
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -3,12 +3,14 @@ node_modules/
|
|||||||
.pnp
|
.pnp
|
||||||
.pnp.js
|
.pnp.js
|
||||||
docs/node_modules/
|
docs/node_modules/
|
||||||
|
www/node_modules/
|
||||||
|
|
||||||
# Build
|
# Build
|
||||||
dist/
|
dist/
|
||||||
build/
|
build/
|
||||||
docs/dist/
|
docs/dist/
|
||||||
docs/.astro/
|
docs/.astro/
|
||||||
|
www/blog/
|
||||||
|
|
||||||
# Data
|
# Data
|
||||||
data/
|
data/
|
||||||
|
|||||||
@@ -1,11 +1,26 @@
|
|||||||
FROM nginx:alpine
|
FROM node:20-alpine
|
||||||
|
|
||||||
COPY www/ /usr/share/nginx/html/
|
WORKDIR /app
|
||||||
COPY docker-entrypoint.d/ /docker-entrypoint.d/
|
|
||||||
|
|
||||||
|
# Install dependencies for blog build
|
||||||
|
COPY www/package.json ./www/package.json
|
||||||
|
RUN cd www && npm install --silent 2>/dev/null || true
|
||||||
|
|
||||||
|
# Copy www source files
|
||||||
|
COPY www/ /app/www/
|
||||||
|
|
||||||
|
# Build blog (processes _posts/*.md to blog/*.html)
|
||||||
|
RUN node www/build-blog.js
|
||||||
|
|
||||||
|
# Run envsubst on HTML files
|
||||||
RUN apk add --no-cache gettext && \
|
RUN apk add --no-cache gettext && \
|
||||||
chmod +x /docker-entrypoint.d/*.sh
|
chmod +x /docker-entrypoint.d/*.sh 2>/dev/null || true
|
||||||
|
|
||||||
|
# Copy entrypoint
|
||||||
|
COPY docker-entrypoint.d/ /docker-entrypoint.d/
|
||||||
|
RUN chmod +x /docker-entrypoint.d/*.sh
|
||||||
|
|
||||||
|
# Copy to nginx document root
|
||||||
|
COPY --from=0 /app/www/ /usr/share/nginx/html/
|
||||||
|
|
||||||
ENTRYPOINT ["/docker-entrypoint.d/30-envsubst.sh", "--", "nginx", "-g", "daemon off;"]
|
ENTRYPOINT ["/docker-entrypoint.d/30-envsubst.sh", "--", "nginx", "-g", "daemon off;"]
|
||||||
|
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
|
||||||
|
|||||||
120
www/AGENTS.md
Normal file
120
www/AGENTS.md
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
# DearDiary Website & Blog - Agent Instructions
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The DearDiary website is a static HTML site generated from Markdown blog posts. No frameworks, no build complexity.
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
www/
|
||||||
|
├── _posts/ # Blog posts in Markdown (source)
|
||||||
|
│ └── YYYY-MM-DD-slug.md
|
||||||
|
├── blog/ # Generated HTML (don't edit manually)
|
||||||
|
│ ├── index.html
|
||||||
|
│ └── YYYY-MM-DD-slug/
|
||||||
|
│ └── index.html
|
||||||
|
├── css/
|
||||||
|
│ └── styles.css
|
||||||
|
├── docs/ # Documentation (also static HTML)
|
||||||
|
├── js/
|
||||||
|
│ └── main.js
|
||||||
|
├── index.html # Main website (uses envsubst for templating)
|
||||||
|
├── build-blog.js # Blog generator script
|
||||||
|
└── package.json # npm deps (gray-matter for parsing)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd www
|
||||||
|
npm install
|
||||||
|
node build-blog.js
|
||||||
|
```
|
||||||
|
|
||||||
|
This converts `_posts/*.md` → `blog/*.html`.
|
||||||
|
|
||||||
|
## Blog Post Format
|
||||||
|
|
||||||
|
Frontmatter:
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
title: Your Post Title
|
||||||
|
date: 2026-03-27
|
||||||
|
author: Konrad Lother
|
||||||
|
excerpt: One sentence summary for the blog index
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tone & Style Guide
|
||||||
|
|
||||||
|
Read unixsheikh.com for inspiration. Key characteristics:
|
||||||
|
|
||||||
|
### Voice
|
||||||
|
- **Direct and authoritative** - Say what you mean, don't hedge unnecessarily
|
||||||
|
- **Personal opinion is welcome** - Write from a first-person perspective
|
||||||
|
- **Technical but accessible** - Explain complex topics simply
|
||||||
|
- **Not afraid to be contrarian** - Challenge conventional wisdom when you disagree with it
|
||||||
|
|
||||||
|
### Structure
|
||||||
|
- **Strong opening** - Hook the reader immediately
|
||||||
|
- **Clear sections** - Use headers to organize thoughts
|
||||||
|
- **Concrete examples** - Illustrate abstract concepts
|
||||||
|
- **Practical takeaways** - End with actionable advice
|
||||||
|
|
||||||
|
### What to Avoid
|
||||||
|
- Corporate/tech-blog speak ("leverage", "synergy", "cutting-edge")
|
||||||
|
- Unnecessary hedging ("might", "could potentially", "in some cases")
|
||||||
|
- Padding or fluff - every sentence should add value
|
||||||
|
- Being mean-spirited - critique ideas, not people
|
||||||
|
|
||||||
|
### Example Openers
|
||||||
|
|
||||||
|
Bad:
|
||||||
|
> "In today's fast-paced digital world, journaling has evolved..."
|
||||||
|
|
||||||
|
Good:
|
||||||
|
> "Traditional journaling apps ask you to write long entries at the end of the day. But let's be honest - who has time for that?"
|
||||||
|
|
||||||
|
### Length
|
||||||
|
- 500-1500 words is ideal
|
||||||
|
- If you're writing more, consider splitting into multiple posts
|
||||||
|
- Short posts can be 200-300 words if the idea is complete
|
||||||
|
|
||||||
|
## Topics to Cover
|
||||||
|
|
||||||
|
### DearDiary-Specific
|
||||||
|
- How to use features effectively
|
||||||
|
- Tips and tricks for better journaling
|
||||||
|
- Behind-the-scenes development stories
|
||||||
|
- Why AI-assisted journaling makes sense
|
||||||
|
- Privacy and self-hosting benefits
|
||||||
|
|
||||||
|
### General Tech Thoughts
|
||||||
|
- Software craftsmanship
|
||||||
|
- Human-AI collaboration
|
||||||
|
- Simplicity in development
|
||||||
|
- Critical thinking about tech trends
|
||||||
|
- The value of boring technology
|
||||||
|
|
||||||
|
### Personal/Philosophy
|
||||||
|
- Productivity and reflection
|
||||||
|
- Memory and identity
|
||||||
|
- The importance of记录 (record-keeping)
|
||||||
|
- Learning from daily life
|
||||||
|
|
||||||
|
## Post Ideas
|
||||||
|
|
||||||
|
1. Why capture events, not essays (the thesis behind DearDiary)
|
||||||
|
2. My journaling experiment - 30 days of DearDiary
|
||||||
|
3. What I learned building an app with AI
|
||||||
|
4. The case for boring technology in side projects
|
||||||
|
5. How to remember your life better
|
||||||
|
6. Self-hosting isn't hard, it's just unfamiliar
|
||||||
|
|
||||||
|
## Technical Notes
|
||||||
|
|
||||||
|
- The `build-blog.js` uses a simple regex-based Markdown converter
|
||||||
|
- For full Markdown support (tables, footnotes), consider using a proper parser
|
||||||
|
- Images should be hosted externally or added to the repo
|
||||||
|
- Code blocks work but syntax highlighting is limited
|
||||||
135
www/_posts/2026-03-27-building-deardiary-with-ai.md
Normal file
135
www/_posts/2026-03-27-building-deardiary-with-ai.md
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
---
|
||||||
|
title: Building DearDiary with AI - Lessons from Human-AI Collaboration
|
||||||
|
date: 2026-03-27
|
||||||
|
author: Konrad Lother
|
||||||
|
excerpt: What I learned from building a full-stack app using an AI coding assistant
|
||||||
|
---
|
||||||
|
|
||||||
|
# Building DearDiary with AI
|
||||||
|
|
||||||
|
## A Tale of Miscommunication and Debugging
|
||||||
|
|
||||||
|
I built DearDiary using an AI coding assistant. It was enlightening, frustrating, sometimes hilarious, and ultimately successful. Here's what I learned about human-AI collaboration.
|
||||||
|
|
||||||
|
## The Setup
|
||||||
|
|
||||||
|
DearDiary is a full-stack journaling app: Bun + Hono backend, React + Vite frontend, SQLite database, Docker deployment. Not trivial, but not rocket science either.
|
||||||
|
|
||||||
|
I gave the AI context about the project structure, my preferences, and let it work.
|
||||||
|
|
||||||
|
## The Problems We Hit
|
||||||
|
|
||||||
|
### 1. "It Should Work, But..."
|
||||||
|
|
||||||
|
The first major issue was the most classic: the AI made changes that *should* have worked according to its understanding, but didn't.
|
||||||
|
|
||||||
|
We consolidated environment variables into a single `.env` file with prefixes. The AI updated most references to `DATABASE_URL` → `BACKEND_DATABASE_URL`, but missed several:
|
||||||
|
- The Prisma schema
|
||||||
|
- The test helpers
|
||||||
|
- A variable in the healthcheck config
|
||||||
|
|
||||||
|
The app failed to start. The error message? Cryptic Prisma errors that took time to trace back to a simple env var mismatch.
|
||||||
|
|
||||||
|
**Lesson**: AI is great at systematic changes, but when it misses something, the gap is invisible to it. Always verify systematically.
|
||||||
|
|
||||||
|
### 2. The Disappearing Routes
|
||||||
|
|
||||||
|
The AI moved API routes into a separate file (`events.ts`) and mounted them at `/api/v1`. Simple, clean.
|
||||||
|
|
||||||
|
Except the routes were at `/api/v1/events` but the frontend was calling `/events`. The AI didn't catch that the mounting path was part of the route definition.
|
||||||
|
|
||||||
|
**Lesson**: AI understands code structure well, but context about how pieces connect across files is easily lost.
|
||||||
|
|
||||||
|
### 3. "I Fixed That"
|
||||||
|
|
||||||
|
Multiple times, the AI would say "Fixed!" and show the corrected code, but the actual file hadn't been changed. Or it would describe a solution that wasn't implemented.
|
||||||
|
|
||||||
|
This is the most dangerous mode of failure - confidence without execution.
|
||||||
|
|
||||||
|
**Lesson**: Never trust "fixed" without verification. Make it show you the actual changes.
|
||||||
|
|
||||||
|
### 4. Permission Denied
|
||||||
|
|
||||||
|
Docker entrypoint scripts kept failing with "permission denied". The AI knew about `chmod +x`, but the order of operations was wrong - file copied after chmod, or Docker cache serving old versions.
|
||||||
|
|
||||||
|
**Lesson**: AI knows facts, but execution order matters. Sometimes you need to walk through the sequence step by step.
|
||||||
|
|
||||||
|
### 5. The 404 Debugging Journey
|
||||||
|
|
||||||
|
Events returned 404. We checked:
|
||||||
|
1. Routes - correct
|
||||||
|
2. Mounting - fixed
|
||||||
|
3. Auth middleware - fixed
|
||||||
|
4. The actual problem: nginx port mapping. Port 3000 on the host was mapped directly to the backend, not through nginx. The frontend (served by nginx) couldn't reach the API.
|
||||||
|
|
||||||
|
**Lesson**: The AI focused on the obvious layers. The problem was in the infrastructure/configuration layer. AI needs explicit context about the full stack.
|
||||||
|
|
||||||
|
## What Went Well
|
||||||
|
|
||||||
|
Despite these issues, things also went surprisingly well:
|
||||||
|
|
||||||
|
- **Feature implementation**: The core features (event capture, AI generation, search) worked on first try
|
||||||
|
- **Consistency**: Once a pattern was established, the AI maintained it consistently
|
||||||
|
- **Refactoring**: Moving from multiple `.env` files to one was smooth after the initial issues
|
||||||
|
- **Documentation**: README updates, code comments, and AGENTS.md were accurate
|
||||||
|
|
||||||
|
## The Communication Patterns That Worked
|
||||||
|
|
||||||
|
### Be Specific About Failures
|
||||||
|
Instead of "it doesn't work", I'd say:
|
||||||
|
> "Events endpoint returns 404, checked docker logs and the route is registered"
|
||||||
|
|
||||||
|
The more context, the better the fix.
|
||||||
|
|
||||||
|
### Ask for Verification
|
||||||
|
> "Show me the exact changes you're making before committing"
|
||||||
|
|
||||||
|
This caught the "I said I fixed it" problem.
|
||||||
|
|
||||||
|
### Break Down Complex Changes
|
||||||
|
Instead of "consolidate all env vars", we did it in stages:
|
||||||
|
1. List all current env vars
|
||||||
|
2. Decide on naming convention
|
||||||
|
3. Update backend
|
||||||
|
4. Update frontend
|
||||||
|
5. Update docker-compose
|
||||||
|
6. Verify
|
||||||
|
|
||||||
|
### State What You Know Works
|
||||||
|
> "Previous similar changes worked with `docker compose build && docker compose up -d`"
|
||||||
|
|
||||||
|
Context about what has worked before helps the AI avoid untested approaches.
|
||||||
|
|
||||||
|
## The Meta-Lesson
|
||||||
|
|
||||||
|
Building with AI is like working with a very knowledgeable junior developer who:
|
||||||
|
- Has read every Stack Overflow post
|
||||||
|
- Can write code faster than you can type
|
||||||
|
- Sometimes confidently does the wrong thing
|
||||||
|
- Needs supervision, especially for changes spanning multiple files
|
||||||
|
- Gets better with clearer instructions
|
||||||
|
|
||||||
|
The key insight: **Your job becomes managing the AI, not just writing code.** You need to:
|
||||||
|
1. Provide good context
|
||||||
|
2. Verify systematically
|
||||||
|
3. Catch the invisible gaps
|
||||||
|
4. Maintain the mental model of the system
|
||||||
|
|
||||||
|
## What I'd Do Differently
|
||||||
|
|
||||||
|
1. **Track changes more carefully** - Use a changelog when AI makes changes, not just git diff
|
||||||
|
2. **Test incrementally** - Don't let the AI make 20 changes before testing
|
||||||
|
3. **Be clearer about expectations** - "This should work out of the box" is less clear than explicit test criteria
|
||||||
|
4. **Document the debugging journey** - The process of finding issues is valuable context for future fixes
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
DearDiary is live. The AI and I built it together, argued about typos in environment variables, debugged at 2am, and shipped something I'm proud of.
|
||||||
|
|
||||||
|
Human-AI collaboration isn't about replacing programmers. It's about amplifying what humans do well (context, judgment, verification) with what AI does well (speed, consistency, pattern matching).
|
||||||
|
|
||||||
|
The future is not "AI replaces developers." It's "developers who use AI replace developers who don't."
|
||||||
|
|
||||||
|
Now go build something with AI. Just keep an eye on those env vars.
|
||||||
|
|
||||||
|
*— Konrad*
|
||||||
59
www/_posts/2026-03-27-quick-start-guide.md
Normal file
59
www/_posts/2026-03-27-quick-start-guide.md
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
---
|
||||||
|
title: Quick Start Guide
|
||||||
|
date: 2026-03-26
|
||||||
|
author: Konrad Lother
|
||||||
|
excerpt: How to get started with DearDiary in 5 minutes
|
||||||
|
---
|
||||||
|
|
||||||
|
# Quick Start Guide
|
||||||
|
|
||||||
|
Getting started with DearDiary takes about 5 minutes. Here's how:
|
||||||
|
|
||||||
|
## 1. Create an Event
|
||||||
|
|
||||||
|
Press **Ctrl+J** anywhere in the app to open the Quick Add widget. Type your event and press Enter.
|
||||||
|
|
||||||
|
That's it. It takes 3 seconds.
|
||||||
|
|
||||||
|
Try these:
|
||||||
|
- "Had coffee with Sarah at the new cafe downtown"
|
||||||
|
- "Finished the chapter on machine learning"
|
||||||
|
- "Rain started around 3pm, got soaked walking back"
|
||||||
|
|
||||||
|
## 2. Add More Events
|
||||||
|
|
||||||
|
Throughout your day, capture anything that feels worth remembering:
|
||||||
|
- Meetings and conversations
|
||||||
|
- Meals and what you ate
|
||||||
|
- Exercise and how you felt
|
||||||
|
- Thoughts and ideas
|
||||||
|
- Photos and voice memos
|
||||||
|
|
||||||
|
Don't overthink it. A short note is better than nothing.
|
||||||
|
|
||||||
|
## 3. Generate Your Diary
|
||||||
|
|
||||||
|
When you're ready, click the **Generate** button on today's page. AI reads all your events and writes a narrative diary entry.
|
||||||
|
|
||||||
|
You can:
|
||||||
|
- Regenerate with different instructions
|
||||||
|
- Add context by including previous days' diaries
|
||||||
|
- Edit the generated diary (but the events stay locked)
|
||||||
|
|
||||||
|
## 4. Review and Reflect
|
||||||
|
|
||||||
|
Read your generated diary. Does it capture the essence of your day? If not, regenerate with instructions like:
|
||||||
|
|
||||||
|
> "Focus more on the interesting conversations I had"
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
> "Make it more concise, highlight the key moments"
|
||||||
|
|
||||||
|
## Pro Tips
|
||||||
|
|
||||||
|
- **Be specific**: "Lunch with Marcus, talked about his new hiking trip to Patagonia" beats "Had lunch"
|
||||||
|
- **Capture emotions**: "Felt anxious before the presentation" is more interesting than "Presented to team"
|
||||||
|
- **Use voice**: Record voice memos while driving or walking - very efficient
|
||||||
|
|
||||||
|
That's it. Happy journaling!
|
||||||
200
www/build-blog.js
Normal file
200
www/build-blog.js
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const matter = require('gray-matter');
|
||||||
|
|
||||||
|
const postsDir = path.join(__dirname, '_posts');
|
||||||
|
const outputDir = path.join(__dirname, 'blog');
|
||||||
|
|
||||||
|
// Ensure output directory exists
|
||||||
|
if (!fs.existsSync(outputDir)) {
|
||||||
|
fs.mkdirSync(outputDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all markdown files
|
||||||
|
const files = fs.readdirSync(postsDir).filter(f => f.endsWith('.md'));
|
||||||
|
|
||||||
|
// Parse and generate HTML for each post
|
||||||
|
const posts = files.map(file => {
|
||||||
|
const content = fs.readFileSync(path.join(postsDir, file), 'utf-8');
|
||||||
|
const { data: frontmatter, content: markdown } = matter(content);
|
||||||
|
|
||||||
|
// Simple markdown to HTML conversion
|
||||||
|
let html = markdown
|
||||||
|
// Headers
|
||||||
|
.replace(/^### (.*$)/gim, '<h3>$1</h3>')
|
||||||
|
.replace(/^## (.*$)/gim, '<h2>$1</h2>')
|
||||||
|
.replace(/^# (.*$)/gim, '<h1>$1</h1>')
|
||||||
|
// Bold
|
||||||
|
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
||||||
|
// Italic
|
||||||
|
.replace(/\*(.*?)\*/g, '<em>$1</em>')
|
||||||
|
// Code blocks
|
||||||
|
.replace(/```(\w*)\n([\s\S]*?)```/g, '<pre><code class="$1">$2</code></pre>')
|
||||||
|
// Inline code
|
||||||
|
.replace(/`(.*?)`/g, '<code>$1</code>')
|
||||||
|
// Links
|
||||||
|
.replace(/\[(.*?)\]\((.*?)\)/g, '<a href="$2">$1</a>')
|
||||||
|
// Images
|
||||||
|
.replace(/!\[(.*?)\]\((.*?)\)/g, '<img src="$2" alt="$1"/>')
|
||||||
|
// Line breaks
|
||||||
|
.replace(/\n\n/g, '</p><p>')
|
||||||
|
// Lists
|
||||||
|
.replace(/^\- (.*$)/gim, '<li>$1</li>')
|
||||||
|
.replace(/(<li>.*<\/li>)/s, '<ul>$1</ul>');
|
||||||
|
|
||||||
|
// Wrap in paragraph
|
||||||
|
html = `<p>${html}</p>`;
|
||||||
|
|
||||||
|
// Clean up empty paragraphs
|
||||||
|
html = html.replace(/<p><\/p>/g, '');
|
||||||
|
|
||||||
|
const slug = file.replace('.md', '');
|
||||||
|
const url = `/blog/${slug}/`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...frontmatter,
|
||||||
|
slug,
|
||||||
|
url,
|
||||||
|
html,
|
||||||
|
date: frontmatter.date ? frontmatter.date.toISOString().split('T')[0] : ''
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sort posts by date (newest first)
|
||||||
|
posts.sort((a, b) => new Date(b.date) - new Date(a.date));
|
||||||
|
|
||||||
|
const relativePath = process.env.GIT_URL ? '' : '../';
|
||||||
|
|
||||||
|
// Generate index page
|
||||||
|
const indexHtml = `<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Blog - DearDiary</title>
|
||||||
|
<link rel="stylesheet" href="${relativePath}css/styles.css">
|
||||||
|
<style>
|
||||||
|
.blog-container { max-width: 800px; margin: 0 auto; padding: 2rem; }
|
||||||
|
.blog-header { margin-bottom: 2rem; }
|
||||||
|
.blog-header h1 { font-size: 2rem; margin-bottom: 0.5rem; }
|
||||||
|
.blog-list { list-style: none; padding: 0; }
|
||||||
|
.blog-list li { margin-bottom: 1.5rem; padding-bottom: 1.5rem; border-bottom: 1px solid #334155; }
|
||||||
|
.blog-list h2 { margin: 0 0 0.5rem; font-size: 1.25rem; }
|
||||||
|
.blog-list h2 a { color: inherit; text-decoration: none; }
|
||||||
|
.blog-list h2 a:hover { color: #a855f7; }
|
||||||
|
.blog-meta { color: #64748b; font-size: 0.875rem; margin-bottom: 0.5rem; }
|
||||||
|
.blog-excerpt { color: #94a3b8; }
|
||||||
|
.blog-back { display: inline-block; margin-bottom: 1rem; color: #a855f7; text-decoration: none; }
|
||||||
|
.blog-back:hover { text-decoration: underline; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="navbar">
|
||||||
|
<div class="nav-container">
|
||||||
|
<a href="${relativePath}index.html" class="logo">
|
||||||
|
<svg width="32" height="32" viewBox="0 0 100 100"><defs><linearGradient id="g" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" style="stop-color:#6d28d9"/><stop offset="100%" style="stop-color:#4c1d95"/></linearGradient></defs><rect width="100" height="100" rx="20" fill="url(#g)"/><path d="M25 25 L75 25 L75 80 L25 80 Z" fill="none" stroke="white" stroke-width="3"/><path d="M35 40 L65 40 M35 50 L65 50 M35 60 L55 60" stroke="white" stroke-width="2"/></svg>
|
||||||
|
DearDiary
|
||||||
|
</a>
|
||||||
|
<div class="nav-links">
|
||||||
|
<a href="${relativePath}index.html">Home</a>
|
||||||
|
<a href="${relativePath}docs/">Docs</a>
|
||||||
|
<a href="${relativePath}blog/">Blog</a>
|
||||||
|
<a href="${relativePath}index.html" class="btn btn-primary">Join Free Alpha</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="blog-container">
|
||||||
|
<div class="blog-header">
|
||||||
|
<h1>Blog</h1>
|
||||||
|
<p style="color: #94a3b8;">Updates, tutorials, and thoughts on AI-powered journaling.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="blog-list">
|
||||||
|
${posts.map(post => `
|
||||||
|
<li>
|
||||||
|
<h2><a href="${post.url}">${post.title}</a></h2>
|
||||||
|
<div class="blog-meta">${post.date}${post.author ? ` · ${post.author}` : ''}</div>
|
||||||
|
${post.excerpt ? `<p class="blog-excerpt">${post.excerpt}</p>` : ''}
|
||||||
|
</li>
|
||||||
|
`).join('')}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>`;
|
||||||
|
|
||||||
|
fs.writeFileSync(path.join(outputDir, 'index.html'), indexHtml);
|
||||||
|
|
||||||
|
// Generate individual post pages
|
||||||
|
posts.forEach(post => {
|
||||||
|
const postDir = path.join(outputDir, post.slug);
|
||||||
|
if (!fs.existsSync(postDir)) {
|
||||||
|
fs.mkdirSync(postDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const postRelativePath = process.env.GIT_URL ? '' : '../../';
|
||||||
|
|
||||||
|
const postHtml = `<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>${post.title} - DearDiary Blog</title>
|
||||||
|
<link rel="stylesheet" href="${postRelativePath}css/styles.css">
|
||||||
|
<style>
|
||||||
|
.blog-container { max-width: 700px; margin: 0 auto; padding: 2rem; }
|
||||||
|
.blog-back { display: inline-block; margin-bottom: 1rem; color: #a855f7; text-decoration: none; }
|
||||||
|
.blog-back:hover { text-decoration: underline; }
|
||||||
|
.blog-header { margin-bottom: 2rem; padding-bottom: 1rem; border-bottom: 1px solid #334155; }
|
||||||
|
.blog-header h1 { margin: 0 0 0.5rem; }
|
||||||
|
.blog-meta { color: #64748b; font-size: 0.875rem; }
|
||||||
|
.blog-content h1, .blog-content h2, .blog-content h3 { margin-top: 1.5em; margin-bottom: 0.5em; color: #e2e8f0; }
|
||||||
|
.blog-content p { margin-bottom: 1em; line-height: 1.7; color: #cbd5e1; }
|
||||||
|
.blog-content a { color: #a855f7; }
|
||||||
|
.blog-content a:hover { text-decoration: underline; }
|
||||||
|
.blog-content code { background: #1e293b; padding: 0.2em 0.4em; border-radius: 4px; font-size: 0.9em; }
|
||||||
|
.blog-content pre { background: #1e293b; padding: 1em; border-radius: 8px; overflow-x: auto; margin: 1em 0; }
|
||||||
|
.blog-content pre code { background: none; padding: 0; }
|
||||||
|
.blog-content ul, .blog-content ol { margin: 1em 0; padding-left: 1.5em; color: #cbd5e1; }
|
||||||
|
.blog-content li { margin-bottom: 0.5em; line-height: 1.6; }
|
||||||
|
.blog-content img { max-width: 100%; height: auto; border-radius: 8px; margin: 1em 0; }
|
||||||
|
.blog-content blockquote { border-left: 3px solid #a855f7; padding-left: 1em; margin: 1em 0; color: #94a3b8; font-style: italic; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="navbar">
|
||||||
|
<div class="nav-container">
|
||||||
|
<a href="${postRelativePath}index.html" class="logo">
|
||||||
|
<svg width="32" height="32" viewBox="0 0 100 100"><defs><linearGradient id="g" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" style="stop-color:#6d28d9"/><stop offset="100%" style="stop-color:#4c1d95"/></linearGradient></defs><rect width="100" height="100" rx="20" fill="url(#g)"/><path d="M25 25 L75 25 L75 80 L25 80 Z" fill="none" stroke="white" stroke-width="3"/><path d="M35 40 L65 40 M35 50 L65 50 M35 60 L55 60" stroke="white" stroke-width="2"/></svg>
|
||||||
|
DearDiary
|
||||||
|
</a>
|
||||||
|
<div class="nav-links">
|
||||||
|
<a href="${postRelativePath}index.html">Home</a>
|
||||||
|
<a href="${postRelativePath}docs/">Docs</a>
|
||||||
|
<a href="${postRelativePath}blog/">Blog</a>
|
||||||
|
<a href="${postRelativePath}index.html" class="btn btn-primary">Join Free Alpha</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="blog-container">
|
||||||
|
<a href="${postRelativePath}blog/" class="blog-back">← Back to Blog</a>
|
||||||
|
|
||||||
|
<article>
|
||||||
|
<header class="blog-header">
|
||||||
|
<h1>${post.title}</h1>
|
||||||
|
<div class="blog-meta">${post.date}${post.author ? ` · ${post.author}` : ''}</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="blog-content">
|
||||||
|
${post.html}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>`;
|
||||||
|
|
||||||
|
fs.writeFileSync(path.join(postDir, 'index.html'), postHtml);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Generated ${posts.length} blog posts.`);
|
||||||
123
www/package-lock.json
generated
Normal file
123
www/package-lock.json
generated
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
{
|
||||||
|
"name": "deardiary-website",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "deardiary-website",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"gray-matter": "^4.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/argparse": {
|
||||||
|
"version": "1.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||||
|
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"sprintf-js": "~1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esprima": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"bin": {
|
||||||
|
"esparse": "bin/esparse.js",
|
||||||
|
"esvalidate": "bin/esvalidate.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/extend-shallow": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"is-extendable": "^0.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/gray-matter": {
|
||||||
|
"version": "4.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz",
|
||||||
|
"integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"js-yaml": "^3.13.1",
|
||||||
|
"kind-of": "^6.0.2",
|
||||||
|
"section-matter": "^1.0.0",
|
||||||
|
"strip-bom-string": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/is-extendable": {
|
||||||
|
"version": "0.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
|
||||||
|
"integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/js-yaml": {
|
||||||
|
"version": "3.14.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
|
||||||
|
"integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"argparse": "^1.0.7",
|
||||||
|
"esprima": "^4.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"js-yaml": "bin/js-yaml.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/kind-of": {
|
||||||
|
"version": "6.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||||
|
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/section-matter": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"extend-shallow": "^2.0.1",
|
||||||
|
"kind-of": "^6.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sprintf-js": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/strip-bom-string": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
www/package.json
Normal file
7
www/package.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"name": "deardiary-website",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"gray-matter": "^4.0.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user