about summary refs log tree commit diff
path: root/kittybox-rs/companion-lite/src
diff options
context:
space:
mode:
authorVika <vika@fireburn.ru>2022-09-29 22:39:28 +0300
committerVika <vika@fireburn.ru>2022-09-29 22:39:28 +0300
commitb7d4e5c4686bc8aac41d832567190002542a1743 (patch)
tree6a42a990fbb06057a64de5f68175f94c3637c2c7 /kittybox-rs/companion-lite/src
parentb3508ccb146648950ed392b517d12354203c4347 (diff)
downloadkittybox-b7d4e5c4686bc8aac41d832567190002542a1743.tar.zst
companion-lite: port to TypeScript
Diffstat (limited to 'kittybox-rs/companion-lite/src')
-rw-r--r--kittybox-rs/companion-lite/src/main.ts85
-rw-r--r--kittybox-rs/companion-lite/src/micropub_api.ts58
2 files changed, 143 insertions, 0 deletions
diff --git a/kittybox-rs/companion-lite/src/main.ts b/kittybox-rs/companion-lite/src/main.ts
new file mode 100644
index 0000000..d593188
--- /dev/null
+++ b/kittybox-rs/companion-lite/src/main.ts
@@ -0,0 +1,85 @@
+import { query_channels, submit, MicropubChannel, MF2 } from "./micropub_api.js";
+
+function get_token() {
+  return (form.elements.namedItem("access_token") as HTMLInputElement).value
+}
+
+const form = document.getElementById("micropub") as HTMLFormElement;
+const channel_select_radio = document.getElementById("select_channels") as HTMLInputElement;
+
+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") as HTMLInputElement;
+no_channel_radio.onclick = () => {
+  (document.getElementById("channels") as HTMLElement).style.display = "none";
+  const channel_list = document.getElementById("channels_target") as HTMLElement
+  channel_list.innerHTML = "";
+}
+
+function construct_body(form: HTMLFormElement): MF2 {
+  let content = (form.elements.namedItem("content") as HTMLInputElement).value;
+  let name: string | undefined = (form.elements.namedItem("name") as HTMLInputElement).value || undefined;
+  let category: string[] = (form.elements.namedItem("category") as HTMLInputElement).value
+    .split(",")
+    .map(val => val.trim());
+
+  let channel: string[] | undefined = undefined;
+  let channel_select = (form.elements.namedItem("channel_select") as HTMLInputElement).value;
+  if (channel_select) {
+    let channel_selector = form.elements.namedItem("channel");
+    if (channel_selector instanceof RadioNodeList) {
+      channel = (Array.from(channel_selector) as HTMLInputElement[])
+        .map(i => i.checked ? i.value : false)
+        .filter(i => i) as string[];
+    } else if (channel_selector instanceof HTMLInputElement) {
+      channel = [channel_selector.value]
+    }
+  }
+  return {
+    type: ["h-entry"],
+    properties: {
+      content: [content],
+      name: name ? [name] : undefined,
+      category: category.length ? category : undefined,
+      channel: channel ? channel : undefined
+    }
+  }
+}
+
+function populate_channel_list(channels: MicropubChannel[]) {
+  (document.getElementById("channels") as HTMLElement).style.display = "block";
+  const channel_list = document.getElementById("channels_target") as HTMLElement;
+  channel_list.innerHTML = "";
+  channels.forEach((channel) => {
+    const template = (document.getElementById("channel_selector") as HTMLTemplateElement).content.cloneNode(true) as HTMLElement;
+    const input = template.querySelector("input") as HTMLInputElement;
+    const label = template.querySelector("label") as HTMLLabelElement;
+    input.id = `channel_selector_option_${channel.uid}`
+    input.value = channel.uid
+    label.htmlFor = 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") as HTMLElement).style.display = "block";
diff --git a/kittybox-rs/companion-lite/src/micropub_api.ts b/kittybox-rs/companion-lite/src/micropub_api.ts
new file mode 100644
index 0000000..9eb65a2
--- /dev/null
+++ b/kittybox-rs/companion-lite/src/micropub_api.ts
@@ -0,0 +1,58 @@
+export interface MicropubChannel {
+  uid: string,
+  name: string
+}
+
+export interface MF2 {
+  type: string[],
+  properties: { [key:string]: (string | MF2 | {[key:string]: string})[] | undefined }
+}
+
+export interface MicropubConfig {
+  channels: MicropubChannel[],
+  "media-endpoint": string
+}
+
+export async function query_channels(endpoint: string, token: string): Promise<MicropubChannel[]> {
+  const response = await fetch(endpoint + "?q=config", {
+    headers: {
+      "Authorization": `Bearer ${token}`
+    }
+  })
+
+  if (response.ok) {
+    const config = await response.json() as MicropubConfig;
+
+    return config["channels"]
+  } else {
+    throw new Error(`Micropub endpoint returned ${response.status}: ${await response.json()}`)
+  }
+  
+}
+
+export async function submit(endpoint: string, token: string, mf2: 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
+  }
+}