feat(review): add Escape handler and point thread submit

- Escape priority: draft pin → thread detail collapse → review mode exit
- Draft/detail clearing works even when focus is in input field
- Review mode exit respects input/textarea focus context
- handleCreatePointThread: REST POST with auto-focus on success
- Clear draft/focus/expandRequest when review mode exits
This commit is contained in:
Hiren Kangad
2026-03-12 22:09:12 +05:30
parent 08fe062b92
commit f05a2c8c34
+73
View File
@@ -294,6 +294,79 @@ export default function Editor({ isPublicView }: EditorProps) {
};
}, [reviewMode, pinOverlay, userRole]);
// Review-mode Escape priority: draft → thread detail → review mode
useEffect(() => {
const onKeyDown = (e: KeyboardEvent) => {
if (e.key !== 'Escape') return;
if (draftPin) {
e.preventDefault();
setDraftPin(null);
return;
}
if (openThreadDetailId) {
e.preventDefault();
setFocusedThreadId(null);
// Send collapse signal to FeedbackPanel
expandSeqRef.current += 1;
setExpandRequest({ threadId: null, seq: expandSeqRef.current });
return;
}
if (reviewMode) {
// Don't exit review mode if user is typing in an unrelated input
if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return;
e.preventDefault();
setReviewMode(false);
return;
}
};
window.addEventListener('keydown', onKeyDown);
return () => window.removeEventListener('keydown', onKeyDown);
}, [draftPin, openThreadDetailId, reviewMode]);
// Create point-pinned thread via REST
const handleCreatePointThread = useCallback(async (draft: DraftPin, content: string) => {
if (!resolvedBoardId) return;
const token = localStorage.getItem('refboard_token');
if (!token) return;
try {
const res = await fetch(`/api/boards/${resolvedBoardId}/threads`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
body: JSON.stringify({
object_id: draft.objectId,
anchor_type: 'point',
pin_x: draft.pinX,
pin_y: draft.pinY,
content,
}),
});
if (!res.ok) {
const body = await res.json().catch(() => ({}));
showToast(body.error || 'Failed to create comment');
return;
}
const data = await res.json();
// Clear draft, pre-focus the new thread
setDraftPin(null);
setFocusedThreadId(data.thread.id);
expandSeqRef.current += 1;
setExpandRequest({ threadId: data.thread.id, seq: expandSeqRef.current });
} catch (err: any) {
showToast('Failed to create comment: ' + (err.message || 'network error'));
}
}, [resolvedBoardId, showToast]);
// Clear draft and focus when exiting review mode
useEffect(() => {
if (!reviewMode) {
setDraftPin(null);
setFocusedThreadId(null);
setExpandRequest(null);
}
}, [reviewMode]);
// Tool activation
useEffect(() => {
const viewport = canvasRef.current?.getViewport();