add templater

This commit is contained in:
HRG @ SCExC 2024-06-02 10:20:01 -04:00
parent 4d6c343422
commit c33a49e817
8 changed files with 248 additions and 27 deletions

2
.gitignore vendored
View file

@ -1 +1,3 @@
node_modules
.DS_Store
dbs

103
index.js
View file

@ -3,6 +3,7 @@ const vm = require("node:vm");
const path = require("path");
const express = require("express");
const session = require("express-session");
const fileUpload = require("express-fileupload");
const SQLiteStore = require("better-sqlite3-session-store")(session);
const betterSqlite3 = require("better-sqlite3");
const { Eta } = require("eta");
@ -20,6 +21,7 @@ const routes = { GET: [], POST: [], PUT: [], DELETE: [] };
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static("public"));
app.use(fileUpload());
const db = betterSqlite3("./prime.db");
@ -209,12 +211,42 @@ function updateDb(db, appDb) {
db.prepare(sql).run(...values);
}
function updateTemplate(db, template) {
const fields = ["content", "name", "test_object"];
const values = fields.map((field) => template[field]);
const placeholders = fields.map((field) => `${field} = ?`).join(", ");
const sql = `UPDATE templates SET ${placeholders} WHERE id = ?`;
values.push(template.id);
db.prepare(sql).run(...values);
}
function getTemplates(db, structureId) {
return db
.prepare("SELECT * from templates where structure_id = ?")
.all(structureId);
}
function getTemplate(db, templateId) {
return db.prepare("SELECT * from templates where id = ?").get(templateId);
}
function getTemplateContentByName(db, structId, name) {
return db
.prepare(
"SELECT content from templates where structure_id = ? AND name = ?"
)
.get(structId, name);
}
function createTemplate(db, structureId, name, content, testObjectString) {
return db
.prepare(
"INSERT INTO templates (structure_id, name, content, test_object) VALUES (?, ?, ?, ?)"
)
.run(structureId, name, content, testObjectString).lastInsertRowid;
}
function getDbsForStructure(db, structureId) {
return db
.prepare(
@ -280,8 +312,6 @@ function createDb(db, structId, name) {
}
const { LRUCache } = require("lru-cache");
const { templateSettings } = require("dot");
// we'll figure out max size later
const templateCache = new LRUCache({ max: 100 });
function getTemplater(structId) {
@ -293,7 +323,7 @@ function getTemplater(structId) {
return path;
};
etaInstance.readFile = function (templateAlias) {
return getTemplateString(db, templateAlias, structId);
return getTemplateContentByName(db, structId, templateAlias).content;
};
templateCache.set(structId, etaInstance);
}
@ -315,9 +345,8 @@ function getDbInstance(dbId) {
return dbInstance;
}
function bootstrapTemplateWithHTMXetc(htmlString, blissRoute) {
console.log(htmlString);
if (htmlString.startsWith("<html>")) {
function bootstrapTemplateWithHTMXetc(htmlString, blissRoute, wrapHTML) {
if (htmlString.startsWith("<html>") || wrapHTML) {
const $ = cheerio.load(htmlString);
let head = $("head");
@ -487,6 +516,63 @@ app.post("/workshop/:structure_id/db/:db_id/repl", (req, res) => {
}
});
app.post("/workshop/:structure_id/template", (req, res) => {
let name = req.body.name;
const template = createTemplate(
db,
req.params.structure_id,
name,
"<div>henlo <%= it.name %></div>",
"it = { name: 'templates!' };"
);
return smartRedirect(
req,
res,
`/workshop/${req.params.structure_id}/template/${template}`
);
});
app.put("/workshop/:structure_id/template/:template_id", (req, res) => {
let id = req.params.template_id;
let content = req.body.content;
let test_object = req.body.test_object;
updateTemplate(db, { ...getTemplate(db, id), content, test_object });
return res.send("good");
});
app.get("/workshop/:structure_id/template/:template_id", (req, res) => {
const template = getTemplate(db, req.params.template_id);
return res.send(
bootstrapTemplateWithHTMXetc(
eta.render("workshop/template", {
template: template,
...sidebarStuff(db, req.params.structure_id),
})
)
);
});
app.get("/workshop/:structure_id/template/:template_id/preview", (req, res) => {
const template = getTemplate(db, req.params.template_id);
const eta = getTemplater(req.params.structure_id);
const context = vm.createContext({ it: null });
vm.runInContext(template.test_object, context);
return res.send(
bootstrapTemplateWithHTMXetc(
eta.render(template.name, context.it),
null,
true
)
);
});
app.get("/workshop/:structure_id/route/:id", (req, res) => {
const route = getRoute(db, req.params.id);
return res.send(
@ -581,7 +667,6 @@ app.all("*", (req, res) => {
res.rawSend = res.send;
res.send = (...args) => {
console.log(args[0], args[1], "bingo")
res.rawSend(
bootstrapTemplateWithHTMXetc(
args[0],
@ -589,6 +674,10 @@ app.all("*", (req, res) => {
)
);
};
res.render = (template, context) => {
const eta = getTemplater(route.structure_id);
res.send(bootstrapTemplateWithHTMXetc(eta.render(template, context)));
};
return vm.runInContext(
(route.handler += `\n\nhandler(req, res)`),
context

View file

@ -32,13 +32,13 @@ CREATE TABLE templates (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
content TEXT,
user_id INTEGER,
structure_id INTEGER,
test_object TEXT DEFAULT "it = {}",
engine TEXT NOT NULL DEFAULT "eta",
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(user_id) REFERENCES users(id),
FOREIGN KEY(structure_id) REFERENCES structures(id)
UNIQUE(name, structure_id)
);
CREATE TABLE routes (

52
package-lock.json generated
View file

@ -18,6 +18,7 @@
"ejs": "^3.1.9",
"eta": "^3.2.0",
"express": "^4.18.2",
"express-fileupload": "^1.5.0",
"express-session": "^1.18.0",
"jsdom": "^24.1.0",
"lru-cache": "^10.2.0",
@ -290,6 +291,17 @@
"ieee754": "^1.1.13"
}
},
"node_modules/busboy": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
"integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
"dependencies": {
"streamsearch": "^1.1.0"
},
"engines": {
"node": ">=10.16.0"
}
},
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@ -829,6 +841,17 @@
"node": ">= 0.10.0"
}
},
"node_modules/express-fileupload": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/express-fileupload/-/express-fileupload-1.5.0.tgz",
"integrity": "sha512-jSW3w9evqM37VWkEPkL2Ck5wUo2a8qa03MH+Ou/0ZSTpNlQFBvSLjU12k2nYcHhaMPv4JVvv6+Ac1OuLgUZb7w==",
"dependencies": {
"busboy": "^1.6.0"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/express-session": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.0.tgz",
@ -2189,6 +2212,14 @@
"node": ">= 0.8"
}
},
"node_modules/streamsearch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
"integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
@ -2711,6 +2742,14 @@
"ieee754": "^1.1.13"
}
},
"busboy": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
"integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
"requires": {
"streamsearch": "^1.1.0"
}
},
"bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@ -3136,6 +3175,14 @@
}
}
},
"express-fileupload": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/express-fileupload/-/express-fileupload-1.5.0.tgz",
"integrity": "sha512-jSW3w9evqM37VWkEPkL2Ck5wUo2a8qa03MH+Ou/0ZSTpNlQFBvSLjU12k2nYcHhaMPv4JVvv6+Ac1OuLgUZb7w==",
"requires": {
"busboy": "^1.6.0"
}
},
"express-session": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.0.tgz",
@ -4075,6 +4122,11 @@
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="
},
"streamsearch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
"integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg=="
},
"string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",

