protoml-parser
Version:
ProtoML is a lightweight, declarative markup language designed for writing and structuring meeting protocols, notes and task lists in a human-readable and machine-parseable format.
132 lines (113 loc) • 3.28 kB
JavaScript
const fs = require("fs");
const path = require("path");
function loadTheme(themeName, skip = 0) {
if (skip) {
return "";
}
const themePath = path.join(
__dirname,
"./themes",
`${themeName || "default"}.css`
);
try {
return fs.readFileSync(themePath, "utf8");
} catch (err) {
console.warn(`⚠️ Theme "${themeName}" not found. Using default.`);
return fs.readFileSync(
path.join(__dirname, "./themes/default.css"),
"utf8"
);
}
}
function escape(text) {
return String(text).replace(
/[&<>"']/g,
(c) =>
({
"&": "&",
"<": "<",
">": ">",
'"': """,
"'": "'",
}[c])
);
}
function renderHTML(ast, options = {}) {
const css = loadTheme(options.theme, options.skipTheme);
const html = [];
html.push(`<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Protocol – ${escape(ast.meta?.date || "Untitled")}</title>
<style>${css}</style>
</head>
<body>`);
html.push(`<h1>Protocol – ${escape(ast.meta?.date || "unknown")}</h1>`);
// Participants
if (ast.participants) {
html.push(`<section><h2>Participants</h2><ul>`);
for (const id in ast.participants) {
const p = ast.participants[id];
html.push(`<li>${escape(p.name)} (${escape(p.alias || id)})</li>`);
}
html.push(`</ul></section>`);
}
// Subjects
if (ast.subjects) {
html.push(`<section><h2>Subjects</h2><ul>`);
for (const id in ast.subjects) {
html.push(`<li><b>${id}:</b> ${ast.subjects[id]}</li>`);
}
html.push(`</ul></section>`);
}
// Tasks
if (ast.tasks?.length) {
html.push(`<section><h2>Tasks</h2><ul>`);
for (const task of ast.tasks) {
const cls = task.done ? "done" : "";
let extra = [];
// Teilnehmer anzeigen
if (task.meta?.ptp && ast.participants?.[task.meta.ptp]) {
const p = ast.participants[task.meta.ptp];
extra.push(`Assigned to: ${escape(p.name)}`);
}
// Subject anzeigen
if (task.meta?.subject && ast.subjects?.[Number(task.meta.subject)]) {
extra.push(
`Subject: ${escape(ast.subjects[Number(task.meta.subject)])}`
);
}
// Tag anzeigen
if (task.meta?.tag && ast.tags?.[task.meta.tag]) {
extra.push(
`Tag: <span class="tag">${escape(ast.tags[task.meta.tag])}</span>`
);
}
const metaInfo = extra.length
? `<div class="meta">${extra.join(" • ")}</div>`
: "";
html.push(`<li class="${cls}">${task.text}${metaInfo}</li>`);
}
html.push(`</ul></section>`);
}
// Notes
if (ast.notes?.length) {
html.push(`<section><h2>Notes</h2><ul>`);
for (const note of ast.notes) {
html.push(`<li>${note}</li>`);
}
html.push(`</ul></section>`);
}
// Meeting Text
if (ast.meeting?.length) {
html.push(`<section><h2>Rendered Meeting</h2>`);
for (const line of ast.meeting) {
html.push(`<p>${line}</p>`);
}
html.push(`</section>`);
}
html.push(`</body></html>`);
return html.join("\n");
}
module.exports = renderHTML;