diff --git a/packages/web/src/javascripts/Components/ApplicationView/ApplicationView.tsx b/packages/web/src/javascripts/Components/ApplicationView/ApplicationView.tsx index a28f6625166..ccad56b07c1 100644 --- a/packages/web/src/javascripts/Components/ApplicationView/ApplicationView.tsx +++ b/packages/web/src/javascripts/Components/ApplicationView/ApplicationView.tsx @@ -29,6 +29,7 @@ import AndroidBackHandlerProvider from '@/NativeMobileWeb/useAndroidBackHandler' import ConfirmDeleteAccountContainer from '@/Components/ConfirmDeleteAccountModal/ConfirmDeleteAccountModal' import DarkModeHandler from '../DarkModeHandler/DarkModeHandler' import ApplicationProvider from './ApplicationProvider' +import { ErrorBoundary } from '@/Utils/ErrorBoundary' type Props = { application: WebApplication @@ -219,7 +220,9 @@ const ApplicationView: FunctionComponent = ({ application, mainApplicatio searchOptionsController={viewControllerManager.searchOptionsController} linkingController={viewControllerManager.linkingController} /> - + + + diff --git a/packages/web/src/javascripts/Components/BlockEditor/BlockEditorComponent.tsx b/packages/web/src/javascripts/Components/BlockEditor/BlockEditorComponent.tsx index 367f6d03766..1aa2a1af648 100644 --- a/packages/web/src/javascripts/Components/BlockEditor/BlockEditorComponent.tsx +++ b/packages/web/src/javascripts/Components/BlockEditor/BlockEditorComponent.tsx @@ -9,10 +9,12 @@ import FilePlugin from './Plugins/EncryptedFilePlugin/FilePlugin' import BlockPickerMenuPlugin from './Plugins/BlockPickerPlugin/BlockPickerPlugin' import { ErrorBoundary } from '@/Utils/ErrorBoundary' import { LinkingController } from '@/Controllers/LinkingController' -import LinkingControllerProvider from './Contexts/LinkingControllerProvider' +import LinkingControllerProvider from '../../Controllers/LinkingControllerProvider' import { BubbleNode } from './Plugins/ItemBubblePlugin/Nodes/BubbleNode' import ItemBubblePlugin from './Plugins/ItemBubblePlugin/ItemBubblePlugin' import { NodeObserverPlugin } from './Plugins/NodeObserverPlugin/NodeObserverPlugin' +import { FilesController } from '@/Controllers/FilesController' +import FilesControllerProvider from '@/Controllers/FilesControllerProvider' const StringEllipses = '...' const NotePreviewCharLimit = 160 @@ -21,9 +23,10 @@ type Props = { application: WebApplication note: SNNote linkingController: LinkingController + filesController: FilesController } -export const BlockEditor: FunctionComponent = ({ note, application, linkingController }) => { +export const BlockEditor: FunctionComponent = ({ note, application, linkingController, filesController }) => { const controller = useRef(new BlockEditorController(note, application)) const handleChange = useCallback( @@ -51,19 +54,21 @@ export const BlockEditor: FunctionComponent = ({ note, application, linki
- - - - - - - - - - + + + + + + + + + + + +
diff --git a/packages/web/src/javascripts/Components/BlockEditor/Plugins/EncryptedFilePlugin/FilePlugin.ts b/packages/web/src/javascripts/Components/BlockEditor/Plugins/EncryptedFilePlugin/FilePlugin.ts index 60ce6e77d71..01073b4edf8 100644 --- a/packages/web/src/javascripts/Components/BlockEditor/Plugins/EncryptedFilePlugin/FilePlugin.ts +++ b/packages/web/src/javascripts/Components/BlockEditor/Plugins/EncryptedFilePlugin/FilePlugin.ts @@ -6,9 +6,12 @@ import { FileNode } from './Nodes/FileNode' import { $createParagraphNode, $insertNodes, $isRootOrShadowRoot, COMMAND_PRIORITY_EDITOR } from 'lexical' import { $createFileNode } from './Nodes/FileUtils' import { $wrapNodeInElement } from '@lexical/utils' +import { useFilesController } from '@/Controllers/FilesControllerProvider' +import { FilesControllerEvent } from '@/Controllers/FilesController' export default function FilePlugin(): JSX.Element | null { const [editor] = useLexicalComposerContext() + const filesController = useFilesController() useEffect(() => { if (!editor.hasNodes([FileNode])) { @@ -19,7 +22,6 @@ export default function FilePlugin(): JSX.Element | null { INSERT_FILE_COMMAND, (payload) => { const fileNode = $createFileNode(payload) - // $insertNodeToNearestRoot(fileNode) $insertNodes([fileNode]) if ($isRootOrShadowRoot(fileNode.getParentOrThrow())) { $wrapNodeInElement(fileNode, $createParagraphNode).selectEnd() @@ -31,5 +33,16 @@ export default function FilePlugin(): JSX.Element | null { ) }, [editor]) + useEffect(() => { + const disposer = filesController.addEventObserver((event, data) => { + if (event === FilesControllerEvent.FileUploadedToNote) { + const fileUuid = data[FilesControllerEvent.FileUploadedToNote].uuid + editor.dispatchCommand(INSERT_FILE_COMMAND, fileUuid) + } + }) + + return disposer + }, [filesController, editor]) + return null } diff --git a/packages/web/src/javascripts/Components/BlockEditor/Plugins/ItemBubblePlugin/Nodes/BubbleComponent.tsx b/packages/web/src/javascripts/Components/BlockEditor/Plugins/ItemBubblePlugin/Nodes/BubbleComponent.tsx index e820044c6bf..66b43957a43 100644 --- a/packages/web/src/javascripts/Components/BlockEditor/Plugins/ItemBubblePlugin/Nodes/BubbleComponent.tsx +++ b/packages/web/src/javascripts/Components/BlockEditor/Plugins/ItemBubblePlugin/Nodes/BubbleComponent.tsx @@ -2,7 +2,7 @@ import { useCallback, useMemo } from 'react' import { useApplication } from '@/Components/ApplicationView/ApplicationProvider' import LinkedItemBubble from '@/Components/LinkedItems/LinkedItemBubble' import { createLinkFromItem } from '@/Utils/Items/Search/createLinkFromItem' -import { useLinkingController } from '@/Components/BlockEditor/Contexts/LinkingControllerProvider' +import { useLinkingController } from '@/Controllers/LinkingControllerProvider' import { LinkableItem } from '@/Utils/Items/Search/LinkableItem' import { useResponsiveAppPane } from '@/Components/ResponsivePane/ResponsivePaneProvider' import { LexicalNode } from 'lexical' diff --git a/packages/web/src/javascripts/Components/BlockEditor/Plugins/ItemSelectionPlugin/ItemSelectionPlugin.tsx b/packages/web/src/javascripts/Components/BlockEditor/Plugins/ItemSelectionPlugin/ItemSelectionPlugin.tsx index d40974a5671..ba20b06f0c0 100644 --- a/packages/web/src/javascripts/Components/BlockEditor/Plugins/ItemSelectionPlugin/ItemSelectionPlugin.tsx +++ b/packages/web/src/javascripts/Components/BlockEditor/Plugins/ItemSelectionPlugin/ItemSelectionPlugin.tsx @@ -9,7 +9,7 @@ import { ContentType, SNNote } from '@standardnotes/snjs' import { getLinkingSearchResults } from '@/Utils/Items/Search/getSearchResults' import Popover from '@/Components/Popover/Popover' import { INSERT_BUBBLE_COMMAND, INSERT_FILE_COMMAND } from '../Commands' -import { useLinkingController } from '../../Contexts/LinkingControllerProvider' +import { useLinkingController } from '../../../../Controllers/LinkingControllerProvider' import { PopoverClassNames } from '../ClassNames' type Props = { diff --git a/packages/web/src/javascripts/Components/NoteView/NoteView.tsx b/packages/web/src/javascripts/Components/NoteView/NoteView.tsx index d86c4c9cf6c..edbcf09ae2d 100644 --- a/packages/web/src/javascripts/Components/NoteView/NoteView.tsx +++ b/packages/web/src/javascripts/Components/NoteView/NoteView.tsx @@ -8,7 +8,6 @@ import ProtectedItemOverlay from '@/Components/ProtectedItemOverlay/ProtectedIte import { ElementIds } from '@/Constants/ElementIDs' import { PrefDefaults } from '@/Constants/PrefDefaults' import { StringDeleteNote, STRING_DELETE_LOCKED_ATTEMPT, STRING_DELETE_PLACEHOLDER_ATTEMPT } from '@/Constants/Strings' -import { featureTrunkEnabled, FeatureTrunkName } from '@/FeatureTrunk' import { log, LoggingDomain } from '@/Logging' import { debounce, isDesktopApplication, isMobileScreen } from '@/Utils' import { classNames } from '@/Utils/ConcatenateClassNames' @@ -996,7 +995,7 @@ class NoteView extends AbstractComponent { const renderHeaderOptions = isMobileScreen() ? !this.state.plaintextEditorFocused : true const editorMode = - featureTrunkEnabled(FeatureTrunkName.Blocks) && this.note.noteType === NoteType.Blocks + this.note.noteType === NoteType.Blocks ? 'blocks' : this.state.editorStateDidLoad && !this.state.editorComponentViewer && !this.state.textareaUnloading ? 'plain' @@ -1155,6 +1154,7 @@ class NoteView extends AbstractComponent { application={this.application} note={this.note} linkingController={this.viewControllerManager.linkingController} + filesController={this.viewControllerManager.filesController} /> )} diff --git a/packages/web/src/javascripts/Controllers/Abstract/AbstractViewController.ts b/packages/web/src/javascripts/Controllers/Abstract/AbstractViewController.ts index 9dcc477a54b..c9c37c19965 100644 --- a/packages/web/src/javascripts/Controllers/Abstract/AbstractViewController.ts +++ b/packages/web/src/javascripts/Controllers/Abstract/AbstractViewController.ts @@ -1,11 +1,14 @@ import { CrossControllerEvent } from '../CrossControllerEvent' -import { InternalEventBus, InternalEventPublishStrategy } from '@standardnotes/snjs' +import { InternalEventBus, InternalEventPublishStrategy, removeFromArray } from '@standardnotes/snjs' import { WebApplication } from '../../Application/Application' import { Disposer } from '@/Types/Disposer' -export abstract class AbstractViewController { +type ControllerEventObserver = (event: Event, data: EventData) => void + +export abstract class AbstractViewController { dealloced = false protected disposers: Disposer[] = [] + private eventObservers: ControllerEventObserver[] = [] constructor(public application: WebApplication, protected eventBus: InternalEventBus) {} @@ -23,5 +26,19 @@ export abstract class AbstractViewController { } ;(this.disposers as unknown) = undefined + + this.eventObservers.length = 0 + } + + addEventObserver(observer: ControllerEventObserver): () => void { + this.eventObservers.push(observer) + + return () => { + removeFromArray(this.eventObservers, observer) + } + } + + notifyEvent(event: Event, data: EventData): void { + this.eventObservers.forEach((observer) => observer(event, data)) } } diff --git a/packages/web/src/javascripts/Controllers/FilesController.ts b/packages/web/src/javascripts/Controllers/FilesController.ts index 3643c06b428..53bf88f1614 100644 --- a/packages/web/src/javascripts/Controllers/FilesController.ts +++ b/packages/web/src/javascripts/Controllers/FilesController.ts @@ -34,7 +34,17 @@ const NonMutatingFileActions = [PopoverFileItemActionType.DownloadFile, PopoverF type FileContextMenuLocation = { x: number; y: number } -export class FilesController extends AbstractViewController { +export type FilesControllerEventData = { + [FilesControllerEvent.FileUploadedToNote]: { + uuid: string + } +} + +export enum FilesControllerEvent { + FileUploadedToNote, +} + +export class FilesController extends AbstractViewController { allFiles: FileItem[] = [] attachedFiles: FileItem[] = [] showFileContextMenu = false @@ -388,6 +398,10 @@ export class FilesController extends AbstractViewController { type: ToastType.Success, message: `Uploaded file "${uploadedFile.name}"`, }) + + this.notifyEvent(FilesControllerEvent.FileUploadedToNote, { + [FilesControllerEvent.FileUploadedToNote]: { uuid: uploadedFile.uuid }, + }) } return uploadedFiles diff --git a/packages/web/src/javascripts/Controllers/FilesControllerProvider.tsx b/packages/web/src/javascripts/Controllers/FilesControllerProvider.tsx new file mode 100644 index 00000000000..dcd77760f2c --- /dev/null +++ b/packages/web/src/javascripts/Controllers/FilesControllerProvider.tsx @@ -0,0 +1,35 @@ +import { ReactNode, createContext, useContext, memo } from 'react' +import { observer } from 'mobx-react-lite' +import { FilesController } from '@/Controllers/FilesController' + +const FilesControllerContext = createContext(undefined) + +export const useFilesController = () => { + const value = useContext(FilesControllerContext) + + if (!value) { + throw new Error('Component must be a child of ') + } + + return value +} + +type ChildrenProps = { + children: ReactNode +} + +type ProviderProps = { + controller: FilesController +} & ChildrenProps + +const MemoizedChildren = memo(({ children }: ChildrenProps) => <>{children}) + +const FilesControllerProvider = ({ controller, children }: ProviderProps) => { + return ( + + + + ) +} + +export default observer(FilesControllerProvider) diff --git a/packages/web/src/javascripts/Components/BlockEditor/Contexts/LinkingControllerProvider.tsx b/packages/web/src/javascripts/Controllers/LinkingControllerProvider.tsx similarity index 99% rename from packages/web/src/javascripts/Components/BlockEditor/Contexts/LinkingControllerProvider.tsx rename to packages/web/src/javascripts/Controllers/LinkingControllerProvider.tsx index 8ee1f42229c..f27a5247841 100644 --- a/packages/web/src/javascripts/Components/BlockEditor/Contexts/LinkingControllerProvider.tsx +++ b/packages/web/src/javascripts/Controllers/LinkingControllerProvider.tsx @@ -1,5 +1,4 @@ import { ReactNode, createContext, useContext, memo } from 'react' - import { observer } from 'mobx-react-lite' import { LinkingController } from '@/Controllers/LinkingController'