diff --git a/package-lock.json b/package-lock.json index e123ea77..ab0b27b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "reor-project", - "version": "0.2.24", + "version": "0.2.26", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "reor-project", - "version": "0.2.24", + "version": "0.2.26", "license": "AGPL-3.0", "dependencies": { "@aarkue/tiptap-math-extension": "^1.2.2", @@ -94,6 +94,7 @@ "react-icons": "^4.12.0", "react-markdown": "^9.0.1", "react-quill": "^2.0.0", + "react-resizable-panels": "^2.1.6", "react-rnd": "^10.4.1", "react-switch": "^7.0.0", "react-toastify": "^10.0.4", @@ -20676,6 +20677,15 @@ } } }, + "node_modules/react-resizable-panels": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-2.1.6.tgz", + "integrity": "sha512-oIqo/7pp2TsR+Dp1qZMr1l4RBDV4Zz/0HEG5zxliBJoHqqFnG0MbmFbk+5Q1VMGfPQ4uhXxefunLC1o7v38PDQ==", + "peerDependencies": { + "react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, "node_modules/react-rnd": { "version": "10.4.1", "license": "MIT", diff --git a/package.json b/package.json index 593639fa..39dadda2 100644 --- a/package.json +++ b/package.json @@ -113,6 +113,7 @@ "react-icons": "^4.12.0", "react-markdown": "^9.0.1", "react-quill": "^2.0.0", + "react-resizable-panels": "^2.1.6", "react-rnd": "^10.4.1", "react-switch": "^7.0.0", "react-toastify": "^10.0.4", diff --git a/src/components/Chat/ChatInput.tsx b/src/components/Chat/ChatInput.tsx index 7bb9b5f2..22aabb44 100644 --- a/src/components/Chat/ChatInput.tsx +++ b/src/components/Chat/ChatInput.tsx @@ -33,6 +33,8 @@ const ChatInput: React.FC = ({ name="Outlined" placeholder="Follow up..." rows={1} + // eslint-disable-next-line jsx-a11y/no-autofocus + autoFocus style={{ backgroundColor: 'rgba(255, 255, 255, 0)', color: 'rgb(212 212 212)', diff --git a/src/components/Chat/ChatMessages.tsx b/src/components/Chat/ChatMessages.tsx index 81ba7f61..c308a7aa 100644 --- a/src/components/Chat/ChatMessages.tsx +++ b/src/components/Chat/ChatMessages.tsx @@ -38,7 +38,7 @@ const Message: React.FC = ({ message, index, currentChat, setCurre } interface ChatMessagesProps { - currentChat: Chat + currentChat: Chat | undefined setCurrentChat: React.Dispatch> loadingState: LoadingState handleNewChatMessage: (userTextFieldInput?: string, agentConfig?: AgentConfig) => void @@ -76,9 +76,15 @@ const ChatMessages: React.FC = ({ } } - const handleSubmitNewMessage = () => { + const handleSubmitNewMessage = async () => { if (userTextFieldInput) { - handleNewChatMessage(userTextFieldInput) + // this for v1 could just use the default agent config... + const agentConfigs = await window.electronStore.getAgentConfigs() + if (agentConfigs && agentConfigs.length > 0) { + handleNewChatMessage(userTextFieldInput, agentConfigs[0]) + } else { + handleNewChatMessage(userTextFieldInput) + } setUserTextFieldInput('') setShouldAutoScroll(true) } @@ -103,7 +109,9 @@ const ChatMessages: React.FC = ({
- {currentChat?.messages?.length > 0 && + {currentChat && + currentChat.messages && + currentChat.messages.length > 0 && currentChat.messages.map((message, index) => ( // eslint-disable-next-line react/no-array-index-key
@@ -120,16 +128,14 @@ const ChatMessages: React.FC = ({
- {currentChat && ( -
- -
- )} +
+ +
) } diff --git a/src/components/Chat/index.tsx b/src/components/Chat/index.tsx index 56c1a663..58adf4b1 100644 --- a/src/components/Chat/index.tsx +++ b/src/components/Chat/index.tsx @@ -15,13 +15,20 @@ import { useChatContext } from '@/contexts/ChatContext' import StartChat from './StartChat' import resolveLLMClient from '@/lib/llm/client' import { appendToolCallsAndAutoExecuteTools, convertToolConfigToZodSchema } from '../../lib/llm/tools/utils' +import useResizeObserver from '@/lib/hooks/use-resize-observer' const ChatComponent: React.FC = () => { const [loadingState, setLoadingState] = useState('idle') const [defaultModelName, setDefaultLLMName] = useState('') + const [containerWidth, setContainerWidth] = useState(0) + const containerRef = useRef(null) const { currentChat, setCurrentChat, saveChat } = useChatContext() const abortControllerRef = useRef(null) + useResizeObserver(containerRef, (entry) => { + setContainerWidth(entry.contentRect.width) + }) + useEffect(() => { const fetchDefaultLLM = async () => { const defaultName = await window.llm.getDefaultLLMName() @@ -134,8 +141,8 @@ const ChatComponent: React.FC = () => { ) return ( -
-
+
+
{currentChat && currentChat.messages && currentChat.messages.length > 0 ? ( { } /> ) : ( - - handleNewChatMessage(undefined, userTextFieldInput, agentConfig) - } - /> + // eslint-disable-next-line react/jsx-no-useless-fragment + <> + {containerWidth > 600 ? ( + + handleNewChatMessage(undefined, userTextFieldInput, agentConfig) + } + /> + ) : ( + + handleNewChatMessage(currentChat, userTextFieldInput, chatFilters) + } + /> + )} + )}
diff --git a/src/components/Common/CommonModals.tsx b/src/components/Common/CommonModals.tsx index 3c99e3b7..3dac2139 100644 --- a/src/components/Common/CommonModals.tsx +++ b/src/components/Common/CommonModals.tsx @@ -5,14 +5,17 @@ import SettingsModal from '../Settings/Settings' import { useFileContext } from '@/contexts/FileContext' import RenameNoteModal from '../File/RenameNote' import RenameDirModal from '../File/RenameDirectory' +import NewDirectoryComponent from '../File/NewDirectory' const CommonModals: React.FC = () => { - const { isSettingsModalOpen, setIsSettingsModalOpen } = useModalOpeners() + const { isNewDirectoryModalOpen, setIsNewDirectoryModalOpen, isSettingsModalOpen, setIsSettingsModalOpen } = + useModalOpeners() const { noteToBeRenamed, fileDirToBeRenamed } = useFileContext() return (
+ setIsNewDirectoryModalOpen(false)} /> {noteToBeRenamed && } {fileDirToBeRenamed && } setIsSettingsModalOpen(false)} /> diff --git a/src/components/Common/EmptyPage.tsx b/src/components/Common/EmptyPage.tsx index 02bc7e95..0ba0bc17 100644 --- a/src/components/Common/EmptyPage.tsx +++ b/src/components/Common/EmptyPage.tsx @@ -1,10 +1,10 @@ -import React, { useState } from 'react' +import React from 'react' import { ImFileEmpty } from 'react-icons/im' import { useContentContext } from '@/contexts/ContentContext' -import NewDirectoryComponent from '../File/NewDirectory' +import { useModalOpeners } from '@/contexts/ModalContext' const EmptyPage: React.FC = () => { - const [isNewDirectoryModalOpen, setIsNewDirectoryModalOpen] = useState(false) + const { setIsNewDirectoryModalOpen } = useModalOpeners() const { createUntitledNote } = useContentContext() return ( @@ -29,7 +29,6 @@ const EmptyPage: React.FC = () => { > Create a Folder - setIsNewDirectoryModalOpen(false)} />
) diff --git a/src/components/Editor/EditorManager.tsx b/src/components/Editor/EditorManager.tsx index c3681d59..e47db2ef 100644 --- a/src/components/Editor/EditorManager.tsx +++ b/src/components/Editor/EditorManager.tsx @@ -11,10 +11,8 @@ const EditorManager: React.FC = () => { const [contextMenuVisible, setContextMenuVisible] = useState(false) const [menuPosition, setMenuPosition] = useState({ x: 0, y: 0 }) const [editorFlex, setEditorFlex] = useState(true) - const [showPlaceholder, setShowPlaceholder] = useState(false) - const [writingAssistantTextPosition, setWritingAssistantTextPosition] = useState({ top: 0, left: 0 }) - const { editor, suggestionsState, vaultFilesFlattened, currentlyOpenFilePath } = useFileContext() + const { editor, suggestionsState, vaultFilesFlattened } = useFileContext() const [showDocumentStats, setShowDocumentStats] = useState(false) const { openContent } = useContentContext() @@ -69,65 +67,6 @@ const EditorManager: React.FC = () => { window.ipcRenderer.on('show-doc-stats-changed', handleDocStatsChange) }, []) - useEffect(() => { - if (!editor) return - - const handleUpdate = () => { - try { - const { state } = editor - const { from, to } = state.selection - - const $from = state.doc.resolve(from) - const $to = state.doc.resolve(to) - const start = $from.before() - const end = $to.after() - - const currentLineText = state.doc.textBetween(start, end, '\n', ' ').trim() - - if (currentLineText === '') { - const { node } = editor.view.domAtPos(from) - const rect = (node as HTMLElement).getBoundingClientRect() - const editorRect = editor.view.dom.getBoundingClientRect() - setWritingAssistantTextPosition({ top: rect.top - editorRect.top, left: rect.left - editorRect.left }) - setShowPlaceholder(true) - } else { - setShowPlaceholder(false) - } - } catch (error) { - setShowPlaceholder(false) - } - } - - editor.on('update', handleUpdate) - editor.on('selectionUpdate', handleUpdate) - - // eslint-disable-next-line consistent-return - return () => { - editor.off('update', handleUpdate) - editor.off('selectionUpdate', handleUpdate) - } - }, [editor]) - - const handleInput = () => { - if (editor) { - const { state } = editor - const { from, to } = state.selection - - const $from = state.doc.resolve(from) - const $to = state.doc.resolve(to) - const start = $from.before() - const end = $to.after() - - const currentLineText = state.doc.textBetween(start, end, '\n', ' ').trim() - setShowPlaceholder(currentLineText === '') - } - } - useEffect(() => { - if (editor) { - editor.commands.focus() - } - }, [editor, currentlyOpenFilePath]) - return (
{ }} onContextMenu={handleContextMenu} onClick={handleClick} - onInput={handleInput} editor={editor} /> - {showPlaceholder && ( -
- Press 'space' for AI writing assistant -
- )}
{suggestionsState && ( diff --git a/src/components/MainPage.tsx b/src/components/MainPage.tsx index 64bf019e..2ca1c9da 100644 --- a/src/components/MainPage.tsx +++ b/src/components/MainPage.tsx @@ -1,88 +1,103 @@ -import React, { useCallback, useEffect, useState } from 'react' +/* eslint-disable react/button-has-type */ +/* eslint-disable jsx-a11y/control-has-associated-label */ +/* eslint-disable react/jsx-no-useless-fragment */ +/* eslint-disable no-nested-ternary */ +import React from 'react' +import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@/components/ui/resizable' import '../styles/global.css' import ChatComponent from './Chat' -import ResizableComponent from './Common/ResizableComponent' import TitleBar from './TitleBar/TitleBar' import EditorManager from './Editor/EditorManager' import IconsSidebar from './Sidebars/IconsSidebar' import SidebarManager from './Sidebars/MainSidebar' -import SimilarFilesSidebarComponent from './Sidebars/SimilarFilesSidebar' import EmptyPage from './Common/EmptyPage' -import { ContentProvider } from '../contexts/ContentContext' +import { ContentProvider, useContentContext } from '../contexts/ContentContext' import WritingAssistant from './WritingAssistant/WritingAssistant' import { ChatProvider, useChatContext } from '@/contexts/ChatContext' import { FileProvider, useFileContext } from '@/contexts/FileContext' import ModalProvider from '@/contexts/ModalContext' import CommonModals from './Common/CommonModals' -import useAppShortcuts from './shortcuts/use-shortcut' +import useAppShortcuts from '../lib/shortcuts/use-shortcut' +import WindowControls from './ui/window-controls' -const MainPageContent: React.FC = () => { - const [showSimilarFiles, setShowSimilarFiles] = useState(false) - const [isNewDirectoryModalOpen, setIsNewDirectoryModalOpen] = useState(false) +// Moved MainContent outside as a separate component +const MainContent: React.FC = () => { const { currentlyOpenFilePath } = useFileContext() + const { setShowChatbot } = useChatContext() + const { showEditor, setShowEditor } = useContentContext() + + return ( +
+ {currentlyOpenFilePath && showEditor && ( +
+ setShowEditor(false)} + onMaximize={() => { + setShowChatbot(false) + }} + /> + +
+ )} + +
+ ) +} - const { showChatbot } = useChatContext() +const MainPageContent: React.FC = () => { + const { currentlyOpenFilePath } = useFileContext() + const { showChatbot, setShowChatbot } = useChatContext() + const { setShowEditor, showEditor } = useContentContext() const { getShortcutDescription } = useAppShortcuts() - const openNewDirectoryModal = useCallback(() => { - setIsNewDirectoryModalOpen(true) - }, []) - useEffect(() => { - window.addEventListener('open-new-directory-modal', openNewDirectoryModal) - return () => { - window.removeEventListener('open-new-directory-modal', openNewDirectoryModal) - } - }, [openNewDirectoryModal]) + const panelGroupKey = `${showChatbot}-${showEditor}-${!!currentlyOpenFilePath}` return ( -
- { - setShowSimilarFiles(!showSimilarFiles) - }} - /> -
+
+ +
- +
- -
- -
-
- - {!showChatbot && currentlyOpenFilePath ? ( -
-
- + + +
+
- - {showSimilarFiles && ( -
- -
- )} -
- ) : ( - !showChatbot && ( -
- + + + + + +
+ {currentlyOpenFilePath || showChatbot ? ( + + {currentlyOpenFilePath && showEditor && ( + <> + + + + + + )} + {showChatbot && ( + +
+ setShowChatbot(false)} onMaximize={() => setShowEditor(false)} /> + +
+
+ )} +
+ ) : ( + + )}
- ) - )} +
+ - {showChatbot && ( -
- -
- )}
diff --git a/src/components/Settings/LLMSettings/InitialSetupLLMSettings.tsx b/src/components/Settings/LLMSettings/InitialSetupLLMSettings.tsx index 7c28a917..80f2eb27 100644 --- a/src/components/Settings/LLMSettings/InitialSetupLLMSettings.tsx +++ b/src/components/Settings/LLMSettings/InitialSetupLLMSettings.tsx @@ -1,7 +1,7 @@ import React from 'react' import { CheckCircleIcon, CogIcon } from '@heroicons/react/24/solid' import { Button } from '@material-tailwind/react' -import useLLMConfigs from './hooks/use-llm-configs' +import useLLMConfigs from '../../../lib/hooks/use-llm-configs' import LLMSettingsContent from './LLMSettingsContent' import { Dialog, DialogContent, DialogTrigger } from '@/components/ui/dialog' diff --git a/src/components/Settings/LLMSettings/LLMSettingsContent.tsx b/src/components/Settings/LLMSettings/LLMSettingsContent.tsx index 9bfee766..63f1750c 100644 --- a/src/components/Settings/LLMSettings/LLMSettingsContent.tsx +++ b/src/components/Settings/LLMSettings/LLMSettingsContent.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react' import DefaultLLMSelector from './DefaultLLMSelector' -import useLLMConfigs from './hooks/use-llm-configs' +import useLLMConfigs from '../../../lib/hooks/use-llm-configs' import SettingsRow from '../Shared/SettingsRow' import { Button } from '@/components/ui/button' import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu' diff --git a/src/components/Sidebars/IconsSidebar.tsx b/src/components/Sidebars/IconsSidebar.tsx index c3caf50b..ca025507 100644 --- a/src/components/Sidebars/IconsSidebar.tsx +++ b/src/components/Sidebars/IconsSidebar.tsx @@ -11,23 +11,16 @@ import { HiOutlinePencilAlt } from 'react-icons/hi' import { useModalOpeners } from '../../contexts/ModalContext' import { useChatContext } from '@/contexts/ChatContext' import { useContentContext } from '@/contexts/ContentContext' -import NewDirectoryComponent from '../File/NewDirectory' export interface IconsSidebarProps { getShortcutDescription: (action: string) => string - isNewDirectoryModalOpen: boolean - setIsNewDirectoryModalOpen: React.Dispatch> } -const IconsSidebar: React.FC = ({ - getShortcutDescription, - isNewDirectoryModalOpen, - setIsNewDirectoryModalOpen, -}) => { +const IconsSidebar: React.FC = ({ getShortcutDescription }) => { const { sidebarShowing, setSidebarShowing } = useChatContext() const [sidebarWidth, setSidebarWidth] = useState(40) - const { isSettingsModalOpen, setIsSettingsModalOpen } = useModalOpeners() + const { isSettingsModalOpen, setIsSettingsModalOpen, setIsNewDirectoryModalOpen } = useModalOpeners() const { createUntitledNote } = useContentContext() useEffect(() => { @@ -136,7 +129,6 @@ const IconsSidebar: React.FC = ({ title={getShortcutDescription('open-settings-modal') || 'Settings'} /> - setIsNewDirectoryModalOpen(false)} />
) } diff --git a/src/components/TitleBar/TitleBar.tsx b/src/components/TitleBar/TitleBar.tsx index 45ba2627..e07229bd 100644 --- a/src/components/TitleBar/TitleBar.tsx +++ b/src/components/TitleBar/TitleBar.tsx @@ -1,16 +1,13 @@ import React, { useEffect, useState } from 'react' -import { PiSidebar, PiSidebarFill } from 'react-icons/pi' +import { PiSidebarFill } from 'react-icons/pi' import FileHistoryNavigator from './NavigationButtons' import ExternalLink from '../Common/ExternalLink' +import { useChatContext } from '@/contexts/ChatContext' export const titleBarHeight = '30px' -interface TitleBarProps { - similarFilesOpen: boolean - toggleSimilarFiles: () => void -} - -const TitleBar: React.FC = ({ similarFilesOpen, toggleSimilarFiles }) => { +const TitleBar: React.FC = () => { + const { setShowChatbot } = useChatContext() const [platform, setPlatform] = useState('') useEffect(() => { @@ -35,22 +32,12 @@ const TitleBar: React.FC = ({ similarFilesOpen, toggleSimilarFile Feedback - {similarFilesOpen ? ( - - ) : ( - - )} + setShowChatbot((show) => !show)} + />
) diff --git a/src/components/WritingAssistant/WritingAssistant.tsx b/src/components/WritingAssistant/WritingAssistant.tsx index fe9a5672..a9eb4778 100644 --- a/src/components/WritingAssistant/WritingAssistant.tsx +++ b/src/components/WritingAssistant/WritingAssistant.tsx @@ -4,7 +4,7 @@ import TextField from '@mui/material/TextField' import Button from '@mui/material/Button' import posthog from 'posthog-js' import { streamText } from 'ai' -import useOutsideClick from './hooks/use-outside-click' +import useOutsideClick from '../../lib/hooks/use-outside-click' import { generatePromptString, getLastMessage } from './utils' import { ReorChatMessage } from '../../lib/llm/types' import { useFileContext } from '@/contexts/FileContext' diff --git a/src/components/ui/resizable.tsx b/src/components/ui/resizable.tsx new file mode 100644 index 00000000..5963fe1f --- /dev/null +++ b/src/components/ui/resizable.tsx @@ -0,0 +1,38 @@ +/* eslint-disable react/jsx-props-no-spreading */ +/* eslint-disable react/react-in-jsx-scope */ +import { DragHandleDots2Icon } from '@radix-ui/react-icons' +import * as ResizablePrimitive from 'react-resizable-panels' +import { cn } from '@/lib/ui' + +const ResizablePanelGroup = ({ className, ...props }: React.ComponentProps) => ( + +) + +const ResizablePanel = ResizablePrimitive.Panel + +const ResizableHandle = ({ + withHandle, + className, + ...props +}: React.ComponentProps & { + withHandle?: boolean +}) => ( + div]:rotate-90', + className, + )} + {...props} + > + {withHandle && ( +
+ +
+ )} +
+) + +export { ResizablePanelGroup, ResizablePanel, ResizableHandle } diff --git a/src/components/ui/window-controls.tsx b/src/components/ui/window-controls.tsx new file mode 100644 index 00000000..2db3a8ac --- /dev/null +++ b/src/components/ui/window-controls.tsx @@ -0,0 +1,33 @@ +/* eslint-disable jsx-a11y/control-has-associated-label */ +import React from 'react' +import { X, Maximize2 } from 'lucide-react' + +interface WindowControlsProps { + onClose: () => void + onMaximize: () => void +} + +const WindowControls = ({ onClose, onMaximize }: WindowControlsProps) => ( +
+
+ + +
+
+) + +export default WindowControls diff --git a/src/contexts/ChatContext.tsx b/src/contexts/ChatContext.tsx index 9ee0db36..e303377f 100644 --- a/src/contexts/ChatContext.tsx +++ b/src/contexts/ChatContext.tsx @@ -8,7 +8,7 @@ interface ChatContextType { sidebarShowing: SidebarAbleToShow setSidebarShowing: (option: SidebarAbleToShow) => void showChatbot: boolean - setShowChatbot: (show: boolean) => void + setShowChatbot: React.Dispatch> allChatsMetadata: ChatMetadata[] deleteChat: (chatID: string | undefined) => Promise saveChat: (updatedChat: Chat) => Promise diff --git a/src/contexts/ContentContext.tsx b/src/contexts/ContentContext.tsx index e3e8d475..6f9ceb01 100644 --- a/src/contexts/ContentContext.tsx +++ b/src/contexts/ContentContext.tsx @@ -6,6 +6,8 @@ import { useFileContext } from './FileContext' import { getFilesInDirectory, getNextAvailableFileNameGivenBaseName } from '@/lib/file' interface ContentContextType { + showEditor: boolean + setShowEditor: (showEditor: boolean) => void openContent: (pathOrChatID: string, optionalContentToWriteOnCreate?: string, dontUpdateChatHistory?: boolean) => void currentOpenFileOrChatID: string | null createUntitledNote: (parentFileOrDirectory?: string) => void @@ -26,6 +28,7 @@ interface ContentProviderProps { } export const ContentProvider: React.FC = ({ children }) => { + const [showEditor, setShowEditor] = useState(false) const [currentOpenFileOrChatID, setCurrentOpenFileOrChatID] = useState(null) const { allChatsMetadata, setShowChatbot, openNewChat } = useChatContext() @@ -44,7 +47,7 @@ export const ContentProvider: React.FC = ({ children }) => setShowChatbot(true) openNewChat(pathOrChatID) } else { - setShowChatbot(false) + setShowEditor(true) openOrCreateFile(pathOrChatID, optionalContentToWriteOnCreate) } setCurrentOpenFileOrChatID(pathOrChatID) @@ -74,11 +77,13 @@ export const ContentProvider: React.FC = ({ children }) => const ContentContextMemo = useMemo( () => ({ + showEditor, + setShowEditor, openContent, currentOpenFileOrChatID, createUntitledNote, }), - [openContent, currentOpenFileOrChatID, createUntitledNote], + [showEditor, openContent, currentOpenFileOrChatID, createUntitledNote], ) return {children} diff --git a/src/contexts/FileContext.tsx b/src/contexts/FileContext.tsx index 2a42c040..3480dc77 100644 --- a/src/contexts/FileContext.tsx +++ b/src/contexts/FileContext.tsx @@ -28,14 +28,13 @@ import { getNextAvailableFileNameGivenBaseName, sortFilesAndDirectories, } from '@/lib/file' -import { BacklinkExtension } from '@/components/Editor/BacklinkExtension' import { SuggestionsState } from '@/components/Editor/BacklinkSuggestionsDisplay' import HighlightExtension, { HighlightData } from '@/components/Editor/HighlightExtension' import { RichTextLink } from '@/components/Editor/RichTextLink' import '@/styles/tiptap.scss' import SearchAndReplace from '@/components/Editor/Search/SearchAndReplaceExtension' import getMarkdown from '@/components/Editor/utils' -import useOrderedSet from './hooks/use-ordered-set' +import useOrderedSet from '../lib/hooks/use-ordered-set' import welcomeNote from '@/lib/welcome-note' type FileContextType = { @@ -194,7 +193,6 @@ export const FileProvider: React.FC<{ children: ReactNode }> = ({ children }) => linkOnPaste: true, openOnClick: true, }), - BacklinkExtension(setSuggestionsState), CharacterCount, ], }) diff --git a/src/contexts/ModalContext.tsx b/src/contexts/ModalContext.tsx index bebdcb9a..8cd5ac61 100644 --- a/src/contexts/ModalContext.tsx +++ b/src/contexts/ModalContext.tsx @@ -5,6 +5,8 @@ interface ModalProviderProps { } interface ModalOpenContextType { + isNewDirectoryModalOpen: boolean + setIsNewDirectoryModalOpen: (newDirectoryOpen: boolean) => void isSettingsModalOpen: boolean setIsSettingsModalOpen: (settingsOpen: boolean) => void } @@ -20,14 +22,17 @@ export const useModalOpeners = (): ModalOpenContextType => { } export const ModalProvider: React.FC = ({ children }) => { + const [isNewDirectoryModalOpen, setIsNewDirectoryModalOpen] = useState(false) const [isSettingsModalOpen, setIsSettingsModalOpen] = useState(false) const modalOpenContextValue = useMemo( () => ({ + isNewDirectoryModalOpen, + setIsNewDirectoryModalOpen, isSettingsModalOpen, setIsSettingsModalOpen, }), - [isSettingsModalOpen], + [isNewDirectoryModalOpen, isSettingsModalOpen], ) return {children} diff --git a/src/components/Settings/LLMSettings/hooks/use-llm-configs.ts b/src/lib/hooks/use-llm-configs.ts similarity index 100% rename from src/components/Settings/LLMSettings/hooks/use-llm-configs.ts rename to src/lib/hooks/use-llm-configs.ts diff --git a/src/contexts/hooks/use-ordered-set.tsx b/src/lib/hooks/use-ordered-set.tsx similarity index 100% rename from src/contexts/hooks/use-ordered-set.tsx rename to src/lib/hooks/use-ordered-set.tsx diff --git a/src/components/WritingAssistant/hooks/use-outside-click.ts b/src/lib/hooks/use-outside-click.ts similarity index 100% rename from src/components/WritingAssistant/hooks/use-outside-click.ts rename to src/lib/hooks/use-outside-click.ts diff --git a/src/lib/hooks/use-resize-observer.tsx b/src/lib/hooks/use-resize-observer.tsx new file mode 100644 index 00000000..2c36fec6 --- /dev/null +++ b/src/lib/hooks/use-resize-observer.tsx @@ -0,0 +1,20 @@ +import { useEffect, RefObject } from 'react' + +const useResizeObserver = (ref: RefObject, callback: (entry: ResizeObserverEntry) => void) => { + // eslint-disable-next-line consistent-return + useEffect(() => { + if (ref.current) { + const observer = new ResizeObserver((entries) => { + callback(entries[0]) + }) + + observer.observe(ref.current) + + return () => { + observer.disconnect() + } + } + }, [ref, callback]) +} + +export default useResizeObserver diff --git a/src/components/shortcuts/shortcutDefinitions.ts b/src/lib/shortcuts/shortcutDefinitions.ts similarity index 100% rename from src/components/shortcuts/shortcutDefinitions.ts rename to src/lib/shortcuts/shortcutDefinitions.ts diff --git a/src/components/shortcuts/use-shortcut.ts b/src/lib/shortcuts/use-shortcut.ts similarity index 89% rename from src/components/shortcuts/use-shortcut.ts rename to src/lib/shortcuts/use-shortcut.ts index 9b9f3ceb..5b672fb0 100644 --- a/src/components/shortcuts/use-shortcut.ts +++ b/src/lib/shortcuts/use-shortcut.ts @@ -3,10 +3,12 @@ import { useDebouncedCallback } from 'use-debounce' import { useChatContext } from '../../contexts/ChatContext' import { useContentContext } from '@/contexts/ContentContext' import { shortcuts } from './shortcutDefinitions' +import { useModalOpeners } from '@/contexts/ModalContext' function useAppShortcuts() { const { setSidebarShowing, openNewChat } = useChatContext() const { createUntitledNote } = useContentContext() + const { setIsNewDirectoryModalOpen } = useModalOpeners() const handleShortcut = useCallback( (action: string) => { @@ -15,7 +17,7 @@ function useAppShortcuts() { createUntitledNote() break case 'open-new-directory-modal': - window.dispatchEvent(new CustomEvent('open-new-directory-modal')) + setIsNewDirectoryModalOpen(true) break case 'open-search': setSidebarShowing('search') @@ -31,7 +33,7 @@ function useAppShortcuts() { break } }, - [createUntitledNote, setSidebarShowing, openNewChat], + [createUntitledNote, setSidebarShowing, openNewChat, setIsNewDirectoryModalOpen], ) const handleShortcutRef = useRef(handleShortcut)