From a5386f72c645beeaec701c746c3b1587afb9db1b Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 10 Nov 2026 17:58:22 +0800 Subject: [PATCH] fix(submit): make the draft chain survive continuous writing, reloads and flaky networks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Autosave only ran after 4s of idle, and every keystroke reset that timer, so a writer who never stopped had zero saves (measured: 15s of typing = 0 requests). Saving also never rebound the URL, so a new draft could not be found again after a reload, the local backup was cleared on every autosave, and a failed save had no retry and stayed failed once the network came back. Client - autosave: keep the idle debounce but add a 30s max-wait window that is not reset by further keystrokes, and retry failures at 5s / 15s / 60s - bind `?edit=` into the URL once the server returns a post id, so reloads, new tabs and history all reopen the same draft - only clear the local backup when the save settled and nothing changed since - key the local backup by draft id instead of the constant `jio-studio-0` - restore snapshots through hydrateBlocks so code blocks come back editable - never disable publish while an autosave is in flight, and mark autosave states separately (自动保存中… / 已自动保存 / 自动保存失败,将重试) - on 409, adopt the server rev and tell the author instead of failing silently - status bar reports the real save state instead of a bogus Markdown hint Server - draft saves no longer rewrite the lifecycle: pending / future / publish / private posts keep their status, so an autosave cannot pull a submitted post out of the review queue - drafts and autosaves stop consuming the publish flood quota, which is meant to cap actual publishing (measured: 4 drafts used to burn 4 slots) - autosave skips wp_save_post_revision for both plain drafts and submission versions; manual saves and submits still create a revision to roll back to - media-only drafts (image / table / code / hr) count as content - the response now carries the resulting status so the client can tell "just submitted" from "still under review" Tests cover the max-wait window, retry schedule, URL binding, backup clearing, status labelling and every server-side draft rule. --- .../src/components/save-state.tsx | 26 +++++- .../src/components/status-bar.tsx | 8 +- .../creator-studio/src/hooks/use-autosave.ts | 91 +++++++++++++++---- .../src/hooks/use-local-recovery.ts | 25 +++-- assets/creator-studio/src/index.tsx | 70 +++++++++++--- src/Core/CreatorRest.php | 2 + src/Core/FrontSubmitSave.php | 59 +++++++++++- src/Core/SubmissionVersion.php | 11 ++- .../creator-studio-contract.test.cjs | 78 +++++++++++++++- .../front-submit-pending-harness.php | 9 ++ 10 files changed, 330 insertions(+), 49 deletions(-) diff --git a/assets/creator-studio/src/components/save-state.tsx b/assets/creator-studio/src/components/save-state.tsx index 378b8687..b3bde488 100644 --- a/assets/creator-studio/src/components/save-state.tsx +++ b/assets/creator-studio/src/components/save-state.tsx @@ -1,5 +1,8 @@ /** - * 顶栏保存状态。状态文案即用户对“内容有没有保存”的唯一反馈。 + * 保存状态。状态文案即用户对「内容有没有保存」的唯一反馈。 + * + * 自动保存与手动保存共用同一套取值,但 `autosave` 标记让界面能区别对待: + * 自动保存不该抢走提交按钮的可用性,也不该把「保存中」渲染成用户主动操作。 * * @package Jio */ @@ -8,7 +11,12 @@ import { __ } from '@wordpress/i18n'; export type SaveStatus = 'ready' | 'saving' | 'saved' | 'error' | 'conflict' | 'submitting' | 'submitted'; -export type SaveStateValue = { state: SaveStatus; at: string }; +export type SaveStateValue = { + state: SaveStatus; + at: string; + /** 当前状态是否来自后台自动保存(不阻塞提交按钮)。 */ + autosave?: boolean; +}; const LABELS: Record = { ready: __('尚未保存', 'jio'), @@ -20,11 +28,23 @@ const LABELS: Record = { submitted: __('已提交', 'jio'), }; +/** 自动保存专用的说法:避免让作者以为是自己点了保存。 */ +const AUTOSAVE_LABELS: Partial> = { + saving: __('自动保存中…', 'jio'), + saved: __('已自动保存', 'jio'), + error: __('自动保存失败,将重试', 'jio'), +}; + +export function saveStateLabel(value: SaveStateValue): string { + const autosave = value.autosave ? AUTOSAVE_LABELS[value.state] : ''; + return autosave || LABELS[value.state] || LABELS.ready; +} + export function SaveState({ value }: { value: SaveStateValue }) { return (