/** * 底部固定状态栏。 * * 与公众号/知乎一致:左侧是字数与输入法状态,中间是草稿时间,右侧是预览与发布。 * 固定在视口底部,不随正文滚动。 * * @package Jio */ import { __, sprintf } from '@wordpress/i18n'; import { SaveState, saveStateLabel, type SaveStateValue } from './save-state'; type Props = { charCount: number; saveState: SaveStateValue; isPublishedEdit: boolean; saving: boolean; onSaveDraft: () => void; onPreview: () => void; onPublish: () => void; }; const MINUTE = 60 * 1000; const HOUR = 60 * MINUTE; const DAY = 24 * HOUR; const MONTH = 30 * DAY; const YEAR = 365 * DAY; /** 相对时间:刚刚 / X分钟前 / X小时前 … 与参考编辑器一致。 */ export function relativeTime(iso: string, now = Date.now()): string { const at = Date.parse(iso); if (!Number.isFinite(at)) { return ''; } const diff = Math.max(0, now - at); if (diff < MINUTE) { return __('刚刚', 'jio'); } if (diff < HOUR) { return sprintf( /* translators: %d: minutes */ __('%d分钟前', 'jio'), Math.floor(diff / MINUTE) ); } if (diff < DAY) { return sprintf( /* translators: %d: hours */ __('%d小时前', 'jio'), Math.floor(diff / HOUR) ); } if (diff < MONTH) { return sprintf( /* translators: %d: days */ __('%d天前', 'jio'), Math.floor(diff / DAY) ); } if (diff < YEAR) { return sprintf( /* translators: %d: months */ __('%d个月前', 'jio'), Math.floor(diff / MONTH) ); } return sprintf( /* translators: %d: years */ __('%d年前', 'jio'), Math.floor(diff / YEAR) ); } const STATUS_LABELS: Record = { ready: __('草稿', 'jio'), saving: __('保存中', 'jio'), saved: __('草稿', 'jio'), error: __('保存失败', 'jio'), conflict: __('冲突', 'jio'), submitting: __('提交中', 'jio'), submitted: __('已提交', 'jio'), }; export function StatusBar({ charCount, saveState, isPublishedEdit, saving, onSaveDraft, onPreview, onPublish, }: Props) { const savedAt = saveState.at ? relativeTime(saveState.at) : ''; const status = STATUS_LABELS[saveState.state] || STATUS_LABELS.ready; // 时间和状态各显示一次会重复(「17:04:15 · 草稿」),有具体时间时以时间为准。 const meta = savedAt || status; return ( ); }