View file

@ -18,6 +18,7 @@
"ejs": "^3.1.9",
"eta": "^3.2.0",
"express": "^4.18.2",
"express-fileupload": "^1.5.0",
"express-session": "^1.18.0",
"jsdom": "^24.1.0",
"lru-cache": "^10.2.0",

View file

@ -1,16 +1,22 @@
<div class="window" _="on closeModal remove me">
<div class="fixed inset-0 flex items-center justify-center z-10" _="on closeModal remove me">
<div class="window max-w-[768px]">
<div class="title-bar">
<div class="title-bar-text">Command Prompt</div>
<div class="title-bar-text">Create Template</div>
<div class="title-bar-controls">
<button aria-label="Minimize"></button>
<button aria-label="Maximize"></button>
<button aria-label="Close" _="on click trigger closeModal"></button>
</div>
</div>
<div class="window-body">
<pre>Microsoft&#10094;R&#10095; Windows DOS
&#10094;C&#10095; Copyright Microsoft Corp 1990-2001.
<br>C:&#92;WINDOWS&#92;SYSTEM32> You can build a command line easily with a window and pre tag
</pre>
<form hx-post="/workshop/<%= it.structure.id %>/template" hx-target="#response">
<div class="field-row-stacked" style="width: 200px">
<label>Name</label>
<input name="name" id="name" type="text" />
</div>
<section class="field-row" style="justify-content: flex-end">
<button type="submit">OK</button>
<button type="button" _="on click trigger closeModal">Cancel</button>
</section>
</form>
<div id="response"></div>
</div>
</div>
</div>

