Просмотр исходного кода

docs: share add copy button to messages in web interface (#902)

Co-authored-by: Jay <[email protected]>
John Henry Rudden 7 месяцев назад
Родитель
Сommit
7c91f668d1

+ 3 - 0
packages/web/src/components/share/content-markdown.tsx

@@ -2,6 +2,7 @@ import { marked } from "marked"
 import { codeToHtml } from "shiki"
 import { codeToHtml } from "shiki"
 import markedShiki from "marked-shiki"
 import markedShiki from "marked-shiki"
 import { createOverflow } from "./common"
 import { createOverflow } from "./common"
+import { CopyButton } from "./copy-button"
 import { createResource, createSignal } from "solid-js"
 import { createResource, createSignal } from "solid-js"
 import { transformerNotationDiff } from "@shikijs/transformers"
 import { transformerNotationDiff } from "@shikijs/transformers"
 import style from "./content-markdown.module.css"
 import style from "./content-markdown.module.css"
@@ -41,6 +42,7 @@ export function ContentMarkdown(props: Props) {
       class={style.root}
       class={style.root}
       data-highlight={props.highlight === true ? true : undefined}
       data-highlight={props.highlight === true ? true : undefined}
       data-expanded={expanded() || props.expand === true ? true : undefined}
       data-expanded={expanded() || props.expand === true ? true : undefined}
+      style={{ position: "relative" }}
     >
     >
       <div data-slot="markdown" ref={overflow.ref} innerHTML={html()} />
       <div data-slot="markdown" ref={overflow.ref} innerHTML={html()} />
 
 
@@ -54,6 +56,7 @@ export function ContentMarkdown(props: Props) {
           {expanded() ? "Show less" : "Show more"}
           {expanded() ? "Show less" : "Show more"}
         </button>
         </button>
       )}
       )}
+      <CopyButton text={props.text} />
     </div>
     </div>
   )
   )
 }
 }

+ 1 - 0
packages/web/src/components/share/content-text.module.css

@@ -2,6 +2,7 @@
   color: var(--sl-color-text);
   color: var(--sl-color-text);
   background-color: var(--sl-color-bg-surface);
   background-color: var(--sl-color-bg-surface);
   padding: 0.5rem calc(0.5rem + 3px);
   padding: 0.5rem calc(0.5rem + 3px);
+  padding-right: calc(1rem + 18px);
   border-radius: 0.25rem;
   border-radius: 0.25rem;
   display: flex;
   display: flex;
   flex-direction: column;
   flex-direction: column;

+ 3 - 0
packages/web/src/components/share/content-text.tsx

@@ -1,6 +1,7 @@
 import style from "./content-text.module.css"
 import style from "./content-text.module.css"
 import { createSignal } from "solid-js"
 import { createSignal } from "solid-js"
 import { createOverflow } from "./common"
 import { createOverflow } from "./common"
+import { CopyButton } from "./copy-button"
 
 
 interface Props {
 interface Props {
   text: string
   text: string
@@ -16,6 +17,7 @@ export function ContentText(props: Props) {
       class={style.root}
       class={style.root}
       data-expanded={expanded() || props.expand === true ? true : undefined}
       data-expanded={expanded() || props.expand === true ? true : undefined}
       data-compact={props.compact === true ? true : undefined}
       data-compact={props.compact === true ? true : undefined}
+      style={{ position: "relative" }}
     >
     >
       <pre data-slot="text" ref={overflow.ref}>
       <pre data-slot="text" ref={overflow.ref}>
         {props.text}
         {props.text}
@@ -30,6 +32,7 @@ export function ContentText(props: Props) {
           {expanded() ? "Show less" : "Show more"}
           {expanded() ? "Show less" : "Show more"}
         </button>
         </button>
       )}
       )}
+      <CopyButton text={props.text} />
     </div>
     </div>
   )
   )
 }
 }

+ 47 - 0
packages/web/src/components/share/copy-button.module.css

@@ -0,0 +1,47 @@
+.copyButtonWrapper {
+  position: absolute;
+  top: 0.5rem;
+  right: 0.5rem;
+  opacity: 0;
+  visibility: hidden;
+  transition: opacity 0.15s ease;
+}
+
+.copyButton {
+  width: 18px;
+  cursor: pointer;
+  background: none;
+  border: none;
+  padding: 0;
+  color: var(--sl-color-text-secondary);
+
+  svg {
+    display: block;
+    width: 16px;
+    height: 16px;
+  }
+
+  &[data-copied="true"] {
+    color: var(--sl-color-green-high);
+  }
+}
+
+/* Show copy button when parent is hovered */
+*:hover > .copyButtonWrapper {
+  opacity: 0.65;
+  visibility: visible;
+}
+
+.copyTooltip {
+  position: absolute;
+  top: 50%;
+  left: calc(100% + 12px);
+  transform: translate(0, -50%);
+  padding: 0.375em 0.5em;
+  background: var(--sl-color-white);
+  color: var(--sl-color-text-invert);
+  font-size: 0.6875rem;
+  border-radius: 7px;
+  white-space: nowrap;
+  z-index: 11;
+}

+ 35 - 0
packages/web/src/components/share/copy-button.tsx

@@ -0,0 +1,35 @@
+import { createSignal } from "solid-js"
+import { IconClipboard, IconCheckCircle } from "../icons"
+import styles from "./copy-button.module.css"
+
+interface CopyButtonProps {
+  text: string
+}
+
+export function CopyButton(props: CopyButtonProps) {
+  const [copied, setCopied] = createSignal(false)
+
+  function handleCopyClick() {
+    if (props.text) {
+      navigator.clipboard.writeText(props.text).catch((err) => console.error("Copy failed", err))
+
+      setCopied(true)
+      setTimeout(() => setCopied(false), 2000)
+    }
+  }
+
+  return (
+    <div class={styles.copyButtonWrapper}>
+      <button
+        type="button"
+        class={styles.copyButton}
+        onClick={handleCopyClick}
+        data-copied={copied() ? true : undefined}
+        title="Copy content"
+      >
+        {copied() ? <IconCheckCircle width={16} height={16} /> : <IconClipboard width={16} height={16} />}
+      </button>
+      {copied() && <span class={styles.copyTooltip}>Copied!</span>}
+    </div>
+  )
+}