aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--apps/web/components/dashboard/bookmarks/EditorCard.tsx34
-rw-r--r--apps/web/components/ui/kbd.tsx28
-rw-r--r--apps/web/lib/i18n/locales/en/translation.json1
-rw-r--r--apps/web/package.json1
-rw-r--r--pnpm-lock.yaml30
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'