-
Notifications
You must be signed in to change notification settings - Fork 367
Overall redesign 0925 #1496
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
duckduckhero
wants to merge
29
commits into
main
Choose a base branch
from
overall-redesign-0925
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Overall redesign 0925 #1496
Changes from all commits
Commits
Show all changes
29 commits
Select commit
Hold shift + click to select a range
4a5034f
added header chips
duckduckhero d239d69
before adding transcript tools
duckduckhero 0ecd73e
huge wip...
duckduckhero 107f7ee
relieved version
duckduckhero dd979e5
fixed transcript editor issue
duckduckhero ef6a091
ideation in progress
duckduckhero 1b2057d
before moving chips
duckduckhero 965a4c4
going towards the right direction?
duckduckhero 0171911
tab switching fixed
duckduckhero 9ff7eea
added a red recording dot
duckduckhero 93c3c7d
search box initial version - partially working
duckduckhero 8da5f4f
it works now at least
duckduckhero 6a99619
minor changes
duckduckhero ed0cc7f
Merge branch 'main' of https://github.com/fastrepl/hyprnote into over…
duckduckhero be092c9
regenerate button floating
duckduckhero 198e845
added top search bar
duckduckhero a387a18
wip
duckduckhero d2f81f4
thinigs trying to work?
duckduckhero 080a3fc
something weird
duckduckhero e5d96b7
chat buttom position fixed
duckduckhero 08bb7f6
Merge branch 'main' of https://github.com/fastrepl/hyprnote into over…
duckduckhero 45595ce
expandable feeling metadata modal
duckduckhero b7929e0
popover metadata
duckduckhero 1cbda4e
success?
duckduckhero 0e848b7
wip
duckduckhero 482ed9a
merged current main
duckduckhero 1bbd1f6
lots of advancements
duckduckhero c236284
fixed floating buttons
duckduckhero d8e2e36
fixed test errors
duckduckhero File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
304 changes: 304 additions & 0 deletions
304
apps/desktop/src/components/editor-area/floating-search-box.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,304 @@ | ||
import { type TiptapEditor } from "@hypr/tiptap/editor"; | ||
import { type TranscriptEditorRef } from "@hypr/tiptap/transcript"; | ||
import { Button } from "@hypr/ui/components/ui/button"; | ||
import { Input } from "@hypr/ui/components/ui/input"; | ||
import useDebouncedCallback from "beautiful-react-hooks/useDebouncedCallback"; | ||
import { ChevronDownIcon, ChevronUpIcon, XIcon } from "lucide-react"; | ||
import { useCallback, useEffect, useRef, useState } from "react"; | ||
|
||
interface FloatingSearchBoxProps { | ||
editorRef: React.RefObject<TranscriptEditorRef | null> | React.RefObject<{ editor: TiptapEditor | null }>; | ||
onClose: () => void; | ||
isVisible: boolean; | ||
} | ||
|
||
export function FloatingSearchBox({ editorRef, onClose, isVisible }: FloatingSearchBoxProps) { | ||
const [searchTerm, setSearchTerm] = useState(""); | ||
const [replaceTerm, setReplaceTerm] = useState(""); | ||
const [resultCount, setResultCount] = useState(0); | ||
const [currentIndex, setCurrentIndex] = useState(0); | ||
|
||
// Get the editor - NO useCallback, we want fresh ref every time | ||
const getEditor = () => { | ||
const ref = editorRef.current; | ||
if (!ref) { | ||
return null; | ||
} | ||
|
||
// For both normal editor and transcript editor, just access the editor property | ||
if ("editor" in ref && ref.editor) { | ||
return ref.editor; | ||
} | ||
|
||
return null; | ||
}; | ||
|
||
// Add ref for the search box container | ||
const searchBoxRef = useRef<HTMLDivElement>(null); | ||
|
||
// Debounced search term update - NO getEditor in deps | ||
const debouncedSetSearchTerm = useDebouncedCallback( | ||
(value: string) => { | ||
const editor = getEditor(); | ||
if (editor && editor.commands) { | ||
try { | ||
editor.commands.setSearchTerm(value); | ||
editor.commands.resetIndex(); | ||
setTimeout(() => { | ||
const storage = editor.storage?.searchAndReplace; | ||
const results = storage?.results || []; | ||
setResultCount(results.length); | ||
setCurrentIndex((storage?.resultIndex ?? 0) + 1); | ||
}, 100); | ||
} catch (e) { | ||
// Editor might not be ready yet, ignore | ||
console.warn("Editor not ready for search:", e); | ||
} | ||
} | ||
}, | ||
[], // Empty deps to prevent infinite re-creation | ||
300, | ||
); | ||
|
||
useEffect(() => { | ||
debouncedSetSearchTerm(searchTerm); | ||
}, [searchTerm, debouncedSetSearchTerm]); | ||
|
||
useEffect(() => { | ||
const editor = getEditor(); | ||
if (editor && editor.commands) { | ||
try { | ||
editor.commands.setReplaceTerm(replaceTerm); | ||
} catch (e) { | ||
// Editor might not be ready yet, ignore | ||
} | ||
} | ||
}, [replaceTerm]); // Removed getEditor from deps | ||
|
||
// Click outside handler | ||
useEffect(() => { | ||
const handleClickOutside = (event: MouseEvent) => { | ||
if (searchBoxRef.current && !searchBoxRef.current.contains(event.target as Node)) { | ||
handleClose(); | ||
} | ||
}; | ||
|
||
if (isVisible) { | ||
document.addEventListener("mousedown", handleClickOutside); | ||
return () => document.removeEventListener("mousedown", handleClickOutside); | ||
} | ||
}, [isVisible]); | ||
|
||
// Keyboard shortcuts | ||
useEffect(() => { | ||
const handleKeyDown = (e: KeyboardEvent) => { | ||
if (e.key === "Escape") { | ||
handleClose(); | ||
} | ||
}; | ||
|
||
if (isVisible) { | ||
document.addEventListener("keydown", handleKeyDown); | ||
return () => document.removeEventListener("keydown", handleKeyDown); | ||
} | ||
}, [isVisible]); | ||
|
||
const scrollCurrentResultIntoView = useCallback(() => { | ||
const editor = getEditor(); | ||
if (!editor) { | ||
return; | ||
} | ||
|
||
try { | ||
const editorElement = editor.view.dom; | ||
const current = editorElement.querySelector(".search-result-current") as HTMLElement | null; | ||
if (current) { | ||
current.scrollIntoView({ | ||
behavior: "smooth", | ||
block: "center", | ||
inline: "nearest", | ||
}); | ||
} | ||
} catch (e) { | ||
// Editor view not ready yet, ignore | ||
} | ||
}, []); | ||
|
||
const handleNext = useCallback(() => { | ||
const editor = getEditor(); | ||
if (editor) { | ||
editor.commands.nextSearchResult(); | ||
setTimeout(() => { | ||
const storage = editor.storage.searchAndReplace; | ||
setCurrentIndex((storage?.resultIndex ?? 0) + 1); | ||
scrollCurrentResultIntoView(); | ||
}, 100); | ||
} | ||
}, [scrollCurrentResultIntoView]); | ||
|
||
const handlePrevious = useCallback(() => { | ||
const editor = getEditor(); | ||
if (editor) { | ||
editor.commands.previousSearchResult(); | ||
setTimeout(() => { | ||
const storage = editor.storage.searchAndReplace; | ||
setCurrentIndex((storage?.resultIndex ?? 0) + 1); | ||
scrollCurrentResultIntoView(); | ||
}, 100); | ||
} | ||
}, [scrollCurrentResultIntoView]); | ||
|
||
const handleReplace = useCallback(() => { | ||
const editor = getEditor(); | ||
if (editor) { | ||
editor.commands.replace(); | ||
setTimeout(() => { | ||
const storage = editor.storage.searchAndReplace; | ||
const results = storage?.results || []; | ||
setResultCount(results.length); | ||
setCurrentIndex((storage?.resultIndex ?? 0) + 1); | ||
}, 100); | ||
} | ||
}, []); | ||
|
||
const handleReplaceAll = useCallback(() => { | ||
const editor = getEditor(); | ||
if (editor) { | ||
editor.commands.replaceAll(); | ||
setTimeout(() => { | ||
const storage = editor.storage.searchAndReplace; | ||
const results = storage?.results || []; | ||
setResultCount(results.length); | ||
setCurrentIndex(0); | ||
}, 100); | ||
} | ||
}, []); | ||
|
||
const handleClose = useCallback(() => { | ||
const editor = getEditor(); | ||
if (editor) { | ||
editor.commands.setSearchTerm(""); | ||
} | ||
setSearchTerm(""); | ||
setReplaceTerm(""); | ||
setResultCount(0); | ||
setCurrentIndex(0); | ||
onClose(); | ||
}, [onClose]); | ||
|
||
const handleKeyDown = (e: React.KeyboardEvent) => { | ||
if (e.key === "Enter") { | ||
e.preventDefault(); | ||
if (e.shiftKey) { | ||
handlePrevious(); | ||
} else { | ||
handleNext(); | ||
} | ||
} else if (e.key === "F3") { | ||
e.preventDefault(); | ||
if (e.shiftKey) { | ||
handlePrevious(); | ||
} else { | ||
handleNext(); | ||
} | ||
} | ||
}; | ||
|
||
if (!isVisible) { | ||
return null; | ||
} | ||
|
||
return ( | ||
<div className="absolute top-6 right-6 z-50"> | ||
<div | ||
ref={searchBoxRef} | ||
className="bg-white border border-neutral-200 rounded-lg shadow-lg p-3 min-w-96" | ||
> | ||
<div className="flex items-center gap-2 mb-2"> | ||
{/* Search Input */} | ||
<div className="flex items-center gap-1 bg-transparent border border-neutral-200 rounded px-2 py-1 flex-1"> | ||
<Input | ||
className="h-6 border-0 focus-visible:ring-0 focus-visible:ring-offset-0 px-1 bg-transparent flex-1 text-sm" | ||
value={searchTerm} | ||
onChange={(e) => setSearchTerm(e.target.value)} | ||
onKeyDown={handleKeyDown} | ||
placeholder="Search..." | ||
autoFocus | ||
/> | ||
</div> | ||
|
||
{/* Results Counter */} | ||
{searchTerm && ( | ||
<span className="text-xs text-neutral-500 whitespace-nowrap min-w-12 text-center"> | ||
{resultCount > 0 ? `${currentIndex}/${resultCount}` : "0/0"} | ||
</span> | ||
)} | ||
|
||
{/* Navigation Buttons */} | ||
<Button | ||
variant="ghost" | ||
size="sm" | ||
className="h-6 w-6 p-0" | ||
onClick={handlePrevious} | ||
disabled={resultCount === 0} | ||
> | ||
<ChevronUpIcon size={12} /> | ||
</Button> | ||
<Button | ||
variant="ghost" | ||
size="sm" | ||
className="h-6 w-6 p-0" | ||
onClick={handleNext} | ||
disabled={resultCount === 0} | ||
> | ||
<ChevronDownIcon size={12} /> | ||
</Button> | ||
|
||
{/* Close Button */} | ||
<Button | ||
variant="ghost" | ||
size="sm" | ||
className="h-6 w-6 p-0" | ||
onClick={handleClose} | ||
> | ||
<XIcon size={12} /> | ||
</Button> | ||
</div> | ||
|
||
{/* Replace Row */} | ||
<div className="flex items-center gap-2"> | ||
{/* Replace Input */} | ||
<div className="flex items-center gap-1 bg-transparent border border-neutral-200 rounded px-2 py-1 flex-1"> | ||
<Input | ||
className="h-6 border-0 focus-visible:ring-0 focus-visible:ring-offset-0 px-1 bg-transparent flex-1 text-sm" | ||
value={replaceTerm} | ||
onChange={(e) => setReplaceTerm(e.target.value)} | ||
onKeyDown={handleKeyDown} | ||
placeholder="Replace..." | ||
/> | ||
</div> | ||
|
||
{/* Replace Buttons */} | ||
<Button | ||
variant="ghost" | ||
size="sm" | ||
className="h-6 px-2 text-xs" | ||
onClick={handleReplace} | ||
disabled={resultCount === 0 || !replaceTerm} | ||
> | ||
Replace | ||
</Button> | ||
<Button | ||
variant="ghost" | ||
size="sm" | ||
className="h-6 px-2 text-xs" | ||
onClick={handleReplaceAll} | ||
disabled={resultCount === 0 || !replaceTerm} | ||
> | ||
All | ||
</Button> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Type compile error: React.RefObject used without importing React types
Using React.RefObject in a type position requires importing the React type (or switching to RefObject). Add a type‑only import to avoid “Cannot find namespace 'React'”.
Apply this diff:
📝 Committable suggestion
🤖 Prompt for AI Agents