diff --git a/pages/index.vue b/pages/index.vue
index 0accd10..60eb990 100644
--- a/pages/index.vue
+++ b/pages/index.vue
@@ -49,9 +49,12 @@
onClick: pasteSelectedItem,
}" :secondary-action="{
text: 'Actions',
- icon: IconsK,
+ icon: IconsKey,
+ input: 'K',
showModifier: true,
+ onClick: toggleActionsMenu,
}" />
+
@@ -73,22 +76,17 @@ import type {
InfoColor,
InfoCode,
} from "~/types/types";
-import { Key, keyboard } from "wrdu-keyboard";
-import {
- selectedGroupIndex,
- selectedItemIndex,
- selectedElement,
- useSelectedResult,
-} from "~/lib/selectedResult";
import IconsEnter from "~/components/Icons/Enter.vue";
-import IconsK from "~/components/Icons/K.vue";
+import IconsKey from "~/components/Icons/Key.vue";
+import ActionsMenu from "~/components/ActionsMenu.vue";
interface GroupedHistory {
label: string;
items: HistoryItem[];
}
-const { $history } = useNuxtApp();
+const { $history, $keyboard, $selectedResult } = useNuxtApp();
+const { selectedGroupIndex, selectedItemIndex, selectedElement, useSelectedResult } = $selectedResult;
const CHUNK_SIZE = 50;
const SCROLL_THRESHOLD = 100;
@@ -113,9 +111,24 @@ const imageLoadError = ref(false);
const imageLoading = ref(false);
const pageTitle = ref("");
const pageOgImage = ref("");
+const isActionsMenuVisible = ref(false);
const topBar = ref<{ searchInput: HTMLInputElement | null } | null>(null);
+const toggleActionsMenu = () => {
+ nextTick(() => {
+ isActionsMenuVisible.value = !isActionsMenuVisible.value;
+ if (isActionsMenuVisible.value) {
+ $keyboard.enableContext('actionsMenu');
+ }
+ });
+};
+
+const closeActionsMenu = () => {
+ isActionsMenuVisible.value = false;
+ $keyboard.disableContext('actionsMenu');
+};
+
const isSameDay = (date1: Date, date2: Date): boolean => {
return (
date1.getFullYear() === date2.getFullYear() &&
@@ -527,70 +540,47 @@ const setupEventListeners = async (): Promise => {
}
focusSearchInput();
- keyboard.clear();
- keyboard.prevent.down([Key.DownArrow], () => {
- selectNext();
+ $keyboard.clearAll();
+ $keyboard.setupAppShortcuts({
+ onNavigateDown: selectNext,
+ onNavigateUp: selectPrevious,
+ onSelect: pasteSelectedItem,
+ onEscape: () => {
+ if (isActionsMenuVisible.value) {
+ closeActionsMenu();
+ } else {
+ hideApp();
+ }
+ },
+ onToggleActions: toggleActionsMenu,
+ contextName: 'main',
+ priority: $keyboard.PRIORITY.LOW
});
-
- keyboard.prevent.down([Key.UpArrow], () => {
- selectPrevious();
- });
-
- keyboard.prevent.down([Key.Enter], () => {
- pasteSelectedItem();
- });
-
- keyboard.prevent.down([Key.Escape], () => {
- hideApp();
- });
-
- switch (os.value) {
- case "macos":
- keyboard.prevent.down([Key.LeftMeta, Key.K], () => { });
- keyboard.prevent.down([Key.RightMeta, Key.K], () => { });
- break;
-
- case "linux":
- case "windows":
- keyboard.prevent.down([Key.LeftControl, Key.K], () => { });
- keyboard.prevent.down([Key.RightControl, Key.K], () => { });
- break;
- }
+ $keyboard.enableContext('main');
});
await listen("tauri://blur", () => {
searchInput.value?.blur();
- keyboard.clear();
+ $keyboard.clearAll();
+ $keyboard.disableContext('main');
});
- keyboard.prevent.down([Key.DownArrow], () => {
- selectNext();
+ $keyboard.setupAppShortcuts({
+ onNavigateDown: selectNext,
+ onNavigateUp: selectPrevious,
+ onSelect: pasteSelectedItem,
+ onEscape: () => {
+ if (isActionsMenuVisible.value) {
+ closeActionsMenu();
+ } else {
+ hideApp();
+ }
+ },
+ onToggleActions: toggleActionsMenu,
+ contextName: 'main',
+ priority: $keyboard.PRIORITY.LOW
});
-
- keyboard.prevent.down([Key.UpArrow], () => {
- selectPrevious();
- });
-
- keyboard.prevent.down([Key.Enter], () => {
- pasteSelectedItem();
- });
-
- keyboard.prevent.down([Key.Escape], () => {
- hideApp();
- });
-
- switch (os.value) {
- case "macos":
- keyboard.prevent.down([Key.LeftMeta, Key.K], () => { });
- keyboard.prevent.down([Key.RightMeta, Key.K], () => { });
- break;
-
- case "linux":
- case "windows":
- keyboard.prevent.down([Key.LeftControl, Key.K], () => { });
- keyboard.prevent.down([Key.RightControl, Key.K], () => { });
- break;
- }
+ $keyboard.enableContext('main');
};
const hideApp = async (): Promise => {
diff --git a/pages/settings.vue b/pages/settings.vue
index f67a52b..f291861 100644
--- a/pages/settings.vue
+++ b/pages/settings.vue
@@ -79,7 +79,7 @@ import { platform } from "@tauri-apps/plugin-os";
import { useRouter } from "vue-router";
import { KeyValues, KeyLabels } from "../types/keys";
import { disable, enable } from "@tauri-apps/plugin-autostart";
-import { Key, keyboard } from "wrdu-keyboard";
+import { useNuxtApp } from "#app";
import BottomBar from "../components/BottomBar.vue";
import IconsEnter from "~/components/Icons/Enter.vue";
@@ -92,7 +92,7 @@ const os = ref("");
const router = useRouter();
const showEmptyKeybindError = ref(false);
const autostart = ref(false);
-const { $settings } = useNuxtApp();
+const { $settings, $keyboard } = useNuxtApp();
const modifierKeySet = new Set([
KeyValues.AltLeft,
@@ -174,56 +174,71 @@ const toggleAutostart = async () => {
os.value = platform();
onMounted(async () => {
- keyboard.prevent.down([Key.All], (event: KeyboardEvent) => {
- if (isKeybindInputFocused.value) {
- onKeyDown(event);
+ $keyboard.setupKeybindCapture({
+ onCapture: (key: string) => {
+ if (isKeybindInputFocused.value) {
+ const keyValue = key as KeyValues;
+
+ if (isModifier(keyValue)) {
+ activeModifiers.add(keyValue);
+ } else if (!keybind.value.includes(keyValue)) {
+ keybind.value = keybind.value.filter((k) => isModifier(k));
+ keybind.value.push(keyValue);
+ }
+
+ updateKeybind();
+ showEmptyKeybindError.value = false;
+ }
+ },
+ onComplete: () => {
+ if (isKeybindInputFocused.value) {
+ keybindInput.value?.blur();
+ } else {
+ router.push("/");
+ }
}
});
- keyboard.prevent.down([Key.Escape], () => {
- if (isKeybindInputFocused.value) {
- keybindInput.value?.blur();
- } else {
- router.push("/");
- }
- });
-
- switch (os.value) {
- case "macos":
- keyboard.prevent.down([Key.LeftMeta, Key.Enter], () => {
- if (!isKeybindInputFocused.value) {
- saveKeybind();
- }
- });
-
- keyboard.prevent.down([Key.RightMeta, Key.Enter], () => {
- if (!isKeybindInputFocused.value) {
- saveKeybind();
- }
- });
- break;
-
- case "linux":
- case "windows":
- keyboard.prevent.down([Key.LeftControl, Key.Enter], () => {
- if (!isKeybindInputFocused.value) {
- saveKeybind();
- }
- });
-
- keyboard.prevent.down([Key.RightControl, Key.Enter], () => {
- if (!isKeybindInputFocused.value) {
- saveKeybind();
- }
- });
- break;
+ if (os.value === "macos") {
+ $keyboard.on("settings", [$keyboard.Key.LeftMeta, $keyboard.Key.Enter], () => {
+ if (!isKeybindInputFocused.value) {
+ saveKeybind();
+ }
+ }, { priority: $keyboard.PRIORITY.MEDIUM });
+
+ $keyboard.on("settings", [$keyboard.Key.RightMeta, $keyboard.Key.Enter], () => {
+ if (!isKeybindInputFocused.value) {
+ saveKeybind();
+ }
+ }, { priority: $keyboard.PRIORITY.MEDIUM });
+ } else {
+ $keyboard.on("settings", [$keyboard.Key.LeftControl, $keyboard.Key.Enter], () => {
+ if (!isKeybindInputFocused.value) {
+ saveKeybind();
+ }
+ }, { priority: $keyboard.PRIORITY.MEDIUM });
+
+ $keyboard.on("settings", [$keyboard.Key.RightControl, $keyboard.Key.Enter], () => {
+ if (!isKeybindInputFocused.value) {
+ saveKeybind();
+ }
+ }, { priority: $keyboard.PRIORITY.MEDIUM });
}
+ $keyboard.on("settings", [$keyboard.Key.Escape], () => {
+ if (!isKeybindInputFocused.value) {
+ router.push("/");
+ }
+ }, { priority: $keyboard.PRIORITY.MEDIUM });
+
+ $keyboard.enableContext("settings");
+
autostart.value = (await $settings.getSetting("autostart")) === "true";
});
onUnmounted(() => {
- keyboard.clear();
+ $keyboard.disableContext("settings");
+ $keyboard.clearAll();
});
diff --git a/plugins/keyboard.ts b/plugins/keyboard.ts
new file mode 100644
index 0000000..a1f3ab0
--- /dev/null
+++ b/plugins/keyboard.ts
@@ -0,0 +1,239 @@
+import { Key, keyboard } from "wrdu-keyboard";
+import { platform } from "@tauri-apps/plugin-os";
+
+type KeyboardHandler = (event: KeyboardEvent) => void;
+
+const activeContexts = new Set();
+const handlersByContext: Record<
+ string,
+ Array<{
+ keys: Key[];
+ callback: KeyboardHandler;
+ prevent: boolean;
+ priority?: number;
+ }>
+> = {};
+
+const PRIORITY = {
+ HIGH: 100,
+ MEDIUM: 50,
+ LOW: 0,
+};
+
+let currentOS = "windows";
+const initOS = async () => {
+ currentOS = await platform();
+};
+
+const useKeyboard = {
+ PRIORITY,
+
+ registerContext: (contextName: string) => {
+ if (!handlersByContext[contextName]) {
+ handlersByContext[contextName] = [];
+ }
+ },
+
+ enableContext: (contextName: string) => {
+ if (!handlersByContext[contextName]) {
+ useKeyboard.registerContext(contextName);
+ }
+ activeContexts.add(contextName);
+
+ initKeyboardHandlers();
+ },
+
+ disableContext: (contextName: string) => {
+ activeContexts.delete(contextName);
+
+ initKeyboardHandlers();
+ },
+
+ on: (
+ contextName: string,
+ keys: Key[],
+ callback: KeyboardHandler,
+ options: { prevent?: boolean; priority?: number } = {}
+ ) => {
+ if (!handlersByContext[contextName]) {
+ useKeyboard.registerContext(contextName);
+ }
+ handlersByContext[contextName].push({
+ keys,
+ callback,
+ prevent: options.prevent ?? true,
+ priority: options.priority ?? PRIORITY.LOW,
+ });
+
+ if (activeContexts.has(contextName)) {
+ initKeyboardHandlers();
+ }
+ },
+
+ clearAll: () => {
+ keyboard.clear();
+ },
+
+ setupAppShortcuts: (options: {
+ onNavigateUp?: () => void;
+ onNavigateDown?: () => void;
+ onSelect?: () => void;
+ onEscape?: () => void;
+ onToggleActions?: () => void;
+ contextName?: string;
+ priority?: number;
+ }) => {
+ const {
+ onNavigateUp,
+ onNavigateDown,
+ onSelect,
+ onEscape,
+ onToggleActions,
+ contextName = "app",
+ priority = PRIORITY.LOW,
+ } = options;
+
+ if (!handlersByContext[contextName]) {
+ useKeyboard.registerContext(contextName);
+ }
+
+ if (onNavigateUp) {
+ useKeyboard.on(contextName, [Key.UpArrow], () => onNavigateUp(), {
+ priority,
+ });
+ }
+
+ if (onNavigateDown) {
+ useKeyboard.on(contextName, [Key.DownArrow], () => onNavigateDown(), {
+ priority,
+ });
+ }
+
+ if (onSelect) {
+ useKeyboard.on(contextName, [Key.Enter], () => onSelect(), { priority });
+ }
+
+ if (onEscape) {
+ useKeyboard.on(contextName, [Key.Escape], () => onEscape(), { priority });
+ }
+
+ if (onToggleActions) {
+ const togglePriority = PRIORITY.HIGH;
+
+ if (currentOS === "macos") {
+ useKeyboard.on(
+ contextName,
+ [Key.LeftMeta, Key.K],
+ () => onToggleActions(),
+ { priority: togglePriority }
+ );
+ useKeyboard.on(
+ contextName,
+ [Key.RightMeta, Key.K],
+ () => onToggleActions(),
+ { priority: togglePriority }
+ );
+ } else {
+ useKeyboard.on(
+ contextName,
+ [Key.LeftControl, Key.K],
+ () => onToggleActions(),
+ { priority: togglePriority }
+ );
+ useKeyboard.on(
+ contextName,
+ [Key.RightControl, Key.K],
+ () => onToggleActions(),
+ { priority: togglePriority }
+ );
+ }
+ }
+ },
+
+ setupKeybindCapture: (options: {
+ onCapture: (key: string) => void;
+ onComplete: () => void;
+ }) => {
+ const { onCapture, onComplete } = options;
+
+ keyboard.prevent.down([Key.All], (event: KeyboardEvent) => {
+ if (event.code === "Escape") {
+ onComplete();
+ return;
+ }
+ onCapture(event.code);
+ });
+ },
+};
+
+const initKeyboardHandlers = () => {
+ keyboard.clear();
+
+ let allHandlers: Array<{ keys: Key[], callback: KeyboardHandler, prevent: boolean, priority: number, contextName: string }> = [];
+
+ for (const contextName of activeContexts) {
+ const handlers = handlersByContext[contextName] || [];
+ allHandlers = [...allHandlers, ...handlers.map(handler => ({
+ ...handler,
+ priority: handler.priority ?? PRIORITY.LOW,
+ contextName
+ }))];
+ }
+
+ allHandlers.sort((a, b) => b.priority - a.priority);
+
+ const handlersByKeyCombination: Record> = {};
+
+ allHandlers.forEach(handler => {
+ const keyCombo = handler.keys.join('+');
+ if (!handlersByKeyCombination[keyCombo]) {
+ handlersByKeyCombination[keyCombo] = [];
+ }
+ handlersByKeyCombination[keyCombo].push(handler);
+ });
+
+ Object.values(handlersByKeyCombination).forEach(handlers => {
+ const handler = handlers[0];
+
+ const wrappedCallback: KeyboardHandler = (event) => {
+ const isMetaCombo = handler.keys.length > 1 &&
+ (handler.keys.includes(Key.LeftMeta) ||
+ handler.keys.includes(Key.RightMeta) ||
+ handler.keys.includes(Key.LeftControl) ||
+ handler.keys.includes(Key.RightControl));
+
+ const isNavigationKey = event.key === 'ArrowUp' ||
+ event.key === 'ArrowDown' ||
+ event.key === 'Enter' ||
+ event.key === 'Escape';
+
+ const isInInput = event.target instanceof HTMLInputElement ||
+ event.target instanceof HTMLTextAreaElement;
+
+ if (isMetaCombo || isNavigationKey || !isInInput) {
+ handler.callback(event);
+ }
+ };
+
+ if (handler.prevent) {
+ keyboard.prevent.down(handler.keys, wrappedCallback);
+ } else {
+ keyboard.down(handler.keys, wrappedCallback);
+ }
+ });
+};
+
+export default defineNuxtPlugin(async () => {
+ await initOS();
+
+ initKeyboardHandlers();
+
+ return {
+ provide: {
+ keyboard: {
+ ...useKeyboard,
+ Key,
+ },
+ },
+ };
+});