diff --git a/packages/sanity/playwright-ct/tests/comments/CommentInput.spec.tsx b/packages/sanity/playwright-ct/tests/comments/CommentInput.spec.tsx
index 199a1e4d899..1b34489d901 100644
--- a/packages/sanity/playwright-ct/tests/comments/CommentInput.spec.tsx
+++ b/packages/sanity/playwright-ct/tests/comments/CommentInput.spec.tsx
@@ -23,9 +23,27 @@ test.describe('Comments', () => {
test('Should bring up mentions menu when typing @', async ({mount, page}) => {
await mount()
const $editable = page.getByTestId('comment-input-editable')
- await expect($editable).toBeEditable()
- await page.keyboard.type(`@`)
+ await $editable.waitFor({state: 'visible'})
+ await page.keyboard.type('@')
await expect(page.getByTestId('comments-mentions-menu')).toBeVisible()
})
+
+ test('Should be able to submit', async ({mount, page}) => {
+ const {insertPortableText} = testHelpers({page})
+ let submitted = false
+ const onSubmit = () => {
+ submitted = true
+ }
+ await mount()
+ const $editable = page.getByTestId('comment-input-editable')
+ await expect($editable).toBeEditable()
+ // Test that blank comments can't be submitted
+ await page.keyboard.press('Enter')
+ expect(submitted).toBe(false)
+ await insertPortableText('This is a comment!', $editable)
+ await expect($editable).toHaveText('This is a comment!')
+ await page.keyboard.press('Enter')
+ expect(submitted).toBe(true)
+ })
})
})
diff --git a/packages/sanity/playwright-ct/tests/comments/CommentInputStory.tsx b/packages/sanity/playwright-ct/tests/comments/CommentInputStory.tsx
index 28f356b81d1..a7ff572789c 100644
--- a/packages/sanity/playwright-ct/tests/comments/CommentInputStory.tsx
+++ b/packages/sanity/playwright-ct/tests/comments/CommentInputStory.tsx
@@ -16,9 +16,18 @@ const currentUser: CurrentUser = {
const SCHEMA_TYPES: [] = []
-export function CommentsInputStory() {
- const [value, setValue] = useState(null)
-
+export function CommentsInputStory({
+ onDiscardCancel = noop,
+ onDiscardConfirm = noop,
+ onSubmit = noop,
+ value = null,
+}: {
+ onDiscardCancel?: () => void
+ onDiscardConfirm?: () => void
+ onSubmit?: () => void
+ value?: PortableTextBlock[] | null
+}) {
+ const [valueState, setValueState] = useState(value)
return (
)
diff --git a/packages/sanity/src/desk/comments/plugin/field/CommentFieldButton.tsx b/packages/sanity/src/desk/comments/plugin/field/CommentFieldButton.tsx
index 078d3e66cd6..74b078d546f 100644
--- a/packages/sanity/src/desk/comments/plugin/field/CommentFieldButton.tsx
+++ b/packages/sanity/src/desk/comments/plugin/field/CommentFieldButton.tsx
@@ -58,6 +58,7 @@ interface CommentFieldButtonProps {
onClick?: () => void
onCommentAdd: () => void
onDiscard: () => void
+ onInputKeyDown?: (event: React.KeyboardEvent) => void
open: boolean
setOpen: (open: boolean) => void
value: CommentMessage
@@ -74,11 +75,11 @@ export function CommentFieldButton(props: CommentFieldButtonProps) {
onClick,
onCommentAdd,
onDiscard,
+ onInputKeyDown,
open,
setOpen,
value,
} = props
- const [mentionMenuOpen, setMentionMenuOpen] = useState(false)
const [popoverElement, setPopoverElement] = useState(null)
const commentInputHandle = useRef(null)
const hasComments = Boolean(count > 0)
@@ -93,15 +94,31 @@ export function CommentFieldButton(props: CommentFieldButtonProps) {
const hasValue = useMemo(() => hasCommentMessageValue(value), [value])
const startDiscard = useCallback(() => {
- if (mentionMenuOpen) return
-
if (!hasValue) {
closePopover()
return
}
commentInputHandle.current?.discardDialogController.open()
- }, [closePopover, hasValue, mentionMenuOpen])
+ }, [closePopover, hasValue])
+
+ const handleInputKeyDown = useCallback(
+ (event: React.KeyboardEvent) => {
+ // Don't act if the input already prevented this event
+ if (event.isDefaultPrevented()) {
+ return
+ }
+ // Discard the input text
+ if (event.key === 'Escape') {
+ event.preventDefault()
+ event.stopPropagation()
+ startDiscard()
+ }
+ // Call parent handler
+ if (onInputKeyDown) onInputKeyDown(event)
+ },
+ [onInputKeyDown, startDiscard],
+ )
const handleDiscardCancel = useCallback(() => {
commentInputHandle.current?.discardDialogController.close()
@@ -132,8 +149,7 @@ export function CommentFieldButton(props: CommentFieldButtonProps) {
onChange={onChange}
onDiscardCancel={handleDiscardCancel}
onDiscardConfirm={handleDiscardConfirm}
- onEscapeKeyDown={startDiscard}
- onMentionMenuOpenChange={setMentionMenuOpen}
+ onKeyDown={handleInputKeyDown}
onSubmit={handleSubmit}
placeholder={placeholder}
readOnly={isRunningSetup}
diff --git a/packages/sanity/src/desk/comments/src/components/list/CommentsListItem.tsx b/packages/sanity/src/desk/comments/src/components/list/CommentsListItem.tsx
index e7ad15b2114..5a40d7f9416 100644
--- a/packages/sanity/src/desk/comments/src/components/list/CommentsListItem.tsx
+++ b/packages/sanity/src/desk/comments/src/components/list/CommentsListItem.tsx
@@ -77,6 +77,7 @@ interface CommentsListItemProps {
onCreateRetry: (id: string) => void
onDelete: (id: string) => void
onEdit: (id: string, payload: CommentEditPayload) => void
+ onKeyDown?: (event: React.KeyboardEvent) => void
onPathSelect?: (path: Path) => void
onReply: (payload: CommentCreatePayload) => void
onStatusChange?: (id: string, status: CommentStatus) => void
@@ -95,6 +96,7 @@ export const CommentsListItem = React.memo(function CommentsListItem(props: Comm
onCreateRetry,
onDelete,
onEdit,
+ onKeyDown,
onPathSelect,
onReply,
onStatusChange,
@@ -123,8 +125,6 @@ export const CommentsListItem = React.memo(function CommentsListItem(props: Comm
onReply?.(nextComment)
setValue(EMPTY_ARRAY)
-
- replyInputRef.current?.focus()
}, [
onReply,
parentComment._id,
@@ -143,13 +143,38 @@ export const CommentsListItem = React.memo(function CommentsListItem(props: Comm
replyInputRef.current?.discardDialogController.open()
}, [hasValue])
+ const handleInputKeyDown = useCallback(
+ (event: React.KeyboardEvent) => {
+ // Don't act if the input already prevented this event
+ if (event.isDefaultPrevented()) {
+ return
+ }
+ // Discard input text with Escape
+ if (event.key === 'Escape') {
+ event.preventDefault()
+ event.stopPropagation()
+ startDiscard()
+ }
+ // TODO: this would be cool
+ // Edit last comment if current user is the owner and pressing arrowUp
+ // if (event.key === 'ArrowUp') {
+ // const lastReply = replies.splice(-1)[0]
+ // if (lastReply?.authorId === currentUser.id && !hasValue) {
+ //
+ // }
+ // }
+ },
+ [startDiscard],
+ )
+
const cancelDiscard = useCallback(() => {
replyInputRef.current?.discardDialogController.close()
}, [])
const confirmDiscard = useCallback(() => {
- replyInputRef.current?.discardDialogController.close()
setValue(EMPTY_ARRAY)
+ replyInputRef.current?.discardDialogController.close()
+ replyInputRef.current?.focus()
}, [])
const handleThreadRootClick = useCallback(() => {
@@ -185,6 +210,40 @@ export const CommentsListItem = React.memo(function CommentsListItem(props: Comm
}
}, [replies])
+ const renderedReplies = useMemo(
+ () =>
+ splicedReplies.map((reply) => (
+
+
+
+ )),
+ [
+ currentUser,
+ handleInputKeyDown,
+ mentionOptions,
+ onCopyLink,
+ onCreateRetry,
+ onDelete,
+ onEdit,
+ readOnly,
+ splicedReplies,
+ ],
+ )
+
return (
@@ -236,25 +296,7 @@ export const CommentsListItem = React.memo(function CommentsListItem(props: Comm
)}
- {splicedReplies.map((reply) => (
-
-
-
- ))}
-
+ {renderedReplies}
{canReply && (
void
onDelete: (id: string) => void
onEdit: (id: string, message: CommentEditPayload) => void
+ onInputKeyDown?: (event: React.KeyboardEvent) => void
onStatusChange?: (id: string, status: CommentStatus) => void
readOnly?: boolean
}
@@ -137,6 +138,7 @@ export function CommentsListItemLayout(props: CommentsListItemLayoutProps) {
onCreateRetry,
onDelete,
onEdit,
+ onInputKeyDown,
onStatusChange,
readOnly,
} = props
@@ -146,7 +148,6 @@ export function CommentsListItemLayout(props: CommentsListItemLayoutProps) {
const [value, setValue] = useState(message)
const [isEditing, setIsEditing] = useState(false)
const [rootElement, setRootElement] = useState(null)
- const [mentionMenuOpen, setMentionMenuOpen] = useState(false)
const startMessage = useRef(message)
const [menuOpen, setMenuOpen] = useState(false)
@@ -184,10 +185,27 @@ export function CommentsListItemLayout(props: CommentsListItemLayoutProps) {
cancelEdit()
return
}
-
commentInputRef.current?.discardDialogController.open()
}, [cancelEdit, hasChanges, hasValue])
+ const handleInputKeyDown = useCallback(
+ (event: React.KeyboardEvent) => {
+ // Don't act if the input already prevented this event
+ if (event.isDefaultPrevented()) {
+ return
+ }
+ // Discard the input text
+ if (event.key === 'Escape') {
+ event.preventDefault()
+ event.stopPropagation()
+ startDiscard()
+ }
+ // Call parent handler
+ if (onInputKeyDown) onInputKeyDown(event)
+ },
+ [onInputKeyDown, startDiscard],
+ )
+
const cancelDiscard = useCallback(() => {
commentInputRef.current?.discardDialogController.close()
}, [])
@@ -215,7 +233,7 @@ export function CommentsListItemLayout(props: CommentsListItemLayoutProps) {
})
useGlobalKeyDown((event) => {
- if (event.key === 'Escape' && !mentionMenuOpen && !hasChanges) {
+ if (event.key === 'Escape' && !hasChanges) {
cancelEdit()
}
})
@@ -223,7 +241,6 @@ export function CommentsListItemLayout(props: CommentsListItemLayoutProps) {
useClickOutside(() => {
if (!hasChanges) {
cancelEdit()
- commentInputRef.current?.blur()
}
}, [rootElement])
@@ -294,8 +311,7 @@ export function CommentsListItemLayout(props: CommentsListItemLayoutProps) {
onChange={setValue}
onDiscardCancel={cancelDiscard}
onDiscardConfirm={confirmDiscard}
- onEscapeKeyDown={startDiscard}
- onMentionMenuOpenChange={setMentionMenuOpen}
+ onKeyDown={handleInputKeyDown}
onSubmit={handleEditSubmit}
readOnly={readOnly}
ref={commentInputRef}
diff --git a/packages/sanity/src/desk/comments/src/components/list/CreateNewThreadInput.tsx b/packages/sanity/src/desk/comments/src/components/list/CreateNewThreadInput.tsx
index be680801be7..44cb74133e0 100644
--- a/packages/sanity/src/desk/comments/src/components/list/CreateNewThreadInput.tsx
+++ b/packages/sanity/src/desk/comments/src/components/list/CreateNewThreadInput.tsx
@@ -10,8 +10,8 @@ interface CreateNewThreadInputProps {
fieldName: string
mentionOptions: MentionOptionsHookValue
onBlur?: CommentInputProps['onBlur']
- onEditDiscard?: () => void
onFocus?: CommentInputProps['onFocus']
+ onKeyDown?: (event: React.KeyboardEvent) => void
onNewThreadCreate: (payload: CommentMessage) => void
readOnly?: boolean
}
@@ -22,8 +22,8 @@ export function CreateNewThreadInput(props: CreateNewThreadInputProps) {
fieldName,
mentionOptions,
onBlur,
- onEditDiscard,
onFocus,
+ onKeyDown,
onNewThreadCreate,
readOnly,
} = props
@@ -34,31 +34,43 @@ export function CreateNewThreadInput(props: CreateNewThreadInputProps) {
const handleSubmit = useCallback(() => {
onNewThreadCreate?.(value)
setValue(EMPTY_ARRAY)
- commentInputHandle.current?.focus()
}, [onNewThreadCreate, value])
const hasValue = useMemo(() => hasCommentMessageValue(value), [value])
const startDiscard = useCallback(() => {
if (!hasValue) {
- onEditDiscard?.()
- commentInputHandle.current?.focus()
return
}
-
commentInputHandle.current?.discardDialogController.open()
- }, [hasValue, onEditDiscard])
+ }, [hasValue])
+
+ const handleInputKeyDown = useCallback(
+ (event: React.KeyboardEvent) => {
+ // Don't act if the input already prevented this event
+ if (event.isDefaultPrevented()) {
+ return
+ }
+ // Discard the input text
+ if (event.key === 'Escape') {
+ event.preventDefault()
+ event.stopPropagation()
+ startDiscard()
+ }
+ // Call parent handler
+ if (onKeyDown) onKeyDown(event)
+ },
+ [onKeyDown, startDiscard],
+ )
const confirmDiscard = useCallback(() => {
- commentInputHandle.current?.discardDialogController.close()
setValue(EMPTY_ARRAY)
- onEditDiscard?.()
+ commentInputHandle.current?.discardDialogController.close()
commentInputHandle.current?.focus()
- }, [onEditDiscard])
+ }, [])
const cancelDiscard = useCallback(() => {
commentInputHandle.current?.discardDialogController.close()
- commentInputHandle.current?.focus()
}, [])
const placeholder = (
@@ -76,7 +88,7 @@ export function CreateNewThreadInput(props: CreateNewThreadInputProps) {
onChange={setValue}
onDiscardCancel={cancelDiscard}
onDiscardConfirm={confirmDiscard}
- onEscapeKeyDown={startDiscard}
+ onKeyDown={handleInputKeyDown}
onFocus={onFocus}
onSubmit={handleSubmit}
placeholder={placeholder}
diff --git a/packages/sanity/src/desk/comments/src/components/pte/comment-input/CommentInput.tsx b/packages/sanity/src/desk/comments/src/components/pte/comment-input/CommentInput.tsx
index de78064160d..6714fc76134 100644
--- a/packages/sanity/src/desk/comments/src/components/pte/comment-input/CommentInput.tsx
+++ b/packages/sanity/src/desk/comments/src/components/pte/comment-input/CommentInput.tsx
@@ -1,5 +1,5 @@
import React, {forwardRef, useCallback, useImperativeHandle, useMemo, useRef, useState} from 'react'
-import {EditorChange, PortableTextEditor} from '@sanity/portable-text-editor'
+import {EditorChange, PortableTextEditor, keyGenerator} from '@sanity/portable-text-editor'
import {CurrentUser, PortableTextBlock} from '@sanity/types'
import FocusLock from 'react-focus-lock'
import {Stack} from '@sanity/ui'
@@ -27,8 +27,8 @@ export interface CommentInputProps {
onChange: (value: PortableTextBlock[]) => void
onDiscardCancel: () => void
onDiscardConfirm: () => void
- onEscapeKeyDown?: () => void
onFocus?: (e: React.FormEvent) => void
+ onKeyDown?: (e: React.KeyboardEvent) => void
onMentionMenuOpenChange?: (open: boolean) => void
onSubmit: () => void
placeholder?: React.ReactNode
@@ -47,6 +47,7 @@ export interface CommentInputHandle {
discardDialogController: CommentDiscardDialogController
focus: () => void
scrollTo: () => void
+ reset: () => void
}
/**
@@ -65,8 +66,8 @@ export const CommentInput = forwardRef(
onChange,
onDiscardCancel,
onDiscardConfirm,
- onEscapeKeyDown,
onFocus,
+ onKeyDown,
onMentionMenuOpenChange,
onSubmit,
placeholder,
@@ -79,15 +80,26 @@ export const CommentInput = forwardRef(
const editorContainerRef = useRef(null)
const [showDiscardDialog, setShowDiscardDialog] = useState(false)
+ // A unique (React) key for the editor instance.
+ const [editorInstanceKey, setEditorInstanceKey] = useState(keyGenerator())
+
+ const requestFocus = useCallback(() => {
+ requestAnimationFrame(() => {
+ if (!editorRef.current) return
+ PortableTextEditor.focus(editorRef.current)
+ })
+ }, [])
+
+ const resetEditorInstance = useCallback(() => {
+ setEditorInstanceKey(keyGenerator())
+ }, [])
+
const handleChange = useCallback(
(change: EditorChange) => {
// Focus the editor when ready if focusOnMount is true
if (change.type === 'ready') {
- if (focusOnMount && editorRef.current) {
- requestAnimationFrame(() => {
- if (!editorRef.current) return
- PortableTextEditor.focus(editorRef.current)
- })
+ if (focusOnMount) {
+ requestFocus()
}
}
if (change.type === 'focus') {
@@ -101,70 +113,71 @@ export const CommentInput = forwardRef(
// Update the comment value whenever the comment is edited by the user.
if (change.type === 'patch' && editorRef.current) {
const editorStateValue = PortableTextEditor.getValue(editorRef.current)
- if (editorStateValue) {
- onChange(editorStateValue)
- }
+ onChange(editorStateValue || EMPTY_ARRAY)
}
},
- [focusOnMount, onChange],
+ [focusOnMount, onChange, requestFocus],
)
const scrollToEditor = useCallback(() => {
editorContainerRef.current?.scrollIntoView(SCROLL_INTO_VIEW_OPTIONS)
}, [])
+ const handleSubmit = useCallback(() => {
+ onSubmit()
+ resetEditorInstance()
+ requestFocus()
+ scrollToEditor()
+ }, [onSubmit, requestFocus, resetEditorInstance, scrollToEditor])
+
+ const handleDiscardConfirm = useCallback(() => {
+ onDiscardConfirm()
+ resetEditorInstance()
+ }, [onDiscardConfirm, resetEditorInstance])
+
// The way a user a comment can be discarded varies from the context it is used in.
// This controller is used to take care of the main logic of the discard process, while
// specific behavior is handled by the consumer.
const discardDialogController = useMemo(() => {
return {
open: () => {
- if (editorRef?.current) {
- PortableTextEditor.blur(editorRef.current)
- }
-
setShowDiscardDialog(true)
},
close: () => {
setShowDiscardDialog(false)
-
- if (editorRef?.current) {
- PortableTextEditor.focus(editorRef.current)
- }
+ requestFocus()
},
} satisfies CommentDiscardDialogController
- }, [])
+ }, [requestFocus])
useImperativeHandle(
ref,
() => {
return {
- focus() {
- if (editorRef?.current) {
- PortableTextEditor.focus(editorRef.current)
- }
- },
+ focus: requestFocus,
blur() {
- if (editorRef?.current) {
+ if (editorRef.current) {
PortableTextEditor.blur(editorRef.current)
}
},
scrollTo: scrollToEditor,
+ reset: resetEditorInstance,
discardDialogController,
}
},
- [discardDialogController, scrollToEditor],
+ [discardDialogController, requestFocus, resetEditorInstance, scrollToEditor],
)
return (
<>
{showDiscardDialog && (
-
+
)}
(
currentUser={currentUser}
focusLock={focusLock}
onBlur={onBlur}
- onEscapeKeyDown={onEscapeKeyDown}
onFocus={onFocus}
- onSubmit={onSubmit}
+ onKeyDown={onKeyDown}
+ onSubmit={handleSubmit}
placeholder={placeholder}
withAvatar={withAvatar}
/>
diff --git a/packages/sanity/src/desk/comments/src/components/pte/comment-input/CommentInputInner.tsx b/packages/sanity/src/desk/comments/src/components/pte/comment-input/CommentInputInner.tsx
index 9e3f34f609f..04fcd29288b 100644
--- a/packages/sanity/src/desk/comments/src/components/pte/comment-input/CommentInputInner.tsx
+++ b/packages/sanity/src/desk/comments/src/components/pte/comment-input/CommentInputInner.tsx
@@ -77,36 +77,20 @@ interface CommentInputInnerProps {
currentUser: CurrentUser
focusLock?: boolean
onBlur?: (e: React.FormEvent) => void
- onEscapeKeyDown?: () => void
onFocus?: (e: React.FormEvent) => void
+ onKeyDown?: (e: React.KeyboardEvent) => void
onSubmit: () => void
placeholder?: React.ReactNode
withAvatar?: boolean
}
export function CommentInputInner(props: CommentInputInnerProps) {
- const {
- currentUser,
- focusLock,
- onBlur,
- onEscapeKeyDown,
- onFocus,
- onSubmit,
- placeholder,
- withAvatar,
- } = props
+ const {currentUser, focusLock, onBlur, onFocus, onKeyDown, onSubmit, placeholder, withAvatar} =
+ props
const [user] = useUser(currentUser.id)
- const {
- canSubmit,
- expandOnFocus,
- focused,
- hasChanges,
- insertAtChar,
- mentionsMenuOpen,
- openMentions,
- readOnly,
- } = useCommentInput()
+ const {canSubmit, expandOnFocus, focused, hasChanges, insertAtChar, openMentions, readOnly} =
+ useCommentInput()
const avatar = withAvatar ? : null
@@ -119,21 +103,8 @@ export function CommentInputInner(props: CommentInputInnerProps) {
[insertAtChar, openMentions],
)
- const handleKeyDown = useCallback(
- (e: React.KeyboardEvent) => {
- if (e.key === 'Escape') {
- e.stopPropagation()
- e.preventDefault()
- if (mentionsMenuOpen) return
-
- onEscapeKeyDown?.()
- }
- },
- [mentionsMenuOpen, onEscapeKeyDown],
- )
-
return (
-
+
{avatar}
diff --git a/packages/sanity/src/desk/comments/src/components/pte/comment-input/CommentInputProvider.tsx b/packages/sanity/src/desk/comments/src/components/pte/comment-input/CommentInputProvider.tsx
index cfa65bf039f..a150b45754e 100644
--- a/packages/sanity/src/desk/comments/src/components/pte/comment-input/CommentInputProvider.tsx
+++ b/packages/sanity/src/desk/comments/src/components/pte/comment-input/CommentInputProvider.tsx
@@ -73,16 +73,14 @@ export function CommentInputProvider(props: CommentInputProviderProps) {
setMentionsMenuOpen(false)
setMentionsSearchTerm('')
setSelectionAtMentionInsert(null)
- focusEditor()
- }, [focusEditor])
+ }, [])
const openMentions = useCallback(() => {
setMentionsMenuOpen(true)
setMentionsSearchTerm('')
setMentionsMenuOpen(true)
setSelectionAtMentionInsert(PortableTextEditor.getSelection(editor))
- focusEditor()
- }, [focusEditor, editor])
+ }, [editor])
// This function activates or deactivates the mentions menu and updates
// the mention search term when the user types into the Portable Text Editor.
@@ -200,10 +198,8 @@ export function CommentInputProvider(props: CommentInputProviderProps) {
}
}
}
-
- closeMentions()
},
- [closeMentions, editor, selectionAtMentionInsert],
+ [editor, selectionAtMentionInsert],
)
const ctxValue = useMemo(
diff --git a/packages/sanity/src/desk/comments/src/components/pte/comment-input/Editable.tsx b/packages/sanity/src/desk/comments/src/components/pte/comment-input/Editable.tsx
index f1724db8f40..0688f4c4f44 100644
--- a/packages/sanity/src/desk/comments/src/components/pte/comment-input/Editable.tsx
+++ b/packages/sanity/src/desk/comments/src/components/pte/comment-input/Editable.tsx
@@ -53,6 +53,8 @@ interface EditableProps {
focusLock?: boolean
onBlur?: (e: React.FormEvent) => void
onFocus?: (e: React.FormEvent) => void
+ onKeyDown?: (e: React.KeyboardEvent) => void
+ onSubmit?: () => void
placeholder?: React.ReactNode
}
@@ -61,7 +63,14 @@ export interface EditableHandle {
}
export function Editable(props: EditableProps) {
- const {focusLock, placeholder = 'Create a new comment', onFocus, onBlur} = props
+ const {
+ focusLock,
+ placeholder = 'Create a new comment',
+ onFocus,
+ onBlur,
+ onKeyDown,
+ onSubmit,
+ } = props
const [popoverElement, setPopoverElement] = useState(null)
const rootElementRef = useRef(null)
const editableRef = useRef(null)
@@ -69,8 +78,8 @@ export function Editable(props: EditableProps) {
const selection = usePortableTextEditorSelection()
const {
+ canSubmit,
closeMentions,
- focusEditor,
insertMention,
mentionOptions,
mentionsMenuOpen,
@@ -116,26 +125,45 @@ export function Editable(props: EditableProps) {
const handleKeyDown = useCallback(
(event: KeyboardEvent) => {
- switch (event.code) {
+ switch (event.key) {
case 'Enter':
+ // Shift enter is used to insert a new line,
+ // keep the default behavior
+ if (event.shiftKey) {
+ break
+ }
+ // Enter is being used both to select something from the mentionsMenu
+ // or to submit the comment. Prevent the default behavior.
+ event.preventDefault()
+ event.stopPropagation()
+
+ // If the mention menu is open close it, but don't submit.
if (mentionsMenuOpen) {
- // Stop the event from creating a new block in the editor here
- event.preventDefault()
- event.stopPropagation()
+ closeMentions()
+ break
+ }
+
+ // Submit the comment if eligible for submission
+ if (onSubmit && canSubmit) {
+ onSubmit()
}
break
case 'Escape':
case 'ArrowLeft':
case 'ArrowRight':
if (mentionsMenuOpen) {
+ // stop these events if the menu is open
+ event.preventDefault()
+ event.stopPropagation()
closeMentions()
- focusEditor()
}
break
default:
}
+ // Call parent key handler
+ if (onKeyDown) onKeyDown(event)
},
- [closeMentions, focusEditor, mentionsMenuOpen],
+ [canSubmit, closeMentions, mentionsMenuOpen, onKeyDown, onSubmit],
)
const initialSelectionAtEndOfContent: EditorSelection | undefined = useMemo(() => {