From a64f99f73424c64aef2055faf88bc144d76c1e3e Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Thu, 20 Jan 2022 16:14:59 +0100 Subject: [PATCH] Emulate mkdocs-material copy-to-clipboard functionality. Signed-off-by: Eric Peterson --- .changeset/techdocs-my-apocalypse.md | 5 ++ .../techdocs/src/reader/components/Reader.tsx | 4 ++ .../transformers/copyToClipboard.test.ts | 49 +++++++++++++++++++ .../reader/transformers/copyToClipboard.ts | 39 +++++++++++++++ .../techdocs/src/reader/transformers/index.ts | 1 + 5 files changed, 98 insertions(+) create mode 100644 .changeset/techdocs-my-apocalypse.md create mode 100644 plugins/techdocs/src/reader/transformers/copyToClipboard.test.ts create mode 100644 plugins/techdocs/src/reader/transformers/copyToClipboard.ts diff --git a/.changeset/techdocs-my-apocalypse.md b/.changeset/techdocs-my-apocalypse.md new file mode 100644 index 0000000000..b7dfb86675 --- /dev/null +++ b/.changeset/techdocs-my-apocalypse.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Code snippets now include a "copy to clipboard" button. diff --git a/plugins/techdocs/src/reader/components/Reader.tsx b/plugins/techdocs/src/reader/components/Reader.tsx index 3fb23e8f55..7869fc1b9b 100644 --- a/plugins/techdocs/src/reader/components/Reader.tsx +++ b/plugins/techdocs/src/reader/components/Reader.tsx @@ -47,6 +47,7 @@ import { simplifyMkdocsFooter, scrollIntoAnchor, transform as transformer, + copyToClipboard, } from '../transformers'; import { TechDocsSearch } from './TechDocsSearch'; @@ -215,6 +216,8 @@ export const useTechDocsReaderDom = (entityRef: EntityName): Element | null => { --md-code-fg-color: ${theme.palette.text.primary}; --md-code-bg-color: ${theme.palette.background.paper}; + --md-accent-fg-color: ${theme.palette.primary.main}; + --md-default-fg-color--lightest: ${theme.palette.textVerySubtle}; } .md-main__inner { margin-top: 0; } .md-sidebar { position: fixed; bottom: 100px; width: 20rem; } @@ -372,6 +375,7 @@ export const useTechDocsReaderDom = (entityRef: EntityName): Element | null => { async (transformedElement: Element) => transformer(transformedElement, [ scrollIntoAnchor(), + copyToClipboard(), addLinkClickListener({ baseUrl: window.location.origin, onClick: (event: MouseEvent, url: string) => { diff --git a/plugins/techdocs/src/reader/transformers/copyToClipboard.test.ts b/plugins/techdocs/src/reader/transformers/copyToClipboard.test.ts new file mode 100644 index 0000000000..679121d9bd --- /dev/null +++ b/plugins/techdocs/src/reader/transformers/copyToClipboard.test.ts @@ -0,0 +1,49 @@ +/* + * Copyright 2020 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 { createTestShadowDom } from '../../test-utils'; +import { copyToClipboard } from './copyToClipboard'; + +const clipboardSpy = jest.fn(); +Object.defineProperty(navigator, 'clipboard', { + value: { + writeText: clipboardSpy, + }, +}); + +describe('copyToClipboard', () => { + it('calls navigator.clipboard.writeText when clipboard button has been clicked', async () => { + const expectedClipboard = 'function foo() {return "bar";}'; + const shadowDom = await createTestShadowDom( + ` + + + + ${expectedClipboard} + + + `, + { + preTransformers: [], + postTransformers: [copyToClipboard()], + }, + ); + + shadowDom.querySelector('button')?.click(); + + expect(clipboardSpy).toHaveBeenCalledWith(expectedClipboard); + }); +}); diff --git a/plugins/techdocs/src/reader/transformers/copyToClipboard.ts b/plugins/techdocs/src/reader/transformers/copyToClipboard.ts new file mode 100644 index 0000000000..24b46c0b85 --- /dev/null +++ b/plugins/techdocs/src/reader/transformers/copyToClipboard.ts @@ -0,0 +1,39 @@ +/* + * 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('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/index.ts b/plugins/techdocs/src/reader/transformers/index.ts index 530bea2092..aa0fba2a18 100644 --- a/plugins/techdocs/src/reader/transformers/index.ts +++ b/plugins/techdocs/src/reader/transformers/index.ts @@ -18,6 +18,7 @@ export * from './addBaseUrl'; export * from './addGitFeedbackLink'; export * from './rewriteDocLinks'; export * from './addLinkClickListener'; +export * from './copyToClipboard'; export * from './removeMkdocsHeader'; export * from './simplifyMkdocsFooter'; export * from './onCssReady';