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:
2026-04-19 09:09:53 +10:00
parent 34c2aa4316
commit 40fd8e8e42
13 changed files with 930 additions and 42 deletions

View File

@@ -0,0 +1,125 @@
---
title: Markdown Formatting Test
date: 2026-04-20
author: Test Author
tags: test, formatting, markdown
---
# Heading 1
## Heading 2
### Heading 3
#### Heading 4
##### Heading 5
###### Heading 6
## Text Formatting
This is **bold text** and this is *italic text* and this is ***bold italic text***.
Here's some `inline code` in a sentence.
## Links
Here's a [regular link](https://example.com) and here's a [YouTube link](https://youtube.com/watch?v=test).
## Lists
Unordered list:
- First item
- Second item
- Third item
Ordered list:
1. First step
2. Second step
3. Third step
## Code Blocks
Standard JavaScript:
```javascript
const hello = "world";
console.log(hello);
function test() {
return true;
}
```
Python with syntax highlighting:
```python
def hello_world():
print("Hello, World!")
return True
```
Code with max height (200px):
```javascript {maxHeight: 200}
// This is a long code block that will scroll
const data = [1, 2, 3, 4, 5];
function processData(arr) {
return arr.map(x => x * 2);
}
console.log(processData(data));
// Adding more lines to demonstrate scrolling
for (let i = 0; i < 10; i++) {
console.log(`Iteration ${i}`);
}
// Even more content
const obj = {
name: "Test",
value: 42,
nested: {
deep: true
}
};
```
## Blockquotes
> This is a blockquote.
> It can span multiple lines.
> And continues here.
## Horizontal Rule
---
## Images
![Test Image](data/blog/media/test.png)
## Mixed Content
You can mix **bold**, *italic*, and `code` in the same paragraph. Here's a [link with **bold** text](https://example.com) too.
### Nested Lists
- Top level item
- Another top level
- Nested item (if supported)
- Another nested
1. Numbered item
2. Another numbered
- Mixed with bullets
- More bullets
## Special Characters
Testing special chars: < > & " '
## Long Paragraph
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

View File

@@ -0,0 +1,52 @@
---
title: Building Interactive Node Graphs
date: 2026-04-15
author: Max Litruv Boonzaayer
tags: javascript, graph-systems, tutorial
---
# Building Interactive Node Graphs
In this post, I'll share some insights on building interactive node-based graph systems for the web.
## Why Node Graphs?
Node graphs are powerful visual programming tools that make complex logic easier to understand and manipulate:
- **Visual Clarity**: See the flow of data and execution at a glance
- **Modularity**: Each node is a self-contained unit
- **Flexibility**: Easy to add new node types and behaviors
## The Architecture
The system is built on several key classes:
### NodeBase
The abstract base class that all nodes inherit from. It defines the blueprint interface:
```javascript
class NodeBase {
static NodeType = "";
static BlueprintPure_GetDefaultPins() { }
BlueprintNativeEvent_OnRender(article, graphNode, renderCtx) { }
async BlueprintCallable_Execute(ctx) { }
}
```
### Node Registry
Automatically registers node types and creates instances based on type strings.
### Execution Context
Handles the graph traversal and execution flow when nodes are triggered.
## Creating Custom Nodes
To create a new node type:
1. Extend `NodeBase`
2. Set a unique `NodeType`
3. Define pins with `BlueprintPure_GetDefaultPins()`
4. Implement rendering in `BlueprintNativeEvent_OnRender()`
5. Implement logic in `BlueprintCallable_Execute()`
That's the blueprint system in a nutshell!

View File

@@ -0,0 +1,46 @@
---
title: Welcome to My Blog
date: 2026-04-19
author: Max Litruv Boonzaayer
tags: welcome, meta, introduction
---
# Welcome to My Blog
This is my first blog post! I'm excited to share my thoughts and experiences with you.
## What This Blog Is About
This blog is built using a **custom node-based graph system** where each blog post is represented as a node in an interactive visual graph. Pretty cool, right?
### Features
- 📝 Markdown support with YAML front matter
- 📅 Date-based organization
- 🏷️ Tag system for categorization
- 🎨 Interactive node-based visualization
- 📁 Media support in `data/blog/media/`
## Technical Details
The build system automatically:
1. Scans the `data/blog/` directory for `.md` files
2. Parses YAML front matter for metadata
3. Generates a `blogs.json` file
4. Makes posts available to the BlogPostNode
You can reference images like this:
![Example](data/blog/media/example.png)
## Code Examples
Here's some example code:
```javascript
const greeting = "Hello, World!";
console.log(greeting);
```
## Conclusion
Stay tuned for more posts! This is just the beginning of an exciting journey.

38
website/data/blogs.json Normal file
View File

@@ -0,0 +1,38 @@
[
{
"slug": "markdown-test",
"title": "Markdown Formatting Test",
"date": "2026-04-20",
"author": "Test Author",
"tags": [
"test",
"formatting",
"markdown"
],
"content": "\r\n# Heading 1\r\n\r\n## Heading 2\r\n\r\n### Heading 3\r\n\r\n#### Heading 4\r\n\r\n##### Heading 5\r\n\r\n###### Heading 6\r\n\r\n## Text Formatting\r\n\r\nThis is **bold text** and this is *italic text* and this is ***bold italic text***.\r\n\r\nHere's some `inline code` in a sentence.\r\n\r\n## Links\r\n\r\nHere's a [regular link](https://example.com) and here's a [YouTube link](https://youtube.com/watch?v=test).\r\n\r\n## Lists\r\n\r\nUnordered list:\r\n- First item\r\n- Second item\r\n- Third item\r\n\r\nOrdered list:\r\n1. First step\r\n2. Second step\r\n3. Third step\r\n\r\n## Code Blocks\r\n\r\nStandard JavaScript:\r\n```javascript\r\nconst hello = \"world\";\r\nconsole.log(hello);\r\n\r\nfunction test() {\r\n return true;\r\n}\r\n```\r\n\r\nPython with syntax highlighting:\r\n```python\r\ndef hello_world():\r\n print(\"Hello, World!\")\r\n return True\r\n```\r\n\r\nCode with max height (200px):\r\n```javascript {maxHeight: 200}\r\n// This is a long code block that will scroll\r\nconst data = [1, 2, 3, 4, 5];\r\n\r\nfunction processData(arr) {\r\n return arr.map(x => x * 2);\r\n}\r\n\r\nconsole.log(processData(data));\r\n\r\n// Adding more lines to demonstrate scrolling\r\nfor (let i = 0; i < 10; i++) {\r\n console.log(`Iteration ${i}`);\r\n}\r\n\r\n// Even more content\r\nconst obj = {\r\n name: \"Test\",\r\n value: 42,\r\n nested: {\r\n deep: true\r\n }\r\n};\r\n```\r\n\r\n## Blockquotes\r\n\r\n> This is a blockquote.\r\n> It can span multiple lines.\r\n> And continues here.\r\n\r\n## Horizontal Rule\r\n\r\n---\r\n\r\n## Images\r\n\r\n![Test Image](data/blog/media/test.png)\r\n\r\n## Mixed Content\r\n\r\nYou can mix **bold**, *italic*, and `code` in the same paragraph. Here's a [link with **bold** text](https://example.com) too.\r\n\r\n### Nested Lists\r\n\r\n- Top level item\r\n- Another top level\r\n - Nested item (if supported)\r\n - Another nested\r\n\r\n1. Numbered item\r\n2. Another numbered\r\n - Mixed with bullets\r\n - More bullets\r\n\r\n## Special Characters\r\n\r\nTesting special chars: < > & \" ' \r\n\r\n## Long Paragraph\r\n\r\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\r\n\r\nDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\r\n"
},
{
"slug": "welcome",
"title": "Welcome to My Blog",
"date": "2026-04-19",
"author": "Max Litruv Boonzaayer",
"tags": [
"welcome",
"meta",
"introduction"
],
"content": "\r\n# Welcome to My Blog\r\n\r\nThis is my first blog post! I'm excited to share my thoughts and experiences with you.\r\n\r\n## What This Blog Is About\r\n\r\nThis blog is built using a **custom node-based graph system** where each blog post is represented as a node in an interactive visual graph. Pretty cool, right?\r\n\r\n### Features\r\n\r\n- 📝 Markdown support with YAML front matter\r\n- 📅 Date-based organization\r\n- 🏷️ Tag system for categorization\r\n- 🎨 Interactive node-based visualization\r\n- 📁 Media support in `data/blog/media/`\r\n\r\n## Technical Details\r\n\r\nThe build system automatically:\r\n1. Scans the `data/blog/` directory for `.md` files\r\n2. Parses YAML front matter for metadata\r\n3. Generates a `blogs.json` file\r\n4. Makes posts available to the BlogPostNode\r\n\r\nYou can reference images like this:\r\n![Example](data/blog/media/example.png)\r\n\r\n## Code Examples\r\n\r\nHere's some example code:\r\n\r\n```javascript\r\nconst greeting = \"Hello, World!\";\r\nconsole.log(greeting);\r\n```\r\n\r\n## Conclusion\r\n\r\nStay tuned for more posts! This is just the beginning of an exciting journey.\r\n"
},
{
"slug": "node-graphs",
"title": "Building Interactive Node Graphs",
"date": "2026-04-15",
"author": "Max Litruv Boonzaayer",
"tags": [
"javascript",
"graph-systems",
"tutorial"
],
"content": "\r\n# Building Interactive Node Graphs\r\n\r\nIn this post, I'll share some insights on building interactive node-based graph systems for the web.\r\n\r\n## Why Node Graphs?\r\n\r\nNode graphs are powerful visual programming tools that make complex logic easier to understand and manipulate:\r\n\r\n- **Visual Clarity**: See the flow of data and execution at a glance\r\n- **Modularity**: Each node is a self-contained unit\r\n- **Flexibility**: Easy to add new node types and behaviors\r\n\r\n## The Architecture\r\n\r\nThe system is built on several key classes:\r\n\r\n### NodeBase\r\nThe abstract base class that all nodes inherit from. It defines the blueprint interface:\r\n\r\n```javascript\r\nclass NodeBase {\r\n static NodeType = \"\";\r\n static BlueprintPure_GetDefaultPins() { }\r\n BlueprintNativeEvent_OnRender(article, graphNode, renderCtx) { }\r\n async BlueprintCallable_Execute(ctx) { }\r\n}\r\n```\r\n\r\n### Node Registry\r\nAutomatically registers node types and creates instances based on type strings.\r\n\r\n### Execution Context\r\nHandles the graph traversal and execution flow when nodes are triggered.\r\n\r\n## Creating Custom Nodes\r\n\r\nTo create a new node type:\r\n\r\n1. Extend `NodeBase`\r\n2. Set a unique `NodeType`\r\n3. Define pins with `BlueprintPure_GetDefaultPins()`\r\n4. Implement rendering in `BlueprintNativeEvent_OnRender()`\r\n5. Implement logic in `BlueprintCallable_Execute()`\r\n\r\nThat's the blueprint system in a nutshell!\r\n"
}
]

View File

@@ -94,8 +94,8 @@
"type": "sequence",
"title": "Sequence",
"position": {
"x": 1180,
"y": 120
"x": 1080,
"y": 20
},
"focusOptions": {
"minWorldBox": {
@@ -137,8 +137,8 @@
"type": "info",
"title": "Who Am I",
"position": {
"x": 1780,
"y": -640
"x": 1600,
"y": -860
},
"width": 340,
"markdownSrc": "data/whoami.md",
@@ -186,8 +186,8 @@
"type": "info",
"title": "Find Me Online",
"position": {
"x": 2460,
"y": 140
"x": 2420,
"y": -280
},
"markdownSrc": "data/socials.md",
"focusOptions": {
@@ -211,8 +211,8 @@
"type": "pure",
"title": "String Node",
"position": {
"x": 2260,
"y": -280
"x": 2180,
"y": -500
},
"inputs": [],
"outputs": [
@@ -230,8 +230,8 @@
"type": "button",
"title": "Run",
"position": {
"x": 2180,
"y": -360
"x": 2100,
"y": -600
},
"inputs": [],
"outputs": [
@@ -248,8 +248,8 @@
"type": "timer",
"title": "Timer",
"position": {
"x": 2180,
"y": -460
"x": 2100,
"y": -700
},
"inputs": [
{
@@ -276,8 +276,8 @@
"type": "print",
"title": "Print String",
"position": {
"x": 2540,
"y": -460
"x": 2380,
"y": -680
},
"inputs": [
{
@@ -308,8 +308,8 @@
"type": "pure",
"title": "Homeserver",
"position": {
"x": 1520,
"y": 1020
"x": 1480,
"y": 340
},
"inputs": [],
"outputs": [
@@ -327,8 +327,8 @@
"type": "pure",
"title": "Room",
"position": {
"x": 1520,
"y": 1100
"x": 1480,
"y": 420
},
"inputs": [],
"outputs": [
@@ -346,8 +346,8 @@
"type": "chat",
"title": "Matrix Chat",
"position": {
"x": 1800,
"y": 920
"x": 1740,
"y": 220
},
"width": 400,
"focusOptions": {
@@ -420,8 +420,8 @@
"type": "random_name",
"title": "Random Name",
"position": {
"x": 1520,
"y": 1180
"x": 1480,
"y": 500
},
"inputs": [],
"outputs": [
@@ -438,8 +438,8 @@
"type": "button",
"title": "Connect Chat",
"position": {
"x": 1960,
"y": 820
"x": 1920,
"y": 120
},
"inputs": [],
"outputs": [
@@ -456,8 +456,8 @@
"type": "chat_connect",
"title": "Connect Chat",
"position": {
"x": 2220,
"y": 880
"x": 2260,
"y": 220
},
"inputs": [
{
@@ -487,8 +487,8 @@
"type": "get_matrix_chat",
"title": "Get MatrixChat",
"position": {
"x": 1520,
"y": 940
"x": 1500,
"y": 240
},
"inputs": [],
"outputs": [
@@ -506,8 +506,8 @@
"type": "bind_event",
"title": "Bind: Chat On Message",
"position": {
"x": 2460,
"y": 880
"x": 2540,
"y": 220
},
"inputs": [
{
@@ -557,8 +557,8 @@
"type": "append",
"title": "Append",
"position": {
"x": 2700,
"y": 960
"x": 2780,
"y": 280
},
"inputs": [
{
@@ -597,8 +597,8 @@
"type": "print",
"title": "Print Chat Message",
"position": {
"x": 2940,
"y": 900
"x": 3040,
"y": 220
},
"inputs": [
{
@@ -623,6 +623,73 @@
"kind": "exec"
}
]
},
{
"id": "blog_markdown-test",
"type": "blog_post",
"title": "Markdown Formatting Test",
"blogSlug": "markdown-test",
"position": {
"x": 1500,
"y": 900
},
"width": 600,
"inputs": [],
"outputs": [
{
"id": "exec_out",
"name": "Next",
"direction": "output",
"kind": "exec"
}
]
},
{
"id": "blog_welcome",
"type": "blog_post",
"title": "Welcome to My Blog",
"blogSlug": "welcome",
"position": {
"x": 2200,
"y": 900
},
"width": 600,
"inputs": [
{
"id": "exec_in",
"name": "Previous",
"direction": "input",
"kind": "exec"
}
],
"outputs": [
{
"id": "exec_out",
"name": "Next",
"direction": "output",
"kind": "exec"
}
]
},
{
"id": "blog_node-graphs",
"type": "blog_post",
"title": "Building Interactive Node Graphs",
"blogSlug": "node-graphs",
"position": {
"x": 2900,
"y": 900
},
"width": 600,
"inputs": [
{
"id": "exec_in",
"name": "Previous",
"direction": "input",
"kind": "exec"
}
],
"outputs": []
}
],
"connections": [
@@ -853,6 +920,30 @@
"pinId": "target"
},
"kind": "object"
},
{
"id": "blog_connection_0",
"from": {
"nodeId": "blog_markdown-test",
"pinId": "exec_out"
},
"to": {
"nodeId": "blog_welcome",
"pinId": "exec_in"
},
"kind": "exec"
},
{
"id": "blog_connection_1",
"from": {
"nodeId": "blog_welcome",
"pinId": "exec_out"
},
"to": {
"nodeId": "blog_node-graphs",
"pinId": "exec_in"
},
"kind": "exec"
}
]
}