From 6eae809e6de35a432d6b3243680b5b7bdcf42e06 Mon Sep 17 00:00:00 2001 From: Qijia Yang <2421653893@qq.com> Date: Mon, 23 Feb 2026 11:50:28 +0800 Subject: [PATCH] feat: add copy-path button in file detail title --- src/components/Info.vue | 133 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 125 insertions(+), 8 deletions(-) diff --git a/src/components/Info.vue b/src/components/Info.vue index 8e25ab9..140d0d8 100644 --- a/src/components/Info.vue +++ b/src/components/Info.vue @@ -3,7 +3,7 @@ import Summary from "../components/Summary.vue"; import DropdownSelect from "./DropdownSelect.vue"; import { store, selectDataset, pathType, hasTableForFile } from "../store.js"; import router from "../router/index.js"; -import { computed, ref } from "vue"; +import { computed, onBeforeUnmount, ref } from "vue"; import { useRoute } from "vue-router"; const props = defineProps({ @@ -22,14 +22,63 @@ function onDatasetChange(value) { const pathKind = computed(() => pathType(props.path)); const hasTables = computed(() => hasTableForFile(props.path)); +const titleText = computed( + () => + (props.path + ? props.path.split("/").pop() + : store.metadata.repo?.split("/").pop()) || "Overview", +); +const canCopyPath = computed(() => pathKind.value === "file" && !!props.path); const tableVisible = ref( hasTables && (route.query?.showTable?.toLowerCase() === "true" ?? false), ); +const copyState = ref("idle"); +const copyButtonLabel = computed(() => + copyState.value === "copied" ? "Copied file path" : "Copy file path", +); +let copyStateTimer; + +const copyWithExecCommand = (text) => { + const textarea = document.createElement("textarea"); + textarea.value = text; + textarea.setAttribute("readonly", ""); + textarea.style.position = "fixed"; + textarea.style.opacity = "0"; + document.body.appendChild(textarea); + textarea.select(); + const copied = document.execCommand("copy"); + document.body.removeChild(textarea); + return copied; +}; + +const copyCurrentPath = async () => { + if (!canCopyPath.value) return; + + try { + if (navigator.clipboard?.writeText) { + await navigator.clipboard.writeText(props.path); + } else if (!copyWithExecCommand(props.path)) { + throw new Error("Clipboard API unavailable"); + } + copyState.value = "copied"; + } catch (_) { + copyState.value = "idle"; + } + + window.clearTimeout(copyStateTimer); + copyStateTimer = window.setTimeout(() => { + copyState.value = "idle"; + }, 1600); +}; const toggleTables = () => { tableVisible.value = !tableVisible.value; router.replace({ query: { ...route.query, showTable: tableVisible.value } }); }; + +onBeforeUnmount(() => { + window.clearTimeout(copyStateTimer); +});