enhance: allow longpress and contextmenu copy paste
This commit is contained in:
parent
518e05b842
commit
e755a4ec37
7 changed files with 144 additions and 18 deletions
34
src/App.vue
34
src/App.vue
|
@ -2,11 +2,11 @@
|
|||
<Modal />
|
||||
<ContextMenu />
|
||||
|
||||
<Header @contextmenu.prevent />
|
||||
<div class="grow overflow-x-hidden overflow-y-auto p-2 md:p-4" @contextmenu.prevent>
|
||||
<AppHeader />
|
||||
<div class="grow overflow-x-hidden overflow-y-auto p-2 md:p-4">
|
||||
<RouterView />
|
||||
</div>
|
||||
<Footer @contextmenu.prevent />
|
||||
<AppFooter />
|
||||
<Notification />
|
||||
|
||||
<Teleport to="head">
|
||||
|
@ -18,10 +18,11 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import { defineAsyncComponent, defineComponent, markRaw } from "vue";
|
||||
import { onLongPress } from "@vueuse/core";
|
||||
import { RouterView } from "vue-router";
|
||||
import Header from "./components/Header.vue";
|
||||
import Footer from "./components/Footer.vue";
|
||||
import AppHeader from "./components/Header.vue";
|
||||
import AppFooter from "./components/Footer.vue";
|
||||
import { mapActions, mapState } from "pinia";
|
||||
import { useAuthStore } from "./stores/auth";
|
||||
import { isAuthenticatedPromise } from "./router/authGuard";
|
||||
|
@ -31,6 +32,7 @@ import Notification from "./components/Notification.vue";
|
|||
import { config } from "./config";
|
||||
import { useConfigurationStore } from "@/stores/configuration";
|
||||
import { resetAllPiniaStores } from "@/helpers/piniaReset";
|
||||
import { useContextMenuStore } from "./stores/context-menu";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
|
@ -40,6 +42,11 @@ export default defineComponent({
|
|||
...mapState(useConfigurationStore, ["clubName"]),
|
||||
},
|
||||
mounted() {
|
||||
document.body.addEventListener("contextmenu", (event) => {
|
||||
this.handleContextMenu(event);
|
||||
});
|
||||
onLongPress(document.body, this.handleContextMenu);
|
||||
|
||||
resetAllPiniaStores();
|
||||
this.configure();
|
||||
|
||||
|
@ -52,6 +59,21 @@ export default defineComponent({
|
|||
},
|
||||
methods: {
|
||||
...mapActions(useConfigurationStore, ["configure"]),
|
||||
...mapActions(useContextMenuStore, ["openContextMenu"]),
|
||||
handleContextMenu(e: MouseEvent) {
|
||||
e.preventDefault();
|
||||
|
||||
// TODO allow contextmenu on elements with special attribute with reduced selection
|
||||
const target = e.target as HTMLElement | null;
|
||||
if (!target) return;
|
||||
|
||||
if (["INPUT", "TEXTAREA", "P", "H1", "H2", "H3", "H4"].includes((target as HTMLElement).nodeName)) {
|
||||
this.openContextMenu(e, {
|
||||
component_ref: markRaw(defineAsyncComponent(() => import("@/components/CopyPasteContextMenu.vue"))),
|
||||
data: ["INPUT", "TEXTAREA"].includes((e.target as HTMLElement).nodeName) ? "" : "nopaste",
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
<template>
|
||||
<div
|
||||
ref="contextMenu"
|
||||
class="absolute flex flex-col gap-1 border border-gray-400 bg-white rounded-md select-none text-left shadow-md z-50 p-1"
|
||||
class="absolute flex flex-col gap-1 border border-gray-400 bg-white rounded-md select-none text-left shadow-md z-[100] p-1"
|
||||
v-show="show"
|
||||
:style="contextMenuStyle"
|
||||
@contextmenu.prevent
|
||||
@click="closeContextMenu"
|
||||
>
|
||||
<component :is="component_ref" :data="data" />
|
||||
|
|
61
src/components/CopyPasteContextMenu.vue
Normal file
61
src/components/CopyPasteContextMenu.vue
Normal file
|
@ -0,0 +1,61 @@
|
|||
<template>
|
||||
<div class="flex flex-row gap-2 cursor-pointer hover:bg-gray-300 p-1 rounded-md" @click="copy">
|
||||
<DocumentDuplicateIcon class="w-5 h-5" />
|
||||
<p>kopieren</p>
|
||||
</div>
|
||||
<div
|
||||
v-if="data != 'nopaste'"
|
||||
class="flex flex-row gap-2 cursor-pointer hover:bg-gray-300 p-1 rounded-md"
|
||||
@click="paste"
|
||||
>
|
||||
<ClipboardDocumentIcon class="w-5 h-5" />
|
||||
<p>einfügen</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import { ClipboardDocumentIcon, DocumentDuplicateIcon } from "@heroicons/vue/24/outline";
|
||||
import { mapState } from "pinia";
|
||||
import { useContextMenuStore } from "@/stores/context-menu";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
props: ["data"],
|
||||
data() {
|
||||
return {
|
||||
selectedText: "",
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(useContextMenuStore, ["clickedOnEl"]),
|
||||
},
|
||||
mounted() {
|
||||
this.selectedText =
|
||||
document.getSelection()?.toString() || this.clickedOnEl.value || this.clickedOnEl.innerText || "";
|
||||
|
||||
let selection = document.getSelection()?.toString();
|
||||
console.log(selection);
|
||||
if (selection == "") {
|
||||
console.log("jo");
|
||||
const range = document.createRange();
|
||||
range.selectNode(this.clickedOnEl);
|
||||
console.log(range);
|
||||
window.getSelection()?.removeAllRanges();
|
||||
window.getSelection()?.addRange(range);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
copy() {
|
||||
navigator.clipboard.writeText(this.selectedText);
|
||||
},
|
||||
paste() {
|
||||
const el = this.clickedOnEl;
|
||||
navigator.clipboard.readText().then((e) => {
|
||||
el.value = e;
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -3,21 +3,15 @@
|
|||
ref="contextMenu"
|
||||
class="absolute inset-0 w-full h-full flex justify-center items-center bg-black/50 select-none z-50 p-2"
|
||||
v-show="show"
|
||||
@contextmenu.prevent
|
||||
>
|
||||
<!-- @click="closeModal" -->
|
||||
<component
|
||||
:is="component_ref"
|
||||
:data="data"
|
||||
@click.stop
|
||||
class="p-4 bg-white rounded-lg max-h-[95%] overflow-y-auto"
|
||||
/>
|
||||
<component :is="component_ref" :data="data" class="p-4 bg-white rounded-lg max-h-[95%] overflow-y-auto" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { mapState, mapActions } from "pinia";
|
||||
import { useModalStore } from "@/stores/modal";
|
||||
import { useContextMenuStore } from "@/stores/context-menu";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
|
@ -27,6 +21,7 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
...mapActions(useModalStore, ["closeModal"]),
|
||||
...mapActions(useContextMenuStore, ["closeContextMenu"]),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -8,6 +8,7 @@ export const useContextMenuStore = defineStore("context-menu", {
|
|||
show: false,
|
||||
component_ref: null as any,
|
||||
data: null as any,
|
||||
clickedOnEl: null as any,
|
||||
};
|
||||
},
|
||||
getters: {
|
||||
|
@ -16,16 +17,18 @@ export const useContextMenuStore = defineStore("context-menu", {
|
|||
},
|
||||
},
|
||||
actions: {
|
||||
openContextMenu(e: MouseEvent, content: { component_ref: any; data: any }) {
|
||||
openContextMenu(e: MouseEvent, content: { component_ref: any; data?: any }) {
|
||||
this.component_ref = content.component_ref;
|
||||
this.data = content.data;
|
||||
this.contextX = e.pageX;
|
||||
this.contextY = e.pageY;
|
||||
this.clickedOnEl = e.target;
|
||||
this.show = true;
|
||||
},
|
||||
closeContextMenu() {
|
||||
this.component_ref = null;
|
||||
this.data = null;
|
||||
this.clickedOnEl = null;
|
||||
this.show = false;
|
||||
},
|
||||
},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue