evcc-io/assets/js/components/Helper/CopyButton.vue

125 lines
2.9 KiB
Vue

<template>
<slot :copy="handleCopy" :copied="copied" :copying="copying"></slot>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
name: "CopyButton",
props: {
content: {
type: String,
required: true,
},
targetElement: {
type: Object,
default: null,
},
},
data() {
return {
copied: false,
copying: false,
};
},
methods: {
async handleCopy() {
if (this.copying) return;
this.copying = true;
try {
// Modern browser API - works in secure contexts and PWAs
if (this.hasClipboardAPI()) {
await navigator.clipboard.writeText(this.content);
this.showCopiedFeedback();
} else {
// Fallback method for HTTP/non-secure contexts
this.fallbackCopy();
}
} catch (err) {
console.error("Failed to copy to clipboard:", err);
this.fallbackCopy();
} finally {
this.copying = false;
}
},
fallbackCopy() {
try {
let targetEl = this.targetElement as HTMLTextAreaElement;
let createdElement = false;
if (!targetEl) {
// Create a temporary textarea element if no target provided
targetEl = document.createElement("textarea");
targetEl["value"] = this.content;
targetEl["style"]["position"] = "fixed";
targetEl["style"]["left"] = "-999999px";
targetEl["style"]["top"] = "-999999px";
document.body.appendChild(targetEl as Node);
createdElement = true;
}
// Select and copy the text
targetEl["focus"]();
targetEl["select"]();
// For input/textarea elements, also try setSelectionRange
if (targetEl["setSelectionRange"]) {
targetEl["setSelectionRange"](0, targetEl["value"].length);
} else if ((targetEl as any)["createTextRange"]) {
// IE fallback
const range = (targetEl as any)["createTextRange"]();
range.select();
}
const successful = document.execCommand("copy");
if (createdElement) {
document.body.removeChild(targetEl as Node);
}
if (successful) {
this.showCopiedFeedback();
} else {
alert("Copy failed. Please copy manually.");
}
} catch (err) {
console.error("Fallback copy failed:", err);
alert("Copy failed. Please copy manually.");
}
},
isSecureContext() {
return (
window.isSecureContext ||
window.location.protocol === "https:" ||
window.location.hostname === "localhost"
);
},
isPWA() {
// Check if running as PWA (installed web app)
return (
window.matchMedia("(display-mode: standalone)").matches ||
(window.navigator as any).standalone === true || // iOS Safari
document.referrer.includes("android-app://")
); // Android
},
hasClipboardAPI() {
// PWAs get clipboard access even over HTTP in many cases
return navigator.clipboard && (this.isSecureContext() || this.isPWA());
},
showCopiedFeedback() {
this.copied = true;
setTimeout(() => {
this.copied = false;
}, 2000);
},
},
});
</script>