mirror of
https://github.com/litruv/lit.ruv.wtf.git
synced 2026-07-24 10:46:03 +10:00
feat: Add blog post functionality with Markdown support
- Introduced BlogPostNode to render blog posts with metadata and content. - Updated graph.json to include new blog post nodes. - Enhanced MarkdownRenderer to support code block options for max height. - Added CSS styles for improved code block scrolling and syntax highlighting. - Created example blog posts: "Markdown Formatting Test", "Welcome to My Blog", and "Building Interactive Node Graphs". - Implemented dynamic loading of blog data from blogs.json. - Updated WorkspaceNavigator to allow smooth panning over blog nodes.
This commit is contained in:
180
build.js
180
build.js
@@ -1,6 +1,179 @@
|
||||
const fs = require('fs').promises;
|
||||
const path = require('path');
|
||||
|
||||
/**
|
||||
* Parse YAML front matter from markdown content
|
||||
* @param {string} content - The markdown file content
|
||||
* @returns {{metadata: Object, content: string}}
|
||||
*/
|
||||
function parseMarkdownWithFrontmatter(content) {
|
||||
const frontmatterRegex = /^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/;
|
||||
const match = content.match(frontmatterRegex);
|
||||
|
||||
if (!match) {
|
||||
return { metadata: {}, content: content };
|
||||
}
|
||||
|
||||
const yamlContent = match[1];
|
||||
const markdownContent = match[2];
|
||||
|
||||
// Simple YAML parser for basic key: value pairs
|
||||
const metadata = {};
|
||||
const lines = yamlContent.split(/\r?\n/);
|
||||
for (const line of lines) {
|
||||
const colonIndex = line.indexOf(':');
|
||||
if (colonIndex > 0) {
|
||||
const key = line.substring(0, colonIndex).trim();
|
||||
const value = line.substring(colonIndex + 1).trim();
|
||||
// Remove quotes if present
|
||||
metadata[key] = value.replace(/^["']|["']$/g, '');
|
||||
}
|
||||
}
|
||||
|
||||
return { metadata, content: markdownContent };
|
||||
}
|
||||
|
||||
/**
|
||||
* Process blog markdown files and generate blogs.json, also update graph.json
|
||||
*/
|
||||
async function processBlogPosts() {
|
||||
const blogDir = path.join(__dirname, 'website', 'data', 'blog');
|
||||
const outputPath = path.join(__dirname, 'website', 'data', 'blogs.json');
|
||||
const graphPath = path.join(__dirname, 'website', 'data', 'graph.json');
|
||||
|
||||
try {
|
||||
const files = await fs.readdir(blogDir);
|
||||
const mdFiles = files.filter(f => f.endsWith('.md'));
|
||||
|
||||
const posts = [];
|
||||
|
||||
for (const file of mdFiles) {
|
||||
const filePath = path.join(blogDir, file);
|
||||
const content = await fs.readFile(filePath, 'utf-8');
|
||||
const { metadata, content: markdown } = parseMarkdownWithFrontmatter(content);
|
||||
|
||||
const slug = path.basename(file, '.md');
|
||||
|
||||
// Extract and process tags separately
|
||||
const tags = metadata.tags ? metadata.tags.split(',').map(t => t.trim()) : [];
|
||||
|
||||
// Remove processed fields from metadata
|
||||
const { tags: _tags, title, date, author, ...extraMetadata } = metadata;
|
||||
|
||||
posts.push({
|
||||
slug,
|
||||
title: title || slug,
|
||||
date: date || null,
|
||||
author: author || null,
|
||||
tags,
|
||||
content: markdown,
|
||||
...extraMetadata
|
||||
});
|
||||
}
|
||||
|
||||
// Sort by date descending (newest first)
|
||||
posts.sort((a, b) => {
|
||||
if (!a.date) return 1;
|
||||
if (!b.date) return -1;
|
||||
return new Date(b.date) - new Date(a.date);
|
||||
});
|
||||
|
||||
await fs.writeFile(outputPath, JSON.stringify(posts, null, 2));
|
||||
console.log(`✓ Processed ${posts.length} blog post(s)`);
|
||||
|
||||
// Update graph.json with blog nodes
|
||||
if (posts.length > 0) {
|
||||
await updateGraphWithBlogNodes(posts, graphPath);
|
||||
}
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
console.log('⚠ No blog directory found, skipping blog processing');
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update graph.json with blog post nodes
|
||||
* @param {Array} posts - Array of blog post objects
|
||||
* @param {string} graphPath - Path to graph.json
|
||||
*/
|
||||
async function updateGraphWithBlogNodes(posts, graphPath) {
|
||||
const graphData = JSON.parse(await fs.readFile(graphPath, 'utf-8'));
|
||||
|
||||
// Remove existing blog nodes and their connections
|
||||
graphData.nodes = graphData.nodes.filter(node => node.type !== 'blog_post');
|
||||
graphData.connections = graphData.connections.filter(conn => {
|
||||
const fromNode = graphData.nodes.find(n => n.id === conn.from.nodeId);
|
||||
const toNode = graphData.nodes.find(n => n.id === conn.to.nodeId);
|
||||
return fromNode && toNode;
|
||||
});
|
||||
|
||||
const startX = 1500;
|
||||
const startY = 900;
|
||||
const horizontalSpacing = 700;
|
||||
const nodeWidth = 600;
|
||||
|
||||
const blogNodes = [];
|
||||
const blogConnections = [];
|
||||
|
||||
posts.forEach((post, index) => {
|
||||
const nodeId = `blog_${post.slug}`;
|
||||
const x = startX + (index * horizontalSpacing);
|
||||
|
||||
const node = {
|
||||
id: nodeId,
|
||||
type: 'blog_post',
|
||||
title: post.title,
|
||||
blogSlug: post.slug,
|
||||
position: {
|
||||
x: x,
|
||||
y: startY
|
||||
},
|
||||
width: nodeWidth,
|
||||
inputs: index > 0 ? [{
|
||||
id: 'exec_in',
|
||||
name: 'Previous',
|
||||
direction: 'input',
|
||||
kind: 'exec'
|
||||
}] : [],
|
||||
outputs: index < posts.length - 1 ? [{
|
||||
id: 'exec_out',
|
||||
name: 'Next',
|
||||
direction: 'output',
|
||||
kind: 'exec'
|
||||
}] : []
|
||||
};
|
||||
|
||||
blogNodes.push(node);
|
||||
|
||||
// Create connection to next post
|
||||
if (index < posts.length - 1) {
|
||||
const nextNodeId = `blog_${posts[index + 1].slug}`;
|
||||
blogConnections.push({
|
||||
id: `blog_connection_${index}`,
|
||||
from: {
|
||||
nodeId: nodeId,
|
||||
pinId: 'exec_out'
|
||||
},
|
||||
to: {
|
||||
nodeId: nextNodeId,
|
||||
pinId: 'exec_in'
|
||||
},
|
||||
kind: 'exec'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add blog nodes and connections to graph
|
||||
graphData.nodes.push(...blogNodes);
|
||||
graphData.connections.push(...blogConnections);
|
||||
|
||||
await fs.writeFile(graphPath, JSON.stringify(graphData, null, 2));
|
||||
console.log(`✓ Added ${blogNodes.length} blog node(s) to graph`);
|
||||
}
|
||||
|
||||
// Copy files recursively from website to build
|
||||
async function copyWebsiteFiles() {
|
||||
const websiteDir = path.join(__dirname, 'website');
|
||||
@@ -43,4 +216,9 @@ async function copyWebsiteFiles() {
|
||||
}
|
||||
|
||||
// Run the build
|
||||
copyWebsiteFiles();
|
||||
async function build() {
|
||||
await processBlogPosts();
|
||||
await copyWebsiteFiles();
|
||||
}
|
||||
|
||||
build();
|
||||
|
||||
Reference in New Issue
Block a user