Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ dist-ssr
dist-electron
release
*.local
AGENTS.md

# Editor directories and files
.vscode/.debug.env
Expand Down
10 changes: 10 additions & 0 deletions electron/main/electron-store/ipcHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,16 @@ export const registerStoreHandlers = (store: Store<StoreSchema>, windowsManager:
return store.get(StoreKeys.showDocumentStats, false)
})

// File index date visibility
ipcMain.handle('set-file-index-date-visibility', (event, showDates: boolean) => {
store.set(StoreKeys.ShowFileDatesInSidebar, showDates)
event.sender.send('file-index-date-visibility-changed', showDates)
})

ipcMain.handle('get-file-index-date-visibility', () => {
return store.get(StoreKeys.ShowFileDatesInSidebar, true)
})

ipcMain.handle('has-user-opened-app-before', () => store.get(StoreKeys.hasUserOpenedAppBefore))

ipcMain.handle('set-user-has-opened-app-before', () => {
Expand Down
2 changes: 2 additions & 0 deletions electron/main/electron-store/storeConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export interface StoreSchema {
spellCheck: string
EditorFlexCenter: boolean
showDocumentStats: boolean
showFileDatesInSidebar: boolean
autoContext: boolean
tamaguiTheme: TamaguiThemeTypes
searchParams: SearchProps
Expand All @@ -86,6 +87,7 @@ export enum StoreKeys {
SpellCheck = 'spellCheck',
EditorFlexCenter = 'editorFlexCenter',
showDocumentStats = 'showDocumentStats',
ShowFileDatesInSidebar = 'showFileDatesInSidebar',
AutoContext = 'autoContext',
TamaguiTheme = 'tamaguiTheme',
SearchParams = 'searchParams',
Expand Down
2 changes: 2 additions & 0 deletions electron/preload/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ const electronStore = {
setSpellCheckMode: createIPCHandler<(isSpellCheck: boolean) => Promise<void>>('set-spellcheck-mode'),
getDocumentStats: createIPCHandler<() => Promise<boolean>>('get-document-stats'),
setDocumentStats: createIPCHandler<(showWordCount: boolean) => Promise<void>>('set-document-stats'),
getFileIndexDateVisibility: createIPCHandler<() => Promise<boolean>>('get-file-index-date-visibility'),
setFileIndexDateVisibility: createIPCHandler<(showDates: boolean) => Promise<void>>('set-file-index-date-visibility'),
getHasUserOpenedAppBefore: createIPCHandler<() => Promise<boolean>>('has-user-opened-app-before'),
setHasUserOpenedAppBefore: createIPCHandler<() => Promise<void>>('set-user-has-opened-app-before'),
getAllChatsMetadata: createIPCHandler<() => Promise<ChatMetadata[]>>('get-all-chats-metadata'),
Expand Down
2 changes: 1 addition & 1 deletion shared/defaultLLMs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const openAIDefaultLLMs: LLMConfig[] = [
export const anthropicDefaultLLMs: LLMConfig[] = [
{
contextLength: 180000,
modelName: 'claude-3-5-sonnet-latest',
modelName: 'claude-sonnet-4-20250514',
apiName: anthropicDefaultAPIName,
},
{
Expand Down
4 changes: 3 additions & 1 deletion src/components/Editor/EditorManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import InEditorBacklinkSuggestionsDisplay from './BacklinkSuggestionsDisplay'
import { useFileContext } from '@/contexts/FileContext'
import { BlockNoteView, FormattingToolbarPositioner, SlashMenuPositioner, SideMenuPositioner } from '@/lib/blocknote'
import SearchBar from './Search/SearchBar'
import NoteTimestamps from './NoteTimestamps'

const EditorManager: React.FC = () => {
const [editorFlex, setEditorFlex] = useState(true)
Expand Down Expand Up @@ -45,9 +46,10 @@ const EditorManager: React.FC = () => {
{editor && <SearchBar editor={editor._tiptapEditor} />}

<YStack
className={`relative h-full py-4 ${editorFlex ? 'flex justify-center px-24' : 'px-12'} ${showDocumentStats ? 'pb-3' : ''}`}
className={`relative h-full pb-4 pt-1 ${editorFlex ? 'flex justify-center px-24' : 'px-12'} ${showDocumentStats ? 'pb-3' : ''}`}
>
<YStack className="relative size-full">
<NoteTimestamps />
{editor && (
<BlockNoteView editor={editor}>
<FormattingToolbarPositioner editor={editor} />
Expand Down
38 changes: 38 additions & 0 deletions src/components/Editor/NoteTimestamps.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React, { useMemo, useState } from 'react'
import { format } from 'date-fns'
import { useFileContext } from '@/contexts/FileContext'

const NoteTimestamps: React.FC = () => {
const { currentlyOpenFilePath, vaultFilesFlattened } = useFileContext()
const [showUpdated, setShowUpdated] = useState(false)

const fileInfo = useMemo(() => {
if (!currentlyOpenFilePath) return null
return vaultFilesFlattened.find((f) => f.path === currentlyOpenFilePath) || null
}, [currentlyOpenFilePath, vaultFilesFlattened])

if (!fileInfo) return null

const createdAt = fileInfo.dateCreated
const updatedAt = fileInfo.dateModified

const label = showUpdated ? 'Updated' : 'Created'
const date = showUpdated ? updatedAt : createdAt
const display = date instanceof Date ? format(date, 'yyyy-MM-dd HH:mm') : String(date)

return (
<div
className="mb-1 mt-2 cursor-pointer select-none pl-4 text-[11px] leading-4 text-neutral-400 opacity-30 transition-opacity hover:text-neutral-300 hover:opacity-80"
onClick={() => setShowUpdated((prev) => !prev)}
role="button"
tabIndex={0}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') setShowUpdated((prev) => !prev)
Copy link

Choose a reason for hiding this comment

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

logic: Missing preventDefault() call for space key to prevent page scrolling

Suggested change
if (e.key === 'Enter' || e.key === ' ') setShowUpdated((prev) => !prev)
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault()
setShowUpdated((prev) => !prev)
}

}}
>
{label}: {display}
</div>
)
}

export default NoteTimestamps
136 changes: 129 additions & 7 deletions src/components/Sidebars/FileSideBar/FileItemRows.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@ import { ListChildComponentProps } from 'react-window'
import posthog from 'posthog-js'
import { isFileNodeDirectory } from '@shared/utils'
import { XStack, Text } from 'tamagui'
import { format } from 'date-fns'
import { ChevronRight, ChevronDown } from '@tamagui/lucide-icons'
import { toast } from 'react-toastify'
import { useFileContext } from '@/contexts/FileContext'
import { removeFileExtension } from '@/lib/file'
import { getFilesInDirectory, removeFileExtension } from '@/lib/file'
import { useContentContext } from '@/contexts/ContentContext'
import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger } from '@/components/ui/context-menu'
import NewDirectoryComponent from '@/components/File/NewDirectory'

const FileItemRows: React.FC<ListChildComponentProps> = ({ index, style, data }) => {
const { file, indentation } = data.filesAndIndentations[index]
const showDates: boolean = data.showDates ?? true

const {
handleDirectoryToggle,
Expand All @@ -22,14 +25,28 @@ const FileItemRows: React.FC<ListChildComponentProps> = ({ index, style, data })
selectedDirectory,
setSelectedDirectory,
renameFile,
selectedFilePaths,
selectSingleFile,
toggleSelectFile,
selectRangeTo,
vaultFilesFlattened,
} = useFileContext()
const { openContent, createUntitledNote } = useContentContext()
const [isNewDirectoryModalOpen, setIsNewDirectoryModalOpen] = useState(false)
const [parentDirectoryPathForNewDirectory, setParentDirectoryPathForNewDirectory] = useState<string | undefined>()
const [isDragOver, setIsDragOver] = useState(false)

const isDirectory = isFileNodeDirectory(file)
const isSelected = isDirectory ? file.path === selectedDirectory : file.path === currentlyOpenFilePath
const formattedDate =
showDates && !isDirectory && file.dateModified instanceof Date ? format(file.dateModified, 'MM-dd-yy') : ''
let isSelected = false
if (isDirectory) {
isSelected = selectedFilePaths.includes(file.path) || file.path === selectedDirectory
} else if (selectedFilePaths.length > 0) {
isSelected = selectedFilePaths.includes(file.path)
} else {
isSelected = file.path === currentlyOpenFilePath
}

const indentationPadding = indentation ? 10 * indentation : 0
const isExpanded = expandedDirectories.get(file.path)
Expand Down Expand Up @@ -65,17 +82,40 @@ const FileItemRows: React.FC<ListChildComponentProps> = ({ index, style, data })

const clickOnFileOrDirectory = useCallback(
(event: any) => {
const e = event.nativeEvent
const e = event.nativeEvent || event
if (isDirectory) {
handleDirectoryToggle(file.path)
setSelectedDirectory(file.path)
if (e.metaKey || e.ctrlKey) {
toggleSelectFile(file.path)
} else {
handleDirectoryToggle(file.path)
setSelectedDirectory(file.path)
}
e.stopPropagation()
return
}

if (e.shiftKey) {
selectRangeTo(file.path)
} else if (e.metaKey || e.ctrlKey) {
toggleSelectFile(file.path)
} else {
selectSingleFile(file.path)
openContent(file.path)
posthog.capture('open_file_from_sidebar')
}

e.stopPropagation()
},
[file.path, isDirectory, handleDirectoryToggle, openContent, setSelectedDirectory],
[
file.path,
isDirectory,
handleDirectoryToggle,
openContent,
setSelectedDirectory,
selectRangeTo,
toggleSelectFile,
selectSingleFile,
],
)

const openNewDirectoryModal = useCallback(async () => {
Expand Down Expand Up @@ -108,6 +148,75 @@ const FileItemRows: React.FC<ListChildComponentProps> = ({ index, style, data })
New file
</ContextMenuItem>
<ContextMenuItem onClick={openNewDirectoryModal}>New folder</ContextMenuItem>
<ContextMenuItem
onClick={async () => {
try {
// Determine current selection scope
const baseSelection =
selectedFilePaths.length > 1 && selectedFilePaths.includes(file.path) ? selectedFilePaths : [file.path]

// Partition into directories and files
const dirPaths: string[] = []
const filePaths: string[] = []
for (const p of baseSelection) {
// eslint-disable-next-line no-await-in-loop
const isDir = await window.fileSystem.isDirectory(p)
if (isDir) dirPaths.push(p)
else filePaths.push(p)
}

// Build folder sections with H1 headers
const seenFiles = new Set<string>()
const parts: string[] = []
for (const dir of dirPaths) {
// eslint-disable-next-line no-await-in-loop
const folderName = await window.path.basename(dir)
// eslint-disable-next-line no-await-in-loop
const files = await getFilesInDirectory(dir, vaultFilesFlattened)
const folderContents: string[] = []
for (const f of files) {
if (!seenFiles.has(f.path)) {
seenFiles.add(f.path)
// eslint-disable-next-line no-await-in-loop
const c = await window.fileSystem.readFile(f.path, 'utf-8')
folderContents.push(c || '')
}
}
if (folderContents.length > 0) {
parts.push(`# Folder: ${folderName}`)
parts.push(folderContents.join('\n\n'))
}
}

// Add loose files not already included via folders
for (const p of filePaths) {
if (!seenFiles.has(p)) {
seenFiles.add(p)
// eslint-disable-next-line no-await-in-loop
const c = await window.fileSystem.readFile(p, 'utf-8')
parts.push(c || '')
}
}

if (parts.length === 0) {
toast.info('No markdown files found')
return
}

const output = parts.join('\n\n')
await navigator.clipboard.writeText(output)
const totalNotes = seenFiles.size
toast.success(totalNotes > 1 ? `Copied ${totalNotes} notes to clipboard` : 'Markdown copied to clipboard')
} catch (err) {
toast.error('Failed to copy markdown')
}
}}
>
{(() => {
const inMulti = selectedFilePaths.length > 1 && selectedFilePaths.includes(file.path)
return inMulti ? `Copy Markdown (${selectedFilePaths.length})` : 'Copy Markdown'
})()}
</ContextMenuItem>
<ContextMenuItem onClick={() => setNoteToBeRenamed(file.path)}>Rename</ContextMenuItem>
<ContextMenuItem onClick={handleDelete}>Delete</ContextMenuItem>
</>
Expand All @@ -132,6 +241,8 @@ const FileItemRows: React.FC<ListChildComponentProps> = ({ index, style, data })
onPress={clickOnFileOrDirectory}
className={itemClasses}
overflow="hidden"
justifyContent="space-between"
width="100%"
>
{isDirectory && (
<span className="mr-2 mt-1">
Expand All @@ -144,9 +255,20 @@ const FileItemRows: React.FC<ListChildComponentProps> = ({ index, style, data })
)}
</span>
)}
<Text color="$gray11" numberOfLines={1}>
<Text color="$gray11" numberOfLines={1} flex={1}>
{isDirectory ? file.name : removeFileExtension(file.name)}
</Text>
{!isDirectory && (
<Text
color="$gray9"
numberOfLines={1}
flexShrink={0}
textAlign="right"
className="ml-2 w-14 whitespace-nowrap text-[10px] opacity-40"
>
{formattedDate}
</Text>
)}
</XStack>
<NewDirectoryComponent
isOpen={isNewDirectoryModalOpen}
Expand Down
Loading