Skip to content
This repository was archived by the owner on Mar 7, 2026. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 40 additions & 5 deletions src/components/File/DBResultPreview.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react'
import { formatDistanceToNow } from 'date-fns'
import { DBQueryResult } from 'electron/main/vector-database/schema'
import removeMd from 'remove-markdown'
import MarkdownRenderer from '../Common/MarkdownRenderer'

const cosineDistanceToPercentage = (similarity: number) => {
Expand All @@ -9,9 +10,10 @@ const cosineDistanceToPercentage = (similarity: number) => {
return percentage < 1 ? '1.00' : percentage.toFixed(2)
}

export function getFileName(filePath: string): string {
const parts = filePath.split(/[/\\]/)
return parts.pop() || ''
const getFileName = (path: string) => {
if (!path) return null
const parts = path.split('/')
return parts[parts.length - 1]
}
Comment on lines +13 to 17

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: getFileName now returns null for invalid paths but this isn't handled in the UI rendering - could cause runtime errors

Suggested change
const getFileName = (path: string) => {
if (!path) return null
const parts = path.split('/')
return parts[parts.length - 1]
}
{fileName ? <span className="text-xs text-gray-400">{fileName} </span> : null} | Similarity:{' '}


const formatModifiedDate = (date: Date) => {
Expand Down Expand Up @@ -50,17 +52,50 @@ export const DBResultPreview: React.FC<DBResultPreviewProps> = ({ dbResult: entr

interface DBSearchPreviewProps {
dbResult: DBQueryResult
onSelect: (path: string) => void
onSelect: (path: string, content: string) => void
}

export const DBSearchPreview: React.FC<DBSearchPreviewProps> = ({ dbResult: entry, onSelect }) => {
const modified = formatModifiedDate(entry.filemodified)
const fileName = getFileName(entry.notepath)

const handleClick = () => {
// First extract the title from the content if possible
const lines = removeMd(entry.content).split('\n')
let textToFind = ''

// Try to find the title/heading line (usually at the beginning)
const firstFewLines = lines.slice(0, 5)
const titleLine = firstFewLines.find((line) => {
const trimmed = line.trim()
return trimmed && trimmed.length > 3 && trimmed.length < 100
})

if (titleLine) {
textToFind = titleLine.trim()
} else {
// If no title found, use the first non-empty line with reasonable length
const firstGoodLine = lines.find((line) => {
const trimmed = line.trim()
return trimmed && trimmed.length > 5 && trimmed.length < 120
})

if (firstGoodLine) {
textToFind = firstGoodLine.trim()
} else {
// If still no good text found, use first few words of content
const plainText = removeMd(entry.content).trim()
textToFind = plainText.substring(0, Math.min(50, plainText.length))
}
}

onSelect(entry.notepath, textToFind)
}

return (
<div
className="mb-4 mt-0 max-w-full cursor-pointer overflow-hidden rounded border border-gray-600 bg-neutral-800 p-2 shadow-md transition-transform duration-300 hover:bg-neutral-700 hover:shadow-lg"
onClick={() => onSelect(entry.notepath)}
onClick={handleClick}
>
<div className="text-sm text-gray-200">
<MarkdownRenderer content={entry.content} />
Expand Down
4 changes: 2 additions & 2 deletions src/components/Sidebars/SearchComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,8 @@ const SearchComponent: React.FC<SearchComponentProps> = ({
}, [searchParams.searchMode, searchParams.vectorWeight, debouncedSearch, searchQuery])

const openFileSelectSearch = useCallback(
(path: string) => {
openTabContent(path)
(path: string, content: string) => {
openTabContent(path, undefined, false, content)
posthog.capture('open_file_from_search')
},
[openTabContent],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { CircularProgress } from '@mui/material'
import { DBQueryResult } from 'electron/main/vector-database/schema'
import { FiRefreshCw } from 'react-icons/fi'
import { PiGraph } from 'react-icons/pi'
import removeMd from 'remove-markdown'

import '../../../styles/global.css'
import ResizableComponent from '@/components/Common/ResizableComponent'
Expand All @@ -13,7 +14,7 @@ import { useFileContext } from '@/contexts/FileContext'
interface SimilarEntriesComponentProps {
similarEntries: DBQueryResult[]
setSimilarEntries?: (entries: DBQueryResult[]) => void
onSelect: (path: string) => void
onSelect: (path: string, content?: string) => void
updateSimilarEntries?: (isRefined?: boolean) => Promise<void>
titleText: string
isLoadingSimilarEntries: boolean
Expand All @@ -30,14 +31,47 @@ const SimilarEntriesComponent: React.FC<SimilarEntriesComponentProps> = ({
let content
const { saveCurrentlyOpenedFile } = useFileContext()

const handleResultSelect = (path: string, dbResult: DBQueryResult) => {
// First extract the title from the content if possible
const lines = removeMd(dbResult.content).split('\n')
let textToFind = ''

// Try to find the title/heading line (usually at the beginning)
const firstFewLines = lines.slice(0, 5)
const titleLine = firstFewLines.find((line) => {
const trimmed = line.trim()
return trimmed && trimmed.length > 3 && trimmed.length < 100

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: length > 3 may miss valid single-word titles

})

if (titleLine) {
textToFind = titleLine.trim()
} else {
// If no title found, use the first non-empty line with reasonable length
const firstGoodLine = lines.find((line) => {
const trimmed = line.trim()
return trimmed && trimmed.length > 5 && trimmed.length < 120
})

if (firstGoodLine) {
textToFind = firstGoodLine.trim()
} else {
// If still no good text found, use first few words of content
const plainText = removeMd(dbResult.content).trim()
textToFind = plainText.substring(0, Math.min(50, plainText.length))
}
}

onSelect(path, textToFind)
}

if (similarEntries.length > 0) {
content = (
<div className="size-full">
{similarEntries
.filter((dbResult) => dbResult)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: filter(dbResult => dbResult) won't catch empty content - should be more specific

.map((dbResult) => (
<div className="px-2 pb-2 pt-1" key={`${dbResult.notepath}-${dbResult.subnoteindex}`}>
<DBResultPreview dbResult={dbResult} onSelect={onSelect} />
<DBResultPreview dbResult={dbResult} onSelect={(path) => handleResultSelect(path, dbResult)} />
</div>
))}
</div>
Expand Down
4 changes: 2 additions & 2 deletions src/components/Sidebars/SimilarFilesSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ const SimilarFilesSidebarComponent: React.FC = () => {
<SimilarEntriesComponent
similarEntries={similarEntries}
setSimilarEntries={setSimilarEntries}
onSelect={(path) => {
openTabContent(path)
onSelect={(path, content) => {
openTabContent(path, undefined, false, content)
posthog.capture('open_file_from_related_notes')
Comment on lines +105 to 107

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: The undefined parameter passed to openTabContent could be removed since it's not being used meaningfully

}}
updateSimilarEntries={updateSimilarEntries}
Expand Down
16 changes: 13 additions & 3 deletions src/contexts/ContentContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ import { getFilesInDirectory, getNextAvailableFileNameGivenBaseName } from '@/li
interface ContentContextType {
showEditor: boolean
setShowEditor: (showEditor: boolean) => void
openContent: (pathOrChatID: string, optionalContentToWriteOnCreate?: string, dontUpdateChatHistory?: boolean) => void
openContent: (
pathOrChatID: string,
optionalContentToWriteOnCreate?: string,
dontUpdateChatHistory?: boolean,
contentToScrollTo?: string,
) => void
currentOpenFileOrChatID: string | null
createUntitledNote: (parentFileOrDirectory?: string) => void
}
Expand Down Expand Up @@ -40,14 +45,19 @@ export const ContentProvider: React.FC<ContentProviderProps> = ({ children }) =>
} = useFileContext()

const openContent = React.useCallback(
async (pathOrChatID: string, optionalContentToWriteOnCreate?: string, dontUpdateChatHistory?: boolean) => {
async (
pathOrChatID: string,
optionalContentToWriteOnCreate?: string,
dontUpdateChatHistory?: boolean,
contentToScrollTo?: string,
) => {
if (!pathOrChatID) return
const chatMetadata = allChatsMetadata.find((chat) => chat.id === pathOrChatID)
if (chatMetadata) {
openNewChat(pathOrChatID)
} else {
setShowEditor(true)
openOrCreateFile(pathOrChatID, optionalContentToWriteOnCreate)
openOrCreateFile(pathOrChatID, optionalContentToWriteOnCreate, contentToScrollTo)
}
setCurrentOpenFileOrChatID(pathOrChatID)
if (!dontUpdateChatHistory) {
Expand Down
121 changes: 117 additions & 4 deletions src/contexts/FileContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,11 @@ type FileContextType = {
editor: Editor | null
navigationHistory: string[]
addToNavigationHistory: (value: string) => void
openOrCreateFile: (filePath: string, optionalContentToWriteOnCreate?: string) => Promise<void>
openOrCreateFile: (
filePath: string,
optionalContentToWriteOnCreate?: string,
contentToScrollTo?: string,
) => Promise<void>
suggestionsState: SuggestionsState | null | undefined
spellCheckEnabled: boolean
highlightData: HighlightData
Expand Down Expand Up @@ -128,7 +132,7 @@ export const FileProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
return absolutePath
}

const loadFileIntoEditor = async (filePath: string) => {
const loadFileIntoEditor = async (filePath: string, contentToScrollTo?: string) => {
setCurrentlyChangingFilePath(true)
await writeEditorContentToDisk(editor, currentlyOpenFilePath)
if (currentlyOpenFilePath && needToIndexEditorContent) {
Expand All @@ -141,11 +145,120 @@ export const FileProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
setCurrentlyChangingFilePath(false)
const parentDirectory = await window.path.dirname(filePath)
setSelectedDirectory(parentDirectory)

if (editor && contentToScrollTo) {
// A simple solution: Find all headings and paragraphs in the document
setTimeout(() => {
try {
// Find all heading and paragraph elements to search within
const { dom } = editor.view
const headingsNodeList = dom.querySelectorAll('h1, h2, h3, h4, h5, h6, p')
// Explicitly cast to HTMLElement array to avoid type errors
const headings = Array.from(headingsNodeList).filter((el): el is HTMLElement => el instanceof HTMLElement)

const searchText = contentToScrollTo.toLowerCase().trim()

let bestMatchElement: HTMLElement | null = null
let bestMatchScore = 0

headings.forEach((element) => {
const elementText = element.textContent?.toLowerCase() || ''

let matchScore = 0

// If exact match, high score
if (elementText.includes(searchText)) {
matchScore = 100
}
// If contains first 20 chars, decent score
else if (searchText.length > 20 && elementText.includes(searchText.substring(0, 20))) {
matchScore = 80
}
// If contains first few words, lower score
else if (searchText.length > 10 && elementText.includes(searchText.substring(0, 10))) {
matchScore = 60
}

if (matchScore > bestMatchScore) {
bestMatchScore = matchScore
bestMatchElement = element
}
})

if (bestMatchElement) {
// @ts-ignore - HTMLElement does have scrollIntoView
bestMatchElement.scrollIntoView({ behavior: 'smooth', block: 'center' })
Comment on lines +189 to +190

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Remove @ts-ignore and properly type HTMLElement. scrollIntoView is a standard method that shouldn't need ignoring

Suggested change
// @ts-ignore - HTMLElement does have scrollIntoView
bestMatchElement.scrollIntoView({ behavior: 'smooth', block: 'center' })
bestMatchElement.scrollIntoView({ behavior: 'smooth', block: 'center' })


return true
}

// The DOM approach failed, try using a word-by-word search as fallback
const words = searchText.split(/\s+/).filter((w) => w.length > 3) // Only use meaningful words

if (words.length > 0) {
const docText = editor.state.doc.textContent.toLowerCase()
// Try to find first occurrence of any significant word
let bestPos = -1

words.some((word) => {
if (word.length < 4) return false

const pos = docText.indexOf(word)
if (pos >= 0) {
bestPos = pos
return true
}
return false
})

if (bestPos >= 0) {
editor.commands.setTextSelection(bestPos)

try {
const { node } = editor.view.domAtPos(editor.state.selection.anchor)

let element: HTMLElement | null = null
if (node instanceof HTMLElement) {
element = node
} else if (node.parentElement instanceof HTMLElement) {
element = node.parentElement
}

if (element) {
// @ts-ignore - HTMLElement does have scrollIntoView
element.scrollIntoView({ behavior: 'smooth', block: 'center' })

return true
}
} catch (e) {
// eslint-disable-next-line no-console
console.error('Error handling DOM node:', e)
}
}
}

// If all else fails, scroll to the top of the document
editor.commands.setTextSelection(0)
editor.view.dom.scrollTo(0, 0)

return false
} catch (e) {
// eslint-disable-next-line no-console
console.error('Error in scroll to content:', e)
editor.view.dom.scrollTo(0, 0)
return false
}
}, 800)
}
}

const openOrCreateFile = async (filePath: string, optionalContentToWriteOnCreate?: string): Promise<void> => {
const openOrCreateFile = async (
filePath: string,
optionalContentToWriteOnCreate?: string,
contentToScrollTo?: string,
): Promise<void> => {
const absolutePath = await createFileIfNotExists(filePath, optionalContentToWriteOnCreate)
await loadFileIntoEditor(absolutePath)
await loadFileIntoEditor(absolutePath, contentToScrollTo)
}

const editor = useEditor({
Expand Down