Files
wordpress-theme-aksrt/assets/creator-studio/src/components/save-state.tsx
T
kate522 a5386f72c6 fix(submit): make the draft chain survive continuous writing, reloads and flaky networks
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=<id>` 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.
2026-11-10 17:58:22 +08:00

52 lines
1.8 KiB
TypeScript

/**
* 保存状态。状态文案即用户对「内容有没有保存」的唯一反馈。
*
* 自动保存与手动保存共用同一套取值,但 `autosave` 标记让界面能区别对待:
* 自动保存不该抢走提交按钮的可用性,也不该把「保存中」渲染成用户主动操作。
*
* @package Jio
*/
import { __ } from '@wordpress/i18n';
export type SaveStatus = 'ready' | 'saving' | 'saved' | 'error' | 'conflict' | 'submitting' | 'submitted';
export type SaveStateValue = {
state: SaveStatus;
at: string;
/** 当前状态是否来自后台自动保存(不阻塞提交按钮)。 */
autosave?: boolean;
};
const LABELS: Record<SaveStatus, string> = {
ready: __('尚未保存', 'jio'),
saving: __('保存中…', 'jio'),
saved: __('已保存', 'jio'),
error: __('保存失败', 'jio'),
conflict: __('内容已在别处更新', 'jio'),
submitting: __('提交中…', 'jio'),
submitted: __('已提交', 'jio'),
};
/** 自动保存专用的说法:避免让作者以为是自己点了保存。 */
const AUTOSAVE_LABELS: Partial<Record<SaveStatus, string>> = {
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 (
<span className={`jio-creator__save is-${value.state}`} role="status" aria-live="polite">
<i className="fa fas fa-circle" aria-hidden="true" />
<span>{saveStateLabel(value)}</span>
{value.state === 'saved' && value.at ? <time>{value.at}</time> : null}
</span>
);
}