11<template >
2- <pre
3- class =" tw-font-mono tw-bg-gray-100 tw-p-2 tw-whitespace-pre-wrap tw-break-all tw-overflow-hidden"
4- :class =" { 'tw-border': bordered, 'tw-rounded': bordered }"
5- ><template
6- v-for =" segment in textSegments "
7- ><a
8- v-if =" segment.href"
9- class =" tw-link tw-link-hover tw-link-info"
10- :href =" segment.href"
11- >{{ segment.text }}</a ><span
12- v-else
13- v-html =" segment.text"
14- /></template ></pre >
2+ <div class =" tw-relative" >
3+ <button
4+ v-if =" showCopyButton && String(text).length > 0"
5+ type =" button"
6+ class =" tw-btn tw-btn-ghost tw-btn-sm tw-absolute tw-top-1 tw-right-1"
7+ :title =" copied ? 'Copied!' : 'Copy'"
8+ data-test =" copy-button"
9+ @click =" copyText"
10+ >
11+ <FontAwesomeIcon :icon =" copied ? FA .faCheck : FA .faCopy " />
12+ </button >
13+
14+ <!-- Must be formatted like this to avoid extra whitespace -->
15+ <pre
16+ class =" tw-font-mono tw-bg-gray-100 tw-p-2 tw-whitespace-pre-wrap tw-break-all tw-overflow-hidden"
17+ :class =" { 'tw-border': bordered, 'tw-rounded': bordered }"
18+ ><template
19+ v-for =" segment in textSegments "
20+ ><a
21+ v-if =" segment.href"
22+ class =" tw-link tw-link-hover tw-link-info"
23+ :href =" segment.href"
24+ >{{ segment.text }}</a ><span
25+ v-else
26+ v-html =" segment.text"
27+ /></template ></pre >
28+ </div >
1529</template >
1630
1731<script >
1832import { AnsiUp } from ' ansi_up' ;
33+ import { FontAwesomeIcon } from ' @fortawesome/vue-fontawesome' ;
34+ import { faCopy , faCheck } from ' @fortawesome/free-solid-svg-icons' ;
1935
2036export default {
2137 name: ' CodeBox' ,
2238
39+ components: {
40+ FontAwesomeIcon,
41+ },
42+
2343 props: {
2444 text: {
2545 type: String ,
@@ -39,9 +59,27 @@ export default {
3959 required: false ,
4060 default: undefined ,
4161 },
62+
63+ showCopyButton: {
64+ type: Boolean ,
65+ default: true ,
66+ },
67+ },
68+
69+ data () {
70+ return {
71+ copied: false ,
72+ };
4273 },
4374
4475 computed: {
76+ FA () {
77+ return {
78+ faCopy,
79+ faCheck,
80+ };
81+ },
82+
4583 ansiText () {
4684 const escapedText = String (this .text ).replace (/ \[ NON-XML-CHAR-0x1B\] / g , ' \x1B ' ) ?? ' ' ;
4785 return (new AnsiUp ).ansi_to_html (escapedText);
@@ -97,5 +135,20 @@ export default {
97135 return segments;
98136 },
99137 },
138+
139+ methods: {
140+ async copyText () {
141+ try {
142+ await navigator .clipboard .writeText (String (this .text ));
143+ this .copied = true ;
144+ setTimeout (() => {
145+ this .copied = false ;
146+ }, 2000 );
147+ }
148+ catch (err) {
149+ console .error (' Failed to copy: ' , err);
150+ }
151+ },
152+ },
100153};
101154< / script>
0 commit comments