diff --git a/app/frontend/src/components/NoteEditor.tsx b/app/frontend/src/components/NoteEditor.tsx index 613002a..df8716c 100644 --- a/app/frontend/src/components/NoteEditor.tsx +++ b/app/frontend/src/components/NoteEditor.tsx @@ -11,7 +11,7 @@ import { useAuth } from '../auth'; import { maxUploadBytes, preparedUpload } from '../files'; import type { Item } from '../spaces'; import { colorFor, useLiveDocument, useLiveStatus, type Connection } from './live-document'; -import { internalPath, mentionExtension, StoredImageExtension } from './note-extensions'; +import { blockMenuExtension, internalPath, mentionExtension, StoredImageExtension } from './note-extensions'; function Toolbar({ editor }: { editor: Editor }) { const state = useEditorState({ @@ -68,6 +68,7 @@ function CollaborativeEditor({ connection, editable, noteId, spaceId }: { connec Collaboration.configure({ document: connection.document }), CollaborationCaret.configure({ provider: connection.provider, user: { name: user.displayName, color: colorFor(user.id) } }), mentionExtension(auth.request, noteId), + blockMenuExtension(insertImages), StoredImageExtension, ], editorProps: { diff --git a/app/frontend/src/components/note-extensions.tsx b/app/frontend/src/components/note-extensions.tsx index 6b3fa4c..4a39110 100644 --- a/app/frontend/src/components/note-extensions.tsx +++ b/app/frontend/src/components/note-extensions.tsx @@ -1,19 +1,28 @@ import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'; +import { Extension, type ChainedCommands, type Editor } from '@tiptap/core'; import Image from '@tiptap/extension-image'; import Mention from '@tiptap/extension-mention'; import { NodeViewWrapper, ReactNodeViewRenderer, ReactRenderer, type NodeViewProps } from '@tiptap/react'; -import type { SuggestionKeyDownProps, SuggestionProps } from '@tiptap/suggestion'; +import Suggestion, { type SuggestionKeyDownProps, type SuggestionOptions, type SuggestionProps } from '@tiptap/suggestion'; import { readBlob } from '../api'; import { useAuth } from '../auth'; import { itemHref } from '../spaces'; -export interface MentionCandidate { +interface Candidate { id: string; label: string; - href: string; hint: string; } +export interface MentionCandidate extends Candidate { + href: string; +} + +interface BlockChoice extends Candidate { + words: string; + run: (chain: ChainedCommands, editor: Editor) => void; +} + interface SearchReply { items: { id: string; spaceId: string; spaceName: string; kind: string; title: string }[]; } @@ -24,7 +33,7 @@ interface ListHandle { onKeyDown: (props: SuggestionKeyDownProps) => boolean; } -const MentionList = forwardRef>(function MentionList({ command, items }, ref) { +const SuggestionList = forwardRef>(function SuggestionList({ command, items }, ref) { const [active, setActive] = useState(0); useEffect(() => setActive(0), [items]); useImperativeHandle(ref, () => ({ @@ -32,22 +41,22 @@ const MentionList = forwardRef>(fu if (items.length === 0) return false; if (event.key === 'ArrowDown') setActive((current) => (current + 1) % items.length); else if (event.key === 'ArrowUp') setActive((current) => (current - 1 + items.length) % items.length); - else if (event.key === 'Enter') command({ id: items[active]!.id, label: items[active]!.label, href: items[active]!.href } as never); + else if (event.key === 'Enter') command(items[active]!); else return false; return true; }, })); return ( -
+
{items.length === 0 ? (

Nothing matches

) : ( items.map((item, index) => (