feat(editor): apply word wrap to config editors

This commit is contained in:
二丫讲梵 authored and GitHub committed 2026-08-13 01:59:04 +08:00
1 parent f88e39563d
commit 1d2a47fee0
13 files changed
+48 -12

No files matched your search

@@ -4,6 +4,7 @@ import { useI18n } from "vue-i18n";
import { Pane, Splitpanes } from "splitpanes";
import "splitpanes/dist/splitpanes.css";
import { useConnectionStore } from "@/stores/connectionStore";
import { useSettingsStore } from "@/stores/settingsStore";
import { Activity, Check, ChevronDown, ChevronRight, Clock3, Copy, Download, FolderClosed, FolderOpen, KeyRound, Loader2, LockKeyhole, Pencil, Plus, RefreshCw, Search, Square, Trash2 } from "@lucide/vue";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
@@ -237,6 +238,7 @@ const emit = defineEmits<{
const { t } = useI18n();
const { toast } = useToast();
const connectionStore = useConnectionStore();
const settingsStore = useSettingsStore();
const searchInputRef = ref<HTMLInputElement>();
const prefix = ref("");
const keySuggestionOpen = ref(false);
@@ -2024,7 +2026,8 @@ defineExpose({
</Button>
<pre
data-native-clipboard
class="dbx-editor-font-family m-0 max-h-[40vh] min-h-32 overflow-auto rounded-md border bg-muted/20 whitespace-pre-wrap break-words p-3 pr-12 text-sm"
class="dbx-editor-font-family m-0 max-h-[40vh] min-h-32 overflow-auto rounded-md border bg-muted/20 p-3 pr-12 text-sm"
:class="settingsStore.editorSettings.wordWrap ? 'whitespace-pre-wrap break-words' : 'whitespace-pre'"
><template v-for="(segment, index) in selectedValueHighlightSegments" :key="index"><mark v-if="segment.matched" class="rounded-sm bg-amber-300/80 px-0.5 text-foreground dark:bg-amber-500/40">{{ segment.text }}</mark><span v-else>{{ segment.text }}</span></template></pre>
</div>
<div v-if="selectedValueCanPrettyJson" class="mt-2 flex justify-end">
@@ -13,6 +13,7 @@ import { properties } from "@codemirror/legacy-modes/mode/properties";
import { shell } from "@codemirror/legacy-modes/mode/shell";
import { onBeforeUnmount, onMounted, ref, watch } from "vue";
import type { KvValueFormat } from "@/lib/kv/kvValueFormat";
import { useSettingsStore } from "@/stores/settingsStore";
const props = withDefaults(
defineProps<{
@@ -28,8 +29,10 @@ const props = withDefaults(
const emit = defineEmits<{ "update:modelValue": [value: string] }>();
const host = ref<HTMLElement>();
const settingsStore = useSettingsStore();
const languageCompartment = new Compartment();
const readOnlyCompartment = new Compartment();
const wordWrapCompartment = new Compartment();
let view: EditorView | null = null;
function languageExtension(format: KvValueFormat) {
@@ -48,6 +51,10 @@ function readOnlyExtensions(readOnly: boolean) {
return [EditorState.readOnly.of(readOnly), EditorView.editable.of(!readOnly)];
}
function wordWrapExtension(wordWrap = settingsStore.editorSettings.wordWrap) {
return wordWrap ? EditorView.lineWrapping : [];
}
onMounted(() => {
if (!host.value) return;
view = new EditorView({
@@ -58,7 +65,7 @@ onMounted(() => {
basicSetup,
languageCompartment.of(languageExtension(props.format)),
readOnlyCompartment.of(readOnlyExtensions(props.readOnly)),
EditorView.lineWrapping,
wordWrapCompartment.of(wordWrapExtension()),
EditorView.theme({
"&": { height: "100%", fontSize: "13px", backgroundColor: "transparent" },
".cm-scroller": { fontFamily: "var(--dbx-editor-font-family, ui-monospace)", overflow: "auto" },
@@ -91,6 +98,11 @@ watch(
(readOnly) => view?.dispatch({ effects: readOnlyCompartment.reconfigure(readOnlyExtensions(readOnly)) }),
);
watch(
() => settingsStore.editorSettings.wordWrap,
(wordWrap) => view?.dispatch({ effects: wordWrapCompartment.reconfigure(wordWrapExtension(wordWrap)) }),
);
onBeforeUnmount(() => {
view?.destroy();
view = null;
@@ -2,6 +2,7 @@ import { readFileSync } from "node:fs";
import { describe, expect, it } from "vitest";
const browserSource = readFileSync(new URL("../KvKeyBrowser.vue", import.meta.url), "utf8");
const valueEditorSource = readFileSync(new URL("../KvValueEditor.vue", import.meta.url), "utf8");
describe("KvKeyBrowser edit dialog layout", () => {
it("keeps the value format label and selector together in the editor toolbar", () => {
@@ -28,4 +29,16 @@ describe("KvKeyBrowser edit dialog layout", () => {
expect(browserSource).toContain('<Copy v-else class="h-4 w-4" />');
expect(browserSource).toMatch(/<pre\s+data-native-clipboard/);
});
it("keeps the selected value preview word wrapping aligned with the global editor setting", () => {
expect(browserSource).toContain(":class=\"settingsStore.editorSettings.wordWrap ? 'whitespace-pre-wrap break-words' : 'whitespace-pre'\"");
expect(browserSource).not.toContain("bg-muted/20 whitespace-pre-wrap break-words p-3");
});
it("keeps shared KV value editor word wrapping aligned with the global editor setting", () => {
expect(valueEditorSource).toContain("const wordWrapCompartment = new Compartment()");
expect(valueEditorSource).toContain("wordWrapCompartment.of(wordWrapExtension())");
expect(valueEditorSource).toContain("wordWrapCompartment.reconfigure(wordWrapExtension(wordWrap))");
expect(valueEditorSource).not.toContain("\n EditorView.lineWrapping,\n");
});
});
@@ -197,6 +197,7 @@ const importSource = shallowRef<string | File | null>(null);
const importSourceName = ref("");
const configEditorTheme = new Compartment();
const configEditorFontTheme = new Compartment();
const configEditorWordWrap = new Compartment();
const configEditorLanguage = new Compartment();
const configListRequestGuard = createNacosLatestRequestGuard();
const configDetailRequestGuard = createNacosLatestRequestGuard();
@@ -553,7 +554,7 @@ async function mountConfigEditor() {
configEditorLanguage.of(language),
configEditorTheme.of(theme),
configEditorFontTheme.of(editorFontTheme(EditorView, editorSettings.fontSize, editorSettings.fontFamily, { fixedHeight: true, scrollable: true })),
EditorView.lineWrapping,
configEditorWordWrap.of(editorSettings.wordWrap ? EditorView.lineWrapping : []),
EditorState.readOnly.of(!!props.readOnly),
EditorView.editable.of(!props.readOnly),
EditorView.updateListener.of((update) => {
@@ -2314,7 +2315,7 @@ watch(
if (configEditorView.value !== view) return;
configEditorFontSize.value = clampEditorFontSize(settings.fontSize);
view.dispatch({
effects: [configEditorTheme.reconfigure(theme), configEditorFontTheme.reconfigure(editorFontTheme(EditorView, settings.fontSize, settings.fontFamily, { fixedHeight: true, scrollable: true }))],
effects: [configEditorTheme.reconfigure(theme), configEditorFontTheme.reconfigure(editorFontTheme(EditorView, settings.fontSize, settings.fontFamily, { fixedHeight: true, scrollable: true })), configEditorWordWrap.reconfigure(settings.wordWrap ? EditorView.lineWrapping : [])],
});
},
{ deep: true },
@@ -123,6 +123,13 @@ describe("NacosAdminConsole config workbench layout", () => {
expect(source).toContain("configEditorZoomCommitScheduler.dispose()");
});
it("keeps the configuration editor word wrapping aligned with the global editor setting", () => {
expect(source).toContain("const configEditorWordWrap = new Compartment()");
expect(source).toContain("configEditorWordWrap.of(editorSettings.wordWrap ? EditorView.lineWrapping : [])");
expect(source).toContain("configEditorWordWrap.reconfigure(settings.wordWrap ? EditorView.lineWrapping : [])");
expect(source).not.toContain("\n EditorView.lineWrapping,\n");
});
it("allows optional configuration columns to be hidden without hiding the data ID", () => {
const configListHeader = source.indexOf('class="sticky top-0 z-20 grid border-b bg-muted');
const columnVisibility = source.indexOf("nacos.visibleColumns");