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.
52 lines
1.8 KiB
TypeScript
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>
|
|
);
|
|
}
|