Files
multica/packages/views/editor/use-editor-upload.ts
Naiyuan Qing 3b08456264 feat(views): block draft-fixing actions while attachments upload (#5465)
Submitting a composer mid-upload silently lost the file: the editor holds a
`blob:` placeholder that gets stripped during serialization, and the
attachment id does not exist yet, so the send succeeded without the file.
Chat and Quick Create gated this; manual issue create, Feedback's button,
comments, replies and comment edit did not.

Gate every action that FIXES a draft — Send/Create/Save, Cmd/Ctrl+Enter,
Enter on the title, and the manual/agent mode switches — behind one source
of truth: the editor document, which IS the upload queue. ContentEditor
publishes queue transitions via `onUploadingChange` (a transaction listener,
not `onUpdate` — that path is debounced and skips no-change emissions, and a
failed upload leaves byte-identical markdown, so the un-gate would never
fire). `useUploadGate` pairs that render state with an `isBlocked()` re-read
at submit time, since the shortcut paths never consult the button.

The publisher emits its current answer on subscribe rather than only on
flips: hosts outlive editor instances (comment edit remounts on cancel,
chat swaps by key on agent switch), and an editor torn down mid-upload would
otherwise leave submit wedged shut with no pending node left to reopen it.

Also fixes the failure toast that never fired: `uploadWithToast` only
reported errors if a caller passed `onError`, and none did, so failed uploads
removed their placeholder and vanished unexplained. `useEditorUpload` now
supplies it once for every composer.

Per MUL-4808: drops Quick Create's upload-time lock on the attach button
(files can queue), and leaves description autosave ungated by design.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-16 09:56:24 +08:00

35 lines
1.1 KiB
TypeScript

"use client";
import { useCallback } from "react";
import { toast } from "sonner";
import { api } from "@multica/core/api";
import { useFileUpload } from "@multica/core/hooks/use-file-upload";
import { useT } from "../i18n";
/**
* `useFileUpload` wired to the failure toast the upload gate depends on.
*
* The gate's failure fallback is "drop the placeholder, say so, let them
* submit again". Dropping the placeholder is handled in the upload extension,
* but without the toast the file just vanishes mid-upload with no explanation
* — which is what every composer did, since `uploadWithToast` only toasts if
* the caller supplies `onError` and no caller ever did (MUL-4808).
*
* `useFileUpload` lives in `@multica/core`, which may not import a UI library,
* so the toast is supplied here — once, rather than per composer.
*/
function useEditorUpload() {
const { t } = useT("editor");
const onError = useCallback(
(error: Error, file: File) => {
toast.error(
t(($) => $.upload.failed, { filename: file.name, reason: error.message }),
);
},
[t],
);
return useFileUpload(api, onError);
}
export { useEditorUpload };