Welcome to My Blog
+This is a sample blog post written in Markdown.
+Features
+-
+
- Easy to write +
- Converts to clean HTML +
- Supports bold, italic, and
inline code
+
Code Example
+console.log("Hello, world!");
+
+
+diff --git a/.gitignore b/.gitignore index 296c6fe..78fcba0 100755 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ # .gitignore: - +static # macOS .DS_Store diff --git a/build-static.js b/build-static.js new file mode 100644 index 0000000..75f6d28 --- /dev/null +++ b/build-static.js @@ -0,0 +1,101 @@ +// build-static.js +const fs = require("fs").promises; +const path = require("path"); +const matter = require("gray-matter"); +const { marked } = require("marked"); +const handlebars = require("handlebars"); + +const postsDir = path.join(__dirname, "posts"); +const outputDir = path.join(__dirname, "static"); + +// Load and compile templates +async function loadTemplate(name) { + const file = await fs.readFile( + path.join(__dirname, "src", "views", "pages", `${name}.handlebars`), + "utf8" + ); + return handlebars.compile(file); +} + +async function getAllPosts() { + const years = await fs.readdir(postsDir); + let posts = []; + + for (const year of years) { + const yearDir = path.join(postsDir, year); + const months = await fs.readdir(yearDir); + + for (const month of months) { + const monthDir = path.join(yearDir, month); + const files = await fs.readdir(monthDir); + + for (const file of files) { + if (!file.endsWith(".md")) continue; + const filePath = path.join(monthDir, file); + const contentRaw = await fs.readFile(filePath, "utf8"); + const { data: frontmatter, content } = matter(contentRaw); + posts.push({ + year, + month, + name: path.basename(file, ".md"), + frontmatter, + content, + }); + } + } + } + + return posts; +} + +async function build() { + // Prepare output directories + await fs.rm(outputDir, { recursive: true, force: true }); + await fs.mkdir(outputDir, { recursive: true }); + + // Load templates + const postTemplate = await loadTemplate("post"); + const homeTemplate = await loadTemplate("home"); + const errorTemplate = await loadTemplate("error"); + + // Build posts pages + const posts = await getAllPosts(); + + for (const post of posts) { + const htmlContent = marked(post.content); + const context = { + title: post.frontmatter.title, + date: post.frontmatter.date, + author: post.frontmatter.author, + content: htmlContent, + }; + + const html = postTemplate(context); + const outDir = path.join(outputDir, "post", post.year, post.month); + await fs.mkdir(outDir, { recursive: true }); + await fs.writeFile(path.join(outDir, `${post.name}.html`), html, "utf8"); + } + + // Build home page + const homeContext = { + title: "Blog Home", + content: "Welcome to the blog.", + }; + const homeHtml = homeTemplate(homeContext); + await fs.writeFile(path.join(outputDir, "index.html"), homeHtml, "utf8"); + + // Optionally build error page + const errorContext = { + statusCode: 404, + title: "Not Found", + message: "The requested blog post could not be found.", + content: "", + }; + const errorHtml = errorTemplate(errorContext); + await fs.writeFile(path.join(outputDir, "404.html"), errorHtml, "utf8"); +} + +build().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/public/css/styles.css b/public/css/styles.css new file mode 100644 index 0000000..7d70a75 --- /dev/null +++ b/public/css/styles.css @@ -0,0 +1,164 @@ +/* Reset */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +/* Base styles */ +html, body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background-color: #f8f9fa; + color: #212529; + min-height: 100vh; + line-height: 1.6; +} + +/* Container utility */ +.container { + max-width: 800px; + margin: 0 auto; + padding: 1rem 1.5rem; +} + +/* Header */ +header { + background-color: #2c3e50; + color: #ecf0f1; + box-shadow: 0 2px 5px rgba(0,0,0,0.1); +} + +header .container { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 0.5rem; + padding: 1rem 0; +} + +header h1 { + font-size: 1.8rem; + font-weight: 700; +} + +header nav { + display: flex; + gap: 1rem; +} + +header nav a { + color: #ecf0f1; + text-decoration: none; + font-weight: 600; + padding: 0.3rem 0.6rem; + border-radius: 3px; + transition: background-color 0.2s ease-in-out; +} + +header nav a:hover, +header nav a:focus { + background-color: #34495e; +} + +/* Layout */ +.layout { + display: flex; + max-width: 1200px; + margin: 2rem auto; + padding: 0 1.5rem; + gap: 2rem; +} + +/* Sidebar */ +.sidebar { + width: 260px; + background-color: #ffffff; + border-right: 1px solid #e0e0e0; + padding: 1.5rem; + font-size: 0.95rem; + box-shadow: 2px 0 5px rgba(0, 0, 0, 0.05); +} +/* .sidebar nav .menu-year { + font-weight: bold; + margin-bottom: 1rem; + font-size: 1.1rem; +} */ +.sidebar .menu-year { + font-weight: 700; + font-size: 1.2rem; + color: #333333; + margin-bottom: 1rem; + border-bottom: 2px solid #2c3e50; + padding-bottom: 0.5rem; + text-transform: uppercase; + letter-spacing: 1px; +} + +.sidebar nav ul { + list-style: none; /* Remove default bullets */ + padding-left: 0; /* Remove indent */ +} + +.sidebar .menu-month { + margin-bottom: 1rem; + font-weight: 600; + color: #555555; +} + +.sidebar .menu-month ul { + list-style: none; + padding-left: 1rem; + margin-top: 0.3rem; +} + +.sidebar .menu-month li a { + color: #2c3e50; + text-decoration: none; + display: block; + padding: 0.2rem 0; + transition: color 0.2s ease-in-out; +} + +.sidebar .menu-month li a:hover { + color: #1a73e8; + text-decoration: underline; +} +/* Main content */ +main.container { + flex: 1; + background-color: #ffffff; + padding: 2rem; + border-radius: 6px; + box-shadow: 0 2px 6px rgba(0,0,0,0.05); +} + +main h2 { + font-size: 1.5rem; + font-weight: 600; + margin-bottom: 1rem; + border-bottom: 2px solid #2c3e50; + padding-bottom: 0.3rem; +} + +main article p { + font-size: 1rem; + color: #444; +} + +/* Responsive */ +@media (max-width: 900px) { + .layout { + flex-direction: column; + } + + .sidebar { + width: 100%; + border-right: none; + border-bottom: 1px solid #e0e0e0; + margin-bottom: 1rem; + } + + main.container { + margin: 0; + } +} diff --git a/src/middleware/index.js b/src/middleware/index.js index 9e0e3c2..bccf674 100644 --- a/src/middleware/index.js +++ b/src/middleware/index.js @@ -24,7 +24,7 @@ app.use(morganError); app.use(loggingMiddleware); // app.use(helmet()); // Sets secure HTTP headers. Prevents common attacks. - app.use("/static", express.static("static")); + app.use("/static", express.static("public")); app.use(bodyParser.urlencoded({ extended: true })); app.use(routes); app.use(errorHandler); diff --git a/static/404.html b/static/404.html new file mode 100644 index 0000000..166c358 --- /dev/null +++ b/static/404.html @@ -0,0 +1,3 @@ +
The requested blog post could not be found.
+ diff --git a/static/css/styles.css b/static/css/styles.css deleted file mode 100644 index 7d70a75..0000000 --- a/static/css/styles.css +++ /dev/null @@ -1,164 +0,0 @@ -/* Reset */ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -/* Base styles */ -html, body { - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; - background-color: #f8f9fa; - color: #212529; - min-height: 100vh; - line-height: 1.6; -} - -/* Container utility */ -.container { - max-width: 800px; - margin: 0 auto; - padding: 1rem 1.5rem; -} - -/* Header */ -header { - background-color: #2c3e50; - color: #ecf0f1; - box-shadow: 0 2px 5px rgba(0,0,0,0.1); -} - -header .container { - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 0.5rem; - padding: 1rem 0; -} - -header h1 { - font-size: 1.8rem; - font-weight: 700; -} - -header nav { - display: flex; - gap: 1rem; -} - -header nav a { - color: #ecf0f1; - text-decoration: none; - font-weight: 600; - padding: 0.3rem 0.6rem; - border-radius: 3px; - transition: background-color 0.2s ease-in-out; -} - -header nav a:hover, -header nav a:focus { - background-color: #34495e; -} - -/* Layout */ -.layout { - display: flex; - max-width: 1200px; - margin: 2rem auto; - padding: 0 1.5rem; - gap: 2rem; -} - -/* Sidebar */ -.sidebar { - width: 260px; - background-color: #ffffff; - border-right: 1px solid #e0e0e0; - padding: 1.5rem; - font-size: 0.95rem; - box-shadow: 2px 0 5px rgba(0, 0, 0, 0.05); -} -/* .sidebar nav .menu-year { - font-weight: bold; - margin-bottom: 1rem; - font-size: 1.1rem; -} */ -.sidebar .menu-year { - font-weight: 700; - font-size: 1.2rem; - color: #333333; - margin-bottom: 1rem; - border-bottom: 2px solid #2c3e50; - padding-bottom: 0.5rem; - text-transform: uppercase; - letter-spacing: 1px; -} - -.sidebar nav ul { - list-style: none; /* Remove default bullets */ - padding-left: 0; /* Remove indent */ -} - -.sidebar .menu-month { - margin-bottom: 1rem; - font-weight: 600; - color: #555555; -} - -.sidebar .menu-month ul { - list-style: none; - padding-left: 1rem; - margin-top: 0.3rem; -} - -.sidebar .menu-month li a { - color: #2c3e50; - text-decoration: none; - display: block; - padding: 0.2rem 0; - transition: color 0.2s ease-in-out; -} - -.sidebar .menu-month li a:hover { - color: #1a73e8; - text-decoration: underline; -} -/* Main content */ -main.container { - flex: 1; - background-color: #ffffff; - padding: 2rem; - border-radius: 6px; - box-shadow: 0 2px 6px rgba(0,0,0,0.05); -} - -main h2 { - font-size: 1.5rem; - font-weight: 600; - margin-bottom: 1rem; - border-bottom: 2px solid #2c3e50; - padding-bottom: 0.3rem; -} - -main article p { - font-size: 1rem; - color: #444; -} - -/* Responsive */ -@media (max-width: 900px) { - .layout { - flex-direction: column; - } - - .sidebar { - width: 100%; - border-right: none; - border-bottom: 1px solid #e0e0e0; - margin-bottom: 1rem; - } - - main.container { - margin: 0; - } -} diff --git a/static/index.html b/static/index.html new file mode 100644 index 0000000..7265c6c --- /dev/null +++ b/static/index.html @@ -0,0 +1,6 @@ +Welcome to the blog.
+This is a sample blog post written in Markdown.
+inline codeconsole.log("Hello, world!");
+
+
+