add templater
This commit is contained in:
parent
4d6c343422
commit
c33a49e817
8 changed files with 248 additions and 27 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1 +1,3 @@
|
|||
node_modules
|
||||
.DS_Store
|
||||
dbs
|
||||
|
|
|
|||
103
index.js
103
index.js
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
52
package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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❮R❯ Windows DOS
|
||||
❮C❯ Copyright Microsoft Corp 1990-2001.
|
||||
<br>C:\WINDOWS\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>
|
||||
|
|
@ -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">
|
||||
|
|
|
|||
69
views/workshop/template.eta
Normal file
69
views/workshop/template.eta
Normal 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>
|
||||
Loading…
Add table
Add a link
Reference in a new issue