diff options
| -rw-r--r-- | apps/web/components/dashboard/bookmarks/EditorCard.tsx | 34 | ||||
| -rw-r--r-- | apps/web/components/ui/kbd.tsx | 28 | ||||
| -rw-r--r-- | apps/web/lib/i18n/locales/en/translation.json | 1 | ||||
| -rw-r--r-- | apps/web/package.json | 1 | ||||
| -rw-r--r-- | pnpm-lock.yaml | 30 |
5 files changed, 58 insertions, 36 deletions
diff --git a/apps/web/components/dashboard/bookmarks/EditorCard.tsx b/apps/web/components/dashboard/bookmarks/EditorCard.tsx index b80cd889..fa752c5f 100644 --- a/apps/web/components/dashboard/bookmarks/EditorCard.tsx +++ b/apps/web/components/dashboard/bookmarks/EditorCard.tsx @@ -1,8 +1,8 @@ import type { SubmitErrorHandler, SubmitHandler } from "react-hook-form"; -import React, { useEffect, useImperativeHandle, useRef } from "react"; +import React, { useImperativeHandle, useRef } from "react"; import { ActionButton } from "@/components/ui/action-button"; import { Form, FormControl, FormItem } from "@/components/ui/form"; -import InfoTooltip from "@/components/ui/info-tooltip"; +import { Kbd } from "@/components/ui/kbd"; import MultipleChoiceDialog from "@/components/ui/multiple-choice-dialog"; import { Separator } from "@/components/ui/separator"; import { Textarea } from "@/components/ui/textarea"; @@ -17,6 +17,7 @@ import { import { cn, getOS } from "@/lib/utils"; import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; +import { useHotkeys } from "react-hotkeys-hook"; import { z } from "zod"; import { useCreateBookmarkWithPostHook } from "@karakeep/shared-react/hooks/bookmarks"; @@ -24,27 +25,6 @@ import { BookmarkTypes } from "@karakeep/shared/types/bookmarks"; import { useUploadAsset } from "../UploadDropzone"; -function useFocusOnKeyPress( - inputRef: React.RefObject<HTMLTextAreaElement | null>, -) { - useEffect(() => { - function handleKeyPress(e: KeyboardEvent) { - if (!inputRef.current) { - return; - } - if ((e.metaKey || e.ctrlKey) && e.code === "KeyE") { - inputRef.current.focus(); - e.preventDefault(); - } - } - - document.addEventListener("keydown", handleKeyPress); - return () => { - document.removeEventListener("keydown", handleKeyPress); - }; - }, [inputRef]); -} - interface MultiUrlImportState { urls: URL[]; text: string; @@ -70,7 +50,9 @@ export default function EditorCard({ className }: { className?: string }) { }); const { ref, ...textFieldProps } = form.register("text"); useImperativeHandle(ref, () => inputRef.current); - useFocusOnKeyPress(inputRef); + useHotkeys("mod+e", () => { + inputRef.current?.focus(); + }); const { mutate, isPending } = useCreateBookmarkWithPostHook({ onSuccess: (resp) => { @@ -215,9 +197,7 @@ export default function EditorCard({ className }: { className?: string }) { > <div className="flex justify-between"> <p className="text-sm">{t("editor.new_item")}</p> - <InfoTooltip size={15}> - <p className="text-center">{t("editor.quickly_focus")}</p> - </InfoTooltip> + <Kbd>⌘ + E</Kbd> </div> <Separator /> <FormItem className="flex-1"> diff --git a/apps/web/components/ui/kbd.tsx b/apps/web/components/ui/kbd.tsx new file mode 100644 index 00000000..5bc48405 --- /dev/null +++ b/apps/web/components/ui/kbd.tsx @@ -0,0 +1,28 @@ +import { cn } from "@/lib/utils"; + +function Kbd({ className, ...props }: React.ComponentProps<"kbd">) { + return ( + <kbd + data-slot="kbd" + className={cn( + "pointer-events-none inline-flex h-5 w-fit min-w-5 select-none items-center justify-center gap-1 rounded-sm bg-muted px-1 font-sans text-xs font-medium text-muted-foreground", + "[&_svg:not([class*='size-'])]:size-3", + "[[data-slot=tooltip-content]_&]:bg-background/20 [[data-slot=tooltip-content]_&]:text-background dark:[[data-slot=tooltip-content]_&]:bg-background/10", + className, + )} + {...props} + /> + ); +} + +function KbdGroup({ className, ...props }: React.ComponentProps<"div">) { + return ( + <kbd + data-slot="kbd-group" + className={cn("inline-flex items-center gap-1", className)} + {...props} + /> + ); +} + +export { Kbd, KbdGroup }; diff --git a/apps/web/lib/i18n/locales/en/translation.json b/apps/web/lib/i18n/locales/en/translation.json index 5db867a2..abc0d51a 100644 --- a/apps/web/lib/i18n/locales/en/translation.json +++ b/apps/web/lib/i18n/locales/en/translation.json @@ -609,7 +609,6 @@ } }, "editor": { - "quickly_focus": "You can quickly focus on this field by pressing ⌘ + E", "multiple_urls_dialog_title": "Importing URLs as separate Bookmarks?", "multiple_urls_dialog_desc": "The input contains multiple URLs on separate lines. Do you want to import them as separate bookmarks?", "import_as_text": "Import as Text Bookmark", diff --git a/apps/web/package.json b/apps/web/package.json index 02c57718..67ce2560 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -82,6 +82,7 @@ "react-dropzone": "^14.2.3", "react-error-boundary": "^5.0.0", "react-hook-form": "^7.57.0", + "react-hotkeys-hook": "^5.2.1", "react-i18next": "^15.1.1", "react-intersection-observer": "^9.13.1", "react-markdown": "^9.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8527e06a..16ef0918 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -687,6 +687,9 @@ importers: react-hook-form: specifier: ^7.57.0 version: 7.62.0(react@19.1.0) + react-hotkeys-hook: + specifier: ^5.2.1 + version: 5.2.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react-i18next: specifier: ^15.1.1 version: 15.1.1(i18next@23.16.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -1284,7 +1287,7 @@ importers: version: 19.1.0 react-native: specifier: 0.79.5 - version: 0.79.5(@babel/core@7.26.0)(@types/react@19.2.2)(react@19.1.0) + version: 0.79.5(@babel/core@7.26.0)(@types/react@19.2.5)(react@19.1.0) superjson: specifier: ^2.2.1 version: 2.2.1 @@ -12297,6 +12300,12 @@ packages: peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 + react-hotkeys-hook@5.2.1: + resolution: {integrity: sha512-xbKh6zJxd/vJHT4Bw4+0pBD662Fk20V+VFhLqciCg+manTVO4qlqRqiwFOYelfHN9dBvWj9vxaPkSS26ZSIJGg==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + react-i18next@15.1.1: resolution: {integrity: sha512-R/Vg9wIli2P3FfeI8o1eNJUJue5LWpFsQePCHdQDmX0Co3zkr6kdT8gAseb/yGeWbNz1Txc4bKDQuZYsC0kQfw==} peerDependencies: @@ -17565,7 +17574,7 @@ snapshots: '@docusaurus/react-loadable@6.0.0(react@19.1.0)': dependencies: - '@types/react': 19.1.8 + '@types/react': 19.2.5 react: 19.1.0 '@docusaurus/theme-classic@3.8.1(@types/react@19.2.5)(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.9.3)': @@ -19857,14 +19866,14 @@ snapshots: optionalDependencies: '@types/react': 19.1.8 - '@react-native/virtualized-lists@0.79.5(@types/react@19.2.2)(react-native@0.79.5(@babel/core@7.26.0)(@types/react@19.2.2)(react@19.1.0))(react@19.1.0)': + '@react-native/virtualized-lists@0.79.5(@types/react@19.2.5)(react-native@0.79.5(@babel/core@7.26.0)(@types/react@19.2.5)(react@19.1.0))(react@19.1.0)': dependencies: invariant: 2.2.4 nullthrows: 1.1.1 react: 19.1.0 - react-native: 0.79.5(@babel/core@7.26.0)(@types/react@19.2.2)(react@19.1.0) + react-native: 0.79.5(@babel/core@7.26.0)(@types/react@19.2.5)(react@19.1.0) optionalDependencies: - '@types/react': 19.2.2 + '@types/react': 19.2.5 '@react-native/virtualized-lists@0.79.5(@types/react@19.2.5)(react-native@0.79.5(@babel/core@7.28.0)(@types/react@19.2.5)(react@19.1.0))(react@19.1.0)': dependencies: @@ -28705,6 +28714,11 @@ snapshots: dependencies: react: 19.1.0 + react-hotkeys-hook@5.2.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + react-i18next@15.1.1(i18next@23.16.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@babel/runtime': 7.27.6 @@ -29026,7 +29040,7 @@ snapshots: - supports-color - utf-8-validate - react-native@0.79.5(@babel/core@7.26.0)(@types/react@19.2.2)(react@19.1.0): + react-native@0.79.5(@babel/core@7.26.0)(@types/react@19.2.5)(react@19.1.0): dependencies: '@jest/create-cache-key-function': 29.7.0 '@react-native/assets-registry': 0.79.5 @@ -29035,7 +29049,7 @@ snapshots: '@react-native/gradle-plugin': 0.79.5 '@react-native/js-polyfills': 0.79.5 '@react-native/normalize-colors': 0.79.5 - '@react-native/virtualized-lists': 0.79.5(@types/react@19.2.2)(react-native@0.79.5(@babel/core@7.26.0)(@types/react@19.2.2)(react@19.1.0))(react@19.1.0) + '@react-native/virtualized-lists': 0.79.5(@types/react@19.2.5)(react-native@0.79.5(@babel/core@7.26.0)(@types/react@19.2.5)(react@19.1.0))(react@19.1.0) abort-controller: 3.0.0 anser: 1.4.10 ansi-regex: 5.0.1 @@ -29066,7 +29080,7 @@ snapshots: ws: 6.2.3 yargs: 17.7.2 optionalDependencies: - '@types/react': 19.2.2 + '@types/react': 19.2.5 transitivePeerDependencies: - '@babel/core' - '@react-native-community/cli' |
