« get me outta code hell

hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src/util/html.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/util/html.js')
-rw-r--r--src/util/html.js92
1 files changed, 92 insertions, 0 deletions
diff --git a/src/util/html.js b/src/util/html.js
new file mode 100644
index 0000000..4895301
--- /dev/null
+++ b/src/util/html.js
@@ -0,0 +1,92 @@
+// Some really simple functions for formatting HTML content.
+
+// Non-comprehensive. ::::P
+export const selfClosingTags = ['br', 'img'];
+
+// Pass to tag() as an attri8utes key to make tag() return a 8lank string
+// if the provided content is empty. Useful for when you'll only 8e showing
+// an element according to the presence of content that would 8elong there.
+export const onlyIfContent = Symbol();
+
+export function tag(tagName, ...args) {
+    const selfClosing = selfClosingTags.includes(tagName);
+
+    let openTag;
+    let content;
+    let attrs;
+
+    if (typeof args[0] === 'object' && !Array.isArray(args[0])) {
+        attrs = args[0];
+        content = args[1];
+    } else {
+        content = args[0];
+    }
+
+    if (selfClosing && content) {
+        throw new Error(`Tag <${tagName}> is self-closing but got content!`);
+    }
+
+    if (attrs?.[onlyIfContent] && !content) {
+        return '';
+    }
+
+    if (attrs) {
+        const attrString = attributes(args[0]);
+        if (attrString) {
+            openTag = `${tagName} ${attrString}`;
+        }
+    }
+
+    if (!openTag) {
+        openTag = tagName;
+    }
+
+    if (Array.isArray(content)) {
+        content = content.filter(Boolean).join('\n');
+    }
+
+    if (content) {
+        if (content.includes('\n')) {
+            return (
+                `<${openTag}>\n` +
+                content.split('\n').map(line => '    ' + line + '\n').join('') +
+                `</${tagName}>`
+            );
+        } else {
+            return `<${openTag}>${content}</${tagName}>`;
+        }
+    } else {
+        if (selfClosing) {
+            return `<${openTag}>`;
+        } else {
+            return `<${openTag}></${tagName}>`;
+        }
+    }
+}
+
+export function escapeAttributeValue(value) {
+    return value
+        .replaceAll('"', '&quot;')
+        .replaceAll("'", '&apos;');
+}
+
+export function attributes(attribs) {
+    return Object.entries(attribs)
+        .map(([ key, val ]) => {
+            if (!val)
+                return [key, val];
+            else if (typeof val === 'string' || typeof val === 'boolean')
+                return [key, val];
+            else if (typeof val === 'number')
+                return [key, val.toString()];
+            else if (Array.isArray(val))
+                return [key, val.join(' ')];
+            else
+                throw new Error(`Attribute value for ${key} should be primitive or array, got ${typeof val}`);
+        })
+        .filter(([ key, val ]) => val)
+        .map(([ key, val ]) => (typeof val === 'boolean'
+            ? `${key}`
+            : `${key}="${escapeAttributeValue(val)}"`))
+        .join(' ');
+}