diff --git a/superset-frontend/src/components/CopyToClipboard.jsx b/superset-frontend/src/components/CopyToClipboard.jsx index 917d0d1e8b..88b6172f69 100644 --- a/superset-frontend/src/components/CopyToClipboard.jsx +++ b/superset-frontend/src/components/CopyToClipboard.jsx @@ -21,6 +21,7 @@ import PropTypes from 'prop-types'; import { t } from '@superset-ui/core'; import { Tooltip } from 'src/common/components/Tooltip'; import withToasts from 'src/messageToasts/enhancers/withToasts'; +import copyTextToClipboard from 'src/utils/copy'; const propTypes = { copyNode: PropTypes.node, @@ -83,40 +84,20 @@ class CopyToClipboard extends React.Component { } copyToClipboard(textToCopy) { - const selection = document.getSelection(); - selection.removeAllRanges(); - document.activeElement.blur(); - const range = document.createRange(); - const span = document.createElement('span'); - span.textContent = textToCopy; - span.style.all = 'unset'; - span.style.position = 'fixed'; - span.style.top = 0; - span.style.clip = 'rect(0, 0, 0, 0)'; - span.style.whiteSpace = 'pre'; - - document.body.appendChild(span); - range.selectNode(span); - selection.addRange(range); - try { - if (!document.execCommand('copy')) { - throw new Error(t('Not successful')); - } - } catch (err) { - this.props.addDangerToast( - t('Sorry, your browser does not support copying. Use Ctrl / Cmd + C!'), - ); - } - - document.body.removeChild(span); - if (selection.removeRange) { - selection.removeRange(range); - } else { - selection.removeAllRanges(); - } - - this.setState({ hasCopied: true }); - this.props.onCopyEnd(); + copyTextToClipboard(textToCopy) + .then(() => { + this.setState({ hasCopied: true }); + }) + .catch(() => { + this.props.addDangerToast( + t( + 'Sorry, your browser does not support copying. Use Ctrl / Cmd + C!', + ), + ); + }) + .finally(() => { + this.props.onCopyEnd(); + }); } tooltipText() { diff --git a/superset-frontend/src/utils/copy.ts b/superset-frontend/src/utils/copy.ts new file mode 100644 index 0000000000..bab69d1d48 --- /dev/null +++ b/superset-frontend/src/utils/copy.ts @@ -0,0 +1,56 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const copyTextToClipboard = (text: string) => + new Promise((resolve, reject) => { + const selection: Selection | null = document.getSelection(); + if (selection) { + selection.removeAllRanges(); + const range = document.createRange(); + const span = document.createElement('span'); + span.textContent = text; + span.style.position = 'fixed'; + span.style.top = '0'; + span.style.clip = 'rect(0, 0, 0, 0)'; + span.style.whiteSpace = 'pre'; + + document.body.appendChild(span); + range.selectNode(span); + selection.addRange(range); + + try { + if (!document.execCommand('copy')) { + reject(); + } + } catch (err) { + reject(); + } + + document.body.removeChild(span); + if (selection.removeRange) { + selection.removeRange(range); + } else { + selection.removeAllRanges(); + } + } + + resolve(); + }); + +export default copyTextToClipboard; diff --git a/superset-frontend/src/views/CRUD/data/components/SyntaxHighlighterCopy/index.tsx b/superset-frontend/src/views/CRUD/data/components/SyntaxHighlighterCopy/index.tsx index 25882eddb5..d5b46d5a7f 100644 --- a/superset-frontend/src/views/CRUD/data/components/SyntaxHighlighterCopy/index.tsx +++ b/superset-frontend/src/views/CRUD/data/components/SyntaxHighlighterCopy/index.tsx @@ -27,6 +27,7 @@ import github from 'react-syntax-highlighter/dist/cjs/styles/hljs/github'; import SyntaxHighlighter from 'react-syntax-highlighter/dist/cjs/light'; import { ToastProps } from 'src/messageToasts/enhancers/withToasts'; import Icon from 'src/components/Icon'; +import copyTextToClipboard from 'src/utils/copy'; SyntaxHighlighter.registerLanguage('sql', sqlSyntax); SyntaxHighlighter.registerLanguage('markdown', markdownSyntax); @@ -61,41 +62,17 @@ export default function SyntaxHighlighterCopy({ language: 'sql' | 'markdown' | 'html' | 'json'; }) { function copyToClipboard(textToCopy: string) { - const selection: Selection | null = document.getSelection(); - if (selection) { - selection.removeAllRanges(); - const range = document.createRange(); - const span = document.createElement('span'); - span.textContent = textToCopy; - span.style.position = 'fixed'; - span.style.top = '0'; - span.style.clip = 'rect(0, 0, 0, 0)'; - span.style.whiteSpace = 'pre'; - - document.body.appendChild(span); - range.selectNode(span); - selection.addRange(range); - - try { - if (!document.execCommand('copy')) { - throw new Error(t('Not successful')); - } - } catch (err) { + copyTextToClipboard(textToCopy) + .then(() => { if (addDangerToast) { addDangerToast(t('Sorry, your browser does not support copying.')); } - } - - document.body.removeChild(span); - if (selection.removeRange) { - selection.removeRange(range); - } else { - selection.removeAllRanges(); - } - if (addSuccessToast) { - addSuccessToast(t('SQL Copied!')); - } - } + }) + .catch(() => { + if (addSuccessToast) { + addSuccessToast(t('SQL Copied!')); + } + }); } return ( diff --git a/superset-frontend/src/views/CRUD/data/savedquery/SavedQueryList.tsx b/superset-frontend/src/views/CRUD/data/savedquery/SavedQueryList.tsx index 49a9627dc7..9f2040b0a8 100644 --- a/superset-frontend/src/views/CRUD/data/savedquery/SavedQueryList.tsx +++ b/superset-frontend/src/views/CRUD/data/savedquery/SavedQueryList.tsx @@ -40,6 +40,7 @@ import ActionsBar, { ActionProps } from 'src/components/ListView/ActionsBar'; import { IconName } from 'src/components/Icon'; import { commonMenuData } from 'src/views/CRUD/data/common'; import { SavedQueryObject } from 'src/views/CRUD/types'; +import copyTextToClipboard from 'src/utils/copy'; import SavedQueryPreviewModal from './SavedQueryPreviewModal'; const PAGE_SIZE = 25; @@ -154,39 +155,15 @@ function SavedQueryList({ const copyQueryLink = useCallback( (id: number) => { - const selection: Selection | null = document.getSelection(); - - if (selection) { - selection.removeAllRanges(); - const range = document.createRange(); - const span = document.createElement('span'); - span.textContent = `${window.location.origin}/superset/sqllab?savedQueryId=${id}`; - span.style.position = 'fixed'; - span.style.top = '0'; - span.style.clip = 'rect(0, 0, 0, 0)'; - span.style.whiteSpace = 'pre'; - - document.body.appendChild(span); - range.selectNode(span); - selection.addRange(range); - - try { - if (!document.execCommand('copy')) { - throw new Error(t('Not successful')); - } - } catch (err) { + copyTextToClipboard( + `${window.location.origin}/superset/sqllab?savedQueryId=${id}`, + ) + .then(() => { + addSuccessToast(t('Link Copied!')); + }) + .catch(() => { addDangerToast(t('Sorry, your browser does not support copying.')); - } - - document.body.removeChild(span); - if (selection.removeRange) { - selection.removeRange(range); - } else { - selection.removeAllRanges(); - } - - addSuccessToast(t('Link Copied!')); - } + }); }, [addDangerToast, addSuccessToast], ); diff --git a/superset-frontend/src/views/CRUD/hooks.ts b/superset-frontend/src/views/CRUD/hooks.ts index 14de89bec2..013261513a 100644 --- a/superset-frontend/src/views/CRUD/hooks.ts +++ b/superset-frontend/src/views/CRUD/hooks.ts @@ -24,6 +24,7 @@ import { createErrorHandler } from 'src/views/CRUD/utils'; import { FetchDataConfig } from 'src/components/ListView'; import { FilterValue } from 'src/components/ListView/types'; import Chart, { Slice } from 'src/types/Chart'; +import copyTextToClipboard from 'src/utils/copy'; import { FavoriteStatus } from './types'; interface ListViewResourceState { @@ -435,36 +436,13 @@ export const copyQueryLink = ( addDangerToast: (arg0: string) => void, addSuccessToast: (arg0: string) => void, ) => { - const selection: Selection | null = document.getSelection(); - - if (selection) { - selection.removeAllRanges(); - const range = document.createRange(); - const span = document.createElement('span'); - span.textContent = `${window.location.origin}/superset/sqllab?savedQueryId=${id}`; - span.style.position = 'fixed'; - span.style.top = '0'; - span.style.clip = 'rect(0, 0, 0, 0)'; - span.style.whiteSpace = 'pre'; - - document.body.appendChild(span); - range.selectNode(span); - selection.addRange(range); - - try { - if (!document.execCommand('copy')) { - throw new Error(t('Not successful')); - } - } catch (err) { + copyTextToClipboard( + `${window.location.origin}/superset/sqllab?savedQueryId=${id}`, + ) + .then(() => { + addSuccessToast(t('Link Copied!')); + }) + .catch(() => { addDangerToast(t('Sorry, your browser does not support copying.')); - } - - document.body.removeChild(span); - if (selection.removeRange) { - selection.removeRange(range); - } else { - selection.removeAllRanges(); - } - addSuccessToast(t('Link Copied!')); - } + }); };