@@ -169,35 +174,64 @@ export const TaskPlanPanel = memo(function TaskPlanPanel({
);
}
- const { textSelection, setTextSelection } = selectionState;
+ return
;
+});
+function PlanPanelContent({
+ taskId,
+ state,
+}: {
+ taskId: string;
+ state: ReturnType
;
+}) {
+ const { editorWrapperRef, editorInstanceRef, editorInstance, selectionState } = state;
+ const { textSelection, setTextSelection } = selectionState;
+ // Ctrl+F in-document find (registers a keydown listener on the editor wrapper)
+ const planSearch = usePlanFindShortcut(editorWrapperRef, editorInstance);
return (
+
{planSearch.isOpen && (
);
-});
+}
function removeCommentMark(editor: Editor | null, commentId: string) {
if (!editor) return;
@@ -441,66 +475,6 @@ function usePlanSelection(
return { textSelection, setTextSelection, handleCommentHighlightClick, handleSelectionClose };
}
-/** Ctrl+F find-in-plan shortcut + editor wiring */
-function usePlanFindShortcut(
- wrapperRef: React.RefObject,
- editor: Editor | null,
-) {
- const [isOpen, setIsOpen] = useState(false);
- const [query, setQuery] = useState("");
- const [matchInfo, setMatchInfo] = useState({ current: 0, total: 0 });
-
- const readMatchInfo = useCallback(() => {
- if (!editor) return;
- const s = planSearchPluginKey.getState(editor.state);
- if (!s) return;
- const next = {
- current: s.matches.length ? s.current + 1 : 0,
- total: s.matches.length,
- };
- // Bail out when nothing changed — TipTap fires `transaction` on every cursor
- // move, and allocating a new object identity would re-render this hook on
- // every keystroke / click in the editor (and can cascade into update loops).
- setMatchInfo((prev) =>
- prev.current === next.current && prev.total === next.total ? prev : next,
- );
- }, [editor]);
-
- useEffect(() => {
- if (!editor) return;
- const handler = () => readMatchInfo();
- editor.on("transaction", handler);
- return () => {
- editor.off("transaction", handler);
- };
- }, [editor, readMatchInfo]);
-
- useEffect(() => {
- if (!editor) return;
- if (!isOpen) return;
- editor.commands.setPlanSearchQuery(query);
- }, [query, isOpen, editor]);
-
- useEffect(() => {
- if (isOpen) return;
- if (!editor) return;
- editor.commands.clearPlanSearch();
- }, [isOpen, editor]);
-
- const open = useCallback(() => setIsOpen(true), []);
- const close = useCallback(() => setIsOpen(false), []);
- const findNext = useCallback(() => {
- editor?.commands.planSearchNext();
- }, [editor]);
- const findPrev = useCallback(() => {
- editor?.commands.planSearchPrev();
- }, [editor]);
-
- usePanelSearch({ containerRef: wrapperRef, isOpen, onOpen: open, onClose: close });
-
- return { isOpen, open, close, query, setQuery, findNext, findPrev, matchInfo };
-}
-
/** Ctrl+S save shortcut */
function useSaveShortcut(
hasUnsavedChanges: boolean,
@@ -578,3 +552,58 @@ function PlanEmptyState({
);
}
+
+type PlanPanelHeaderProps = {
+ taskId: string;
+ revisions: TaskPlanRevision[];
+ isLoadingRevisions: boolean;
+ isSaving: boolean;
+ onOpenRevisions: () => void;
+ onRevert: (id: string) => Promise