View file

@ -24,7 +24,9 @@
<ul>
<% it.templates.map(template => { %>
<li>
<%= template.alias %>
<a href="/workshop/<%= it.structure.id %>/template/<%= template.id %>">
<%= template.name %>
</a>
</li>
<% }) %>
<li hx-get="/workshop/<%= it.structure.id %>/new_template_modal" hx-target="#modal-zone" hx-swap="beforeend" class="cursor-pointer">

View file

@ -0,0 +1,69 @@
<html>
<head>
<link rel="stylesheet" href="/css/xp.css">
<script src="/js/ace/ace.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<div class="flex flex-col w-full h-full window">
<div class="title-bar">
<div class="title-bar-text">Workshop - <%~ include('/workshop/structure_name', {structure: it.structure}) %>
</div>
</div>
<div class="flex flex-row flex-grow window-body">
<div class="w-1/5 h-full">
<%~ include('/workshop/sidebar', it) %>
</div>
<div id="main-editor" class="flex flex-grow">
<div id="left-pane" class="w-1/2 flex-grow h-full flex flex-col">
Template
<div id="template-editor" class="w-full flex-grow"><%= it.template.content %></div>
Test Object
<div id="object-editor" class="w-full flex-grow"><%= it.template.test_object %></div>
<button id="save-btn" type="submit">Save</button>
</div>
<div id="preview-area" class="w-1/2 h-full flex flex-col">
<form _="on submit halt the event then set #preview's contentWindow.location.href to #url's value">
<input id="url" class="w-full">
</form>
<iframe id="preview"
src="/workshop/<%= it.template.structure_id %>/template/<%= it.template.id %>/preview"
class="w-full flex-grow"
_="on load set #url's value to my contentWindow.location.href">
</iframe>
</div>
</div>
</div>
</div>
<script>
const templateEditor = ace.edit("template-editor");
templateEditor.setTheme("ace/theme/monokai");
templateEditor.session.setMode("ace/mode/ejs");
const objectEditor = ace.edit("object-editor");
objectEditor.setTheme("ace/theme/monokai");
objectEditor.session.setMode("ace/mode/javascript");
document.getElementById('save-btn').onclick = function() {
fetch('/workshop/<%= it.structure.id %>/template/<%= it.template.id %>', {
method: 'PUT',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
content: templateEditor.getValue(),
test_object: objectEditor.getValue(),
}).toString()
}).then(response => {
if (response.ok) {
document.getElementById('preview').contentWindow.location.reload()
}
});
};
</script>
</body>
</html>