ravenscott-blog/app.js

125 lines
4.0 KiB
JavaScript
Raw Normal View History

2024-09-16 07:53:26 -04:00
require('dotenv').config(); // Load environment variables
const express = require('express');
const path = require('path');
const fs = require('fs');
const { marked } = require('marked');
const nodemailer = require('nodemailer');
const hljs = require('highlight.js');
const app = express();
// Set options for marked to use highlight.js for syntax highlighting
marked.setOptions({
highlight: function (code, language) {
// Check if the language is valid
const validLanguage = hljs.getLanguage(language) ? language : 'plaintext';
return hljs.highlight(validLanguage, code).value;
}
});
// Set EJS as templating engine
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));
// Middleware to parse URL-encoded bodies (form submissions)
app.use(express.urlencoded({ extended: false }));
// Serve static files (CSS, Images)
app.use(express.static(path.join(__dirname, 'public')));
// Function to load and parse markdown files and extract lead
function loadMarkdownWithLead(file) {
const markdownContent = fs.readFileSync(path.join(__dirname, 'markdown', file), 'utf-8');
let lead = '';
let contentMarkdown = markdownContent;
// Detect and extract the lead section
const leadKeyword = '<!-- lead -->';
if (contentMarkdown.includes(leadKeyword)) {
const [beforeLead, afterLead] = contentMarkdown.split(leadKeyword);
// Extract the first paragraph after the lead keyword
lead = afterLead.split('\n').find(line => line.trim() !== '').trim();
// Remove the lead from the main content
contentMarkdown = beforeLead + afterLead.replace(lead, '').trim();
}
// Convert markdown to HTML
const contentHtml = marked.parse(contentMarkdown);
return { contentHtml, lead };
}
// Function to convert a title (with spaces) into a URL-friendly slug (with dashes)
function titleToSlug(title) {
return title.replace(/\s+/g, '-').toLowerCase(); // Always lowercase the slug
}
// Function to convert a slug (with dashes) back into a readable title (with spaces)
function slugToTitle(slug) {
return slug.replace(/-/g, ' ');
}
// Function to load all blog posts
function getAllBlogPosts() {
const blogFiles = fs.readdirSync(path.join(__dirname, 'markdown')).filter(file => file.endsWith('.md'));
return blogFiles.map(file => {
const title = file.replace('.md', '').replace(/-/g, ' '); // Keep original casing for title
const slug = titleToSlug(title); // Convert title to slug (lowercase)
return {
title, // Original casing title
slug
};
});
}
// Home Route (Blog Home)
app.get('/', (req, res) => {
const blogPosts = getAllBlogPosts();
res.render('index', { title: 'Raven Scott Blog', blogPosts });
});
// About Route
app.get('/about', (req, res) => {
res.render('about', { title: 'About Raven Scott' });
});
// Display the Request a Quote form
app.get('/contact', (req, res) => {
res.render('contact', { title: 'Contact Raven Scott', msg: undefined });
});
// Blog Post Route
app.get('/blog/:slug', (req, res) => {
const slug = req.params.slug;
const markdownFile = fs.readdirSync(path.join(__dirname, 'markdown'))
.find(file => titleToSlug(file.replace('.md', '')) === slug);
if (markdownFile) {
const originalTitle = markdownFile.replace('.md', ''); // Original title with casing
const blogPosts = getAllBlogPosts();
const { contentHtml, lead } = loadMarkdownWithLead(markdownFile);
res.render('blog-post', {
title: originalTitle, // Use the original title with casing
content: contentHtml,
lead: lead,
blogPosts
});
} else {
res.status(404).render('404', { title: 'Post not found' });
}
});
// Request a Quote form remains unchanged
// ================================
// Server Listening
// ================================
2024-09-16 07:55:16 -04:00
const PORT = process.env.PORT || 8899;
2024-09-16 07:53:26 -04:00
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});