From c377cfd3f32c0ccc112b256a7b5aaa2998f2d5a9 Mon Sep 17 00:00:00 2001 From: Horacio Herrera Date: Wed, 20 Sep 2023 21:04:14 +0200 Subject: [PATCH] frontend: add SlashMenu back (#1421) * add default slash menu * frontend: prevent scroll when slash menu opens --- .../models/{documents.ts => documents.tsx} | 69 +++++++++++++++++-- .../packages/app/src/pages/publication.tsx | 29 -------- frontend/packages/editor/src/block-utils.ts | 34 +++++++++ .../components/SlashMenuPositioner.tsx | 8 ++- frontend/packages/editor/src/editor.tsx | 3 +- frontend/packages/editor/src/embed-block.tsx | 31 +-------- frontend/packages/editor/src/image.tsx | 7 +- frontend/packages/ui/src/layout.tsx | 7 +- 8 files changed, 115 insertions(+), 73 deletions(-) rename frontend/packages/app/src/models/{documents.ts => documents.tsx} (94%) create mode 100644 frontend/packages/editor/src/block-utils.ts diff --git a/frontend/packages/app/src/models/documents.ts b/frontend/packages/app/src/models/documents.tsx similarity index 94% rename from frontend/packages/app/src/models/documents.ts rename to frontend/packages/app/src/models/documents.tsx index b741f7b91d..de6c4e2ca9 100644 --- a/frontend/packages/app/src/models/documents.ts +++ b/frontend/packages/app/src/models/documents.tsx @@ -8,6 +8,8 @@ import {editorBlockToServerBlock} from '@mintter/app/src/client/editor-to-server import {serverChildrenToEditorChildren} from '@mintter/app/src/client/server-to-editor' import {useOpenUrl} from '@mintter/app/src/open-url' import {toast} from '@mintter/app/src/toast' +import {insertOrUpdateBlock} from '@mintter/editor' +import {RiFile2Fill, RiImage2Fill, RiText, RiVideoAddFill} from 'react-icons/ri' import { Block, BlockIdentifier, @@ -786,18 +788,71 @@ export function useDraftEditor( queryClient, openUrl, }, + onEditorReady: (e) => { readyThings.current[0] = e handleMaybeReady() }, blockSchema: hmBlockSchema, - // slashCommands: [ - // ...defaultReactSlashMenuItems.slice(0, 2), - // insertImage, - // insertFile, - // insertVideo, - // ...defaultReactSlashMenuItems.slice(2), - // ], + slashMenuItems: [ + { + name: 'Paragraph', + aliases: ['p'], + icon: , + execute: (editor) => + insertOrUpdateBlock(editor, { + type: 'paragraph', + } as PartialBlock), + }, + { + name: 'Heading', + aliases: ['h', 'heading1', 'subheading'], + execute: (editor) => + insertOrUpdateBlock(editor, { + type: 'heading', + props: {level: '2'}, + } as PartialBlock), + }, + { + name: 'Image', + aliases: ['image', 'img', 'picture'], + icon: , + hint: 'Insert a Image', + execute: (editor) => + insertOrUpdateBlock(editor, { + type: 'image', + props: { + url: '', + }, + } as PartialBlock), + }, + { + name: 'Video', + aliases: ['video', 'vid', 'media'], + icon: , + hint: 'Insert a video', + execute: (editor) => + insertOrUpdateBlock(editor, { + type: 'video', + props: { + url: '', + }, + } as PartialBlock), + }, + { + name: 'File', + aliases: ['file', 'folder'], + icon: , + hint: 'Insert a File', + execute: (editor) => + insertOrUpdateBlock(editor, { + type: 'file', + props: { + url: '', + }, + } as PartialBlock), + }, + ], _tiptapOptions: { extensions: [ diff --git a/frontend/packages/app/src/pages/publication.tsx b/frontend/packages/app/src/pages/publication.tsx index 409dbb4fde..296afe1d08 100644 --- a/frontend/packages/app/src/pages/publication.tsx +++ b/frontend/packages/app/src/pages/publication.tsx @@ -136,7 +136,6 @@ export function PublicationPageEditor() { const docId = route?.documentId const versionId = route?.versionId - const blockId = route?.blockId const accessory = route?.accessory const accessoryKey = accessory?.key const replace = useNavigate('replace') @@ -146,11 +145,6 @@ export function PublicationPageEditor() { ) const publication = usePublicationEditor(docId, versionId, route.pubContext) - // this checks if there's a block in the url, so we can highlight and scroll into the selected block - let [focusBlock] = useState(() => blockId) - - // useScrollToBlock(editor, scrollWrapperRef, focusBlock) - const {data: changes} = useDocChanges( publication.status == 'success' ? docId : undefined, ) @@ -287,29 +281,6 @@ type ResizablePanelMachineServices = { } } -// eslint-disable-next-line -// function useScrollToBlock(editor: SlateEditor, ref: any, blockId?: string) { -// // TODO: find a way to scroll to the block when clicking on a mintter link -// useEffect(() => { -// setTimeout(() => { -// if (blockId) { -// if (ref?.current) { -// let entry = getEditorBlock(editor, {id: blockId}) - -// if (entry) { -// let [block] = entry -// let elm = ReactEditor.toDOMNode(editor, block) - -// let rect = elm.getBoundingClientRect() -// let wrapper = ref.current.getBoundingClientRect() -// ref.current.scrollTo({top: rect.top - wrapper.top - 24}) -// } -// } -// } -// }, 1000) -// }, [ref, blockId, editor]) -// } - function OutOfDateBanner({docId, version}: {docId: string; version: string}) { const route = useNavRoute() const context = route.key === 'publication' ? route.pubContext : undefined diff --git a/frontend/packages/editor/src/block-utils.ts b/frontend/packages/editor/src/block-utils.ts new file mode 100644 index 0000000000..ca3f04e8c6 --- /dev/null +++ b/frontend/packages/editor/src/block-utils.ts @@ -0,0 +1,34 @@ +import {Block as BlockNoteBlock, BlockNoteEditor} from './blocknote' +import {HMBlockSchema} from './schema' +import {useEffect, useState} from 'react' +import {getBlockInfoFromPos} from './blocknote' + +export function useSelected( + block: BlockNoteBlock, + editor: BlockNoteEditor, +) { + const [selected, setSelected] = useState(false) + const tiptapEditor = editor._tiptapEditor + const selection = tiptapEditor.state.selection + + useEffect(() => { + if (editor) { + const selectedNode = getBlockInfoFromPos( + tiptapEditor.state.doc, + tiptapEditor.state.selection.from, + ) + if (selectedNode && selectedNode.id) { + if ( + selectedNode.id === block.id && + selectedNode.startPos === selection.$anchor.pos + ) { + setSelected(true) + } else if (selectedNode.id !== block.id) { + setSelected(false) + } + } + } + }, [selection]) + + return selected +} diff --git a/frontend/packages/editor/src/blocknote/react/SlashMenu/components/SlashMenuPositioner.tsx b/frontend/packages/editor/src/blocknote/react/SlashMenu/components/SlashMenuPositioner.tsx index 707885ad14..0b029a23f4 100644 --- a/frontend/packages/editor/src/blocknote/react/SlashMenu/components/SlashMenuPositioner.tsx +++ b/frontend/packages/editor/src/blocknote/react/SlashMenu/components/SlashMenuPositioner.tsx @@ -29,8 +29,14 @@ export const SlashMenuPositioner = < useState[]>() const [keyboardHoveredItemIndex, setKeyboardHoveredItemIndex] = useState() + const scroller = useRef(null) const referencePos = useRef() + useEffect(() => { + setTimeout(() => { + scroller.current = document.getElementById('scroll-page-wrapper') + }, 100) + }, []) useEffect(() => { return props.editor.slashMenu.onUpdate((slashMenuState) => { @@ -76,7 +82,7 @@ export const SlashMenuPositioner = < return ( - {/* */} + ) diff --git a/frontend/packages/editor/src/embed-block.tsx b/frontend/packages/editor/src/embed-block.tsx index 76ecf23127..1fd4bdd2e1 100644 --- a/frontend/packages/editor/src/embed-block.tsx +++ b/frontend/packages/editor/src/embed-block.tsx @@ -32,6 +32,7 @@ import { import {getBlockInfoFromPos} from './blocknote/core' import {createReactBlockSpec} from './blocknote/react' import {HMBlockSchema, hmBlockSchema} from './schema' +import {useSelected} from './block-utils' const EditorText = styled(Text, { fontSize: '$5', @@ -238,36 +239,6 @@ function StaticEmbedPresentation({block}: {block: EmbedBlockType}) { ) } -function useSelected( - block: BlockNoteBlock, - editor: BlockNoteEditor, -) { - const [selected, setSelected] = useState(false) - const tiptapEditor = editor._tiptapEditor - const selection = tiptapEditor.state.selection - - useEffect(() => { - if (editor) { - const selectedNode = getBlockInfoFromPos( - tiptapEditor.state.doc, - tiptapEditor.state.selection.from, - ) - if (selectedNode && selectedNode.id) { - if ( - selectedNode.id === block.id && - selectedNode.startPos === selection.$anchor.pos - ) { - setSelected(true) - } else if (selectedNode.id !== block.id) { - setSelected(false) - } - } - } - }, [selection]) - - return selected -} - export function StaticBlockNode({block}: {block: BlockNode}) { const children = block.children.length > 0 ? ( diff --git a/frontend/packages/editor/src/image.tsx b/frontend/packages/editor/src/image.tsx index 85a91c0421..c08be95563 100644 --- a/frontend/packages/editor/src/image.tsx +++ b/frontend/packages/editor/src/image.tsx @@ -1,4 +1,5 @@ import {useAppContext} from '@mintter/app/src/app-context' +import {toast} from '@mintter/app/src/toast' import {BACKEND_FILE_UPLOAD_URL, BACKEND_FILE_URL} from '@mintter/shared' import { Button, @@ -13,18 +14,16 @@ import { useTheme, } from '@mintter/ui' import {ChangeEvent, useEffect, useState} from 'react' -import {RiImage2Fill, RiImage2Line} from 'react-icons/ri' +import {RiImage2Line} from 'react-icons/ri' import { Block, BlockNoteEditor, - DefaultBlockSchema, createReactBlockSpec, defaultProps, getBlockInfoFromPos, - insertOrUpdateBlock, } from './blocknote' -import {HMBlockSchema} from './schema' import {InlineContent} from './blocknote/react' +import {HMBlockSchema} from './schema' export const ImageBlock = createReactBlockSpec({ type: 'image', diff --git a/frontend/packages/ui/src/layout.tsx b/frontend/packages/ui/src/layout.tsx index 88ba420574..6fa7d70042 100644 --- a/frontend/packages/ui/src/layout.tsx +++ b/frontend/packages/ui/src/layout.tsx @@ -19,7 +19,12 @@ export const MainWrapper = ({ noScroll?: boolean }) => ( - {noScroll ? children : {children}} + {noScroll ? ( + children + ) : ( + // TODO: we cannot remove this ID here because the SlashMenu is referencing this! + {children} + )} )