Overview
This document provides step-by-step instructions for creating a website
that enables users to write documents using Markdown and publish them in an HTML design format. Additionally, the website will feature a dashboard where users can edit, delete, and view all their documents.
In this document we cover these topics:
- 1.Workflow in Node-RED
- 2.Template Node
- 3.Uibuilder Node
- 4.Function Node
- 5.write file Node
1.Workflow in Node-RED
Template Node
Add a Template Node to your workflow.
Property:
msg.template
Syntax Highlight:
mustache
Note:
The Template Node allows you to write raw HTML code used to design and render your website.
- Write the HTML structure for your web interface.
The goal of this HTML is to create a user interface where users can input their document, format it using Markdown, and click Send to proceed.
<div>
<input id="textInput" type="text" placeholder="Type here..." />
<button id="sendBtn">Send</button>
</div>
- click Done
line-by-line explanation of the HTML code
<div>
<div>
:This is a container element.
<input id="textInput" type="text" placeholder="Type here..." />
<input>:
Creates a text box where users can type something.
id="textInput":
Gives this input box a unique identifier so we can refer to it later in JavaScript or CSS.
type="text":
single-line text input.
placeholder="Type here...":
Shows this light gray text inside the box before the user types anything. It helps users know what to do.
<button id="sendBtn">Send</button>
<button>:
Creates a clickable button labeled Send.
id="sendBtn":
Assigns a unique ID to the button so we can attach a click event or style it.
</div>
Closing </div>:
Ends the container started earlier, wrapping both the input and button together.
Uibuilder Node
Add a Uibuilder to your workflow.
ensure you link it directly to the Template Node
عکس
Note:
The Uibuilder Node allows your frontend (web browser) to exchange data with Node-RED
Purpose:
Uibuilder Node is designed to accept a document from the input provided in the Template Node. It stores the document in a variable and, upon clicking the Send button in the browser, transfers it to the function Node for additional processing.
// @ts-nocheck
import './dynamicsrc/uibuilder.js';
import { createApp } from './dynamicsrc/vue.js';
const app = createApp({
data() {
return {
renderedHtml: "",
etudesData: [],
}
},
components: {},
computed: {
},
destroyed() { },
methods: {
attachScriptToHead(jsCode) {
const existingScript = document.querySelector(`[data-script-id="${btoa(jsCode)}"]`);
if (existingScript) {
return;
}
const script = document.createElement('script');
script.type = 'text/javascript';
script.text = jsCode;
script.setAttribute('data-script-id', btoa(jsCode));
document.head.appendChild(script);
},
bindEvents() {
const button = document.getElementById('sendBtn');
const input = document.getElementById('textInput');
if (button && input) {
button.replaceWith(button.cloneNode(true));
const newButton = document.getElementById('sendBtn');
newButton.addEventListener('click', () => {
const val = input.value;
uibuilder.send({ payload: val });
});
}
}
},
beforeUnmount() {
},
mounted() {
uibuilder.start();
var vueApp = this;
uibuilder.onChange('msg', function (msg) {
if (msg.javascript) {
vueApp.attachScriptToHead(msg.javascript);
}
if (msg.json) {
vueApp.etudesData = msg.json;
}
if (msg && msg.template) {
vueApp.renderedHtml = msg.template;
vueApp.$nextTick(() => vueApp.bindEvents())
}
;
});
document.body.addEventListener("click", (event) => this.handleButtonClick(event));
},
beforeDestroy() {
document.removeEventListener('keydown', this.handleEnterKeyPress);
},
})
app.mount('#app')
line-by-line explanation of the Uibuilder.js
// @ts-nocheck
Explanation:
This line tells TypeScript to ignore errors in this file.
import './dynamicsrc/uibuilder.js';
import { createApp } from './dynamicsrc/vue.js';
Explanation:
import uibuilder.js
likely a helper script for communication between Node-RED and the front-end (used to send/receive messages).
createApp
is a function from Vue 3.
It is used to start or create your Vue app.
const app = createApp({
Explanation:
Starts creating a new Vue app.
data() {
return {
renderedHtml: "", // Will store raw HTML received from backend and display it
etudesData: [], // Will store any JSON data (like a list) from backend
}
},
Explanation:
This part defines the main memory of your app, which Vue will watch and update on the screen automatically when it changes.
methods: {
attachScriptToHead(jsCode) {
const existingScript = document.querySelector(`[data-script-id="${btoa(jsCode)}"]`);
if (existingScript) {
return;
}
const script = document.createElement('script');
script.type = 'text/javascript';
script.text = jsCode;
script.setAttribute('data-script-id', btoa(jsCode));
document.head.appendChild(script);
},
Explanation:
Checks if a script with the same Base64-encoded ID already exists (to avoid duplicates).
btoa(jsCode):
Converts the code into a unique string using Base64 encoding.
Example: "alert('hi')" becomes "YWxlcnQoJ2hpJyk="
.
If such a script is already found, the function just stops here and does nothing.
if such a script is not already found Creates a <script>
element, injects the JS code as text, and adds it to <head>
.
bindEvents() {
const button = document.getElementById('sendBtn');
const input = document.getElementById('textInput');
Explanation:
Gets references to input and button elements in the dynamic HTML.
if (button && input) {
button.replaceWith(button.cloneNode(true));
const newButton = document.getElementById('sendBtn');
newButton.addEventListener('click', () => {
const val = input.value;
uibuilder.send({ payload: val });
});
}
}
Explanation:
Replaces the old button with a clone to clear previous event listeners
(Prevent double-event binding).
Adds a click handler that sends input text as a payload using uibuilder.send()
.
beforeUnmount() { },
Explanation:
You could use it to clean up resources if needed.
mounted() {
uibuilder.start();
Explanation:
Initializes the uibuilder connection (enables communication with Node-RED backend).
var vueApp = this;
Explanation:
Stores this reference (so it's accessible inside nested functions).
uibuilder.onChange('msg', function (msg) {
Explanation:
When a new message arrives from backend:
if (msg.javascript) {
vueApp.attachScriptToHead(msg.javascript);
}
Explanation:
If message contains a JS code snippet, dynamically attach it to the document.
if (msg.json) {
vueApp.etudesData = msg.json;
}
Explanation:
If JSON data is received, store it in etudesData.
if (msg && msg.template) {
vueApp.renderedHtml = msg.template;
vueApp.$nextTick(() => vueApp.bindEvents())
}
Explanation:
If HTML template is received:
Store it as renderedHtml
to be rendered.
After DOM updates ($nextTick)
, re-bind event handlers.
document.body.addEventListener("click", (event) => this.handleButtonClick(event));
},
Explanation:
Listens for any click on the page and calls a handleButtonClick method .
beforeDestroy() {
document.removeEventListener('keydown', this.handleEnterKeyPress);
},
Explanation:
Removes global keydown listener.
})
app.mount('#app')
Explanation:
Mounts the Vue app to the HTML element with id="app"
.
Function Node
- Add a Function Node to your workflow.
- ensure you link it directly to the Uibuilder Node
Note:
The Function Node in Node-RED lets you write custom JavaScript code to process, modify, or analyze messages (msg) that flow through your workflow.
In the Function Node, we receive a document formatted in Markdown and store it in a variable. Then, we analyze the document for Markdown symbols and replace them with corresponding HTML tags to ensure proper display in another Uibuilder.
if (msg._alreadyProcessed) {
return null;
}
msg._alreadyProcessed = true;
let text = msg.payload || ""
text = text.replace(/######(.*?)######/g, '<h6>$1</h6>')
text = text.replace(/#####[^#](.*?)#####/g, '<h5>$1</h5>')
text = text.replace(/####(.*?)####/g, '<h4>$1</h4>')
text = text.replace(/###(.*?)###/g, '<h3>$1</h3>')
text = text.replace(/##(.*?)##/g, '<h2>$1</h2>')
text = text.replace(/#(.*?)#/g, '<h1>$1</h1>')
text = text.replace(/\*\*(.*?)\*\*/g, '<b>$1</b>')
text = text.replace(/_(.*?)_/g, '<i>$1</i>')
text = text.replace(/&&(.*?)&&/g, '<span class="highlight">$1</span>')
text = text.replace(/!\[image]\((.*?)\)/g, '<img src="$1">')
const html = `
<div>
${text}
</div>
`
msg.payload = html
let index = flow.get('fileIndex') || 1
flow.set('fileIndex', index + 1)
const basePath = "/data/user/uibuilder/document/src/output/"
msg.filename = `${basePath}generated_${index}.html`
return msg
line-by-line explanation of the Function Node
if (msg._alreadyProcessed) {
return null;
}
Check if this message has already been processed to avoid duplicate processing (maybe the flow sends it multiple times).
If yes (msg._alreadyProcessed is true)
, stop further processing by returning null.
msg._alreadyProcessed = true;
Mark the message as processed by setting this flag to true so next time it won't be processed again.
let text = msg.payload || ""
Get the input text from msg.payload.
If msg.payload
is empty or undefined, use an empty string ""
as a fallback.
text = text.replace(/######(.*?)######/g, '<h6>$1</h6>')
Replace any text wrapped in six #
on each side, e.g. ######text######
, with an <h6>
HTML heading tag.
text = text.replace(/#####[^#](.*?)#####/g, '<h5>$1</h5>')
Replace text wrapped with five #
(with a non-#
character right after the first 5 #
) on each side, e.g. #####text#####
, with <h5>
.
Note: The regex might be slightly off because of [^#]
here, intended to avoid matching six #
?
Write file Node
Add a Write file Node to your workflow.
Filename:
msg.filename
Action:
overWrite file
Encodeing:
default
Ensure you link it directly to the Function Node
we want to create file that document save in it it has file name and path with should make automatically with some numbers so in function node we have functionlity to do that
Top comments (0)