feat(document): copy document json content

This commit is contained in:
二丫讲梵
2026-09-02 20:20:25 +08:00
committed by GitHub
parent 0f60b6a655
commit dd20081316
2 changed files with 30 additions and 2 deletions
@@ -2,7 +2,7 @@
import { computed, ref, shallowRef, nextTick, watch, onMounted, onBeforeUnmount } from "vue";
import { uuid } from "@/lib/common/utils";
import { useI18n } from "vue-i18n";
import { RefreshCw, Trash2, Plus, Save, ChevronDown, ChevronLeft, ChevronRight, Table2, Braces, X, Search, Wrench, Filter, Columns3Cog, SquareDashed, Minus, Rows3, AlignLeft, AlignRight, EyeOff, Palette } from "@lucide/vue";
import { RefreshCw, Trash2, Plus, Save, ChevronDown, ChevronLeft, ChevronRight, Table2, Braces, X, Search, Wrench, Filter, Columns3Cog, SquareDashed, Minus, Rows3, AlignLeft, AlignRight, EyeOff, Palette, Copy } from "@lucide/vue";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Input } from "@/components/ui/input";
@@ -91,6 +91,7 @@ import { documentGridColumnVisibilityScopeKey, migrateDocumentGridColumnVisibili
import { matchesElasticsearchIndexPattern, subscribeElasticsearchIndexCleared, type ElasticsearchIndexClearedDetail } from "@/lib/sidebar/elasticsearchIndexActions";
import { TABLE_FONT_SIZE_MAX, TABLE_FONT_SIZE_MIN, useSettingsStore } from "@/stores/settingsStore";
import { useToast } from "@/composables/useToast";
import { copyToClipboard } from "@/lib/common/clipboard";
import JsonEditNode from "./JsonEditNode.vue";
import type { EditNode } from "@/types/editor";
import type { ColumnInfo, DatabaseType, QueryResult, QueryTab } from "@/types/database";
@@ -2026,6 +2027,15 @@ function docPreview(doc: JsonRecord): string {
return `${id} - ${preview}`;
}
async function copyDocument() {
try {
await copyToClipboard(editJson.value);
toast(t("grid.copied"), 1500);
} catch (error: unknown) {
toast(t("grid.copyFailed", { message: error instanceof Error ? error.message : String(error) }), 3000);
}
}
function handleDocumentViewerDoubleClick(event: MouseEvent) {
const target = event.target;
if (!(target instanceof Element)) return;
@@ -2689,6 +2699,9 @@ defineExpose({ focusSearch });
<input class="min-w-0 w-full cursor-text select-text appearance-none border-0 bg-transparent p-0 text-inherit outline-none focus:ring-0" :value="selectedDocumentIdLabel" :aria-label="`_id: ${selectedDocumentIdLabel}`" readonly spellcheck="false" />
</Badge>
<span class="flex-1" />
<Button v-if="!isEditing" variant="ghost" size="icon" class="h-6 w-7" :title="t('grid.copy')" @click="copyDocument">
<Copy class="h-3.5 w-3.5" />
</Button>
<Button v-if="!isEditing" variant="ghost" size="sm" class="h-6 text-xs" :disabled="!documentStoreEditable" :title="documentStoreEditDisabledReason" @click="startEdit">{{ t("mongo.edit") }}</Button>
<template v-if="isEditing">
<div class="flex items-center border rounded-md overflow-hidden mr-1">
@@ -2722,7 +2735,7 @@ defineExpose({ focusSearch });
</div>
</div>
<div v-else data-document-json-viewer class="flex-1 min-h-0 bg-muted/10 outline-none" @dblclick="handleDocumentViewerDoubleClick">
<div v-else data-document-json-viewer class="flex-1 min-h-0 select-text bg-muted/10 outline-none" @dblclick="handleDocumentViewerDoubleClick">
<RedisJsonEditor ref="documentJsonEditorRef" :model-value="editJson" read-only :line-numbers="false" presentation="viewer" class="h-full" />
</div>
</template>
@@ -21,6 +21,10 @@ const documentJsonEditor = vi.hoisted(() => ({
openSearch: vi.fn().mockReturnValue(true),
}));
const clipboard = vi.hoisted(() => ({
copyToClipboard: vi.fn(),
}));
const dataGrid = vi.hoisted(() => ({
fullExportResult: undefined as
| ((onProgress?: (info: { rowsExported: number; totalRows: number | null }) => void) => Promise<
@@ -95,6 +99,10 @@ vi.mock("@/stores/settingsStore", () => ({
useSettingsStore: () => settings,
}));
vi.mock("@/lib/common/clipboard", () => ({
copyToClipboard: clipboard.copyToClipboard,
}));
vi.mock("@/components/grid/DataGrid.vue", () => {
return {
default: defineComponent({
@@ -324,6 +332,8 @@ beforeEach(async () => {
dataGrid.paginate = undefined;
dataGrid.editable = true;
documentJsonEditor.openSearch.mockClear();
clipboard.copyToClipboard.mockReset();
clipboard.copyToClipboard.mockResolvedValue(undefined);
backend.documentDeleteDocument.mockResolvedValue(undefined);
backend.documentInsertDocument.mockResolvedValue("created");
backend.documentUpdateDocument.mockResolvedValue(1);
@@ -1086,6 +1096,7 @@ describe("DocumentBrowser MongoDB filter value types", () => {
const jsonText = viewer.querySelector<HTMLElement>(".cm-line .json-string")!;
const documentId = root!.querySelector<HTMLInputElement>('input[aria-label^="_id:"]')!;
expect(root!.firstElementChild?.classList.contains("select-none")).toBe(true);
expect(viewer.classList.contains("select-text")).toBe(true);
expect(documentId.readOnly).toBe(true);
expect(documentId.value).toBe("document-1");
expect(documentId.classList.contains("select-text")).toBe(true);
@@ -1097,6 +1108,10 @@ describe("DocumentBrowser MongoDB filter value types", () => {
expect(viewer.querySelector<HTMLElement>("[data-redis-json-editor-stub]")?.dataset.lineNumbers).toBe("false");
expect(viewer.querySelector<HTMLElement>("[data-redis-json-editor-stub]")?.dataset.presentation).toBe("viewer");
buttonWithTitle("grid.copy").click();
await flushUi();
expect(clipboard.copyToClipboard).toHaveBeenCalledWith(expect.stringContaining('"_id": "document-1"'));
documentId.setSelectionRange(0, documentId.value.length);
expect(documentId.selectionStart).toBe(0);
expect(documentId.selectionEnd).toBe(documentId.value.length);