diff --git a/web/assets/css/style.css b/web/assets/css/style.css index f6a9fcd..c7c408e 100644 --- a/web/assets/css/style.css +++ b/web/assets/css/style.css @@ -55,7 +55,15 @@ html, body { } } -#spinner { +#spinner-container { + position: fixed; + top: 130px; + right: 20px; + height: 50px; + width: 50px; +} + +#spinner-container .spinner { -webkit-animation: .75s linear infinite spinner; animation: .75s linear infinite spinner; -webkit-animation-play-state: inherit; @@ -63,11 +71,8 @@ html, body { border: solid 5px #ffffff; border-bottom-color: transparent; border-radius: 50%; - height: 50px; - width: 50px; - position: fixed; - top: 130px; - right: 20px; + height: 100%; + width: 100%; -webkit-transform: translate3d(-50%, -50%, 0); transform: translate3d(-50%, -50%, 0); will-change: transform; @@ -169,32 +174,42 @@ html, body { .container #content #input { height: 100%; width: 100%; + padding: 0; background-color: transparent; border: none; outline: none; color: inherit; resize: none; - font-size: 16px; + font: inherit; + line-height: 20px; } .container #notifications { position: fixed; bottom: 30px; + right: 0; + padding: 20px; } .container #notifications div { - width: 100vw; - padding: 10px 20px; + border-radius: 10px; + width: 500px; + margin-top: 20px; + padding: 20px 30px; } .container #notifications div.error { - background-color: #ff3d3d; + background-color: #ff4d4d; } .container #notifications div.success { background-color: #389b38; } +.container #notifications div:first-child { + margin-top: 0; +} + #footer { position: fixed; bottom: 0; @@ -261,6 +276,15 @@ html, body { .navigation .meta #version { display: none; } + .container #notifications { + padding: 0; + } + .container #notifications div { + border-radius: 0; + width: 100vw; + -webkit-box-sizing: border-box; + box-sizing: border-box; + } #footer #flex { margin: 0 0 0 25px; } diff --git a/web/assets/css/style.css.map b/web/assets/css/style.css.map index 8e4e562..0554ea3 100644 --- a/web/assets/css/style.css.map +++ b/web/assets/css/style.css.map @@ -1,6 +1,6 @@ { "version": 3, - "mappings": "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", + "mappings": "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", "sources": [ "style.scss" ], diff --git a/web/assets/css/style.scss b/web/assets/css/style.scss index af7ca5c..e7a8e07 100644 --- a/web/assets/css/style.scss +++ b/web/assets/css/style.scss @@ -54,22 +54,26 @@ html, body { transform: translate3d(-50%, -50%, 0) rotate(360deg); } } -#spinner { - -webkit-animation: .75s linear infinite spinner; - animation: .75s linear infinite spinner; - -webkit-animation-play-state: inherit; - animation-play-state: inherit; - border: solid 5px #ffffff; - border-bottom-color: transparent; - border-radius: 50%; - height: 50px; - width: 50px; +#spinner-container { position: fixed; top: 130px; right: 20px; - -webkit-transform: translate3d(-50%, -50%, 0); - transform: translate3d(-50%, -50%, 0); - will-change: transform; + height: 50px; + width: 50px; + & .spinner { + -webkit-animation: .75s linear infinite spinner; + animation: .75s linear infinite spinner; + -webkit-animation-play-state: inherit; + animation-play-state: inherit; + border: solid 5px #ffffff; + border-bottom-color: transparent; + border-radius: 50%; + height: 100%; + width: 100%; + -webkit-transform: translate3d(-50%, -50%, 0); + transform: translate3d(-50%, -50%, 0); + will-change: transform; + } } .navigation { @@ -140,26 +144,35 @@ html, body { & #input { height: 100%; width: 100%; + padding: 0; background-color: transparent; border: none; outline: none; color: inherit; resize: none; - font-size: 16px; + font: inherit; + line-height: 20px; } } & #notifications { position: fixed; bottom: 30px; + right: 0; + padding: 20px; & div { - width: 100vw; - padding: 10px 20px; + border-radius: 10px; + width: 500px; + margin-top: 20px; + padding: 20px 30px; &.error { - background-color: #ff3d3d; + background-color: #ff4d4d; } &.success { background-color: #389b38; } + &:first-child { + margin-top: 0; + } } } } @@ -216,6 +229,15 @@ html, body { } } + .container #notifications { + padding: 0; + & div { + border-radius: 0; + width: 100vw; + box-sizing: border-box; + } + } + #footer { & #flex { diff --git a/web/assets/js/api.js b/web/assets/js/api.js deleted file mode 100644 index cc2d64c..0000000 --- a/web/assets/js/api.js +++ /dev/null @@ -1,36 +0,0 @@ -// apiBase defines the base URL of the API -const apiBase = location.protocol + "//" + location.host + "/api/v2"; - -// getAPIInformation returns the API information -export async function getAPIInformation() { - return fetch(apiBase + "/info"); -} - -// getPaste retrieves a paste -export async function getPaste(id) { - return fetch(apiBase + "/pastes/" + id); -} - -// createPaste creates a new paste -export async function createPaste(content) { - return await fetch(apiBase + "/pastes", { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - content - }) - }); -} - -// deletePaste deletes a paste -export async function deletePaste(id, modificationToken) { - return await fetch(apiBase + "/pastes/" + id, { - method: 'DELETE', - headers: { - 'Content-Type': 'application/json', - 'Authorization': 'Bearer ' + modificationToken, - } - }); -} \ No newline at end of file diff --git a/web/assets/js/app.js b/web/assets/js/app.js new file mode 100644 index 0000000..06e6255 --- /dev/null +++ b/web/assets/js/app.js @@ -0,0 +1,23 @@ +import * as API from "./modules/api.js"; +import * as Notifications from "./modules/notifications.js"; +import * as Spinner from "./modules/spinner.js"; +import * as State from "./modules/state.js"; + +// Load the API information +let API_INFORMATION = { + version: "error", + modificationTokens: false, + reports: false +}; +const response = await API.getAPIInformation(); +if (response.ok) { + API_INFORMATION = await response.json(); +} else { + Notifications.error("Failed loading API information: " + await response.text() + ""); +} + +// Display the API version +document.getElementById("version").innerText = API_INFORMATION.version; + +// Initialize the application state +Spinner.surround(State.initialize); diff --git a/web/assets/js/autoload.js b/web/assets/js/autoload.js deleted file mode 100644 index 6b95383..0000000 --- a/web/assets/js/autoload.js +++ /dev/null @@ -1,91 +0,0 @@ -// Import the used modules -import * as api from "./api.js"; -import * as buttons from "./buttons.js"; -import * as spinner from "./spinner.js"; -import * as notifications from "./notifications.js"; - -// Set up the buttons -buttons.setupButtons(); -buttons.setupKeybinds(); - -// Define element handles -const versionElement = document.getElementById("version"); -const lineNOsElement = document.getElementById("linenos"); -const codeElement = document.getElementById("code"); -const inputElement = document.getElementById("input"); - -// Load the API information -async function loadAPIInformation() { - const response = await api.getAPIInformation(); - if (!response.ok) { - const data = await response.text(); - notifications.error("Failed fetching the API information: " + data + ""); - return; - } - const data = await response.json(); - versionElement.innerText = data.version; -} -loadAPIInformation(); - -// Try to load a paste if one exists -export let PASTE_ID; -let CODE; -async function loadPaste() { - if (location.pathname !== "/") { - // Define the paste ID and language - const split = location.pathname.replace("/", "").split("."); - const pasteID = split[0]; - const language = split[1]; - - // Retrieve the paste from the API and redirect the user to the main page if it could not be found - const response = await api.getPaste(pasteID); - if (!response.ok) { - location.replace(location.protocol + "//" + location.host); - return; - } - CODE = (await response.json()).content; - - // Adjust the button states - document.getElementById("btn_save").setAttribute("disabled", true); - document.getElementById("btn_delete").removeAttribute("disabled"); - document.getElementById("btn_copy").removeAttribute("disabled"); - - // Set the paste content to the DOM - codeElement.innerHTML = language - ? hljs.highlight(language, CODE).value - : hljs.highlightAuto(CODE).value; - - // Display the line numbers - lineNOsElement.innerHTML = CODE.split(/\n/).map((_, index) => `${index + 1}`).join(''); - - // Set the PASTE_ID variable - PASTE_ID = pasteID; - } else { - inputElement.classList.remove("hidden"); - inputElement.focus(); - window.addEventListener("keydown", function (event) { - if (event.keyCode != 9) return; - event.preventDefault(); - - insertTextAtCursor(inputElement, " "); - }); - } -} -spinner.surround(loadPaste); - -// 1:1 skid from https://stackoverflow.com/questions/7404366/how-do-i-insert-some-text-where-the-cursor-is -function insertTextAtCursor(element, text) { - let value = element.value, endIndex, range, doc = element.ownerDocument; - if (typeof element.selectionStart == "number" - && typeof element.selectionEnd == "number") { - endIndex = element.selectionEnd; - element.value = value.slice(0, endIndex) + text + value.slice(endIndex); - element.selectionStart = element.selectionEnd = endIndex + text.length; - } else if (doc.selection != "undefined" && doc.selection.createRange) { - element.focus(); - range = doc.selection.createRange(); - range.collapse(false); - range.text = text; - range.select(); - } -} \ No newline at end of file diff --git a/web/assets/js/buttons.js b/web/assets/js/buttons.js deleted file mode 100644 index 42d6597..0000000 --- a/web/assets/js/buttons.js +++ /dev/null @@ -1,119 +0,0 @@ -// Import the used modules -import * as api from "./api.js"; -import * as autoload from "./autoload.js"; -import * as spinner from "./spinner.js"; -import * as notifications from "./notifications.js"; - -// setupKeybinds initializes the keybinds for the buttons -export function setupKeybinds() { - window.addEventListener("keydown", function (event) { - // Return if the CTRL key was not pressed - if (!event.ctrlKey) return; - - // Define the DOM element of the pressed button - let element = null; - switch (event.code) { - case "KeyQ": { - element = document.getElementById("btn_new"); - break; - } - case "KeyS": { - element = document.getElementById("btn_save"); - break; - } - case "KeyX": { - element = document.getElementById("btn_delete"); - break; - } - case "KeyB": { - element = document.getElementById("btn_copy"); - break; - } - } - - // Call the onClick function of the button - if (element) { - if (element.hasAttribute("disabled")) return; - event.preventDefault(); - element.click(); - } - }); -} - -// setupButtons configures the click listeners of the buttons -export function setupButtons() { - // Define the behavior of the 'new' button - document.getElementById("btn_new").addEventListener("click", function () { - location.replace(location.protocol + "//" + location.host); - }); - - // Define the behavior of the 'save' button - document.getElementById("btn_save").addEventListener("click", function () { - spinner.surround(async function () { - // Return if the text area is empty - const input = document.getElementById("input"); - if (!input.value) return; - - // Create the paste - const response = await api.createPaste(input.value); - if (!response.ok) { - notifications.error("Failed creating the paste: " + await response.text() + ""); - return; - } - const data = await response.json(); - - // Give the user the chance to copy the modification token - if (data.modificationToken) { - prompt("The modification token for your paste is:", data.modificationToken); - } - - // Redirect the user to the paste page - let address = location.protocol + "//" + location.host + "/" + data.id; - location.replace(address); - }); - }); - - // Define the behavior of the 'delete' button - document.getElementById("btn_delete").addEventListener("click", function () { - spinner.surround(async function () { - // Ask the user for the modification token - const modificationToken = prompt("Modification Token:"); - if (!modificationToken) return; - - // Delete the paste - const response = await api.deletePaste(autoload.PASTE_ID, modificationToken); - const data = await response.text(); - if (!response.ok) { - notifications.error("Failed deleting the paste: " + data + ""); - return; - } - - // Redirect the user to the main page - location.replace(location.protocol + "//" + location.host); - }); - }); - - // Define the behavior of the 'copy' button - document.getElementById("btn_copy").addEventListener("click", function () { - spinner.surround(async function () { - // Ask for the clipboard permissions - askClipboardPermissions(); - - // Copy the code - await navigator.clipboard.writeText(document.getElementById("code").innerText); - notifications.success("Copied the code!"); - }); - }); -} - -// askClipboardPermissions asks the user for the clipboard permissions -async function askClipboardPermissions() { - try { - const state = await navigator.permissions.query({ - name: "clipboard-write" - }); - return state === "granted"; - } catch (error) { - return false; - } -} \ No newline at end of file diff --git a/web/assets/js/modules/animation.js b/web/assets/js/modules/animation.js new file mode 100644 index 0000000..174a846 --- /dev/null +++ b/web/assets/js/modules/animation.js @@ -0,0 +1,12 @@ +// Properly animates an element +export function animate(element, animation, duration, after) { + element.style.setProperty("--animate-duration", duration); + element.classList.add("animate__animated", animation); + element.addEventListener("animationend", () => { + element.style.removeProperty("--animate-duration"); + element.classList.remove("animate__animated", animation); + if (after) { + after(); + } + }, {once: true}); +} \ No newline at end of file diff --git a/web/assets/js/modules/api.js b/web/assets/js/modules/api.js new file mode 100644 index 0000000..6140672 --- /dev/null +++ b/web/assets/js/modules/api.js @@ -0,0 +1,57 @@ +const API_BASE_URL = location.protocol + "//" + location.host + "/api/v2"; + +export async function getAPIInformation() { + return fetch(API_BASE_URL + "/info"); +} + +export async function getPaste(pasteID) { + return fetch(API_BASE_URL + "/pastes/" + pasteID); +} + +export async function createPaste(content, metadata) { + return fetch(API_BASE_URL + "/pastes", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + content, + metadata + }) + }); +} + +export async function editPaste(pasteID, modificationToken, content, metadata) { + return fetch(API_BASE_URL + "/pastes/" + pasteID, { + method: "PATCH", + headers: { + "Content-Type": "application/json", + "Authorization": "Bearer " + modificationToken, + }, + body: JSON.stringify({ + content, + metadata + }) + }); +} + +export async function deletePaste(pasteID, modificationToken) { + return fetch(API_BASE_URL + "/pastes/" + pasteID, { + method: "DELETE", + headers: { + "Authorization": "Bearer " + modificationToken, + } + }); +} + +export async function reportPaste(pasteID, reason) { + return fetch(API_BASE_URL + "/pastes/" + pasteID + "/report", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + reason + }) + }); +} diff --git a/web/assets/js/modules/notifications.js b/web/assets/js/modules/notifications.js new file mode 100644 index 0000000..3a27b59 --- /dev/null +++ b/web/assets/js/modules/notifications.js @@ -0,0 +1,25 @@ +import * as Animation from "./animation.js"; + +const ELEMENT = document.getElementById("notifications"); + +// Shows a success notification +export function success(message) { + create("success", message, 3000); +} + +// Shows an error notification +export function error(message) { + create("error", message, 3000); +} + +// Creates a new custom notification +function create(type, message, duration) { + const node = document.createElement("div"); + node.classList.add(type); + Animation.animate(node, "animate__fadeInUp", "0.2s"); + node.innerHTML = message; + + ELEMENT.childNodes.forEach(child => Animation.animate(child, "animate__slideInUp", "0.2s")); + ELEMENT.appendChild(node); + setTimeout(() => Animation.animate(node, "animate__fadeOutUp", "0.2s", () => ELEMENT.removeChild(node)), duration); +} \ No newline at end of file diff --git a/web/assets/js/modules/spinner.js b/web/assets/js/modules/spinner.js new file mode 100644 index 0000000..201dc6b --- /dev/null +++ b/web/assets/js/modules/spinner.js @@ -0,0 +1,21 @@ +import * as Animation from "./animation.js"; + +const ELEMENT = document.getElementById("spinner-container"); + +// SHows the spinner +export function show() { + ELEMENT.classList.remove("hidden"); + Animation.animate(ELEMENT, "animate__zoomIn", "0.2s"); +} + +// Hides the spinner +export function hide() { + Animation.animate(ELEMENT, "animate__zoomOut", "0.2s", () => ELEMENT.classList.add("hidden")); +} + +// Surrounds an async action with a spinner +export async function surround(innerFunction) { + show(); + await innerFunction(); + hide(); +} \ No newline at end of file diff --git a/web/assets/js/modules/state.js b/web/assets/js/modules/state.js new file mode 100644 index 0000000..6ff3eb1 --- /dev/null +++ b/web/assets/js/modules/state.js @@ -0,0 +1,298 @@ +import * as API from "./api.js"; +import * as Notifications from "./notifications.js"; +import * as Spinner from "./spinner.js"; +import * as Animation from "./animation.js"; + +const CODE_ELEMENT = document.getElementById("code"); +const LINE_NUMBERS_ELEMENT = document.getElementById("linenos"); +const INPUT_ELEMENT = document.getElementById("input"); + +const BUTTONS_DEFAULT_ELEMENT = document.getElementById("buttons_default"); +const BUTTON_NEW_ELEMENT = document.getElementById("btn_new"); +const BUTTON_SAVE_ELEMENT = document.getElementById("btn_save"); +const BUTTON_EDIT_ELEMENT = document.getElementById("btn_edit"); +const BUTTON_DELETE_ELEMENT = document.getElementById("btn_delete"); +const BUTTON_COPY_ELEMENT = document.getElementById("btn_copy"); + +const BUTTONS_EDIT_ELEMENT = document.getElementById("buttons_edit"); +const BUTTON_EDIT_CANCEL_ELEMENT = document.getElementById("btn_edit_cancel"); +const BUTTON_EDIT_APPLY_ELEMENT = document.getElementById("btn_edit_apply"); + +let PASTE_ID; +let LANGUAGE; +let CODE; + +let EDIT_MODE = false; + +// Initializes the state system +export async function initialize() { + setupButtonFunctionality(); + setupKeybinds(); + + if (location.pathname !== "/") { + // Extract the paste data (ID and language) + const split = location.pathname.replace("/", "").split("."); + const pasteID = split[0]; + const language = split[1]; + + // Try to retrieve the paste data from the API + const response = await API.getPaste(pasteID); + if (!response.ok) { + Notifications.error("Could not load paste: " + await response.text() + ""); + setTimeout(() => location.replace(location.protocol + "//" + location.host), 3000); + return; + } + + // Set the persistent paste data + PASTE_ID = pasteID; + LANGUAGE = language; + CODE = (await response.json()).content; + + // Fill the code block with the just received data + updateCode(); + } else { + // Give the user the opportunity to paste his code + INPUT_ELEMENT.classList.remove("hidden"); + INPUT_ELEMENT.focus(); + } + + // Update the state of the buttons to match the current state + updateButtonState(); +} + +// Sets the current persistent code to the code block, highlights it and updates the line numbers +function updateCode() { + CODE_ELEMENT.innerHTML = LANGUAGE + ? hljs.highlight(LANGUAGE, CODE).value + : hljs.highlightAuto(CODE).value; + + LINE_NUMBERS_ELEMENT.innerHTML = CODE.split(/\n/).map((_, index) => `${index + 1}`).join(""); +} + +// Updates the button state according to the current state +function updateButtonState() { + if (PASTE_ID) { + BUTTON_SAVE_ELEMENT.setAttribute("disabled", true); + BUTTON_EDIT_ELEMENT.removeAttribute("disabled"); + BUTTON_DELETE_ELEMENT.removeAttribute("disabled"); + BUTTON_COPY_ELEMENT.removeAttribute("disabled"); + } else { + BUTTON_SAVE_ELEMENT.removeAttribute("disabled"); + BUTTON_EDIT_ELEMENT.setAttribute("disabled", true); + BUTTON_DELETE_ELEMENT.setAttribute("disabled", true); + BUTTON_COPY_ELEMENT.setAttribute("disabled", true); + } +} + +// Toggles the edit mode +function toggleEditMode() { + if (EDIT_MODE) { + EDIT_MODE = false; + INPUT_ELEMENT.classList.add("hidden"); + CODE_ELEMENT.classList.remove("hidden"); + Animation.animate(BUTTONS_EDIT_ELEMENT, "animate__fadeOutDown", "0.3s", () => { + BUTTONS_EDIT_ELEMENT.classList.add("hidden"); + BUTTONS_DEFAULT_ELEMENT.classList.remove("hidden"); + Animation.animate(BUTTONS_DEFAULT_ELEMENT, "animate__fadeInDown", "0.3s"); + }); + } else { + EDIT_MODE = true; + CODE_ELEMENT.classList.add("hidden"); + INPUT_ELEMENT.classList.remove("hidden"); + INPUT_ELEMENT.value = CODE; + INPUT_ELEMENT.focus(); + Animation.animate(BUTTONS_DEFAULT_ELEMENT, "animate__fadeOutUp", "0.3s", () => { + BUTTONS_DEFAULT_ELEMENT.classList.add("hidden"); + BUTTONS_EDIT_ELEMENT.classList.remove("hidden"); + Animation.animate(BUTTONS_EDIT_ELEMENT, "animate__fadeInUp", "0.3s"); + }); + } +} + +// Sets up the keybinds for the buttons +function setupKeybinds() { + window.addEventListener("keydown", (event) => { + // All keybinds in the default button set include the CTRL key + if ((EDIT_MODE && !event.ctrlKey && event.code !== "Escape") || (!EDIT_MODE && !event.ctrlKey)) { + return; + } + + // Find the DOM element of the button to trigger + let element; + if (EDIT_MODE) { + switch (event.code) { + case "Escape": { + element = BUTTON_EDIT_CANCEL_ELEMENT; + break + } + case "KeyS": { + element = BUTTON_EDIT_APPLY_ELEMENT; + break; + } + } + } else { + switch (event.code) { + case "KeyQ": { + element = BUTTON_NEW_ELEMENT; + break; + } + case "KeyS": { + element = BUTTON_SAVE_ELEMENT; + break; + } + case "KeyO": { + element = BUTTON_EDIT_ELEMENT; + break; + } + case "KeyX": { + element = BUTTON_DELETE_ELEMENT; + break; + } + case "KeyB": { + element = BUTTON_COPY_ELEMENT; + break; + } + } + } + + // Trigger the found button + if (element) { + event.preventDefault(); + if (element.hasAttribute("disabled")) { + return; + } + element.click(); + } + }); + + // Additionally fix the behaviour of the Tab key + window.addEventListener("keydown", (event) => { + if (event.code != "Tab") { + return; + } + event.preventDefault(); + + insertTextAtCursor(inputElement, " "); + }); +} + +// Sets up the different button functionalities +function setupButtonFunctionality() { + BUTTON_NEW_ELEMENT.addEventListener("click", () => location.replace(location.protocol + "//" + location.host)); + + BUTTON_SAVE_ELEMENT.addEventListener("click", () => { + Spinner.surround(async () => { + // Only proceed if the input is not empty + if (!INPUT_ELEMENT.value) { + return; + } + + // Try to create the paste + const response = await API.createPaste(INPUT_ELEMENT.value); + if (!response.ok) { + Notifications.error("Error while creating paste: " + await response.text() + ""); + return; + } + const data = await response.json(); + + // Display the modification token if provided + if (data.modificationToken) { + prompt("The modification token for your paste is:", data.modificationToken); + } + + // Redirect the user to his newly created paste + location.replace(location.protocol + "//" + location.host + "/" + data.id); + }); + }); + + BUTTON_EDIT_ELEMENT.addEventListener("click", toggleEditMode); + + BUTTON_DELETE_ELEMENT.addEventListener("click", () => { + Spinner.surround(async () => { + // Ask for the modification token + const modificationToken = prompt("Modification token:"); + if (!modificationToken) { + return; + } + + // Try to delete the paste + const response = await API.deletePaste(PASTE_ID, modificationToken); + if (!response.ok) { + Notifications.error("Error while deleting paste: " + await response.text() + ""); + return; + } + + // Redirect the user to the start page + location.replace(location.protocol + "//" + location.host); + }); + }); + + BUTTON_COPY_ELEMENT.addEventListener("click", async () => { + // Ask for clipboard permissions + if (!(await askForClipboardPermission())) { + Notifications.error("Clipboard permission denied."); + return; + } + + // Copy the current code + await navigator.clipboard.writeText(CODE); + Notifications.success("Successfully copied the code."); + }); + + BUTTON_EDIT_CANCEL_ELEMENT.addEventListener("click", toggleEditMode); + + BUTTON_EDIT_APPLY_ELEMENT.addEventListener("click", async () => { + // Only proceed if the input is not empty + if (!INPUT_ELEMENT.value) { + return; + } + + // Ask for the modification token + const modificationToken = prompt("Modification token:"); + if (!modificationToken) { + return; + } + + // Try to edit the paste + const response = await API.editPaste(PASTE_ID, modificationToken, INPUT_ELEMENT.value); + if (!response.ok) { + Notifications.error("Error while editing paste: " + await response.text() + ""); + return; + } + + // Update the code and leave the edit mode + CODE = INPUT_ELEMENT.value; + updateCode(); + toggleEditMode(); + Notifications.success("Successfully edited paste."); + }); +} + +// Asks for clipboard write permission +async function askForClipboardPermission() { + try { + const state = await navigator.permissions.query({ + name: "clipboard-write" + }); + return state.state === "granted" || state.state === "prompt"; + } catch (error) { + return false; + } +} + +// 1:1 skid from https://stackoverflow.com/questions/7404366/how-do-i-insert-some-text-where-the-cursor-is +function insertTextAtCursor(element, text) { + let value = element.value, endIndex, range, doc = element.ownerDocument; + if (typeof element.selectionStart == "number" + && typeof element.selectionEnd == "number") { + endIndex = element.selectionEnd; + element.value = value.slice(0, endIndex) + text + value.slice(endIndex); + element.selectionStart = element.selectionEnd = endIndex + text.length; + } else if (doc.selection != "undefined" && doc.selection.createRange) { + element.focus(); + range = doc.selection.createRange(); + range.collapse(false); + range.text = text; + range.select(); + } +} \ No newline at end of file diff --git a/web/assets/js/notifications.js b/web/assets/js/notifications.js deleted file mode 100644 index cdbd2df..0000000 --- a/web/assets/js/notifications.js +++ /dev/null @@ -1,22 +0,0 @@ -// element holds the notification containers DOM element -const element = document.getElementById("notifications"); - -// error shows an error notifications -export function error(message) { - create("error", message, 3000); -} - -// success shows a success notifications -export function success(message) { - create("success", message, 3000); -} - -// create creates a new notification -function create(type, message, duration) { - const node = document.createElement("div"); - node.classList.add(type); - node.innerHTML = message; - - element.appendChild(node); - setTimeout(() => element.removeChild(node), duration); -} \ No newline at end of file diff --git a/web/assets/js/spinner.js b/web/assets/js/spinner.js deleted file mode 100644 index f86801f..0000000 --- a/web/assets/js/spinner.js +++ /dev/null @@ -1,19 +0,0 @@ -// element holds the spinners DOM element -const element = document.getElementById("spinner"); - -// show shows the spinner -export function show() { - element.classList.remove("hidden"); -} - -// hide hides the spinner -export function hide() { - element.classList.add("hidden"); -} - -// surround surrounds an action with a spinner -export async function surround(action) { - show(); - await action(); - hide(); -} \ No newline at end of file diff --git a/web/index.html b/web/index.html index e2a7c06..14a33b6 100644 --- a/web/index.html +++ b/web/index.html @@ -7,12 +7,13 @@ pasty + - + - + \ No newline at end of file