From 511aadcb967ce632c8f49251db8ba527b66c24be Mon Sep 17 00:00:00 2001
From: Valentin Rigal <rigal@teklia.com>
Date: Thu, 3 Jun 2021 16:40:51 +0200
Subject: [PATCH] Build as a web extension

---
 README.md                          | 11 ++++
 src/components/KeyboardDisplay.vue | 51 ++++++++++++++++++
 src/content/content.js             | 84 +++++++++++++-----------------
 src/manifest.json                  |  3 +-
 4 files changed, 98 insertions(+), 51 deletions(-)
 create mode 100644 src/components/KeyboardDisplay.vue

diff --git a/README.md b/README.md
index fb25430a..02b8d725 100644
--- a/README.md
+++ b/README.md
@@ -2,3 +2,14 @@
 
 ## Development
 `npm run serve`
+
+## Run the application as a web extension
+### Firefox
+* Build the packaged addon `npm run build`
+* Go to `about:debugging#/runtime/this-firefox`
+* Load `artifacts/virtual-keyboard-v<version>-production.zip` as a temporary extension
+
+### Chromium
+* Run `npm run serve` to support live reload
+* Go to `chrome://extensions/`
+* Click on "Load unpacked" and select the `dist` folder
diff --git a/src/components/KeyboardDisplay.vue b/src/components/KeyboardDisplay.vue
new file mode 100644
index 00000000..61a98683
--- /dev/null
+++ b/src/components/KeyboardDisplay.vue
@@ -0,0 +1,51 @@
+<template>
+  <div v-on:click.prevent class="virtual-keyboard">
+    <button v-on:mouseup="addChar('a')">Insert character "a"</button>
+    <button v-on:mouseup="addChar('b')">Insert character "b"</button>
+  </div>
+</template>
+
+<script>
+export default {
+  props: {
+    inputField: {
+      type: HTMLInputElement,
+      required: true,
+    },
+  },
+  mounted() {
+    this.inputField.onkeydown = (e) => {
+      const replacements = { a: "b" };
+      const character = replacements[e.key];
+      if (!character) return;
+      this.addChar(character);
+      return false;
+    };
+  },
+  methods: {
+    addChar(char) {
+      console.log("adding", char);
+      // Add a character to the input depending on selection position
+      const start = this.inputField.selectionStart;
+      const value = this.inputField.value;
+      this.inputField.value =
+        value.slice(0, start) +
+        char +
+        value.slice(this.inputField.selectionEnd);
+      // Reset the input caret after modifying the input value
+      this.inputField.selectionStart = this.inputField.selectionEnd = start + 1;
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.virtual-keyboard {
+  position: absolute;
+  top: 100%;
+  display: flex;
+}
+button {
+  padding: 1rem;
+}
+</style>
diff --git a/src/content/content.js b/src/content/content.js
index cbd64fab..777fc119 100644
--- a/src/content/content.js
+++ b/src/content/content.js
@@ -1,58 +1,44 @@
-const inputFields = document.getElementsByTagName("input");
-const replacements = {
-  a: "b",
-};
-// Create a simple keyboard
-const buttonA = document.createElement("button");
-buttonA.textContent = 'Insert "a"';
-const buttonB = document.createElement("button");
-buttonB.textContent = 'Insert "b"';
-const keyboard = document.createElement("div");
-keyboard.appendChild(buttonA);
-keyboard.appendChild(buttonB);
+import Vue from "vue";
+import Keyboard from "../components/KeyboardDisplay.vue";
+import router from "../router";
+import store from "../store";
 
-let selectedInput = null;
+const inputFields = document.getElementsByTagName("input");
+let keyboard = null;
 
-const addChar = (i, char) => {
-  const start = i.selectionStart;
-  i.value = i.value.slice(0, start) + char + i.value.slice(i.selectionEnd);
-  // Updating the input value moves the caret to the end by default
-  i.selectionStart = i.selectionEnd = start + 1;
+const createKeyboard = (input) => {
+  const keyboardDiv = document.createElement("div");
+  input.parentElement.appendChild(keyboardDiv);
+  keyboard = new Vue({
+    router,
+    store,
+    render: (h) => {
+      return h(Keyboard, {
+        props: { inputField: input },
+      });
+    },
+    el: keyboardDiv,
+  });
 };
 
 for (const input of inputFields) {
-  input.onkeydown = (e) => {
-    const character = replacements[e.key];
-    if (!character) return;
-    addChar(input, character);
-    return false;
-  };
-  // Handle the case where the input is already focused
-  if (document.activeElement === input) {
-    input.parentElement.appendChild(keyboard);
-    selectedInput = input;
-  }
+  // Handle the case where the input is already focused when this script is loaded
+  if (!keyboard && document.activeElement === input) createKeyboard(input);
+
   input.onfocus = () => {
-    input.parentElement.appendChild(keyboard);
-    selectedInput = input;
+    // TODO it is not possible to go from an input field to another with the keyboard global
+    if (!keyboard) createKeyboard(input);
   };
-  // https://css-tricks.com/a-css-approach-to-trap-focus-inside-of-an-element/
-  input.ontransitionend = (e) => {
-    if (input.matches(":focus") || keyboard.contains(document.activeElement))
-      e.target.focus();
-    else {
-      input.parentElement.removeChild(keyboard);
-      selectedInput = null;
-    }
+  input.onblur = (e) => {
+    if (!keyboard) return;
+    // This is a hacky way to keep the focus on the input
+    setTimeout(() => {
+      if (keyboard.$el.contains(document.activeElement)) e.target.focus();
+      else {
+        keyboard.$destroy();
+        keyboard.$el.parentElement.removeChild(keyboard.$el);
+        keyboard = null;
+      }
+    }, 1);
   };
 }
-
-keyboard.onclick = () => {
-  return false;
-};
-buttonA.onmouseup = () => {
-  if (selectedInput) addChar(selectedInput, "a");
-};
-buttonB.onmouseup = () => {
-  if (selectedInput) addChar(selectedInput, "b");
-};
diff --git a/src/manifest.json b/src/manifest.json
index e986c126..7962da25 100644
--- a/src/manifest.json
+++ b/src/manifest.json
@@ -28,8 +28,7 @@
   "content_scripts": [
     {
       "matches": ["<all_urls>"],
-      "js": ["js/content.js"],
-      "css": ["css/content.css"]
+      "js": ["js/content.js"]
     }
   ]
 }
-- 
GitLab