-
Notifications
You must be signed in to change notification settings - Fork 418
Pre Meeting Notes Diff + New Search Bar + Export as PDF #991
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
Merged
Merged
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
2ccf6b0
fix
yujonglee ebaa1da
Transcription search bar (#937)
duckduckhero f649de8
Right panel responsiveness fix (#940)
duckduckhero f2242af
patch
yujonglee 1028681
changed searchbar ui
duckduckhero 1230591
search/replace bar finalized
duckduckhero 5ae4747
merged
duckduckhero 699d44b
fixed search/replace bar, added pre meeting notes
duckduckhero cdb5d85
removed debug prints
duckduckhero ecc225c
done dprint check
duckduckhero c40f030
deleted build.rs file
duckduckhero 8084f2b
deleting pre meeting wip
duckduckhero 3917df5
unstable commit - pdf + pre meeting
duckduckhero 6f6202a
after checks
duckduckhero d988859
one more check
duckduckhero f7a55da
temp change
duckduckhero 9b86ead
changed before the big merge
duckduckhero 8649a53
Merge branch 'main' of https://github.com/fastrepl/hyprnote into duck…
duckduckhero d6d479c
attempting to push to main
duckduckhero 274d90f
Merge branch 'main' of https://github.com/fastrepl/hyprnote into duck…
duckduckhero 0a874d6
commit before branching
duckduckhero a7a5875
Merge remote-tracking branch 'origin/main' into duck-0622
yujonglee 8cee4dd
refactors
yujonglee 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
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
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
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
226 changes: 226 additions & 0 deletions
226
apps/desktop/src/components/right-panel/components/search-header.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,226 @@ | ||
| 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, ReplaceIcon, XIcon } from "lucide-react"; | ||
| import { useEffect, useRef, useState } from "react"; | ||
| interface SearchHeaderProps { | ||
| editorRef: React.RefObject<any>; | ||
duckduckhero marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| onClose: () => void; | ||
| } | ||
|
|
||
| export function SearchHeader({ editorRef, onClose }: SearchHeaderProps) { | ||
| const [searchTerm, setSearchTerm] = useState(""); | ||
| const [replaceTerm, setReplaceTerm] = useState(""); | ||
| const [resultCount, setResultCount] = useState(0); | ||
| const [currentIndex, setCurrentIndex] = useState(0); | ||
|
|
||
| // Add ref for the search header container | ||
| const searchHeaderRef = useRef<HTMLDivElement>(null); | ||
|
|
||
| // Debounced search term update | ||
| const debouncedSetSearchTerm = useDebouncedCallback( | ||
| (value: string) => { | ||
| if (editorRef.current) { | ||
| editorRef.current.editor.commands.setSearchTerm(value); | ||
| editorRef.current.editor.commands.resetIndex(); | ||
| setTimeout(() => { | ||
| const storage = editorRef.current.editor.storage.searchAndReplace; | ||
| const results = storage.results || []; | ||
| setResultCount(results.length); | ||
| setCurrentIndex((storage.resultIndex ?? 0) + 1); | ||
| }, 100); | ||
duckduckhero marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| }, | ||
| [editorRef], | ||
| 300, | ||
| ); | ||
duckduckhero marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| useEffect(() => { | ||
| debouncedSetSearchTerm(searchTerm); | ||
| }, [searchTerm]); | ||
|
|
||
| useEffect(() => { | ||
| if (editorRef.current) { | ||
| editorRef.current.editor.commands.setReplaceTerm(replaceTerm); | ||
| } | ||
| }, [replaceTerm]); | ||
|
|
||
| // Click outside handler | ||
| useEffect(() => { | ||
| const handleClickOutside = (event: MouseEvent) => { | ||
| if (searchHeaderRef.current && !searchHeaderRef.current.contains(event.target as Node)) { | ||
| handleClose(); | ||
| } | ||
| }; | ||
|
|
||
| document.addEventListener("mousedown", handleClickOutside); | ||
| return () => document.removeEventListener("mousedown", handleClickOutside); | ||
| }, []); | ||
|
|
||
| // Keyboard shortcuts | ||
| useEffect(() => { | ||
| const handleKeyDown = (e: KeyboardEvent) => { | ||
| if (e.key === "Escape") { | ||
| handleClose(); | ||
| } | ||
| }; | ||
| document.addEventListener("keydown", handleKeyDown); | ||
| return () => document.removeEventListener("keydown", handleKeyDown); | ||
| }, []); | ||
|
|
||
| const handleNext = () => { | ||
| if (editorRef.current?.editor) { | ||
| editorRef.current.editor.commands.nextSearchResult(); | ||
| setTimeout(() => { | ||
| const storage = editorRef.current.editor.storage.searchAndReplace; | ||
| setCurrentIndex((storage.resultIndex ?? 0) + 1); | ||
| scrollCurrentResultIntoView(editorRef); | ||
| }, 100); | ||
| } | ||
| }; | ||
duckduckhero marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| const handlePrevious = () => { | ||
| if (editorRef.current?.editor) { | ||
| editorRef.current.editor.commands.previousSearchResult(); | ||
| setTimeout(() => { | ||
| const storage = editorRef.current.editor.storage.searchAndReplace; | ||
| setCurrentIndex((storage.resultIndex ?? 0) + 1); | ||
| scrollCurrentResultIntoView(editorRef); | ||
| }, 100); | ||
| } | ||
| }; | ||
|
|
||
| function scrollCurrentResultIntoView(editorRef: React.RefObject<any>) { | ||
| if (!editorRef.current) { | ||
| return; | ||
| } | ||
| const editorElement = editorRef.current.editor.view.dom; | ||
| const current = editorElement.querySelector(".search-result-current") as HTMLElement | null; | ||
| if (current) { | ||
| current.scrollIntoView({ | ||
| behavior: "smooth", | ||
| block: "center", | ||
| inline: "nearest", | ||
| }); | ||
| } | ||
| } | ||
duckduckhero marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| const handleReplaceAll = () => { | ||
| if (editorRef.current && searchTerm) { | ||
| editorRef.current.editor.commands.replaceAll(); | ||
| setTimeout(() => { | ||
| const storage = editorRef.current.editor.storage.searchAndReplace; | ||
| const results = storage.results || []; | ||
| setResultCount(results.length); | ||
| setCurrentIndex(results.length > 0 ? 1 : 0); | ||
| }, 100); | ||
| } | ||
duckduckhero marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }; | ||
|
|
||
| const handleClose = () => { | ||
| if (editorRef.current) { | ||
| editorRef.current.editor.commands.setSearchTerm(""); | ||
| } | ||
| 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(); | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| return ( | ||
| <header | ||
| ref={searchHeaderRef} | ||
| className="flex items-center w-full px-4 py-1 my-1 border-b border-neutral-100 bg-neutral-50" | ||
| > | ||
| <div className="flex items-center gap-2 flex-1"> | ||
| {/* Search Input */} | ||
| <div className="flex items-center gap-1 bg-transparent border border-neutral-200 rounded px-2 py-0.5 mb-1.5 flex-1 max-w-xs"> | ||
| <Input | ||
| className="h-5 border-0 focus-visible:ring-0 focus-visible:ring-offset-0 px-1 bg-transparent flex-1 text-xs" | ||
| value={searchTerm} | ||
| onChange={(e) => setSearchTerm(e.target.value)} | ||
| onKeyDown={handleKeyDown} | ||
| placeholder="Search..." | ||
| autoFocus | ||
| /> | ||
| </div> | ||
|
|
||
| {/* Replace Input */} | ||
| <div className="flex items-center gap-1 bg-transparent border border-neutral-200 rounded px-2 py-0.5 mb-1.5 flex-1 max-w-xs"> | ||
| <Input | ||
| className="h-5 border-0 focus-visible:ring-0 focus-visible:ring-offset-0 px-1 bg-transparent flex-1 text-xs" | ||
| value={replaceTerm} | ||
| onChange={(e) => setReplaceTerm(e.target.value)} | ||
| onKeyDown={handleKeyDown} | ||
| placeholder="Replace..." | ||
| /> | ||
| </div> | ||
|
|
||
| {/* Results Counter */} | ||
| {searchTerm && ( | ||
| <span className="text-xs text-neutral-500 whitespace-nowrap"> | ||
| {resultCount > 0 ? `${currentIndex}/${resultCount}` : "0/0"} | ||
| </span> | ||
| )} | ||
| </div> | ||
|
|
||
| {/* Action Buttons */} | ||
| <div className="flex items-center gap-1 ml-2"> | ||
| <Button | ||
| variant="ghost" | ||
| size="icon" | ||
| className="h-7 w-7" | ||
| onClick={handlePrevious} | ||
| disabled={resultCount === 0} | ||
| title="Previous (Shift+Enter)" | ||
| > | ||
| <ChevronUpIcon size={14} /> | ||
| </Button> | ||
| <Button | ||
| variant="ghost" | ||
| size="icon" | ||
| className="h-7 w-7" | ||
| onClick={handleNext} | ||
| disabled={resultCount === 0} | ||
| title="Next (Enter)" | ||
| > | ||
| <ChevronDownIcon size={14} /> | ||
| </Button> | ||
| <Button | ||
| variant="ghost" | ||
| size="sm" | ||
| onClick={handleReplaceAll} | ||
| disabled={!searchTerm || resultCount === 0} | ||
| className="h-7 px-2" | ||
| title="Replace All" | ||
| > | ||
| <ReplaceIcon size={12} /> | ||
| </Button> | ||
| <Button | ||
| variant="ghost" | ||
| size="icon" | ||
| className="h-7 w-7" | ||
| onClick={handleClose} | ||
| title="Close (Esc)" | ||
| > | ||
| <XIcon size={14} /> | ||
| </Button> | ||
| </div> | ||
| </header> | ||
| ); | ||
| } | ||
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.
Uh oh!
There was an error while loading. Please reload this page.