From 5dc0ebbb04186decb8f9dfa166d5d3759ede4b40 Mon Sep 17 00:00:00 2001 From: Fernando Valverde Date: Tue, 21 Apr 2020 08:36:49 -0600 Subject: [PATCH] Copy to clipboard fixes including support for Android Native Bridge (#7382) * Adds Clipboard Async API & makes use of Android Native Bridge interface for write post image copy to clipboard * Checks for clipboard api support before trying to use it * Adds support for comment dropdown copy link * Adds AndroidBridge to globals definition --- app/assets/javascripts/.eslintrc.js | 1 + .../initializers/initializeCommentDropdown.js | 71 +++++++++++-------- app/javascript/.eslintrc.js | 1 + .../article-form/elements/imageManagement.jsx | 45 +++++++----- 4 files changed, 74 insertions(+), 44 deletions(-) diff --git a/app/assets/javascripts/.eslintrc.js b/app/assets/javascripts/.eslintrc.js index 247661341..193eed87e 100644 --- a/app/assets/javascripts/.eslintrc.js +++ b/app/assets/javascripts/.eslintrc.js @@ -25,5 +25,6 @@ module.exports = { initializeAllFollowButts: false, initializeSponsorshipVisibility: false, ActiveXObject: false, + AndroidBridge: false, }, }; diff --git a/app/assets/javascripts/initializers/initializeCommentDropdown.js b/app/assets/javascripts/initializers/initializeCommentDropdown.js index a3f422e66..2863ccf12 100644 --- a/app/assets/javascripts/initializers/initializeCommentDropdown.js +++ b/app/assets/javascripts/initializers/initializeCommentDropdown.js @@ -3,10 +3,18 @@ function initializeCommentDropdown() { const announcer = document.getElementById('article-copy-link-announcer'); - function isIOSDevice() { + function isClipboardSupported() { return ( - /iPhone|CriOS|iPad/i.test(navigator.userAgent) || - navigator.userAgent === 'DEV-Native-ios' + typeof navigator.clipboard !== "undefined" && + navigator.clipboard !== null + ); + } + + function isNativeAndroidDevice() { + return ( + navigator.userAgent === 'DEV-Native-android' && + typeof AndroidBridge !== "undefined" && + AndroidBridge !== null ); } @@ -23,7 +31,7 @@ function initializeCommentDropdown() { const input = activeElement.localName === 'clipboard-copy' ? activeElement.querySelector('input') - : activeElement; + : document.getElementById('article-copy-link-input'); input.focus(); input.setSelectionRange(0, input.value.length); announcer.hidden = false; @@ -35,11 +43,27 @@ function initializeCommentDropdown() { } } - function iOSCopyText() { - const input = document.getElementById('article-copy-link-input'); - input.setSelectionRange(0, input.value.length); - document.execCommand('copy'); + function execCopyText() { showAnnouncer(); + document.execCommand('copy'); + } + + function copyText() { + const inputValue = document.getElementById('article-copy-link-input').value; + if (isNativeAndroidDevice()) { + AndroidBridge.copyToClipboard(inputValue); + showAnnouncer(); + } else if (isClipboardSupported()) { + navigator.clipboard.writeText(inputValue) + .then(() => { + showAnnouncer(); + }) + .catch((err) => { + execCopyText(); + }); + } else { + execCopyText(); + } } function shouldCloseDropdown(event) { @@ -61,15 +85,11 @@ function initializeCommentDropdown() { } function removeCopyListener() { - if (isIOSDevice()) { - const clipboardCopyElement = document.getElementsByTagName( - 'clipboard-copy', - )[0]; - if (clipboardCopyElement) { - clipboardCopyElement.removeEventListener('click', iOSCopyText); - } - } else { - document.removeEventListener('clipboard-copy', showAnnouncer); + const clipboardCopyElement = document.getElementsByTagName( + 'clipboard-copy', + )[0]; + if (clipboardCopyElement) { + clipboardCopyElement.removeEventListener('click', copyText); } } @@ -98,18 +118,13 @@ function initializeCommentDropdown() { } else { removeAllShowing(); dropdownContent.classList.add('showing'); - if (isIOSDevice()) { - const clipboardCopyElement = document.getElementsByTagName( - 'clipboard-copy', - )[0]; + const clipboardCopyElement = document.getElementsByTagName( + 'clipboard-copy', + )[0]; - document.addEventListener('click', outsideClickListener); - if (clipboardCopyElement) { - clipboardCopyElement.addEventListener('click', iOSCopyText); - } - } else { - document.addEventListener('click', outsideClickListener); - document.addEventListener('clipboard-copy', showAnnouncer); + document.addEventListener('click', outsideClickListener); + if (clipboardCopyElement) { + clipboardCopyElement.addEventListener('click', copyText); } } } diff --git a/app/javascript/.eslintrc.js b/app/javascript/.eslintrc.js index 717da149c..77d60e2d9 100644 --- a/app/javascript/.eslintrc.js +++ b/app/javascript/.eslintrc.js @@ -48,5 +48,6 @@ module.exports = { algoliasearch: false, ga: false, Honeybadger: false, + AndroidBridge: false, }, }; diff --git a/app/javascript/article-form/elements/imageManagement.jsx b/app/javascript/article-form/elements/imageManagement.jsx index cc550a0d5..ea4099920 100644 --- a/app/javascript/article-form/elements/imageManagement.jsx +++ b/app/javascript/article-form/elements/imageManagement.jsx @@ -74,6 +74,15 @@ export default class ImageManagement extends Component { }); }; + execCopyText = () => { + this.imageMarkdownInput.setSelectionRange( + 0, + this.imageMarkdownInput.value.length, + ); + document.execCommand('copy'); + this.imageMarkdownAnnouncer.hidden = false; + } + copyText = () => { this.imageMarkdownAnnouncer = document.getElementById( 'image-markdown-copy-link-announcer', @@ -82,25 +91,29 @@ export default class ImageManagement extends Component { 'image-markdown-copy-link-input', ); - const isIOSDevice = - navigator.userAgent.match(/iPhone|iPad/i) || - navigator.userAgent.match('CriOS') || - navigator.userAgent === 'DEV-Native-ios'; + const isNativeAndroid = + navigator.userAgent === 'DEV-Native-android' && + typeof AndroidBridge !== "undefined" && + AndroidBridge !== null; - if (isIOSDevice) { - this.imageMarkdownInput.setSelectionRange( - 0, - this.imageMarkdownInput.value.length, - ); - document.execCommand('copy'); + const isClipboardSupported = + typeof navigator.clipboard !== "undefined" && + navigator.clipboard !== null; + + if (isNativeAndroid) { + AndroidBridge.copyToClipboard(this.imageMarkdownInput.value); + this.imageMarkdownAnnouncer.hidden = false; + } else if (isClipboardSupported) { + navigator.clipboard.writeText(this.imageMarkdownInput.value) + .then(() => { + this.imageMarkdownAnnouncer.hidden = false; + }) + .catch((err) => { + this.execCopyText(); + }); } else { - this.imageMarkdownInput.focus(); - this.imageMarkdownInput.setSelectionRange( - 0, - this.imageMarkdownInput.value.length, - ); + this.execCopyText(); } - this.imageMarkdownAnnouncer.hidden = false; }; linksToMarkdownForm = imageLinks => {