From 78dab021569f34c844929e8a8d58719419f8aebc Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Sat, 26 Feb 2022 09:48:28 +0100 Subject: [PATCH] feat(plugin-techdocs): add copy to clipboard feedback Signed-off-by: Camila Belo --- .../techdocs/src/reader/components/Reader.tsx | 4 +- .../transformers/copyToClipboard.test.ts | 11 ++- .../reader/transformers/copyToClipboard.ts | 39 -------- .../reader/transformers/copyToClipboard.tsx | 97 +++++++++++++++++++ 4 files changed, 108 insertions(+), 43 deletions(-) delete mode 100644 plugins/techdocs/src/reader/transformers/copyToClipboard.ts create mode 100644 plugins/techdocs/src/reader/transformers/copyToClipboard.tsx diff --git a/plugins/techdocs/src/reader/components/Reader.tsx b/plugins/techdocs/src/reader/components/Reader.tsx index a127208048..4042585b6d 100644 --- a/plugins/techdocs/src/reader/components/Reader.tsx +++ b/plugins/techdocs/src/reader/components/Reader.tsx @@ -753,7 +753,7 @@ export const useTechDocsReaderDom = (entityRef: EntityName): Element | null => { async (transformedElement: Element) => transformer(transformedElement, [ scrollIntoAnchor(), - copyToClipboard(), + copyToClipboard(theme), addLinkClickListener({ baseUrl: window.location.origin, onClick: (event: MouseEvent, url: string) => { @@ -802,7 +802,7 @@ export const useTechDocsReaderDom = (entityRef: EntityName): Element | null => { }, }), ]), - [navigate, techdocsStorageApi], + [theme, navigate, techdocsStorageApi], ); useEffect(() => { diff --git a/plugins/techdocs/src/reader/transformers/copyToClipboard.test.ts b/plugins/techdocs/src/reader/transformers/copyToClipboard.test.ts index daa074f6e4..4613df374d 100644 --- a/plugins/techdocs/src/reader/transformers/copyToClipboard.test.ts +++ b/plugins/techdocs/src/reader/transformers/copyToClipboard.test.ts @@ -16,6 +16,8 @@ import { createTestShadowDom } from '../../test-utils'; import { copyToClipboard } from './copyToClipboard'; +import { lightTheme } from '@backstage/theme'; +import { waitFor } from '@testing-library/react'; const clipboardSpy = jest.fn(); Object.defineProperty(navigator, 'clipboard', { @@ -38,12 +40,17 @@ describe('copyToClipboard', () => { `, { preTransformers: [], - postTransformers: [copyToClipboard()], + postTransformers: [copyToClipboard(lightTheme)], }, ); shadowDom.querySelector('button')?.click(); + await waitFor(() => { + const tooltip = document.querySelector('[role="tooltip"]'); + expect(tooltip).toHaveTextContent('Copied to clipboard'); + }); + expect(clipboardSpy).toHaveBeenCalledWith(expectedClipboard); }); @@ -60,7 +67,7 @@ describe('copyToClipboard', () => { `, { preTransformers: [], - postTransformers: [copyToClipboard()], + postTransformers: [copyToClipboard(lightTheme)], }, ); diff --git a/plugins/techdocs/src/reader/transformers/copyToClipboard.ts b/plugins/techdocs/src/reader/transformers/copyToClipboard.ts deleted file mode 100644 index d0569c0fd9..0000000000 --- a/plugins/techdocs/src/reader/transformers/copyToClipboard.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed 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. - */ - -import type { Transformer } from './transformer'; - -/** - * Recreates copy-to-clipboard functionality attached to snippets that - * is native to mkdocs-material theme. - */ -export const copyToClipboard = (): Transformer => { - return dom => { - Array.from(dom.querySelectorAll('pre > code')).forEach(codeElem => { - const button = document.createElement('button'); - const toBeCopied = codeElem.textContent || ''; - button.className = 'md-clipboard md-icon'; - button.title = 'Copy to clipboard'; - button.innerHTML = - ''; - button.addEventListener('click', () => - navigator.clipboard.writeText(toBeCopied), - ); - codeElem?.parentElement?.prepend(button); - }); - return dom; - }; -}; diff --git a/plugins/techdocs/src/reader/transformers/copyToClipboard.tsx b/plugins/techdocs/src/reader/transformers/copyToClipboard.tsx new file mode 100644 index 0000000000..25a1d67f65 --- /dev/null +++ b/plugins/techdocs/src/reader/transformers/copyToClipboard.tsx @@ -0,0 +1,97 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed 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. + */ + +import React, { useState, useCallback } from 'react'; +import ReactDom from 'react-dom'; +import { + withStyles, + Theme, + ThemeProvider, + SvgIcon, + Tooltip, +} from '@material-ui/core'; + +const CopyToClipboardTooltip = withStyles(theme => ({ + tooltip: { + fontSize: 'inherit', + color: theme.palette.text.primary, + margin: 0, + padding: theme.spacing(0.5), + backgroundColor: 'transparent', + boxShadow: 'none', + }, +}))(Tooltip); + +const CopyToClipboardIcon = () => ( + + + +); + +type CopyToClipboardButtonProps = { + text: string; +}; + +const CopyToClipboardButton = ({ text }: CopyToClipboardButtonProps) => { + const [open, setOpen] = useState(false); + + const handleClick = useCallback(() => { + navigator.clipboard.writeText(text); + setOpen(true); + }, [text]); + + const handleClose = useCallback(() => { + setOpen(false); + }, [setOpen]); + + return ( + + + + ); +}; + +import type { Transformer } from './transformer'; + +/** + * Recreates copy-to-clipboard functionality attached to snippets that + * is native to mkdocs-material theme. + */ +export const copyToClipboard = (theme: Theme): Transformer => { + return dom => { + const codes = dom.querySelectorAll('pre > code'); + for (const code of codes) { + const text = code.textContent || ''; + const container = document.createElement('div'); + code?.parentElement?.prepend(container); + ReactDom.render( + + + , + container, + ); + } + return dom; + }; +};