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 = ''; 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 }); }); // Handle contact form submission app.post('/contact', (req, res) => { const { name, email, subject, message } = req.body; // Validate form inputs (basic example) if (!name || !email || !subject || !message) { return res.render('contact', { title: 'Contact Raven Scott', msg: 'All fields are required.' }); } // Create email content const output = `

You have a new contact request from ${name}.

Contact Details

Message

${message}

`; // Set up Nodemailer transporter let transporter = nodemailer.createTransport({ host: process.env.SMTP_HOST, port: process.env.SMTP_PORT, secure: false, // true for 465, false for other ports auth: { user: process.env.EMAIL_USER, // Email user from environment variables pass: process.env.EMAIL_PASS, // Email password from environment variables }, tls: { rejectUnauthorized: false, }, }); // Set up email options let mailOptions = { from: `"${name}" `, to: process.env.RECEIVER_EMAIL, // Your email address to receive contact form submissions subject: subject, html: output, }; // Send email transporter.sendMail(mailOptions, (error, info) => { if (error) { console.error(error); return res.render('contact', { title: 'Contact Raven Scott', msg: 'An error occurred. Please try again.' }); } else { console.log('Email sent: ' + info.response); return res.render('contact', { title: 'Contact Raven Scott', msg: 'Your message has been sent successfully!' }); } }); }); // 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.redirect('/'); // Redirect to the home page if the blog post is not found } }); // Global 404 handler for any other unmatched routes app.use((req, res) => { res.redirect('/'); // Redirect to the home page for any 404 error }); // ================================ // Server Listening // ================================ const PORT = process.env.PORT || 8899; app.listen(PORT, () => { console.log(`Server running on http://localhost:${PORT}`); });