完善桌面端交互体验与发布检查

This commit is contained in:
Cheng Zhou
2026-06-30 22:26:34 +08:00
parent 628ff8828b
commit c923f887a0
30 changed files with 2507 additions and 141 deletions
@@ -0,0 +1,23 @@
export const DESKTOP_REFRESH_CURRENT_VIEW_EVENT = "ctms:desktop-refresh-current-view";
export interface DesktopRefreshEventDetail {
handled: boolean;
}
export const dispatchDesktopRefreshCurrentView = (): boolean => {
if (typeof window === "undefined") return false;
const detail: DesktopRefreshEventDetail = { handled: false };
window.dispatchEvent(new CustomEvent<DesktopRefreshEventDetail>(DESKTOP_REFRESH_CURRENT_VIEW_EVENT, { detail }));
return detail.handled;
};
export const onDesktopRefreshCurrentView = (handler: () => void | Promise<void>): (() => void) => {
if (typeof window === "undefined") return () => {};
const listener = (event: Event) => {
const detail = (event as CustomEvent<DesktopRefreshEventDetail>).detail;
if (detail) detail.handled = true;
void handler();
};
window.addEventListener(DESKTOP_REFRESH_CURRENT_VIEW_EVENT, listener);
return () => window.removeEventListener(DESKTOP_REFRESH_CURRENT_VIEW_EVENT, listener);
};
@@ -0,0 +1,21 @@
import { describe, expect, it } from "vitest";
import { isEditableShortcutTarget } from "./useDesktopShortcuts";
describe("desktop shortcut target detection", () => {
it("does not hijack form controls", () => {
const input = document.createElement("input");
const textarea = document.createElement("textarea");
const editable = document.createElement("div");
editable.setAttribute("contenteditable", "true");
expect(isEditableShortcutTarget(input)).toBe(true);
expect(isEditableShortcutTarget(textarea)).toBe(true);
expect(isEditableShortcutTarget(editable)).toBe(true);
});
it("allows shortcuts outside editable areas", () => {
const button = document.createElement("button");
expect(isEditableShortcutTarget(button)).toBe(false);
});
});
@@ -0,0 +1,58 @@
import { onBeforeUnmount, onMounted } from "vue";
export interface DesktopShortcutHandlers {
openCommandPalette: () => void;
closeActiveLayer?: () => boolean;
navigateBack?: () => void;
navigateForward?: () => void;
refreshCurrentView?: () => void;
}
export const isEditableShortcutTarget = (target: EventTarget | null): boolean => {
if (!(target instanceof Element)) return false;
const tag = target.tagName.toLowerCase();
if (["input", "textarea", "select"].includes(tag)) return true;
if ((target as HTMLElement).isContentEditable) return true;
if (target.getAttribute("contenteditable") === "true") return true;
return Boolean(target.closest('[contenteditable="true"], .el-input, .el-textarea, .el-select'));
};
export const useDesktopShortcuts = (enabled: () => boolean, handlers: DesktopShortcutHandlers) => {
const onKeydown = (event: KeyboardEvent) => {
if (!enabled() || isEditableShortcutTarget(event.target)) return;
const modifier = event.metaKey || event.ctrlKey;
const key = event.key.toLowerCase();
if (!modifier && key === "escape") {
if (handlers.closeActiveLayer?.()) event.preventDefault();
return;
}
if (!modifier) return;
if (key === "k") {
event.preventDefault();
handlers.openCommandPalette();
return;
}
if (key === "[") {
event.preventDefault();
handlers.navigateBack?.();
return;
}
if (key === "]") {
event.preventDefault();
handlers.navigateForward?.();
return;
}
if (key === "r") {
event.preventDefault();
handlers.refreshCurrentView?.();
}
};
onMounted(() => window.addEventListener("keydown", onKeydown));
onBeforeUnmount(() => window.removeEventListener("keydown", onKeydown));
return { onKeydown };
};