From 91115f41975778a58fe2275ec708a1664381710b Mon Sep 17 00:00:00 2001 From: Paul Gottschling Date: Fri, 28 Jul 2023 12:51:14 -0400 Subject: [PATCH] Copy only commands in command snippets Fixes #121 The `CodeLine` component represents a string within a `code` snippet that is not a command. This is often an example of a command's output. However, for code snippets consisting of, say, example YAML, `CodeLines` represent the value we want a user to copy. This use case enables example values to include `Var` components, and we'll likely deprecate it once we enable support for `Var`s in syntax-highlighted code blocks. Since a `CodeLine` can show up in both of these use cases, we need to make sure that they are copied when there are no commands in a `code` snippet and _not_ copied when there are commands. This is because, when there are commands in a snippet, the intention is for a reader to copy only the commands--the output would be what the reader sees when they enter the commands into their terminal. This change adds branching in the `Pre` component, which handles the copy/paste logic for `Snippet`, to copy `CodeLine`s only when there are no `CommandLine`s in the snippet. --- components/MDX/Pre.tsx | 21 ++++++++++++---- components/Snippet/Snippet.stories.tsx | 34 +++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/components/MDX/Pre.tsx b/components/MDX/Pre.tsx index cb4a68de32..29d7063891 100644 --- a/components/MDX/Pre.tsx +++ b/components/MDX/Pre.tsx @@ -35,14 +35,25 @@ const Pre = ({ children, className }: CodeProps) => { } } - document.body.appendChild(copyText); - const processedInnerText = toCopyContent(copyText, [ - "." + commandStyles.line, - "." + codeStyles.line, + // Assemble an array of class names of elements within copyText to copy + // when a user clicks the copy button. + let classesToCopy = [ // Class name added by rehype-highlight to a `code` element when // highlighting syntax in code snippets ".hljs", - ]); + ]; + + // If copyText includes at least one CommandLine, the intention is for + // users to copy commands and not example outputs (CodeLines). If there + // are no CommandLines, it is fine to copy the CodeLines. + if (copyText.getElementsByClassName(commandStyles.line).length > 0) { + classesToCopy.push("." + commandStyles.line); + } else { + classesToCopy.push("." + codeStyles.line); + } + + document.body.appendChild(copyText); + const processedInnerText = toCopyContent(copyText, classesToCopy); navigator.clipboard.writeText(processedInnerText); document.body.removeChild(copyText); diff --git a/components/Snippet/Snippet.stories.tsx b/components/Snippet/Snippet.stories.tsx index cc4107cf72..9c1d963373 100644 --- a/components/Snippet/Snippet.stories.tsx +++ b/components/Snippet/Snippet.stories.tsx @@ -4,7 +4,7 @@ import { expect } from "@storybook/jest"; import { Var } from "../Variables/Var"; import { default as Snippet } from "./Snippet"; -import Command, { CommandLine } from "../Command/Command"; +import Command, { CommandLine, CommandComment } from "../Command/Command"; import { CodeLine } from "../Code"; import { replaceClipboardWithCopyBuffer } from "utils/clipboard"; @@ -55,6 +55,38 @@ export const CopyCommandVar: Story = { }, }; +// A code snippet with commands should only copy the commands. +export const CopyCommandVarWithOutput: Story = { + render: () => { + return ( + + + + curl https:// + + /v1/webapi/saml/acs/azure-saml + + + + The output of curling + + + ); + }, + play: async ({ canvasElement, step }) => { + replaceClipboardWithCopyBuffer(); + const canvas = within(canvasElement); + + await step("Copy the content", async () => { + await userEvent.click(canvas.getByTestId("copy-button-all")); + expect(navigator.clipboard.readText()).toEqual( + "curl https://example.com/v1/webapi/saml/acs/azure-saml" + ); + }); + }, +}; + +// A code snippet with no commands should copy all content within the snippet. export const CopyCodeLineVar: Story = { render: () => { return (