From dc60c924edeffe0ede1ed04e0ecbfd40cda9a33a Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Wed, 25 Jan 2023 15:55:00 +0100 Subject: [PATCH] Add copy button to codeblocks --- .../changelog/codeblocks_with_copybutton.yaml | 2 + .../src/main/js/CloneInformation.tsx | 76 +++--- .../src/main/js/GitBranchInformation.tsx | 13 +- .../src/main/js/GitMergeInformation.tsx | 35 ++- .../src/main/js/GitTagInformation.tsx | 10 +- .../src/main/js/HgBranchInformation.tsx | 13 +- .../src/main/js/HgTagInformation.tsx | 8 +- .../src/main/js/ProtocolInformation.tsx | 66 +++-- .../src/main/js/ProtocolInformation.tsx | 9 +- .../src/PreformattedCodeBlock.stories.tsx | 64 +++++ .../src/PreformattedCodeBlock.tsx | 67 +++++ .../src/__snapshots__/storyshots.test.ts.snap | 229 ++++++++++++++++++ scm-ui/ui-components/src/index.ts | 1 + 13 files changed, 460 insertions(+), 133 deletions(-) create mode 100644 gradle/changelog/codeblocks_with_copybutton.yaml create mode 100644 scm-ui/ui-components/src/PreformattedCodeBlock.stories.tsx create mode 100644 scm-ui/ui-components/src/PreformattedCodeBlock.tsx diff --git a/gradle/changelog/codeblocks_with_copybutton.yaml b/gradle/changelog/codeblocks_with_copybutton.yaml new file mode 100644 index 0000000000..bf489f19a1 --- /dev/null +++ b/gradle/changelog/codeblocks_with_copybutton.yaml @@ -0,0 +1,2 @@ +- type: added + description: Copy button to codeblocks diff --git a/scm-plugins/scm-git-plugin/src/main/js/CloneInformation.tsx b/scm-plugins/scm-git-plugin/src/main/js/CloneInformation.tsx index 33d74ef25d..cb9c6e3abf 100644 --- a/scm-plugins/scm-git-plugin/src/main/js/CloneInformation.tsx +++ b/scm-plugins/scm-git-plugin/src/main/js/CloneInformation.tsx @@ -24,7 +24,7 @@ import React, { FC } from "react"; import { useTranslation } from "react-i18next"; import { Repository } from "@scm-manager/ui-types"; -import { SubSubtitle, ErrorNotification, Loading } from "@scm-manager/ui-components"; +import { SubSubtitle, ErrorNotification, Loading, PreformattedCodeBlock } from "@scm-manager/ui-components"; import { useChangesets, useDefaultBranch } from "@scm-manager/ui-api"; type Props = { @@ -34,12 +34,18 @@ type Props = { const CloneInformation: FC = ({ url, repository }) => { const [t] = useTranslation("plugins"); - const { data: changesets, error: changesetsError, isLoading: changesetsLoading } = useChangesets(repository, { - limit: 1 + const { + data: changesets, + error: changesetsError, + isLoading: changesetsLoading, + } = useChangesets(repository, { + limit: 1, }); - const { data: defaultBranchData, isLoading: defaultBranchLoading, error: defaultBranchError } = useDefaultBranch( - repository - ); + const { + data: defaultBranchData, + isLoading: defaultBranchLoading, + error: defaultBranchError, + } = useDefaultBranch(repository); if (changesetsLoading || defaultBranchLoading) { return ; @@ -49,56 +55,34 @@ const CloneInformation: FC = ({ url, repository }) => { const branch = defaultBranchData?.defaultBranch; const emptyRepository = (changesets?._embedded?.changesets.length || 0) === 0; + let gitCloneCommand = `git clone ${url}\ncd ${repository.name}`; + if (emptyRepository) { + gitCloneCommand += `\ngit checkout -b ${branch}`; + } + const gitCreateCommand = + `git init ${repository.name}\n` + + `cd ${repository.name}\n` + + `git checkout -b ${branch}\n` + + `echo "# ${repository.name}" > README.md\n` + + "git add README.md\n" + + 'git commit -m "Add readme"\n' + + `git remote add origin ${url}\n` + + `git push -u origin ${branch}`; + const gitCommandCreate = `git remote add origin ${url}`; + return (
{t("scm-git-plugin.information.clone")} -
-        
-          git clone {url}
-          
- cd {repository.name} - {emptyRepository && ( - <> -
- git checkout -b {branch} - - )} -
-
+ {gitCloneCommand} {emptyRepository && ( <> {t("scm-git-plugin.information.create")} -
-            
-              git init {repository.name}
-              
- cd {repository.name} -
- git checkout -b {branch} -
- echo "# {repository.name} - " > README.md -
- git add README.md -
- git commit -m "Add readme" -
- git remote add origin {url} -
- git push -u origin {branch} -
-
-
+ {gitCreateCommand} )} {t("scm-git-plugin.information.replace")} -
-        
-          git remote add origin {url}
-          
-
-
+ {gitCommandCreate}
); }; diff --git a/scm-plugins/scm-git-plugin/src/main/js/GitBranchInformation.tsx b/scm-plugins/scm-git-plugin/src/main/js/GitBranchInformation.tsx index 4aad52f9eb..1a9cfded65 100644 --- a/scm-plugins/scm-git-plugin/src/main/js/GitBranchInformation.tsx +++ b/scm-plugins/scm-git-plugin/src/main/js/GitBranchInformation.tsx @@ -24,7 +24,7 @@ import React from "react"; import { WithTranslation, withTranslation } from "react-i18next"; import { Branch } from "@scm-manager/ui-types"; -import { SubSubtitle } from "@scm-manager/ui-components"; +import { PreformattedCodeBlock, SubSubtitle } from "@scm-manager/ui-components"; type Props = WithTranslation & { branch: Branch; @@ -34,16 +34,15 @@ class GitBranchInformation extends React.Component { render() { const { branch, t } = this.props; + const gitFetchCommand = "git fetch"; + const gitCheckoutCommand = "git checkout " + branch.name; + return (
{t("scm-git-plugin.information.fetch")} -
-          git fetch
-        
+ {gitFetchCommand} {t("scm-git-plugin.information.checkout")} -
-          git checkout {branch.name}
-        
+ {gitCheckoutCommand}
); } diff --git a/scm-plugins/scm-git-plugin/src/main/js/GitMergeInformation.tsx b/scm-plugins/scm-git-plugin/src/main/js/GitMergeInformation.tsx index 26cbac78a3..b0cd9c93fe 100644 --- a/scm-plugins/scm-git-plugin/src/main/js/GitMergeInformation.tsx +++ b/scm-plugins/scm-git-plugin/src/main/js/GitMergeInformation.tsx @@ -25,7 +25,7 @@ import React from "react"; import { WithTranslation, withTranslation } from "react-i18next"; import { Repository } from "@scm-manager/ui-types"; -import { SubSubtitle } from "@scm-manager/ui-components"; +import { PreformattedCodeBlock, SubSubtitle } from "@scm-manager/ui-components"; type Props = WithTranslation & { repository: Repository; @@ -37,35 +37,28 @@ class GitMergeInformation extends React.Component { render() { const { source, target, t } = this.props; + const gitCheckoutCommand = `git checkout ${target}`; + const gitUpdateCommand = "git pull"; + const gitMergeCommand = `git merge ${source}`; + const gitResolveCommand = "git add "; + const gitCommitCommand = `git commit -m "Merge ${source} into ${target}"`; + const gitPushCommand = "git push"; + return (
{t("scm-git-plugin.information.merge.heading")} {t("scm-git-plugin.information.merge.checkout")} -
-          git checkout {target}
-        
+ {gitCheckoutCommand} {t("scm-git-plugin.information.merge.update")} -
-          git pull
-        
+ {gitUpdateCommand} {t("scm-git-plugin.information.merge.merge")} -
-          git merge {source}
-        
+ {gitMergeCommand} {t("scm-git-plugin.information.merge.resolve")} -
-          git add <conflict file>
-        
+ {gitResolveCommand} {t("scm-git-plugin.information.merge.commit")} -
-          
-            git commit -m "Merge {source} into {target}"
-          
-        
+ {gitCommitCommand} {t("scm-git-plugin.information.merge.push")} -
-          git push
-        
+ {gitPushCommand}
); } diff --git a/scm-plugins/scm-git-plugin/src/main/js/GitTagInformation.tsx b/scm-plugins/scm-git-plugin/src/main/js/GitTagInformation.tsx index d86274acef..04bf37ced2 100644 --- a/scm-plugins/scm-git-plugin/src/main/js/GitTagInformation.tsx +++ b/scm-plugins/scm-git-plugin/src/main/js/GitTagInformation.tsx @@ -25,7 +25,7 @@ import React, { FC } from "react"; import { useTranslation } from "react-i18next"; import { Tag } from "@scm-manager/ui-types"; -import { SubSubtitle } from "@scm-manager/ui-components"; +import { PreformattedCodeBlock, SubSubtitle } from "@scm-manager/ui-components"; type Props = { tag: Tag; @@ -34,14 +34,12 @@ type Props = { const GitTagInformation: FC = ({ tag }) => { const [t] = useTranslation("plugins"); + const gitCheckoutTagCommand = `git checkout tags/${tag.name} -b branch/${tag.name}`; + return ( <> {t("scm-git-plugin.information.checkoutTag")} -
-        
-          git checkout tags/{tag.name} -b branch/{tag.name}
-        
-      
+ {gitCheckoutTagCommand} ); }; diff --git a/scm-plugins/scm-hg-plugin/src/main/js/HgBranchInformation.tsx b/scm-plugins/scm-hg-plugin/src/main/js/HgBranchInformation.tsx index 734ae30595..c86b45b02b 100644 --- a/scm-plugins/scm-hg-plugin/src/main/js/HgBranchInformation.tsx +++ b/scm-plugins/scm-hg-plugin/src/main/js/HgBranchInformation.tsx @@ -24,7 +24,7 @@ import React from "react"; import { WithTranslation, withTranslation } from "react-i18next"; import { Branch } from "@scm-manager/ui-types"; -import { SubSubtitle } from "@scm-manager/ui-components"; +import { PreformattedCodeBlock, SubSubtitle } from "@scm-manager/ui-components"; type Props = WithTranslation & { branch: Branch; @@ -34,16 +34,15 @@ class HgBranchInformation extends React.Component { render() { const { branch, t } = this.props; + const hgFetchCommand = "hg pull"; + const hgCheckoutCommand = `hg update ${branch.name}`; + return (
{t("scm-hg-plugin.information.fetch")} -
-          hg pull
-        
+ {hgFetchCommand} {t("scm-hg-plugin.information.checkout")} -
-          hg update {branch.name}
-        
+ {hgCheckoutCommand}
); } diff --git a/scm-plugins/scm-hg-plugin/src/main/js/HgTagInformation.tsx b/scm-plugins/scm-hg-plugin/src/main/js/HgTagInformation.tsx index 10fcd415b7..81d5b768a1 100644 --- a/scm-plugins/scm-hg-plugin/src/main/js/HgTagInformation.tsx +++ b/scm-plugins/scm-hg-plugin/src/main/js/HgTagInformation.tsx @@ -25,7 +25,7 @@ import React, { FC } from "react"; import { useTranslation } from "react-i18next"; import { Tag } from "@scm-manager/ui-types"; -import { SubSubtitle } from "@scm-manager/ui-components"; +import { PreformattedCodeBlock, SubSubtitle } from "@scm-manager/ui-components"; type Props = { tag: Tag; @@ -34,12 +34,12 @@ type Props = { const HgTagInformation: FC = ({ tag }) => { const [t] = useTranslation("plugins"); + const hgCheckoutTagCommand = `hg update ${tag.name}`; + return ( <> {t("scm-hg-plugin.information.checkoutTag")} -
-        hg update {tag.name}
-      
+ {hgCheckoutTagCommand} ); }; diff --git a/scm-plugins/scm-hg-plugin/src/main/js/ProtocolInformation.tsx b/scm-plugins/scm-hg-plugin/src/main/js/ProtocolInformation.tsx index ec5f12e7e8..052f2b18f6 100644 --- a/scm-plugins/scm-hg-plugin/src/main/js/ProtocolInformation.tsx +++ b/scm-plugins/scm-hg-plugin/src/main/js/ProtocolInformation.tsx @@ -24,7 +24,13 @@ import React, { FC } from "react"; import { useTranslation } from "react-i18next"; import { Repository } from "@scm-manager/ui-types"; -import { ErrorNotification, Loading, SubSubtitle, repositories } from "@scm-manager/ui-components"; +import { + ErrorNotification, + Loading, + SubSubtitle, + repositories, + PreformattedCodeBlock, +} from "@scm-manager/ui-components"; import { useChangesets } from "@scm-manager/ui-api"; type Props = { @@ -46,53 +52,37 @@ const ProtocolInformation: FC = ({ repository }) => { const emptyRepository = (data?._embedded?.changesets.length || 0) === 0; + const hgCloneCommand = `hg clone ${href}`; + const hgCreateCommand = + `hg init ${repository.name}\n` + + `cd ${repository.name}\n` + + 'echo "[paths]" > .hg/hgrc\n' + + `echo "default = ${href}` + + '" >> .hg/hgrc\n' + + `echo "# ${repository.name}` + + '" > README.md\n' + + "hg add README.md\n" + + 'hg commit -m "added readme"\n\n' + + "hg push"; + const hgReplaceCommand = + "# add the repository url as default to your .hg/hgrc e.g:\n" + + `default = ${href}\n` + + "# push to remote repository\n" + + "hg push"; + return (
{t("scm-hg-plugin.information.clone")} -
-        hg clone {href}
-      
+ {hgCloneCommand} {emptyRepository && ( <> {t("scm-hg-plugin.information.create")} -
-            
-              hg init {repository.name}
-              
- cd {repository.name} -
- echo "[paths]" > .hg/hgrc -
- echo "default = {href} - " >> .hg/hgrc -
- echo "# {repository.name} - " > README.md -
- hg add README.md -
- hg commit -m "added readme" -
-
- hg push -
-
-
+ {hgCreateCommand} )} {t("scm-hg-plugin.information.replace")} -
-        
-          # add the repository url as default to your .hg/hgrc e.g:
-          
- default = {href} -
- # push to remote repository -
- hg push -
-
+ {hgReplaceCommand}
); }; diff --git a/scm-plugins/scm-svn-plugin/src/main/js/ProtocolInformation.tsx b/scm-plugins/scm-svn-plugin/src/main/js/ProtocolInformation.tsx index 5b6d1a80c1..718c30eef9 100644 --- a/scm-plugins/scm-svn-plugin/src/main/js/ProtocolInformation.tsx +++ b/scm-plugins/scm-svn-plugin/src/main/js/ProtocolInformation.tsx @@ -24,7 +24,7 @@ import React from "react"; import { withTranslation, WithTranslation } from "react-i18next"; import { Repository } from "@scm-manager/ui-types"; -import { repositories, SubSubtitle } from "@scm-manager/ui-components"; +import { PreformattedCodeBlock, repositories, SubSubtitle } from "@scm-manager/ui-components"; type Props = WithTranslation & { repository: Repository; @@ -37,12 +37,13 @@ class ProtocolInformation extends React.Component { if (!href) { return null; } + + const svnCheckoutCommand = `svn checkout ${href}`; + return (
{t("scm-svn-plugin.information.checkout")} -
-          svn checkout {href}
-        
+ {svnCheckoutCommand}
); } diff --git a/scm-ui/ui-components/src/PreformattedCodeBlock.stories.tsx b/scm-ui/ui-components/src/PreformattedCodeBlock.stories.tsx new file mode 100644 index 0000000000..36e0764681 --- /dev/null +++ b/scm-ui/ui-components/src/PreformattedCodeBlock.stories.tsx @@ -0,0 +1,64 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +import * as React from "react"; +import { storiesOf } from "@storybook/react"; +import styled from "styled-components"; +import PreformattedCodeBlock from "./PreformattedCodeBlock"; +import { SubSubtitle } from "./layout"; + +const Wrapper = styled.div``; + +const longContent = + "#!/bin/bash\n" + + "\n" + + "### For this hook to work you need the SCM CLI client (https://scm-manager.org/cli/)\n" + + "### installed and connected to your SCM Server.\n" + + "\n" + + "BRANCH_NAME=$(git symbolic-ref --short HEAD)\n" + + "COMMIT_MSG_FILE=`cat $1`\n" + + "\n" + + 'scm repo commit-message-check aaa/ultimate-repo $BRANCH_NAME "$COMMIT_MSG_FILE"'; + +storiesOf("PreformattedCodeBlock", module) + .addDecorator((storyFn) => {storyFn()}) + .add("Default", () => git checkout main) + .add("With scrollbar", () => ( + + git remote add origin + https://scm-manager-instance4.example.org:8081/scm/repo/my-new-namespace/LoremipsumdolorsitametconsetetursadipscingelitrseddiamnonumyeirmodtemporinviduntutlaboreetdoloremagnaaliquyameratseddiamvoluptuaAtveroeosetaccusametjustoduodoloresetearebumStetclitakasdgubergrennoseatakimatasanctusestLoremipsumdolorsitametLoremipsumdolorsitametconsetetursadipscingelitrseddiamnonumyeirmodtemporinviduntutlaboreetdoloremagnaaliquyameratseddiamvoluptuaAtveroeosetaccusametjustoduodoloresetearebumStetclitakasdgubergrennoseatakimatasanctusestLoremipsumdolorsitamet + + )) + .add("Long content", () => {longContent}) + .add("Combination", () => ( +
+ Clone the Repository + git clone https://fancy-scm.url/scm/repo/test/scmm\n cd scmm + Git hook for commit message validation + {longContent} + Get Remote Changes + git fetch + Switch Branch + git checkout feature/something +
+ )); diff --git a/scm-ui/ui-components/src/PreformattedCodeBlock.tsx b/scm-ui/ui-components/src/PreformattedCodeBlock.tsx new file mode 100644 index 0000000000..b53886acaf --- /dev/null +++ b/scm-ui/ui-components/src/PreformattedCodeBlock.tsx @@ -0,0 +1,67 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +import * as React from "react"; +import { FC, useState } from "react"; +import { useTranslation } from "react-i18next"; +import styled from "styled-components"; +import Button from "./buttons/Button"; +import copyToClipboard from "./CopyToClipboard"; + +type Props = { + children: string; +}; + +const TopRightButton = styled(Button)` + position: absolute; + display: none; + height: inherit; + top: 1.25em; + right: 1.5em; +`; + +const Container = styled.div` + &:hover > ${TopRightButton} { + display: inline-block; + } +`; + +const PreformattedCodeBlock: FC = ({ children }) => { + const [t] = useTranslation("repos"); + const [copied, setCopied] = useState(false); + + const copy = () => copyToClipboard(children).then(() => setCopied(true)); + + return ( + +
+        {children}
+      
+ + + +
+ ); +}; + +export default PreformattedCodeBlock; diff --git a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap index 6c7ecce800..04627cd308 100644 --- a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap +++ b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap @@ -19277,6 +19277,235 @@ exports[`Storyshots Popover Link 1`] = ` `; +exports[`Storyshots PreformattedCodeBlock Combination 1`] = ` +
+
+

+ Clone the Repository +

+
+
+        
+          git clone https://fancy-scm.url/scm/repo/test/scmm\\n cd scmm
+        
+      
+ +
+

+ Git hook for commit message validation +

+
+
+        
+          #!/bin/bash
+
+### For this hook to work you need the SCM CLI client (https://scm-manager.org/cli/)
+### installed and connected to your SCM Server.
+
+BRANCH_NAME=$(git symbolic-ref --short HEAD)
+COMMIT_MSG_FILE=\`cat $1\`
+
+scm repo commit-message-check aaa/ultimate-repo $BRANCH_NAME "$COMMIT_MSG_FILE"
+        
+      
+ +
+

+ Get Remote Changes +

+
+
+        
+          git fetch
+        
+      
+ +
+

+ Switch Branch +

+
+
+        
+          git checkout feature/something
+        
+      
+ +
+
+
+`; + +exports[`Storyshots PreformattedCodeBlock Default 1`] = ` +
+
+
+      
+        git checkout main
+      
+    
+ +
+
+`; + +exports[`Storyshots PreformattedCodeBlock Long content 1`] = ` +
+
+
+      
+        #!/bin/bash
+
+### For this hook to work you need the SCM CLI client (https://scm-manager.org/cli/)
+### installed and connected to your SCM Server.
+
+BRANCH_NAME=$(git symbolic-ref --short HEAD)
+COMMIT_MSG_FILE=\`cat $1\`
+
+scm repo commit-message-check aaa/ultimate-repo $BRANCH_NAME "$COMMIT_MSG_FILE"
+      
+    
+ +
+
+`; + +exports[`Storyshots PreformattedCodeBlock With scrollbar 1`] = ` +
+
+
+      
+        git remote add origin https://scm-manager-instance4.example.org:8081/scm/repo/my-new-namespace/LoremipsumdolorsitametconsetetursadipscingelitrseddiamnonumyeirmodtemporinviduntutlaboreetdoloremagnaaliquyameratseddiamvoluptuaAtveroeosetaccusametjustoduodoloresetearebumStetclitakasdgubergrennoseatakimatasanctusestLoremipsumdolorsitametLoremipsumdolorsitametconsetetursadipscingelitrseddiamnonumyeirmodtemporinviduntutlaboreetdoloremagnaaliquyameratseddiamvoluptuaAtveroeosetaccusametjustoduodoloresetearebumStetclitakasdgubergrennoseatakimatasanctusestLoremipsumdolorsitamet
+      
+    
+ +
+
+`; + exports[`Storyshots Repositories/Annotate Default 1`] = `