fix(core): should not be able to comment with empty content (#13061)

fix AF-2712

#### PR Dependency Tree


* **PR #13061** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* The comment editor now disables the commit button when the editor is
empty, preventing accidental submissions.
* The commit action is now triggered by pressing Enter together with
CMD/CTRL, instead of Enter without Shift.

* **Style**
* The disabled state styling for the commit button now matches the
native HTML `disabled` attribute for improved consistency.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Peng Xiao
2025-07-07 13:49:34 +08:00
committed by GitHub
parent 0be63d6e0e
commit 90b2b33dde
3 changed files with 46 additions and 9 deletions

View File

@@ -93,6 +93,8 @@ export const CommentEditor = forwardRef<CommentEditorRef, CommentEditorProps>(
const snapshotHelper = useService(SnapshotHelper); const snapshotHelper = useService(SnapshotHelper);
const editorRef = useRef<PageEditor>(null); const editorRef = useRef<PageEditor>(null);
const [empty, setEmpty] = useState(true);
useImperativeHandle( useImperativeHandle(
ref, ref,
() => ({ () => ({
@@ -138,12 +140,15 @@ export const CommentEditor = forwardRef<CommentEditorRef, CommentEditorProps>(
}, [autoFocus, doc]); }, [autoFocus, doc]);
useEffect(() => { useEffect(() => {
if (doc && onChange) { if (doc) {
const subscription = doc.slots.blockUpdated.subscribe(() => { const subscription = doc.slots.blockUpdated.subscribe(() => {
const snapshot = snapshotHelper.getSnapshot(doc); if (onChange) {
if (snapshot) { const snapshot = snapshotHelper.getSnapshot(doc);
onChange?.(snapshot); if (snapshot) {
onChange?.(snapshot);
}
} }
setEmpty(snapshotHelper.isDocEmpty(doc));
}); });
return () => { return () => {
subscription?.unsubscribe(); subscription?.unsubscribe();
@@ -152,7 +157,7 @@ export const CommentEditor = forwardRef<CommentEditorRef, CommentEditorProps>(
return; return;
}, [doc, onChange, snapshotHelper]); }, [doc, onChange, snapshotHelper]);
// Add keydown handler to commit on Enter key // Add keydown handler to commit on CMD/CTRL + Enter key
const handleKeyDown = useCallback( const handleKeyDown = useCallback(
(e: React.KeyboardEvent) => { (e: React.KeyboardEvent) => {
if (readonly) return; if (readonly) return;
@@ -161,8 +166,8 @@ export const CommentEditor = forwardRef<CommentEditorRef, CommentEditorProps>(
const activeElement = document.activeElement; const activeElement = document.activeElement;
if (!editorRef.current?.contains(activeElement)) return; if (!editorRef.current?.contains(activeElement)) return;
// If Enter is pressed without Shift key, commit the comment // If Enter is pressed with CMD/CTRL key, commit the comment
if (e.key === 'Enter' && !e.shiftKey) { if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
onCommit?.(); onCommit?.();
@@ -194,7 +199,11 @@ export const CommentEditor = forwardRef<CommentEditorRef, CommentEditorProps>(
{doc && <LitDocEditor ref={editorRef} specs={specs} doc={doc} />} {doc && <LitDocEditor ref={editorRef} specs={specs} doc={doc} />}
{!readonly && ( {!readonly && (
<div className={styles.footer}> <div className={styles.footer}>
<button onClick={onCommit} className={styles.commitButton}> <button
onClick={onCommit}
className={styles.commitButton}
disabled={empty}
>
<ArrowUpBigIcon /> <ArrowUpBigIcon />
</button> </button>
</div> </div>

View File

@@ -52,7 +52,7 @@ export const commitButton = style({
height: '28px', height: '28px',
fontSize: 20, fontSize: 20,
selectors: { selectors: {
'&[data-disabled="true"]': { '&[disabled]': {
background: cssVarV2('button/disable'), background: cssVarV2('button/disable'),
cursor: 'default', cursor: 'default',
}, },

View File

@@ -5,6 +5,7 @@ import {
MarkdownAdapter, MarkdownAdapter,
} from '@blocksuite/affine/shared/adapters'; } from '@blocksuite/affine/shared/adapters';
import { import {
type BlockModel,
type DocSnapshot, type DocSnapshot,
nanoid, nanoid,
type Store, type Store,
@@ -152,4 +153,31 @@ export class SnapshotHelper extends Service {
return undefined; return undefined;
} }
} }
isDocEmpty(store?: Store): boolean {
if (!store) {
return true;
}
const checkBlock = (block: BlockModel) => {
if (block.text && block.text.length > 0) {
return false;
}
const children = block.children;
for (const child of children) {
if (!checkBlock(child)) {
return false;
}
}
return true;
};
const blocks = store.blocks.peek();
for (const block of Object.values(blocks)) {
if (!checkBlock(block.model)) {
return false;
}
}
return true;
}
} }