-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.js
More file actions
119 lines (102 loc) · 4.43 KB
/
Copy pathserver.js
File metadata and controls
119 lines (102 loc) · 4.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
require('dotenv').config();
const express = require('express');
const path = require('path');
const fs = require('fs');
const matter = require('gray-matter');
const { marked } = require('marked');
const { markedHighlight } = require('marked-highlight');
const hljs = require('highlight.js');
const app = express();
const PORT = process.env.PORT || 3000;
const BLOG_TITLE = process.env.BLOG_TITLE || 'Jake Hughes';
const SITE_URL = (process.env.SITE_URL || `http://localhost:${PORT}`).replace(/\/$/, '');
const POSTS_DIR = path.join(__dirname, 'posts');
marked.use(markedHighlight({
langPrefix: 'hljs language-',
highlight(code, lang) {
const language = hljs.getLanguage(lang) ? lang : 'plaintext';
return hljs.highlight(code, { language }).value;
}
}));
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));
app.use(express.static(path.join(__dirname, 'public')));
// Make siteUrl available to every template
app.use((req, res, next) => {
res.locals.siteUrl = SITE_URL;
next();
});
function getPosts() {
if (!fs.existsSync(POSTS_DIR)) return [];
return fs.readdirSync(POSTS_DIR)
.filter(f => f.endsWith('.md'))
.map(file => {
const raw = fs.readFileSync(path.join(POSTS_DIR, file), 'utf8');
const { data } = matter(raw);
return { ...data, slug: file.replace('.md', '') };
})
.sort((a, b) => new Date(b.date) - new Date(a.date));
}
function readingTime(content) {
const words = content.trim().split(/\s+/).length;
return Math.max(1, Math.ceil(words / 200));
}
// ── SEO files ─────────────────────────────────────────────────────────────────
app.get('/robots.txt', (req, res) => {
res.type('text/plain');
res.send(`User-agent: *\nAllow: /\nDisallow: /blog/*?embed=1\nSitemap: ${SITE_URL}/sitemap.xml\n`);
});
app.get('/sitemap.xml', (req, res) => {
const posts = getPosts();
const urls = [
{ loc: SITE_URL, priority: '1.0' },
{ loc: `${SITE_URL}/blog`, priority: '0.8' },
...posts.map(p => ({
loc: `${SITE_URL}/blog/${p.slug}`,
lastmod: p.date || '',
priority: '0.7'
}))
];
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${urls.map(u => ` <url>
<loc>${u.loc}</loc>${u.lastmod ? `\n <lastmod>${u.lastmod}</lastmod>` : ''}
<priority>${u.priority}</priority>
</url>`).join('\n')}
</urlset>`;
res.type('application/xml');
res.send(xml);
});
// ── Portfolio homepage ────────────────────────────────────────────────────────
app.get('/', (req, res) => {
const recentPosts = getPosts().slice(0, 3);
res.render('index', { recentPosts, blogTitle: BLOG_TITLE });
});
// ── Blog ──────────────────────────────────────────────────────────────────────
app.get('/blog', (req, res) => {
const posts = getPosts();
const categories = [...new Set(posts.map(p => p.category).filter(Boolean))];
const filter = req.query.category;
const filtered = filter ? posts.filter(p => p.category === filter) : posts;
res.render('blog', { posts: filtered, categories, activeCategory: filter || null, blogTitle: BLOG_TITLE });
});
app.get('/blog/:slug', (req, res) => {
const filePath = path.join(POSTS_DIR, `${req.params.slug}.md`);
if (!fs.existsSync(filePath)) return res.status(404).render('404', { blogTitle: BLOG_TITLE });
const raw = fs.readFileSync(filePath, 'utf8');
const { data, content } = matter(raw);
const view = req.query.embed === '1' ? 'post-embed' : 'post';
res.render(view, {
post: { ...data, slug: req.params.slug, readingTime: readingTime(content) },
content: marked(content),
blogTitle: BLOG_TITLE
});
});
// ── Watch & start ─────────────────────────────────────────────────────────────
fs.watch(POSTS_DIR, (event, filename) => {
if (filename?.endsWith('.md')) console.log(`[posts] ${event}: ${filename}`);
});
app.listen(PORT, () => {
console.log(`Running at http://localhost:${PORT}`);
console.log(`Watching ${POSTS_DIR} for changes...`);
});