about summary refs log tree commit diff
path: root/kittybox-rs/companion-lite
diff options
context:
space:
mode:
Diffstat (limited to 'kittybox-rs/companion-lite')
-rw-r--r--kittybox-rs/companion-lite/index.html77
-rw-r--r--kittybox-rs/companion-lite/main.js70
-rw-r--r--kittybox-rs/companion-lite/micropub_api.js43
-rw-r--r--kittybox-rs/companion-lite/style.css47
4 files changed, 237 insertions, 0 deletions
diff --git a/kittybox-rs/companion-lite/index.html b/kittybox-rs/companion-lite/index.html
new file mode 100644
index 0000000..b643ba2
--- /dev/null
+++ b/kittybox-rs/companion-lite/index.html
@@ -0,0 +1,77 @@
+<html>
+    <head>
+        <meta charset="utf-8">
+        <title>Kittybox-Micropub debug client</title>
+        <link rel="stylesheet" href="./style.css">
+        <script type="module" src="./main.js"></script>
+    </head>
+    <body>
+      <noscript>
+        <h1 class="header">Kittybox Companion (Lite)</h1>
+        <p>I'm sorry, Kittybox Companion requires JavaScript to work.</p>
+
+        <p>This is a requirement due to multiple interactive features present in Kittybox, such as support for multiple-entry form fields, interactive login sequence and more.</p>
+
+        <p>However, the Micropub standard is extremely flexible, and if you happen to have a token, you can publish articles, notes, likes, follows and more by sending requests directly to the Micropub endpoint.</p>
+
+        <p><a href="https://micropub.spec.indieweb.org/">The Micropub spec is defined here.</a> Good luck!</p>
+      </noscript>
+
+      <div class="view" id="unauthorized" style="display:none">
+        
+      </div>
+
+      <div class="view" id="authorized" style="display:none">
+        <form action="/.kittybox/micropub" method="POST" id="micropub">
+          <fieldset>
+            <legend>Authorization details</legend>
+            <section>
+              <label for="access_token">Access token:</label>
+              <input id="access_token" name="access_token" type="password">
+
+              <p><a href="https://gimme-a-token.5eb.nl/" target="_blank">Get an access token (will open in a new tab)</a></p>
+            </section>
+          </fieldset>
+          <fieldset>
+            <legend>Post details:</legend>
+            <section>
+              <label for="name">Name (leave blank for an unnamed post):</label>
+              <input id="name" type="text">
+            </section>
+            <section>
+              <label for="content">Content:</label>
+              <textarea id="content" placeholder="Your post's text goes here"></textarea>
+            </section>
+            <section>
+              <label for="category">Categories (separeted by commas):</label>
+              <input id="category" type="text">
+            </section>
+            <fieldset>
+              <legend>Channels</legend>
+              <section>
+                <input type="radio" id="no_channel" name="channel_select" checked value="">
+                <label for="no_channel">Default channel only</label>
+              </section>
+
+              <section>
+                <input type="radio" id="select_channels" name="channel_select" value="on">
+                <label for="select_channels">Select channels manually</label>
+              </section>
+              
+              <fieldset id="channels" style="display: none">
+                <legend>Available channels:</legend>
+                <template id="channel_selector">
+                  <section>
+                    <input type="checkbox" name="channel" id="" value="">
+                    <label for=""></label>
+                  </section>
+                </template>
+                <div id="channels_target"></div>
+              </fieldset>
+            </fieldset>
+          </fieldset>
+          <input type="submit">
+        </div>
+      </main>
+    </body>
+</html>
diff --git a/kittybox-rs/companion-lite/main.js b/kittybox-rs/companion-lite/main.js
new file mode 100644
index 0000000..da7e6e1
--- /dev/null
+++ b/kittybox-rs/companion-lite/main.js
@@ -0,0 +1,70 @@
+import { query_channels, submit } from "./micropub_api.js";
+
+function get_token() {
+    return form.elements.access_token.value
+}
+
+const form = document.getElementById("micropub");
+const channel_select_radio = document.getElementById("select_channels");
+
+channel_select_radio.onclick = async () => {
+    const channels = await query_channels(form.action, get_token())
+    if (channels !== undefined) {
+        populate_channel_list(channels)
+    }
+}
+
+const no_channel_radio = document.getElementById("no_channel");
+no_channel_radio.onclick = () => {
+    document.getElementById("channels").style.display = "none";
+    const channel_list = document.getElementById("channels_target")
+    channel_list.innerHTML = "";
+}
+
+function construct_body(form) {
+    return {
+        type: ["h-entry"],
+        properties: {
+            content: [form.elements.content.value],
+            name: form.elements.name.value ? [form.elements.name.value] : undefined,
+            category: form.elements.category.value ? form.elements.category.value.split(",").map(val => val.trim()) : undefined,
+            channel: form.elements.channel_select.value ?  Array.from(form.elements.channel).map(i => i.checked ? i.value : false).filter(i => i) : undefined
+        }
+    }
+}
+
+function populate_channel_list(channels) {
+    document.getElementById("channels").style.display = "block";
+    const channel_list = document.getElementById("channels_target")
+    channel_list.innerHTML = "";
+    channels.forEach((channel) => {
+        const template = document.getElementById("channel_selector").content.cloneNode(true)
+        const input = template.querySelector("input")
+        const label = template.querySelector("label")
+        input.id = `channel_selector_option_${channel.uid}`
+        input.value = channel.uid
+        label.for = input.id
+        label.innerHTML = `<a href="${channel.uid}">${channel.name}</a>`
+
+        channel_list.appendChild(template)
+    })
+}
+
+form.onsubmit = async (event) => {
+    event.preventDefault()
+    const mf2 = construct_body(form);
+    console.log(JSON.stringify(mf2));
+    try {
+        submit(form.action, get_token(), mf2)
+    } catch (e) {
+        // TODO show errors to user
+
+        return
+    }
+    form.clear()
+}
+
+document.getElementById("authorized").style.display = "block";
+// Local Variables:
+// js-indent-level: 4
+// End:
diff --git a/kittybox-rs/companion-lite/micropub_api.js b/kittybox-rs/companion-lite/micropub_api.js
new file mode 100644
index 0000000..402c075
--- /dev/null
+++ b/kittybox-rs/companion-lite/micropub_api.js
@@ -0,0 +1,43 @@
+export async function query_channels(endpoint, token) {
+    const response = await fetch(endpoint + "?q=config", {
+        headers: {
+            "Authorization": `Bearer ${get_token()}`
+        }
+    })
+
+    const config = await response.json();
+
+    return config["channels"]
+}
+
+export async function submit(endpoint, token, mf2) {
+    try {
+        const response = await fetch(endpoint, {
+            method: "POST",
+            headers: {
+                "Authorization": `Bearer ${token}`,
+                "Content-Type": "application/json"
+            },
+            body: JSON.stringify(mf2)
+        })
+
+
+        if (response.status != 201 || response.status != 202) {
+            let err = await response.json();
+            console.error("Micropub error!", err);
+            
+            return err;
+        } else {
+            return {
+                "location": response.headers.get("Location")
+            }
+        }
+    } catch (e) {
+        console.error("Network error!", e)
+        throw e
+    }
+}
+
+// Local Variables:
+// js-indent-level: 4
+// End:
diff --git a/kittybox-rs/companion-lite/style.css b/kittybox-rs/companion-lite/style.css
new file mode 100644
index 0000000..09ed398
--- /dev/null
+++ b/kittybox-rs/companion-lite/style.css
@@ -0,0 +1,47 @@
+* {
+    box-sizing: border-box;
+}
+
+:root {
+    font-family: sans-serif;
+}
+
+body {
+    margin: 0;
+}
+
+body > main {
+    margin: auto;
+    max-width: 1024px;
+}
+
+h1.header {
+    margin-top: 0.75em;
+    text-align: center;
+}
+
+fieldset + fieldset,
+fieldset + input,
+section + section,
+section + fieldset
+{
+    margin-top: 0.75em;
+}
+
+input[type="submit"] {
+    margin-left: auto;
+    display: block;
+}
+
+form > fieldset > section > label {
+    width: 100%;
+    display: block;
+}
+
+form > fieldset > section > input, form > fieldset > section > textarea {
+    width: 100%;
+}
+
+textarea {
+    min-height: 10em;
+}