diff --git a/public/index.html b/public/index.html index cbb0337..ad8658b 100644 --- a/public/index.html +++ b/public/index.html @@ -238,6 +238,7 @@ + diff --git a/public/scripts/network.js b/public/scripts/network.js index 571749b..d624812 100644 --- a/public/scripts/network.js +++ b/public/scripts/network.js @@ -893,11 +893,11 @@ class Events { window.dispatchEvent(new CustomEvent(type, { detail: detail })); } - static on(type, callback) { - return window.addEventListener(type, callback, false); + static on(type, callback, options = false) { + return window.addEventListener(type, callback, options); } - static off(type, callback) { - return window.removeEventListener(type, callback, false); + static off(type, callback, options = false) { + return window.removeEventListener(type, callback, options); } } diff --git a/public/scripts/ui.js b/public/scripts/ui.js index b7e75f3..580b73c 100644 --- a/public/scripts/ui.js +++ b/public/scripts/ui.js @@ -241,7 +241,7 @@ class PeersUI { const _callback = (e) => this._sendClipboardData(e, files, text); Events.on('paste-pointerdown', _callback); - Events.on('deactivate-paste-mode', _ => this._deactivatePasteMode(_callback)); + Events.on('deactivate-paste-mode', _ => this._deactivatePasteMode(_callback), { once: true }); this.$cancelPasteModeBtn.removeAttribute('hidden'); @@ -1245,21 +1245,21 @@ class Base64ZipDialog extends Dialog { const base64Hash = window.location.hash.substring(1); this.$pasteBtn = this.$el.querySelector('#base64-paste-btn'); + this.$fallbackTextarea = this.$el.querySelector('.textarea'); if (base64Text) { this.show(); if (base64Text === "paste") { // ?base64text=paste // base64 encoded string is ready to be pasted from clipboard - this.$pasteBtn.innerText = 'Tap here to paste text'; - this.$pasteBtn.addEventListener('click', _ => this.processClipboard('text')); + this.preparePasting("text"); } else if (base64Text === "hash") { // ?base64text=hash#BASE64ENCODED // base64 encoded string is url hash which is never sent to server and faster (recommended) this.processBase64Text(base64Hash) .catch(_ => { Events.fire('notify-user', 'Text content is incorrect.'); - console.log("Text content incorrect.") + console.log("Text content incorrect."); }).finally(_ => { this.hide(); }); @@ -1269,7 +1269,7 @@ class Base64ZipDialog extends Dialog { this.processBase64Text(base64Text) .catch(_ => { Events.fire('notify-user', 'Text content is incorrect.'); - console.log("Text content incorrect.") + console.log("Text content incorrect."); }).finally(_ => { this.hide(); }); @@ -1282,14 +1282,13 @@ class Base64ZipDialog extends Dialog { this.processBase64Zip(base64Hash) .catch(_ => { Events.fire('notify-user', 'File content is incorrect.'); - console.log("File content incorrect.") + console.log("File content incorrect."); }).finally(_ => { this.hide(); }); } else { // ?base64zip=paste || ?base64zip=true - this.$pasteBtn.innerText = 'Tap here to paste files'; - this.$pasteBtn.addEventListener('click', _ => this.processClipboard('file')); + this.preparePasting('files'); } } } @@ -1299,39 +1298,60 @@ class Base64ZipDialog extends Dialog { this.$pasteBtn.innerText = "Processing..."; } - async processClipboard(type) { - if (!navigator.clipboard.readText) { - Events.fire('notify-user', 'This feature is not available on your browser.'); - console.log("navigator.clipboard.readText() is not available on your browser.") - this.hide(); - return; - } - - this._setPasteBtnToProcessing(); - - const base64 = await navigator.clipboard.readText(); - - if (!base64) return; - - if (type === "text") { - this.processBase64Text(base64) - .catch(_ => { - Events.fire('notify-user', 'Clipboard content is incorrect.'); - console.log("Clipboard content is incorrect.") - }).finally(_ => { - this.hide(); - }); + preparePasting(type) { + if (navigator.clipboard.readText) { + this.$pasteBtn.innerText = `Tap here to paste ${type}`; + this._clickCallback = _ => this.processClipboard(type); + this.$pasteBtn.addEventListener('click', _ => this._clickCallback()); } else { - this.processBase64Zip(base64) - .catch(_ => { - Events.fire('notify-user', 'Clipboard content is incorrect.'); - console.log("Clipboard content is incorrect.") - }).finally(_ => { - this.hide(); - }); + console.log("`navigator.clipboard.readText()` is not available on your browser.\nOn Firefox you can set `dom.events.asyncClipboard.readText` to true under `about:config` for convenience.") + this.$pasteBtn.setAttribute('hidden', ''); + this.$fallbackTextarea.setAttribute('placeholder', `Paste here to send ${type}`); + this.$fallbackTextarea.removeAttribute('hidden'); + this._inputCallback = _ => this.processInput(type); + this.$fallbackTextarea.addEventListener('input', _ => this._inputCallback()); + this.$fallbackTextarea.focus(); } } + async processInput(type) { + const base64 = this.$fallbackTextarea.textContent; + this.$fallbackTextarea.textContent = ''; + await this.processBase64(type, base64); + } + + async processClipboard(type) { + const base64 = await navigator.clipboard.readText(); + await this.processBase64(type, base64); + } + + isValidBase64(base64) { + try { + // check if input is base64 encoded + window.atob(base64); + return true; + } catch (e) { + // input is not base64 string. + return false; + } + } + + async processBase64(type, base64) { + if (!base64 || !this.isValidBase64(base64)) return; + this._setPasteBtnToProcessing(); + try { + if (type === "text") { + await this.processBase64Text(base64); + } else { + await this.processBase64Zip(base64); + } + } catch(_) { + Events.fire('notify-user', 'Clipboard content is incorrect.'); + console.log("Clipboard content is incorrect.") + } + this.hide(); + } + processBase64Text(base64Text){ return new Promise((resolve) => { this._setPasteBtnToProcessing(); @@ -1365,6 +1385,8 @@ class Base64ZipDialog extends Dialog { hide() { this.clearBrowserHistory(); + this.$pasteBtn.removeEventListener('click', _ => this._clickCallback()); + this.$fallbackTextarea.removeEventListener('input', _ => this._inputCallback()); super.hide(); } } diff --git a/public/styles.css b/public/styles.css index 237764e..db87050 100644 --- a/public/styles.css +++ b/public/styles.css @@ -791,10 +791,29 @@ x-dialog .dialog-subheader { margin: auto -24px; } -#base64-paste-btn { +#base64-paste-btn, +#base64-paste-dialog .textarea { width: 100%; height: 40vh; border: solid 12px #438cff; + text-align: center; +} + +#base64-paste-dialog .textarea { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; +} + +#base64-paste-dialog .textarea::before { + font-size: 15px; + letter-spacing: 0.12em; + color: var(--primary-color); + font-weight: 700; + text-transform: uppercase; + content: attr(placeholder); } #base64-paste-dialog button { diff --git a/public_included_ws_fallback/index.html b/public_included_ws_fallback/index.html index a2a4b4d..549e16e 100644 --- a/public_included_ws_fallback/index.html +++ b/public_included_ws_fallback/index.html @@ -241,6 +241,7 @@ + diff --git a/public_included_ws_fallback/scripts/network.js b/public_included_ws_fallback/scripts/network.js index 4fb57bb..b745b93 100644 --- a/public_included_ws_fallback/scripts/network.js +++ b/public_included_ws_fallback/scripts/network.js @@ -974,11 +974,11 @@ class Events { window.dispatchEvent(new CustomEvent(type, { detail: detail })); } - static on(type, callback) { - return window.addEventListener(type, callback, false); + static on(type, callback, options = false) { + return window.addEventListener(type, callback, options); } - static off(type, callback) { - return window.removeEventListener(type, callback, false); + static off(type, callback, options = false) { + return window.removeEventListener(type, callback, options); } } diff --git a/public_included_ws_fallback/scripts/ui.js b/public_included_ws_fallback/scripts/ui.js index 896608e..e45143e 100644 --- a/public_included_ws_fallback/scripts/ui.js +++ b/public_included_ws_fallback/scripts/ui.js @@ -241,7 +241,7 @@ class PeersUI { const _callback = (e) => this._sendClipboardData(e, files, text); Events.on('paste-pointerdown', _callback); - Events.on('deactivate-paste-mode', _ => this._deactivatePasteMode(_callback)); + Events.on('deactivate-paste-mode', _ => this._deactivatePasteMode(_callback), { once: true }); this.$cancelPasteModeBtn.removeAttribute('hidden'); @@ -1246,21 +1246,21 @@ class Base64ZipDialog extends Dialog { const base64Hash = window.location.hash.substring(1); this.$pasteBtn = this.$el.querySelector('#base64-paste-btn'); + this.$fallbackTextarea = this.$el.querySelector('.textarea'); if (base64Text) { this.show(); if (base64Text === "paste") { // ?base64text=paste // base64 encoded string is ready to be pasted from clipboard - this.$pasteBtn.innerText = 'Tap here to paste text'; - this.$pasteBtn.addEventListener('click', _ => this.processClipboard('text')); + this.preparePasting("text"); } else if (base64Text === "hash") { // ?base64text=hash#BASE64ENCODED // base64 encoded string is url hash which is never sent to server and faster (recommended) this.processBase64Text(base64Hash) .catch(_ => { Events.fire('notify-user', 'Text content is incorrect.'); - console.log("Text content incorrect.") + console.log("Text content incorrect."); }).finally(_ => { this.hide(); }); @@ -1270,7 +1270,7 @@ class Base64ZipDialog extends Dialog { this.processBase64Text(base64Text) .catch(_ => { Events.fire('notify-user', 'Text content is incorrect.'); - console.log("Text content incorrect.") + console.log("Text content incorrect."); }).finally(_ => { this.hide(); }); @@ -1283,14 +1283,13 @@ class Base64ZipDialog extends Dialog { this.processBase64Zip(base64Hash) .catch(_ => { Events.fire('notify-user', 'File content is incorrect.'); - console.log("File content incorrect.") + console.log("File content incorrect."); }).finally(_ => { this.hide(); }); } else { // ?base64zip=paste || ?base64zip=true - this.$pasteBtn.innerText = 'Tap here to paste files'; - this.$pasteBtn.addEventListener('click', _ => this.processClipboard('file')); + this.preparePasting('files'); } } } @@ -1300,39 +1299,60 @@ class Base64ZipDialog extends Dialog { this.$pasteBtn.innerText = "Processing..."; } - async processClipboard(type) { - if (!navigator.clipboard.readText) { - Events.fire('notify-user', 'This feature is not available on your browser.'); - console.log("navigator.clipboard.readText() is not available on your browser.") - this.hide(); - return; - } - - this._setPasteBtnToProcessing(); - - const base64 = await navigator.clipboard.readText(); - - if (!base64) return; - - if (type === "text") { - this.processBase64Text(base64) - .catch(_ => { - Events.fire('notify-user', 'Clipboard content is incorrect.'); - console.log("Clipboard content is incorrect.") - }).finally(_ => { - this.hide(); - }); + preparePasting(type) { + if (navigator.clipboard.readText) { + this.$pasteBtn.innerText = `Tap here to paste ${type}`; + this._clickCallback = _ => this.processClipboard(type); + this.$pasteBtn.addEventListener('click', _ => this._clickCallback()); } else { - this.processBase64Zip(base64) - .catch(_ => { - Events.fire('notify-user', 'Clipboard content is incorrect.'); - console.log("Clipboard content is incorrect.") - }).finally(_ => { - this.hide(); - }); + console.log("`navigator.clipboard.readText()` is not available on your browser.\nOn Firefox you can set `dom.events.asyncClipboard.readText` to true under `about:config` for convenience.") + this.$pasteBtn.setAttribute('hidden', ''); + this.$fallbackTextarea.setAttribute('placeholder', `Paste here to send ${type}`); + this.$fallbackTextarea.removeAttribute('hidden'); + this._inputCallback = _ => this.processInput(type); + this.$fallbackTextarea.addEventListener('input', _ => this._inputCallback()); + this.$fallbackTextarea.focus(); } } + async processInput(type) { + const base64 = this.$fallbackTextarea.textContent; + this.$fallbackTextarea.textContent = ''; + await this.processBase64(type, base64); + } + + async processClipboard(type) { + const base64 = await navigator.clipboard.readText(); + await this.processBase64(type, base64); + } + + isValidBase64(base64) { + try { + // check if input is base64 encoded + window.atob(base64); + return true; + } catch (e) { + // input is not base64 string. + return false; + } + } + + async processBase64(type, base64) { + if (!base64 || !this.isValidBase64(base64)) return; + this._setPasteBtnToProcessing(); + try { + if (type === "text") { + await this.processBase64Text(base64); + } else { + await this.processBase64Zip(base64); + } + } catch(_) { + Events.fire('notify-user', 'Clipboard content is incorrect.'); + console.log("Clipboard content is incorrect.") + } + this.hide(); + } + processBase64Text(base64Text){ return new Promise((resolve) => { this._setPasteBtnToProcessing(); diff --git a/public_included_ws_fallback/styles.css b/public_included_ws_fallback/styles.css index 0f8742d..2d29898 100644 --- a/public_included_ws_fallback/styles.css +++ b/public_included_ws_fallback/styles.css @@ -817,10 +817,29 @@ x-dialog .dialog-subheader { margin: auto -24px; } -#base64-paste-btn { +#base64-paste-btn, +#base64-paste-dialog .textarea { width: 100%; height: 40vh; border: solid 12px #438cff; + text-align: center; +} + +#base64-paste-dialog .textarea { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; +} + +#base64-paste-dialog .textarea::before { + font-size: 15px; + letter-spacing: 0.12em; + color: var(--primary-color); + font-weight: 700; + text-transform: uppercase; + content: attr(placeholder); } #base64-paste-dialog button {