diff --git a/src/components/Search/ContextMenu.tsx b/src/components/Search/ContextMenu.tsx index a02d69e4..fbeb4393 100644 --- a/src/components/Search/ContextMenu.tsx +++ b/src/components/Search/ContextMenu.tsx @@ -1,9 +1,21 @@ import { useOSKeyPress } from "@/hooks/useOSKeyPress"; import { useSearchStore } from "@/stores/searchStore"; -import { useClickAway } from "ahooks"; +import { copyToClipboard, OpenURLWithBrowser } from "@/utils"; +import { isMac } from "@/utils/platform"; +import { + useClickAway, + useCreation, + useEventListener, + useReactive, +} from "ahooks"; import clsx from "clsx"; +import { isNil } from "lodash-es"; import { Link, PanelRight, Send, SquareArrowOutUpRight } from "lucide-react"; -import { cloneElement, useRef } from "react"; +import { cloneElement, useEffect, useRef } from "react"; + +interface State { + activeMenuIndex: number; +} const ContextMenu = () => { const containerRef = useRef(null); @@ -16,30 +28,66 @@ const ContextMenu = () => { return state.setVisibleContextMenu; }); - const menus = [ - { - name: "Open", - icon: , - keys: ["↩︎"], - }, - { - name: "Send to AI", - icon: , - keys: ["⌘", "↩︎"], - }, - { - name: "Copy link", - icon: , - keys: ["⌘", "L"], - }, - { - name: "Toggle details", - icon: , - keys: ["⌘", "D"], - }, - ]; + const selectedSearchContent = useSearchStore((state) => { + return state.selectedSearchContent; + }); + + const menus = useCreation(() => { + if (isNil(selectedSearchContent)) return []; + + return [ + { + name: "Open", + icon: , + keys: ["↩︎"], + shortcut: "enter", + clickEvent: () => { + OpenURLWithBrowser(selectedSearchContent?.url); + }, + }, + { + name: "Send to AI", + icon: , + keys: ["⌘", "↩︎"], + shortcut: isMac ? "meta.enter" : "ctrl.enter", + clickEvent: () => {}, + }, + { + name: "Copy link", + icon: , + keys: ["⌘", "L"], + shortcut: isMac ? "meta.l" : "ctrl.l", + clickEvent: () => { + copyToClipboard(selectedSearchContent?.url); + }, + }, + { + name: "Toggle details", + icon: , + keys: ["⌘", "D"], + shortcut: isMac ? "meta.d" : "ctrl.d", + clickEvent: () => {}, + }, + ]; + }, [selectedSearchContent]); + + const state = useReactive({ + activeMenuIndex: 0, + }); + + useEffect(() => { + state.activeMenuIndex = 0; + }, [visibleContextMenu, selectedSearchContent]); + + useEffect(() => { + if (isNil(selectedSearchContent)) { + setVisibleContextMenu(false); + } + }, [selectedSearchContent]); useOSKeyPress(["meta.k", "ctrl.k"], () => { + if (isNil(selectedSearchContent)) return; + setVisibleContextMenu(!visibleContextMenu); }); @@ -47,46 +95,99 @@ const ContextMenu = () => { setVisibleContextMenu(false); }, containerRef); + useOSKeyPress(["uparrow", "downarrow"], (_, key) => { + if (!visibleContextMenu) return; + + const index = state.activeMenuIndex; + const length = menus.length; + + switch (key) { + case "uparrow": + state.activeMenuIndex = index === 0 ? length - 1 : index - 1; + break; + case "downarrow": + state.activeMenuIndex = index === length - 1 ? 0 : index + 1; + break; + } + }); + + useOSKeyPress( + menus.map((item) => item.shortcut), + (_, key) => { + if (!visibleContextMenu) return; + + const item = menus.find((item) => item.shortcut === key); + + handleClick(item?.clickEvent); + } + ); + + useEventListener("keydown", (event) => { + if (!visibleContextMenu) return; + + event.stopImmediatePropagation(); + }); + + const handleClick = (clickEvent?: () => void) => { + clickEvent?.(); + + setVisibleContextMenu(false); + }; + return ( -
-
    - {menus.map((item) => { - const { name, icon, keys } = item; + <> + {visibleContextMenu &&
    } - return ( -
  • -
    - {cloneElement(icon, { className: "size-4" })} +
    +
      + {menus.map((item, index) => { + const { name, icon, keys, clickEvent } = item; - {name} -
    + return ( +
  • { + state.activeMenuIndex = index; + }} + onClick={() => handleClick(clickEvent)} + > +
    + {cloneElement(icon, { className: "size-4" })} -
    - {keys.map((key) => ( - - {key} - - ))} -
    -
  • - ); - })} -
-
+ {name} + + +
+ {keys.map((key) => ( + + {key} + + ))} +
+ + ); + })} + + + ); }; diff --git a/src/components/Search/DropdownList.tsx b/src/components/Search/DropdownList.tsx index 6384fa92..036cbc09 100644 --- a/src/components/Search/DropdownList.tsx +++ b/src/components/Search/DropdownList.tsx @@ -8,6 +8,8 @@ import TypeIcon from "@/components/Common/Icons/TypeIcon"; import SearchListItem from "./SearchListItem"; import { metaOrCtrlKey, isMetaOrCtrlKey } from "@/utils/keyboardUtils"; import { OpenURLWithBrowser } from "@/utils/index"; +import { isNil, isPlainObject } from "lodash-es"; +import { useUnmount } from "ahooks"; type ISearchData = Record; @@ -41,6 +43,24 @@ function DropdownList({ const containerRef = useRef(null); const itemRefs = useRef<(HTMLDivElement | null)[]>([]); + const setSelectedSearchContent = useSearchStore((state) => { + return state.setSelectedSearchContent; + }); + + useUnmount(() => { + setSelectedSearchContent(void 0); + }); + + useEffect(() => { + if (isNil(selectedItem)) { + setSelectedSearchContent(void 0); + + return; + } + + setSelectedSearchContent(globalItemIndexMap[selectedItem]); + }, [selectedItem]); + useEffect(() => { if (isChatMode) { setSelectedItem(null); @@ -181,6 +201,7 @@ function DropdownList({ ) : null} ) : null} + {items.map((hit: any, index: number) => { const isSelected = selectedItem === globalIndex; const currentIndex = globalIndex; diff --git a/src/components/Search/Search.tsx b/src/components/Search/Search.tsx index 70ebd00b..80cfb552 100644 --- a/src/components/Search/Search.tsx +++ b/src/components/Search/Search.tsx @@ -71,10 +71,6 @@ function Search({ isChatMode, input }: SearchProps) { if (!input) setSuggests([]); }, [input]); - useEffect(() => { - console.log("SearchData", SearchData); - }, [SearchData]); - return (
{ - console.log("Hide Coco"); - }); + // invoke("hide_coco").then(() => { + // console.log("Hide Coco"); + // }); }; const handleFocus = () => { diff --git a/src/stores/searchStore.ts b/src/stores/searchStore.ts index b1641ebc..3a2d165b 100644 --- a/src/stores/searchStore.ts +++ b/src/stores/searchStore.ts @@ -8,6 +8,10 @@ export type ISearchStore = { setSourceDataIds: (prevSourceDataId: string[]) => void; visibleContextMenu: boolean; setVisibleContextMenu: (visibleContextMenu: boolean) => void; + selectedSearchContent?: Record; + setSelectedSearchContent: ( + selectedSearchContent?: Record + ) => void; }; export const useSearchStore = create()( @@ -18,8 +22,12 @@ export const useSearchStore = create()( sourceDataIds: [], setSourceDataIds: (sourceDataIds: string[]) => set({ sourceDataIds }), visibleContextMenu: false, - setVisibleContextMenu: (visibleContextMenu: boolean) => - set({ visibleContextMenu }), + setVisibleContextMenu: (visibleContextMenu) => { + return set({ visibleContextMenu }); + }, + setSelectedSearchContent: (selectedSearchContent) => { + return set({ selectedSearchContent }); + }, }), { name: "search-store",