From 5ce44dca5b7c837c56c1c2239b1690f597442997 Mon Sep 17 00:00:00 2001 From: b1tjoy <103875612+b1tjoy@users.noreply.github.com> Date: Wed, 7 Dec 2022 22:49:24 +0800 Subject: [PATCH 1/4] fix copy and paste text inconsistent on mWeb --- src/libs/Clipboard/index.js | 16 +--- src/libs/Clipboard/index.website.js | 80 ++++++++++++++++++++ src/libs/Clipboard/miscClipboardFunctions.js | 19 +++++ 3 files changed, 102 insertions(+), 13 deletions(-) create mode 100644 src/libs/Clipboard/index.website.js create mode 100644 src/libs/Clipboard/miscClipboardFunctions.js diff --git a/src/libs/Clipboard/index.js b/src/libs/Clipboard/index.js index 502fd3075577c..60c3cff2fb72b 100644 --- a/src/libs/Clipboard/index.js +++ b/src/libs/Clipboard/index.js @@ -1,8 +1,7 @@ -// on Web/desktop this import will be replaced with `react-native-web` -import {Clipboard} from 'react-native-web'; -import lodashGet from 'lodash/get'; +import MiscClipboardFunctions from './miscClipboardFunctions'; -const canSetHtml = () => lodashGet(navigator, 'clipboard.write'); +const canSetHtml = MiscClipboardFunctions.canSetHtml; +const setString = MiscClipboardFunctions.setString; /** * Writes the content as HTML if the web client supports it. @@ -27,15 +26,6 @@ const setHtml = (html, text) => { ]); }; -/** - * Sets a string on the Clipboard object via react-native-web - * - * @param {String} text - */ -const setString = (text) => { - Clipboard.setString(text); -}; - export default { setString, canSetHtml, diff --git a/src/libs/Clipboard/index.website.js b/src/libs/Clipboard/index.website.js new file mode 100644 index 0000000000000..30e2afc4fe18b --- /dev/null +++ b/src/libs/Clipboard/index.website.js @@ -0,0 +1,80 @@ +import CONST from '../../CONST'; +import * as Browser from '../Browser'; +import MiscClipboardFunctions from './miscClipboardFunctions'; + +const canSetHtml = MiscClipboardFunctions.canSetHtml; +const setString = MiscClipboardFunctions.setString; + +/** + * Deprecated method to write the content as HTML to clipboard. + * @param {String} html HTML representation + * @param {String} text Plain text representation + */ +const copyToClipboardDeprecated = (html, text) => { + const node = document.createElement('span'); + node.textContent = html; + node.style.all = 'unset'; + node.style.opacity = '0'; + node.style.position = 'absolute'; + node.style.whiteSpace = 'pre-wrap'; + node.style.userSelect = 'text'; + node.addEventListener('copy', (e) => { + e.stopPropagation(); + e.preventDefault(); + e.clipboardData.clearData(); + e.clipboardData.setData('text/html', html); + e.clipboardData.setData('text/plain', text); + }); + document.body.appendChild(node); + + const selection = window.getSelection(); + selection.removeAllRanges(); + const range = document.createRange(); + range.selectNodeContents(node); + selection.addRange(range); + + try { + document.execCommand('copy'); + // eslint-disable-next-line no-empty + } catch (e) {} + + selection.removeAllRanges(); + document.body.removeChild(node); +}; + +/** + * Writes the content as HTML if the web client supports it. + * @param {String} html HTML representation + * @param {String} text Plain text representation + */ +const setHtml = (html, text) => { + if (!html || !text) { + return; + } + + if (!canSetHtml()) { + throw new Error('clipboard.write is not supported on this platform, thus HTML cannot be copied.'); + } + + if (CONST.BROWSER.SAFARI === Browser.getBrowser()) { + // Safari sanitize "text/html" data before writing to the pasteboard when using Clipboard API, + // whitespaces in the start of line are stripped away. We use the deprecated method to copy + // contents as HTML and keep whitespaces in the start of line on Safari. + // See https://webkit.org/blog/10855/async-clipboard-api/ for more details. + copyToClipboardDeprecated(html, text); + } else { + navigator.clipboard.write([ + // eslint-disable-next-line no-undef + new ClipboardItem({ + 'text/html': new Blob([html], {type: 'text/html'}), + 'text/plain': new Blob([text], {type: 'text/plain'}), + }), + ]); + } +}; + +export default { + setString, + canSetHtml, + setHtml, +}; diff --git a/src/libs/Clipboard/miscClipboardFunctions.js b/src/libs/Clipboard/miscClipboardFunctions.js new file mode 100644 index 0000000000000..259e98a7f5d80 --- /dev/null +++ b/src/libs/Clipboard/miscClipboardFunctions.js @@ -0,0 +1,19 @@ +// on Web/desktop this import will be replaced with `react-native-web` +import {Clipboard} from 'react-native-web'; +import lodashGet from 'lodash/get'; + +const canSetHtml = () => lodashGet(navigator, 'clipboard.write'); + +/** + * Sets a string on the Clipboard object via react-native-web + * + * @param {String} text + */ +const setString = (text) => { + Clipboard.setString(text); +}; + +export default { + setString, + canSetHtml, +}; From 4c78be6e261ac6dc474387a110f9f1d568d8e5ed Mon Sep 17 00:00:00 2001 From: b1tjoy <103875612+b1tjoy@users.noreply.github.com> Date: Thu, 8 Dec 2022 12:17:38 +0800 Subject: [PATCH 2/4] convert setHTMLSync to normal function and move it to index.js --- src/libs/Clipboard/index.js | 77 ++++++++++++++++--- src/libs/Clipboard/index.website.js | 80 -------------------- src/libs/Clipboard/miscClipboardFunctions.js | 19 ----- 3 files changed, 67 insertions(+), 109 deletions(-) delete mode 100644 src/libs/Clipboard/index.website.js delete mode 100644 src/libs/Clipboard/miscClipboardFunctions.js diff --git a/src/libs/Clipboard/index.js b/src/libs/Clipboard/index.js index 60c3cff2fb72b..fb3e6654e4134 100644 --- a/src/libs/Clipboard/index.js +++ b/src/libs/Clipboard/index.js @@ -1,7 +1,47 @@ -import MiscClipboardFunctions from './miscClipboardFunctions'; +// on Web/desktop this import will be replaced with `react-native-web` +import {Clipboard} from 'react-native-web'; +import lodashGet from 'lodash/get'; +import CONST from '../../CONST'; +import * as Browser from '../Browser'; -const canSetHtml = MiscClipboardFunctions.canSetHtml; -const setString = MiscClipboardFunctions.setString; +const canSetHtml = () => lodashGet(navigator, 'clipboard.write'); + +/** + * Deprecated method to write the content as HTML to clipboard. + * @param {String} html HTML representation + * @param {String} text Plain text representation + */ +function setHTMLSync(html, text) { + const node = document.createElement('span'); + node.textContent = html; + node.style.all = 'unset'; + node.style.opacity = '0'; + node.style.position = 'absolute'; + node.style.whiteSpace = 'pre-wrap'; + node.style.userSelect = 'text'; + node.addEventListener('copy', (e) => { + e.stopPropagation(); + e.preventDefault(); + e.clipboardData.clearData(); + e.clipboardData.setData('text/html', html); + e.clipboardData.setData('text/plain', text); + }); + document.body.appendChild(node); + + const selection = window.getSelection(); + selection.removeAllRanges(); + const range = document.createRange(); + range.selectNodeContents(node); + selection.addRange(range); + + try { + document.execCommand('copy'); + // eslint-disable-next-line no-empty + } catch (e) {} + + selection.removeAllRanges(); + document.body.removeChild(node); +} /** * Writes the content as HTML if the web client supports it. @@ -17,13 +57,30 @@ const setHtml = (html, text) => { throw new Error('clipboard.write is not supported on this platform, thus HTML cannot be copied.'); } - navigator.clipboard.write([ - // eslint-disable-next-line no-undef - new ClipboardItem({ - 'text/html': new Blob([html], {type: 'text/html'}), - 'text/plain': new Blob([text], {type: 'text/plain'}), - }), - ]); + if (CONST.BROWSER.SAFARI === Browser.getBrowser()) { + // Safari sanitize "text/html" data before writing to the pasteboard when using Clipboard API, + // whitespaces in the start of line are stripped away. We use the deprecated method to copy + // contents as HTML and keep whitespaces in the start of line on Safari. + // See https://webkit.org/blog/10855/async-clipboard-api/ for more details. + setHTMLSync(html, text); + } else { + navigator.clipboard.write([ + // eslint-disable-next-line no-undef + new ClipboardItem({ + 'text/html': new Blob([html], {type: 'text/html'}), + 'text/plain': new Blob([text], {type: 'text/plain'}), + }), + ]); + } +}; + +/** + * Sets a string on the Clipboard object via react-native-web + * + * @param {String} text + */ +const setString = (text) => { + Clipboard.setString(text); }; export default { diff --git a/src/libs/Clipboard/index.website.js b/src/libs/Clipboard/index.website.js deleted file mode 100644 index 30e2afc4fe18b..0000000000000 --- a/src/libs/Clipboard/index.website.js +++ /dev/null @@ -1,80 +0,0 @@ -import CONST from '../../CONST'; -import * as Browser from '../Browser'; -import MiscClipboardFunctions from './miscClipboardFunctions'; - -const canSetHtml = MiscClipboardFunctions.canSetHtml; -const setString = MiscClipboardFunctions.setString; - -/** - * Deprecated method to write the content as HTML to clipboard. - * @param {String} html HTML representation - * @param {String} text Plain text representation - */ -const copyToClipboardDeprecated = (html, text) => { - const node = document.createElement('span'); - node.textContent = html; - node.style.all = 'unset'; - node.style.opacity = '0'; - node.style.position = 'absolute'; - node.style.whiteSpace = 'pre-wrap'; - node.style.userSelect = 'text'; - node.addEventListener('copy', (e) => { - e.stopPropagation(); - e.preventDefault(); - e.clipboardData.clearData(); - e.clipboardData.setData('text/html', html); - e.clipboardData.setData('text/plain', text); - }); - document.body.appendChild(node); - - const selection = window.getSelection(); - selection.removeAllRanges(); - const range = document.createRange(); - range.selectNodeContents(node); - selection.addRange(range); - - try { - document.execCommand('copy'); - // eslint-disable-next-line no-empty - } catch (e) {} - - selection.removeAllRanges(); - document.body.removeChild(node); -}; - -/** - * Writes the content as HTML if the web client supports it. - * @param {String} html HTML representation - * @param {String} text Plain text representation - */ -const setHtml = (html, text) => { - if (!html || !text) { - return; - } - - if (!canSetHtml()) { - throw new Error('clipboard.write is not supported on this platform, thus HTML cannot be copied.'); - } - - if (CONST.BROWSER.SAFARI === Browser.getBrowser()) { - // Safari sanitize "text/html" data before writing to the pasteboard when using Clipboard API, - // whitespaces in the start of line are stripped away. We use the deprecated method to copy - // contents as HTML and keep whitespaces in the start of line on Safari. - // See https://webkit.org/blog/10855/async-clipboard-api/ for more details. - copyToClipboardDeprecated(html, text); - } else { - navigator.clipboard.write([ - // eslint-disable-next-line no-undef - new ClipboardItem({ - 'text/html': new Blob([html], {type: 'text/html'}), - 'text/plain': new Blob([text], {type: 'text/plain'}), - }), - ]); - } -}; - -export default { - setString, - canSetHtml, - setHtml, -}; diff --git a/src/libs/Clipboard/miscClipboardFunctions.js b/src/libs/Clipboard/miscClipboardFunctions.js deleted file mode 100644 index 259e98a7f5d80..0000000000000 --- a/src/libs/Clipboard/miscClipboardFunctions.js +++ /dev/null @@ -1,19 +0,0 @@ -// on Web/desktop this import will be replaced with `react-native-web` -import {Clipboard} from 'react-native-web'; -import lodashGet from 'lodash/get'; - -const canSetHtml = () => lodashGet(navigator, 'clipboard.write'); - -/** - * Sets a string on the Clipboard object via react-native-web - * - * @param {String} text - */ -const setString = (text) => { - Clipboard.setString(text); -}; - -export default { - setString, - canSetHtml, -}; From 3b32ee5619d25a7a68e594bf25187c086dbe3e30 Mon Sep 17 00:00:00 2001 From: b1tjoy <103875612+b1tjoy@users.noreply.github.com> Date: Fri, 9 Dec 2022 23:49:03 +0800 Subject: [PATCH 3/4] add comment for empty catch --- src/libs/Clipboard/index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libs/Clipboard/index.js b/src/libs/Clipboard/index.js index fb3e6654e4134..b45099e89e8fd 100644 --- a/src/libs/Clipboard/index.js +++ b/src/libs/Clipboard/index.js @@ -36,6 +36,9 @@ function setHTMLSync(html, text) { try { document.execCommand('copy'); + + // The 'copy' command can throw a SecurityError exception. + // See https://dvcs.w3.org/hg/editing/raw-file/tip/editing.html#the-copy-command for more details. // eslint-disable-next-line no-empty } catch (e) {} From 57e2728dcf744e06ff69f4e4f6832bd92c09d501 Mon Sep 17 00:00:00 2001 From: b1tjoy <103875612+b1tjoy@users.noreply.github.com> Date: Sat, 10 Dec 2022 04:57:54 +0800 Subject: [PATCH 4/4] move comment inside catch block --- src/libs/Clipboard/index.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/libs/Clipboard/index.js b/src/libs/Clipboard/index.js index b45099e89e8fd..efffd9626e03c 100644 --- a/src/libs/Clipboard/index.js +++ b/src/libs/Clipboard/index.js @@ -36,11 +36,10 @@ function setHTMLSync(html, text) { try { document.execCommand('copy'); - - // The 'copy' command can throw a SecurityError exception. + } catch (e) { + // The 'copy' command can throw a SecurityError exception, we ignore this exception on purpose. // See https://dvcs.w3.org/hg/editing/raw-file/tip/editing.html#the-copy-command for more details. - // eslint-disable-next-line no-empty - } catch (e) {} + } selection.removeAllRanges(); document.body.removeChild(node);