Skip to content

Commit

Permalink
Add copy button to gr.Markdown (#8851)
Browse files Browse the repository at this point in the history
* add copy button

* add changeset

* add changeset

* lint

* value tweak

---------

Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
Co-authored-by: Abubakar Abid <abubakar@huggingface.co>
  • Loading branch information
3 people committed Jul 22, 2024
1 parent 5622331 commit 914b193
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .changeset/kind-deer-create.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@gradio/markdown": minor
"gradio": minor
---

feat:Add copy button to `gr.Markdown`
3 changes: 3 additions & 0 deletions gradio/components/markdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ def __init__(
line_breaks: bool = False,
header_links: bool = False,
height: int | str | None = None,
show_copy_button: bool = False,
):
"""
Parameters:
Expand All @@ -64,6 +65,7 @@ def __init__(
line_breaks: If True, will enable Github-flavored Markdown line breaks in chatbot messages. If False (default), single new lines will be ignored.
header_links: If True, will automatically create anchors for headings, displaying a link icon on hover.
height: An optional maximum height of this component, specified in pixels if a number is passed, or in CSS units (e.g., '200px') if a stirng is passed in. If context exceeds this height, a scrollbar is added.
show_copy_button: If True, includes a copy button to copy the text in the Markdown component. Default is False.
"""
self.rtl = rtl
if latex_delimiters is None:
Expand All @@ -73,6 +75,7 @@ def __init__(
self.line_breaks = line_breaks
self.header_links = header_links
self.height = height
self.show_copy_button = show_copy_button

super().__init__(
label=label,
Expand Down
2 changes: 2 additions & 0 deletions js/markdown/Index.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
}[];
export let header_links = false;
export let height: number | string | undefined = undefined;
export let show_copy_button = false;
$: label, gradio.dispatch("change");
</script>
Expand Down Expand Up @@ -63,6 +64,7 @@
{line_breaks}
{header_links}
{height}
{show_copy_button}
/>
</div>
</Block>
Expand Down
9 changes: 9 additions & 0 deletions js/markdown/Markdown.stories.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,12 @@ in two separate lines.`
height: "200px"
}}
/>

<Story
name="Markdown with Copy Button"
args={{
value:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper congue, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat.",
show_copy_button: true
}}
/>
1 change: 1 addition & 0 deletions js/markdown/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
},
"dependencies": {
"@gradio/atoms": "workspace:^",
"@gradio/icons": "workspace:^",
"@gradio/statustracker": "workspace:^",
"@gradio/utils": "workspace:^",
"@types/dompurify": "^3.0.2",
Expand Down
57 changes: 57 additions & 0 deletions js/markdown/shared/Markdown.svelte
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";
import { copy } from "@gradio/utils";
import { Copy, Check } from "@gradio/icons";
import MarkdownCode from "./MarkdownCode.svelte";
import { fade } from "svelte/transition";
export let elem_classes: string[] = [];
export let visible = true;
Expand All @@ -18,6 +20,10 @@
}[];
export let header_links = false;
export let height: number | string | undefined = undefined;
export let show_copy_button = false;
let copied = false;
let timer: NodeJS.Timeout;
const dispatch = createEventDispatcher<{ change: undefined }>();
Expand All @@ -28,6 +34,21 @@
};
$: value, dispatch("change");
async function handle_copy(): Promise<void> {
if ("clipboard" in navigator) {
await navigator.clipboard.writeText(value);
copy_feedback();
}
}
function copy_feedback(): void {
copied = true;
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
copied = false;
}, 1000);
}
</script>

<div
Expand All @@ -39,6 +60,21 @@
use:copy
style={height ? `max-height: ${css_units(height)}; overflow-y: auto;` : ""}
>
{#if show_copy_button}
{#if copied}
<button
in:fade={{ duration: 300 }}
aria-label="Copied"
aria-roledescription="Text copied"><Check /></button
>
{:else}
<button
on:click={handle_copy}
aria-label="Copy"
aria-roledescription="Copy text"><Copy /></button
>
{/if}
{/if}
<MarkdownCode
message={value}
{latex_delimiters}
Expand Down Expand Up @@ -73,4 +109,25 @@
.hide {
display: none;
}
button {
display: flex;
position: absolute;
top: -10px;
right: -10px;
align-items: center;
box-shadow: var(--shadow-drop);
border: 1px solid var(--color-border-primary);
border-top: none;
border-right: none;
border-radius: var(--block-label-right-radius);
background: var(--block-label-background-fill);
padding: 5px;
width: 22px;
height: 22px;
overflow: hidden;
color: var(--block-label-color);
font: var(--font-sans);
font-size: var(--button-small-text-size);
}
</style>
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions test/components/test_markdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ class TestMarkdown:
def test_component_functions(self):
markdown_component = gr.Markdown("# Let's learn about $x$", label="Markdown")
assert markdown_component.get_config()["value"] == "# Let's learn about $x$"
assert not markdown_component.get_config()["show_copy_button"]

def test_in_interface(self):
"""
Expand All @@ -14,3 +15,9 @@ def test_in_interface(self):
input_data = " Here's an [image](https://gradio.app/images/gradio_logo.png)"
output_data = iface(input_data)
assert output_data == input_data.strip()

def test_show_copy_button(self):
markdown_component = gr.Markdown(
"# Let's learn about $x$", show_copy_button=True
)
assert markdown_component.get_config()["show_copy_button"]

0 comments on commit 914b193

Please sign in to comment.