From d7d86b6bc8f6adcfd987a13e2f9e5733b403c3f8 Mon Sep 17 00:00:00 2001 From: Kitteh Date: Sun, 9 May 2021 11:06:45 +0100 Subject: [PATCH] Add upload support to transit encrypt/decrypt. Closes #4. --- src/elements/CopyableInputBox.ts | 2 +- src/elements/FileUploadInput.ts | 42 +++++++++++++++++++++++++++++ src/htmlUtils.ts | 9 ++++++- src/pages/Transit/TransitDecrypt.ts | 23 +++++++++++----- src/pages/Transit/TransitEncrypt.ts | 28 ++++++++++++++----- src/scss/uikit.scss | 2 +- src/translations/en.js | 3 +++ 7 files changed, 93 insertions(+), 16 deletions(-) create mode 100644 src/elements/FileUploadInput.ts diff --git a/src/elements/CopyableInputBox.ts b/src/elements/CopyableInputBox.ts index 3cbc3a2..8c9bef1 100644 --- a/src/elements/CopyableInputBox.ts +++ b/src/elements/CopyableInputBox.ts @@ -29,7 +29,7 @@ export function CopyableInputBox(text: string, copyable = true): CopyableInputBo const inputBoxInput = makeElement({ tag: "input", - class: ["uk-input"], + class: ["uk-input-copyable"], attributes: { "readonly": true, "type": "text" }, }); diff --git a/src/elements/FileUploadInput.ts b/src/elements/FileUploadInput.ts new file mode 100644 index 0000000..bd57144 --- /dev/null +++ b/src/elements/FileUploadInput.ts @@ -0,0 +1,42 @@ +import { makeElement } from "../htmlUtils"; +import i18next from "i18next"; + +export function FileUploadInput(name: string): Element { + const fileInput = makeElement({ + tag: "input", + attributes: { + name: name, + type: "file" + } + }); + + const selectInput = makeElement({ + tag: "input", + class: ["uk-input", "uk-form-width-medium"], + attributes: { + type: "text", + placeholder: i18next.t("file_upload_input_btn") + } + }); + + const fileIcon = makeElement({ + tag: "a", + class: "uk-form-icon", + attributes: { + "uk-icon": "icon: upload", + "role": "img" + }, + }); + + return makeElement({ + tag: "div", + attributes: { + "uk-form-custom": "target: true" + }, + children: [ + fileIcon, + fileInput, + selectInput + ] + }); +} \ No newline at end of file diff --git a/src/htmlUtils.ts b/src/htmlUtils.ts index e0eec93..a95d80e 100644 --- a/src/htmlUtils.ts +++ b/src/htmlUtils.ts @@ -61,4 +61,11 @@ export function setElementAttributes(element: Element, attributes: {[propName: s for (const key of Object.getOwnPropertyNames(attributes)) { element.setAttribute(key, attributes[key]); } -} \ No newline at end of file +} + +export const fileToBase64 = (file: File): Promise => new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = () => resolve(reader.result as string); + reader.onerror = error => reject(error); +}); \ No newline at end of file diff --git a/src/pages/Transit/TransitDecrypt.ts b/src/pages/Transit/TransitDecrypt.ts index 4f31cd5..ae13ba5 100644 --- a/src/pages/Transit/TransitDecrypt.ts +++ b/src/pages/Transit/TransitDecrypt.ts @@ -1,8 +1,9 @@ import { CopyableModal } from "../../elements/CopyableModal"; +import { FileUploadInput } from "../../elements/FileUploadInput"; import { Margin } from "../../elements/Margin"; import { Page } from "../../types/Page"; import { changePage, setErrorText, setPageContent, setTitleElement } from "../../pageUtils"; -import { makeElement } from "../../htmlUtils"; +import { fileToBase64, makeElement } from "../../htmlUtils"; import { pageState } from "../../globalPageState"; import { transitDecrypt } from "../../api/transitDecrypt"; import UIkit from 'uikit/dist/js/uikit.min.js'; @@ -35,6 +36,7 @@ export class TransitDecryptPage extends Page { name: "ciphertext", } })), + Margin(FileUploadInput("ciphertext_file")), Margin([ makeElement({ tag: "div", @@ -70,18 +72,27 @@ export class TransitDecryptPage extends Page { ] }) as HTMLFormElement; setPageContent(this.transitDecryptForm); - this.transitDecryptForm.addEventListener("submit", function (e: Event) { + this.transitDecryptForm.addEventListener("submit", async function (e: Event) { e.preventDefault(); - this.transitEncryptFormHandler(); + await this.transitDecryptFormHandler(); }.bind(this)); } - transitEncryptFormHandler(): void { + async transitDecryptFormHandler(): Promise { const formData = new FormData(this.transitDecryptForm); + + const decodeBase64 = formData.get("decodeBase64Checkbox") as string; - transitDecrypt(pageState.currentBaseMount, pageState.currentSecret, formData.get("ciphertext") as string).then(res => { + let ciphertext = formData.get("ciphertext") as string; + + const ciphertext_file = formData.get("ciphertext_file") as File; + if (ciphertext_file.size > 0) { + ciphertext = atob((await fileToBase64(ciphertext_file) as string).replace("data:text/plain;base64,", "")); + } + + transitDecrypt(pageState.currentBaseMount, pageState.currentSecret, ciphertext).then(res => { let plaintext = res.plaintext; - if (formData.get("decodeBase64Checkbox") as string == "on") { + if (decodeBase64 == "on") { plaintext = atob(plaintext); } const modal = CopyableModal(i18next.t("transit_decrypt_decryption_result_modal_title"), plaintext); diff --git a/src/pages/Transit/TransitEncrypt.ts b/src/pages/Transit/TransitEncrypt.ts index 2bfaec1..b8d3bb9 100644 --- a/src/pages/Transit/TransitEncrypt.ts +++ b/src/pages/Transit/TransitEncrypt.ts @@ -1,8 +1,9 @@ import { CopyableModal } from "../../elements/CopyableModal"; +import { FileUploadInput } from "../../elements/FileUploadInput"; import { Margin } from "../../elements/Margin"; import { Page } from "../../types/Page"; import { changePage, setErrorText, setPageContent, setTitleElement } from "../../pageUtils"; -import { makeElement } from "../../htmlUtils"; +import { fileToBase64, makeElement } from "../../htmlUtils"; import { pageState } from "../../globalPageState"; import { transitEncrypt } from "../../api/transitEncrypt"; import UIkit from 'uikit/dist/js/uikit.min.js'; @@ -37,6 +38,7 @@ export class TransitEncryptPage extends Page { name: "plaintext", } })), + Margin(FileUploadInput("plaintext_file")), Margin([ makeElement({ tag: "div", @@ -73,17 +75,29 @@ export class TransitEncryptPage extends Page { }) as HTMLFormElement; setPageContent(this.transitEncryptForm); - this.transitEncryptForm.addEventListener("submit", function (e) { + this.transitEncryptForm.addEventListener("submit", async function (e) { e.preventDefault(); - this.transitEncryptFormHandler(); + await this.transitEncryptFormHandler(); }.bind(this)); } - transitEncryptFormHandler(): void { + async transitEncryptFormHandler(): Promise { const formData = new FormData(this.transitEncryptForm); - const encodedData = - formData.get("base64Checkbox") as string == "on" ? formData.get("plaintext") as string : btoa(formData.get("plaintext") as string); - transitEncrypt(pageState.currentBaseMount, pageState.currentSecret, encodedData).then(res => { + + const base64Checkbox = formData.get("base64Checkbox") as string; + + let plaintext = formData.get("plaintext") as string; + + const plaintext_file = formData.get("plaintext_file") as File; + if (plaintext_file.size > 0) { + plaintext = (await fileToBase64(plaintext_file) as string).replace("data:text/plain;base64,", ""); + plaintext = base64Checkbox == "on" ? atob(plaintext) : plaintext; + } else { + plaintext = base64Checkbox == "on" ? plaintext : btoa(plaintext); + } + + + transitEncrypt(pageState.currentBaseMount, pageState.currentSecret, plaintext).then(res => { const modal = CopyableModal(i18next.t("transit_encrypt_encryption_result_modal_title"), res.ciphertext); document.body.querySelector("#pageContent").appendChild(modal); UIkit.modal(modal).show(); diff --git a/src/scss/uikit.scss b/src/scss/uikit.scss index f7f036a..0ec0d51 100644 --- a/src/scss/uikit.scss +++ b/src/scss/uikit.scss @@ -76,4 +76,4 @@ $form-radio-background: $global-secondary-background; @import "uikit/src/scss/components/inverse.scss"; // TODO: replace this with a better solution to https://github.com/uikit/uikit/discussions/4458 -.uk-input { padding-left: $form-icon-width !important; } \ No newline at end of file +.uk-input-copyable { padding-left: $form-icon-width !important; } \ No newline at end of file diff --git a/src/translations/en.js b/src/translations/en.js index c831cb4..78df1ca 100644 --- a/src/translations/en.js +++ b/src/translations/en.js @@ -21,6 +21,9 @@ module.exports = { // Copyable Input Box "copy_input_box_copy_icon_text": "Copy Button", + // File Upload Input + "file_upload_input_btn": "Upload File", + // Me Page "me_page_title": "Me/Settings", "log_out_btn": "Log Out",