feat: added cut, copy, paste and show command palette functions in header (#5648)

This commit is contained in:
Ariel Leyva 2026-01-18 02:55:20 -05:00 committed by GitHub
parent 550a73b6ba
commit 785b7abb7b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 105 additions and 1 deletions

View file

@ -185,3 +185,7 @@ html[dir="rtl"] .breadcrumbs a {
.vfm-modal {
z-index: 9999999 !important;
}
body > div[style*="z-index: 9990"] {
z-index: 10000 !important;
}

View file

@ -51,6 +51,20 @@ export function copy(data: ClipboardArgs, opts?: ClipboardOpts) {
});
}
export function read() {
return new Promise<string>((resolve, reject) => {
if (
// Clipboard API requires secure context
window.isSecureContext &&
typeof navigator.clipboard !== "undefined"
) {
navigator.clipboard.readText().then(resolve).catch(reject);
} else {
reject();
}
});
}
function getPermission(name: string) {
return new Promise<void>((resolve, reject) => {
typeof navigator.permissions !== "undefined" &&

View file

@ -41,7 +41,30 @@
</div>
</div>
<template v-else>
<Breadcrumbs base="/files" noLink />
<div class="editor-header">
<Breadcrumbs base="/files" noLink />
<div>
<button
:disabled="isSelectionEmpty"
@click="executeEditorCommand('copy')"
>
<span><i class="material-icons">content_copy</i></span>
</button>
<button
:disabled="isSelectionEmpty"
@click="executeEditorCommand('cut')"
>
<span><i class="material-icons">content_cut</i></span>
</button>
<button @click="executeEditorCommand('paste')">
<span><i class="material-icons">content_paste</i></span>
</button>
<button @click="executeEditorCommand('openCommandPalette')">
<span><i class="material-icons">more_vert</i></span>
</button>
</div>
</div>
<div
v-show="isPreview && isMarkdownFile"
@ -74,6 +97,7 @@ import { marked } from "marked";
import { inject, onBeforeUnmount, onMounted, ref, watchEffect } from "vue";
import { useI18n } from "vue-i18n";
import { onBeforeRouteUpdate, useRoute, useRouter } from "vue-router";
import { read, copy } from "@/utils/clipboard";
const $showError = inject<IToastError>("$showError")!;
@ -95,6 +119,35 @@ const isMarkdownFile =
fileStore.req?.name.endsWith(".md") ||
fileStore.req?.name.endsWith(".markdown");
const isSelectionEmpty = ref(true);
const executeEditorCommand = (name: string) => {
if (name == "paste") {
read()
.then((data) => {
editor.value?.execCommand("paste", {
text: data,
});
})
.catch((e) => {
if (
document.queryCommandSupported &&
document.queryCommandSupported("paste")
) {
document.execCommand("paste");
} else {
console.warn("the clipboard api is not supported", e);
}
});
return;
}
if (name == "copy" || name == "cut") {
const selectedText = editor.value?.getCopyText();
copy({ text: selectedText });
}
editor.value?.execCommand(name);
};
onMounted(() => {
window.addEventListener("keydown", keyEvent);
window.addEventListener("beforeunload", handlePageChange);
@ -132,6 +185,11 @@ onMounted(() => {
editor.value.setFontSize(fontSize.value);
editor.value.focus();
editor.value.getSelection().on("changeSelection", () => {
isSelectionEmpty.value =
editor.value == null || editor.value.getSelectedText().length == 0;
});
});
onBeforeUnmount(() => {
@ -250,4 +308,32 @@ const preview = () => {
margin: 0 0.5em;
color: var(--fg);
}
.editor-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.editor-header > div > button {
background: transparent;
color: var(--action);
border: none;
outline: none;
opacity: 0.8;
cursor: pointer;
}
.editor-header > div > button:hover:not(:disabled) {
opacity: 1;
}
.editor-header > div > button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.editor-header > div > button > span > i {
font-size: 1.2rem;
}
</style>