@elytracms/studio 0.0.42 → 0.0.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/app-views/studio-config.d.ts +12 -0
- package/dist/components/app-views/studio-config.js.map +1 -1
- package/dist/components/assets/asset-detail.js +1 -1
- package/dist/components/assets/asset-detail.js.map +1 -1
- package/dist/components/assets/assets-collection-view-source.d.ts +13 -1
- package/dist/components/assets/assets-collection-view-source.js +42 -51
- package/dist/components/assets/assets-collection-view-source.js.map +1 -1
- package/dist/components/assets/assets-module.js +19 -6
- package/dist/components/assets/assets-module.js.map +1 -1
- package/dist/components/assets/assets-store.d.ts +2 -1
- package/dist/components/assets/assets-store.js +4 -8
- package/dist/components/assets/assets-store.js.map +1 -1
- package/dist/components/assets/assets-view-preference.d.ts +0 -8
- package/dist/components/assets/assets-view-preference.js +12 -13
- package/dist/components/assets/assets-view-preference.js.map +1 -1
- package/dist/components/auth-context.d.ts +2 -1
- package/dist/components/auth-context.js +6 -8
- package/dist/components/auth-context.js.map +1 -1
- package/dist/components/builder/builder-store.d.ts +2 -2
- package/dist/components/builder/builder-store.js +4 -8
- package/dist/components/builder/builder-store.js.map +1 -1
- package/dist/components/cms/bulk-actions.js +2 -1
- package/dist/components/cms/bulk-actions.js.map +1 -1
- package/dist/components/cms/cell-edit.js +2 -1
- package/dist/components/cms/cell-edit.js.map +1 -1
- package/dist/components/cms/cms-composition-validators.d.ts +25 -0
- package/dist/components/cms/cms-composition-validators.js +75 -0
- package/dist/components/cms/cms-composition-validators.js.map +1 -0
- package/dist/components/cms/cms-labels.d.ts +15 -0
- package/dist/components/cms/cms-labels.js +27 -0
- package/dist/components/cms/cms-labels.js.map +1 -0
- package/dist/components/cms/cms-store.d.ts +23 -10
- package/dist/components/cms/cms-store.js +87 -165
- package/dist/components/cms/cms-store.js.map +1 -1
- package/dist/components/cms/collection-table.js +1 -1
- package/dist/components/cms/collection-table.js.map +1 -1
- package/dist/components/cms/detail-editor.js +111 -348
- package/dist/components/cms/detail-editor.js.map +1 -1
- package/dist/components/cms/discard-draft.d.ts +18 -0
- package/dist/components/cms/discard-draft.js +18 -0
- package/dist/components/cms/discard-draft.js.map +1 -1
- package/dist/components/cms/document-editor-context.d.ts +2 -1
- package/dist/components/cms/document-editor-context.js +4 -9
- package/dist/components/cms/document-editor-context.js.map +1 -1
- package/dist/components/cms/document-ref-utils.d.ts +33 -0
- package/dist/components/cms/document-ref-utils.js +47 -0
- package/dist/components/cms/document-ref-utils.js.map +1 -0
- package/dist/components/cms/document-staging.d.ts +8 -3
- package/dist/components/cms/document-staging.js +81 -21
- package/dist/components/cms/document-staging.js.map +1 -1
- package/dist/components/cms/field-control.js +1 -1
- package/dist/components/cms/field-control.js.map +1 -1
- package/dist/components/cms/issues-pane.js +1 -1
- package/dist/components/cms/issues-pane.js.map +1 -1
- package/dist/components/cms/rich-text/block-commands.d.ts +36 -0
- package/dist/components/cms/rich-text/block-commands.js +47 -0
- package/dist/components/cms/rich-text/block-commands.js.map +1 -0
- package/dist/components/cms/rich-text/slash-command.js +25 -8
- package/dist/components/cms/rich-text/slash-command.js.map +1 -1
- package/dist/components/cms/rich-text/toolbar.js +3 -2
- package/dist/components/cms/rich-text/toolbar.js.map +1 -1
- package/dist/components/cms/use-compare-selection.d.ts +27 -0
- package/dist/components/cms/use-compare-selection.js +29 -0
- package/dist/components/cms/use-compare-selection.js.map +1 -0
- package/dist/components/cms/use-discard-controller.d.ts +44 -0
- package/dist/components/cms/use-discard-controller.js +51 -0
- package/dist/components/cms/use-discard-controller.js.map +1 -0
- package/dist/components/cms/use-embed-inspector-host.d.ts +12 -0
- package/dist/components/cms/use-embed-inspector-host.js +17 -0
- package/dist/components/cms/use-embed-inspector-host.js.map +1 -0
- package/dist/components/cms/use-field-focus.d.ts +38 -0
- package/dist/components/cms/use-field-focus.js +70 -0
- package/dist/components/cms/use-field-focus.js.map +1 -0
- package/dist/components/cms/use-live-url.d.ts +41 -0
- package/dist/components/cms/use-live-url.js +55 -0
- package/dist/components/cms/use-live-url.js.map +1 -0
- package/dist/components/cms/use-publish-controller.d.ts +49 -0
- package/dist/components/cms/use-publish-controller.js +102 -0
- package/dist/components/cms/use-publish-controller.js.map +1 -0
- package/dist/components/cms/use-published-perspective.d.ts +67 -0
- package/dist/components/cms/use-published-perspective.js +67 -0
- package/dist/components/cms/use-published-perspective.js.map +1 -0
- package/dist/components/cms/use-restore-version.d.ts +57 -0
- package/dist/components/cms/use-restore-version.js +66 -0
- package/dist/components/cms/use-restore-version.js.map +1 -0
- package/dist/components/cms/use-save-mode.d.ts +35 -0
- package/dist/components/cms/use-save-mode.js +54 -0
- package/dist/components/cms/use-save-mode.js.map +1 -0
- package/dist/components/cms/version-compare.js +14 -17
- package/dist/components/cms/version-compare.js.map +1 -1
- package/dist/components/cms/versions-model.d.ts +31 -0
- package/dist/components/cms/versions-model.js +30 -0
- package/dist/components/cms/versions-model.js.map +1 -1
- package/dist/components/cms/versions-pane.js +21 -19
- package/dist/components/cms/versions-pane.js.map +1 -1
- package/dist/components/cms/view-bar.js +9 -6
- package/dist/components/cms/view-bar.js.map +1 -1
- package/dist/components/collab/collab-context.d.ts +2 -1
- package/dist/components/collab/collab-context.js +4 -8
- package/dist/components/collab/collab-context.js.map +1 -1
- package/dist/components/collection-view/collection-view-context.d.ts +9 -1
- package/dist/components/collection-view/collection-view-context.js +4 -9
- package/dist/components/collection-view/collection-view-context.js.map +1 -1
- package/dist/components/components/component-detail.js +2 -5
- package/dist/components/components/component-detail.js.map +1 -1
- package/dist/components/components/component-preview.js +8 -4
- package/dist/components/components/component-preview.js.map +1 -1
- package/dist/components/components/registry-browser.js +24 -5
- package/dist/components/components/registry-browser.js.map +1 -1
- package/dist/components/components/transfer-dialog.d.ts +21 -6
- package/dist/components/components/transfer-dialog.js +120 -31
- package/dist/components/components/transfer-dialog.js.map +1 -1
- package/dist/components/components/transfer.d.ts +74 -8
- package/dist/components/components/transfer.js +87 -2
- package/dist/components/components/transfer.js.map +1 -1
- package/dist/components/delivery-health-banner.d.ts +12 -6
- package/dist/components/delivery-health-banner.js +27 -29
- package/dist/components/delivery-health-banner.js.map +1 -1
- package/dist/components/dev-login-badge.d.ts +0 -2
- package/dist/components/dev-login-badge.js +1 -3
- package/dist/components/dev-login-badge.js.map +1 -1
- package/dist/components/playground-badge.d.ts +0 -2
- package/dist/components/playground-badge.js +0 -2
- package/dist/components/playground-badge.js.map +1 -1
- package/dist/components/project-list.js +2 -6
- package/dist/components/project-list.js.map +1 -1
- package/dist/components/project-session.d.ts +6 -2
- package/dist/components/project-session.js +32 -83
- package/dist/components/project-session.js.map +1 -1
- package/dist/components/project-switcher.js +2 -6
- package/dist/components/project-switcher.js.map +1 -1
- package/dist/components/rail-store-status.d.ts +7 -5
- package/dist/components/rail-store-status.js +12 -9
- package/dist/components/rail-store-status.js.map +1 -1
- package/dist/components/rail-view-source.d.ts +111 -0
- package/dist/components/rail-view-source.js +139 -0
- package/dist/components/rail-view-source.js.map +1 -0
- package/dist/components/redirects/redirects-collection-view-source.d.ts +13 -1
- package/dist/components/redirects/redirects-collection-view-source.js +42 -70
- package/dist/components/redirects/redirects-collection-view-source.js.map +1 -1
- package/dist/components/redirects/redirects-store.d.ts +2 -1
- package/dist/components/redirects/redirects-store.js +4 -8
- package/dist/components/redirects/redirects-store.js.map +1 -1
- package/dist/components/section-state.d.ts +12 -3
- package/dist/components/section-state.js +8 -4
- package/dist/components/section-state.js.map +1 -1
- package/dist/components/studio-context.d.ts +2 -1
- package/dist/components/studio-context.js +4 -8
- package/dist/components/studio-context.js.map +1 -1
- package/dist/components/studio-shell.js +1 -1
- package/dist/components/studio-shell.js.map +1 -1
- package/dist/components/theme-context.d.ts +2 -1
- package/dist/components/theme-context.js +4 -8
- package/dist/components/theme-context.js.map +1 -1
- package/dist/components/use-hydration-phase.d.ts +65 -0
- package/dist/components/use-hydration-phase.js +64 -0
- package/dist/components/use-hydration-phase.js.map +1 -0
- package/dist/components/users/users-collection-view-source.d.ts +13 -1
- package/dist/components/users/users-collection-view-source.js +60 -79
- package/dist/components/users/users-collection-view-source.js.map +1 -1
- package/dist/components/users/users-store.d.ts +2 -1
- package/dist/components/users/users-store.js +4 -8
- package/dist/components/users/users-store.js.map +1 -1
- package/dist/components/workspace-projects.d.ts +14 -0
- package/dist/components/workspace-projects.js +21 -0
- package/dist/components/workspace-projects.js.map +1 -1
- package/dist/lib/ai/anthropic-provider.d.ts +29 -0
- package/dist/lib/ai/anthropic-provider.js +59 -0
- package/dist/lib/ai/anthropic-provider.js.map +1 -0
- package/dist/lib/ai/apply-content.d.ts +9 -0
- package/dist/lib/ai/apply-content.js +170 -0
- package/dist/lib/ai/apply-content.js.map +1 -0
- package/dist/lib/ai/config.d.ts +58 -0
- package/dist/lib/ai/config.js +134 -0
- package/dist/lib/ai/config.js.map +1 -0
- package/dist/lib/ai/fake-provider.d.ts +34 -0
- package/dist/lib/ai/fake-provider.js +31 -0
- package/dist/lib/ai/fake-provider.js.map +1 -0
- package/dist/lib/ai/index.d.ts +22 -0
- package/dist/lib/ai/index.js +23 -0
- package/dist/lib/ai/index.js.map +1 -0
- package/dist/lib/ai/knowledge.d.ts +39 -0
- package/dist/lib/ai/knowledge.js +60 -0
- package/dist/lib/ai/knowledge.js.map +1 -0
- package/dist/lib/ai/provenance.d.ts +20 -0
- package/dist/lib/ai/provenance.js +15 -0
- package/dist/lib/ai/provenance.js.map +1 -0
- package/dist/lib/ai/route.d.ts +24 -0
- package/dist/lib/ai/route.js +90 -0
- package/dist/lib/ai/route.js.map +1 -0
- package/dist/lib/ai/shape.d.ts +35 -0
- package/dist/lib/ai/shape.js +113 -0
- package/dist/lib/ai/shape.js.map +1 -0
- package/dist/lib/ai/types.d.ts +227 -0
- package/dist/lib/ai/types.js +2 -0
- package/dist/lib/ai/types.js.map +1 -0
- package/dist/lib/data/data-store.d.ts +2 -1
- package/dist/lib/data/data-store.js +4 -8
- package/dist/lib/data/data-store.js.map +1 -1
- package/dist/lib/delivery/instant-publish.d.ts +9 -13
- package/dist/lib/delivery/instant-publish.js +0 -7
- package/dist/lib/delivery/instant-publish.js.map +1 -1
- package/dist/lib/diff-view-preference.js +4 -4
- package/dist/lib/diff-view-preference.js.map +1 -1
- package/dist/lib/i18n/messages.en.js +5 -2
- package/dist/lib/i18n/messages.en.js.map +1 -1
- package/dist/lib/local-preference.d.ts +37 -0
- package/dist/lib/local-preference.js +36 -0
- package/dist/lib/local-preference.js.map +1 -1
- package/dist/lib/persistence/convex-adapter.d.ts +3 -1
- package/dist/lib/persistence/convex-adapter.js +20 -0
- package/dist/lib/persistence/convex-adapter.js.map +1 -1
- package/dist/lib/rail.js +7 -6
- package/dist/lib/rail.js.map +1 -1
- package/dist/lib/required-context.d.ts +24 -0
- package/dist/lib/required-context.js +28 -0
- package/dist/lib/required-context.js.map +1 -0
- package/dist/lib/theme.js +11 -4
- package/dist/lib/theme.js.map +1 -1
- package/dist/lib/workspace/index.d.ts +9 -0
- package/dist/lib/workspace/index.js +11 -0
- package/dist/lib/workspace/index.js.map +1 -1
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/index.js +1 -0
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/segmented-control.d.ts +46 -0
- package/dist/ui/segmented-control.js +13 -0
- package/dist/ui/segmented-control.js.map +1 -0
- package/package.json +8 -2
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { QueryKey } from '@tanstack/react-query';
|
|
2
|
+
import type { CmsDocument, DocumentRef, RestoreScope } from '@elytracms/core/cms-core';
|
|
3
|
+
import type { CollectionViewSource } from '@elytracms/studio/lib/collection-view/collection-view-source';
|
|
4
|
+
/**
|
|
5
|
+
* EC-325 — the version-restore sequence, single-sourced. The Versions pane's
|
|
6
|
+
* per-version restore, the Compare view's scoped/whole restore, and the header's
|
|
7
|
+
* "Discard changes" (a whole-document restore of the pinned published version)
|
|
8
|
+
* all ran their OWN copy of the identical four steps:
|
|
9
|
+
*
|
|
10
|
+
* 1. flush a dirty in-memory buffer FIRST (`flushDirtyDraft`) — `documents.restore`
|
|
11
|
+
* snapshots whatever is currently PERSISTED as its pre-restore version, so an
|
|
12
|
+
* edit still in the autosave debounce window must reach the server before the
|
|
13
|
+
* restore reads it, or it is silently unrecoverable (the undo-guarantee gap
|
|
14
|
+
* the Discard path used to have);
|
|
15
|
+
* 2. `versioning.restore(ref, version, scope?)` — scope absent ⇒ whole document
|
|
16
|
+
* (`documents.restore` reads `scope ?? { kind: 'all' }`, so an omitted scope
|
|
17
|
+
* and `{ kind: 'all' }` are the same restore);
|
|
18
|
+
* 3. adopt the restored draft into the open editor (`replaceDraft`);
|
|
19
|
+
* 4. invalidate the shared version-history cache key so the history re-reads.
|
|
20
|
+
*
|
|
21
|
+
* The pure {@link performRestore} pins that ordering (unit-tested with spies); the
|
|
22
|
+
* {@link useRestoreVersion} hook wraps it with the per-call-site busy flag and the
|
|
23
|
+
* operations cache. The differing signatures reconcile to `(version, scope?)`:
|
|
24
|
+
* whole-document callers pass no scope, the Compare view passes its `RestoreScope`,
|
|
25
|
+
* and Discard passes the pinned published version with `{ kind: 'all' }`.
|
|
26
|
+
*/
|
|
27
|
+
export declare function performRestore(input: {
|
|
28
|
+
dirty: boolean;
|
|
29
|
+
save: () => void;
|
|
30
|
+
restore: (version: number, scope?: RestoreScope) => Promise<CmsDocument | null>;
|
|
31
|
+
replaceDraft: (doc: CmsDocument) => void;
|
|
32
|
+
invalidate: () => Promise<void>;
|
|
33
|
+
version: number;
|
|
34
|
+
scope?: RestoreScope;
|
|
35
|
+
}): Promise<void>;
|
|
36
|
+
/** Dependencies the restore sequence reads from its call site's editor state. */
|
|
37
|
+
export interface RestoreVersionDeps {
|
|
38
|
+
source: CollectionViewSource;
|
|
39
|
+
docRef: DocumentRef;
|
|
40
|
+
/** The shared version-history cache key ({@link documentVersionsKey}). */
|
|
41
|
+
versionKey: QueryKey;
|
|
42
|
+
/** Is the in-memory draft dirty (an unsaved buffer to flush first)? */
|
|
43
|
+
dirty: boolean;
|
|
44
|
+
/** Persist the current draft buffer. */
|
|
45
|
+
save: () => void;
|
|
46
|
+
/** Adopt the restored snapshot as the open editor's draft. */
|
|
47
|
+
replaceDraft: (doc: CmsDocument) => void;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The shared restore action + its in-flight flag. `restore(version, scope?)`
|
|
51
|
+
* runs the {@link performRestore} sequence; `busy` gates the calling surface's
|
|
52
|
+
* restore/discard controls while it round-trips.
|
|
53
|
+
*/
|
|
54
|
+
export declare function useRestoreVersion(deps: RestoreVersionDeps): {
|
|
55
|
+
restore: (version: number, scope?: RestoreScope) => Promise<void>;
|
|
56
|
+
busy: boolean;
|
|
57
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { useCallback, useState } from 'react';
|
|
2
|
+
import { useOperationCache } from '@elytracms/studio/lib/query';
|
|
3
|
+
import { flushDirtyDraft } from './discard-draft';
|
|
4
|
+
/**
|
|
5
|
+
* EC-325 — the version-restore sequence, single-sourced. The Versions pane's
|
|
6
|
+
* per-version restore, the Compare view's scoped/whole restore, and the header's
|
|
7
|
+
* "Discard changes" (a whole-document restore of the pinned published version)
|
|
8
|
+
* all ran their OWN copy of the identical four steps:
|
|
9
|
+
*
|
|
10
|
+
* 1. flush a dirty in-memory buffer FIRST (`flushDirtyDraft`) — `documents.restore`
|
|
11
|
+
* snapshots whatever is currently PERSISTED as its pre-restore version, so an
|
|
12
|
+
* edit still in the autosave debounce window must reach the server before the
|
|
13
|
+
* restore reads it, or it is silently unrecoverable (the undo-guarantee gap
|
|
14
|
+
* the Discard path used to have);
|
|
15
|
+
* 2. `versioning.restore(ref, version, scope?)` — scope absent ⇒ whole document
|
|
16
|
+
* (`documents.restore` reads `scope ?? { kind: 'all' }`, so an omitted scope
|
|
17
|
+
* and `{ kind: 'all' }` are the same restore);
|
|
18
|
+
* 3. adopt the restored draft into the open editor (`replaceDraft`);
|
|
19
|
+
* 4. invalidate the shared version-history cache key so the history re-reads.
|
|
20
|
+
*
|
|
21
|
+
* The pure {@link performRestore} pins that ordering (unit-tested with spies); the
|
|
22
|
+
* {@link useRestoreVersion} hook wraps it with the per-call-site busy flag and the
|
|
23
|
+
* operations cache. The differing signatures reconcile to `(version, scope?)`:
|
|
24
|
+
* whole-document callers pass no scope, the Compare view passes its `RestoreScope`,
|
|
25
|
+
* and Discard passes the pinned published version with `{ kind: 'all' }`.
|
|
26
|
+
*/
|
|
27
|
+
export async function performRestore(input) {
|
|
28
|
+
// Step 1 — flush before the restore reads the persisted document.
|
|
29
|
+
flushDirtyDraft({ dirty: input.dirty, save: input.save });
|
|
30
|
+
// Step 2 — the restore itself (scope absent ⇒ whole document).
|
|
31
|
+
const restored = await input.restore(input.version, input.scope);
|
|
32
|
+
// Step 3 — the open editor adopts the restored snapshot as its new draft.
|
|
33
|
+
if (restored)
|
|
34
|
+
input.replaceDraft(restored);
|
|
35
|
+
// Step 4 — re-read the recorded history (the new pre-restore version appears).
|
|
36
|
+
await input.invalidate();
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The shared restore action + its in-flight flag. `restore(version, scope?)`
|
|
40
|
+
* runs the {@link performRestore} sequence; `busy` gates the calling surface's
|
|
41
|
+
* restore/discard controls while it round-trips.
|
|
42
|
+
*/
|
|
43
|
+
export function useRestoreVersion(deps) {
|
|
44
|
+
const { source, docRef, versionKey, dirty, save, replaceDraft } = deps;
|
|
45
|
+
const cache = useOperationCache();
|
|
46
|
+
const [busy, setBusy] = useState(false);
|
|
47
|
+
const restore = useCallback(async (version, scope) => {
|
|
48
|
+
setBusy(true);
|
|
49
|
+
try {
|
|
50
|
+
await performRestore({
|
|
51
|
+
dirty,
|
|
52
|
+
save,
|
|
53
|
+
restore: async (v, s) => (await source.versioning?.restore(docRef, v, s)) ?? null,
|
|
54
|
+
replaceDraft,
|
|
55
|
+
invalidate: () => cache.invalidate(versionKey),
|
|
56
|
+
version,
|
|
57
|
+
scope,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
setBusy(false);
|
|
62
|
+
}
|
|
63
|
+
}, [source, docRef, versionKey, dirty, save, replaceDraft, cache]);
|
|
64
|
+
return { restore, busy };
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=use-restore-version.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-restore-version.js","sourceRoot":"","sources":["../../../src/components/cms/use-restore-version.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAG7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAA;AAE/D,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEjD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,KAQpC;IACC,kEAAkE;IAClE,eAAe,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAA;IACzD,+DAA+D;IAC/D,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,KAAK,CAAC,CAAA;IAChE,0EAA0E;IAC1E,IAAI,QAAQ;QAAE,KAAK,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAA;IAC1C,+EAA+E;IAC/E,MAAM,KAAK,CAAC,UAAU,EAAE,CAAA;AAC1B,CAAC;AAgBD;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAwB;IAIxD,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,GAAG,IAAI,CAAA;IACtE,MAAM,KAAK,GAAG,iBAAiB,EAAE,CAAA;IACjC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IACvC,MAAM,OAAO,GAAG,WAAW,CACzB,KAAK,EAAE,OAAe,EAAE,KAAoB,EAAE,EAAE;QAC9C,OAAO,CAAC,IAAI,CAAC,CAAA;QACb,IAAI,CAAC;YACH,MAAM,cAAc,CAAC;gBACnB,KAAK;gBACL,IAAI;gBACJ,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,MAAM,CAAC,UAAU,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI;gBACjF,YAAY;gBACZ,UAAU,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,UAAU,CAAC;gBAC9C,OAAO;gBACP,KAAK;aACN,CAAC,CAAA;QACJ,CAAC;gBAAS,CAAC;YACT,OAAO,CAAC,KAAK,CAAC,CAAA;QAChB,CAAC;IACH,CAAC,EACD,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,CAAC,CAC/D,CAAA;IACD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;AAC1B,CAAC","sourcesContent":["import { useCallback, useState } from 'react'\nimport type { QueryKey } from '@tanstack/react-query'\nimport type { CmsDocument, DocumentRef, RestoreScope } from '@elytracms/core/cms-core'\nimport { useOperationCache } from '@elytracms/studio/lib/query'\nimport type { CollectionViewSource } from '@elytracms/studio/lib/collection-view/collection-view-source'\nimport { flushDirtyDraft } from './discard-draft'\n\n/**\n * EC-325 — the version-restore sequence, single-sourced. The Versions pane's\n * per-version restore, the Compare view's scoped/whole restore, and the header's\n * \"Discard changes\" (a whole-document restore of the pinned published version)\n * all ran their OWN copy of the identical four steps:\n *\n * 1. flush a dirty in-memory buffer FIRST (`flushDirtyDraft`) — `documents.restore`\n * snapshots whatever is currently PERSISTED as its pre-restore version, so an\n * edit still in the autosave debounce window must reach the server before the\n * restore reads it, or it is silently unrecoverable (the undo-guarantee gap\n * the Discard path used to have);\n * 2. `versioning.restore(ref, version, scope?)` — scope absent ⇒ whole document\n * (`documents.restore` reads `scope ?? { kind: 'all' }`, so an omitted scope\n * and `{ kind: 'all' }` are the same restore);\n * 3. adopt the restored draft into the open editor (`replaceDraft`);\n * 4. invalidate the shared version-history cache key so the history re-reads.\n *\n * The pure {@link performRestore} pins that ordering (unit-tested with spies); the\n * {@link useRestoreVersion} hook wraps it with the per-call-site busy flag and the\n * operations cache. The differing signatures reconcile to `(version, scope?)`:\n * whole-document callers pass no scope, the Compare view passes its `RestoreScope`,\n * and Discard passes the pinned published version with `{ kind: 'all' }`.\n */\nexport async function performRestore(input: {\n dirty: boolean\n save: () => void\n restore: (version: number, scope?: RestoreScope) => Promise<CmsDocument | null>\n replaceDraft: (doc: CmsDocument) => void\n invalidate: () => Promise<void>\n version: number\n scope?: RestoreScope\n}): Promise<void> {\n // Step 1 — flush before the restore reads the persisted document.\n flushDirtyDraft({ dirty: input.dirty, save: input.save })\n // Step 2 — the restore itself (scope absent ⇒ whole document).\n const restored = await input.restore(input.version, input.scope)\n // Step 3 — the open editor adopts the restored snapshot as its new draft.\n if (restored) input.replaceDraft(restored)\n // Step 4 — re-read the recorded history (the new pre-restore version appears).\n await input.invalidate()\n}\n\n/** Dependencies the restore sequence reads from its call site's editor state. */\nexport interface RestoreVersionDeps {\n source: CollectionViewSource\n docRef: DocumentRef\n /** The shared version-history cache key ({@link documentVersionsKey}). */\n versionKey: QueryKey\n /** Is the in-memory draft dirty (an unsaved buffer to flush first)? */\n dirty: boolean\n /** Persist the current draft buffer. */\n save: () => void\n /** Adopt the restored snapshot as the open editor's draft. */\n replaceDraft: (doc: CmsDocument) => void\n}\n\n/**\n * The shared restore action + its in-flight flag. `restore(version, scope?)`\n * runs the {@link performRestore} sequence; `busy` gates the calling surface's\n * restore/discard controls while it round-trips.\n */\nexport function useRestoreVersion(deps: RestoreVersionDeps): {\n restore: (version: number, scope?: RestoreScope) => Promise<void>\n busy: boolean\n} {\n const { source, docRef, versionKey, dirty, save, replaceDraft } = deps\n const cache = useOperationCache()\n const [busy, setBusy] = useState(false)\n const restore = useCallback(\n async (version: number, scope?: RestoreScope) => {\n setBusy(true)\n try {\n await performRestore({\n dirty,\n save,\n restore: async (v, s) => (await source.versioning?.restore(docRef, v, s)) ?? null,\n replaceDraft,\n invalidate: () => cache.invalidate(versionKey),\n version,\n scope,\n })\n } finally {\n setBusy(false)\n }\n },\n [source, docRef, versionKey, dirty, save, replaceDraft, cache],\n )\n return { restore, busy }\n}\n"]}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { CmsDocument } from '@elytracms/core/cms-core';
|
|
2
|
+
import type { CollectionViewSource } from '@elytracms/studio/lib/collection-view/collection-view-source';
|
|
3
|
+
/**
|
|
4
|
+
* EC-225 / EC-240 — the two persistence disciplines the detail editor supports,
|
|
5
|
+
* as one pure decision so the autosave scheduler and the unmount flush can never
|
|
6
|
+
* diverge on WHEN a dirty draft is persisted silently:
|
|
7
|
+
*
|
|
8
|
+
* - A **publishing** rail AUTOSAVES: the draft persists 1s after the last edit and
|
|
9
|
+
* publish is the deliberate commit — so a dirty draft is flushed both on the
|
|
10
|
+
* debounce timer and on unmount (navigating away inside the debounce window).
|
|
11
|
+
* - A **non-publishing** rail (users, EC-240) SAVES EXPLICITLY: edits stay in the
|
|
12
|
+
* draft until the Save button, and an `UnsavedChangesGuard` protects navigation —
|
|
13
|
+
* so it NEVER persists silently (returns false here).
|
|
14
|
+
*/
|
|
15
|
+
export declare function shouldPersistDraft(input: {
|
|
16
|
+
explicitSave: boolean;
|
|
17
|
+
dirty: boolean;
|
|
18
|
+
}): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* EC-225 autosave · EC-240 explicit save. Owns the debounced autosave timer and
|
|
21
|
+
* the unmount flush for a publishing rail; a non-publishing rail persists only via
|
|
22
|
+
* its Save button (the caller's `UnsavedChangesGuard` covers navigation). Refs let
|
|
23
|
+
* the unmount flush read the latest values without re-subscribing.
|
|
24
|
+
*
|
|
25
|
+
* Returns `explicitSave` — the caller renders the Save button + guard (explicit)
|
|
26
|
+
* or the autosave status text (autosave) from it.
|
|
27
|
+
*/
|
|
28
|
+
export declare function useSaveMode(input: {
|
|
29
|
+
canPublish: boolean;
|
|
30
|
+
dirty: boolean;
|
|
31
|
+
draft: CmsDocument;
|
|
32
|
+
source: CollectionViewSource;
|
|
33
|
+
}): {
|
|
34
|
+
explicitSave: boolean;
|
|
35
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* EC-225 / EC-240 — the two persistence disciplines the detail editor supports,
|
|
4
|
+
* as one pure decision so the autosave scheduler and the unmount flush can never
|
|
5
|
+
* diverge on WHEN a dirty draft is persisted silently:
|
|
6
|
+
*
|
|
7
|
+
* - A **publishing** rail AUTOSAVES: the draft persists 1s after the last edit and
|
|
8
|
+
* publish is the deliberate commit — so a dirty draft is flushed both on the
|
|
9
|
+
* debounce timer and on unmount (navigating away inside the debounce window).
|
|
10
|
+
* - A **non-publishing** rail (users, EC-240) SAVES EXPLICITLY: edits stay in the
|
|
11
|
+
* draft until the Save button, and an `UnsavedChangesGuard` protects navigation —
|
|
12
|
+
* so it NEVER persists silently (returns false here).
|
|
13
|
+
*/
|
|
14
|
+
export function shouldPersistDraft(input) {
|
|
15
|
+
return !input.explicitSave && input.dirty;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* EC-225 autosave · EC-240 explicit save. Owns the debounced autosave timer and
|
|
19
|
+
* the unmount flush for a publishing rail; a non-publishing rail persists only via
|
|
20
|
+
* its Save button (the caller's `UnsavedChangesGuard` covers navigation). Refs let
|
|
21
|
+
* the unmount flush read the latest values without re-subscribing.
|
|
22
|
+
*
|
|
23
|
+
* Returns `explicitSave` — the caller renders the Save button + guard (explicit)
|
|
24
|
+
* or the autosave status text (autosave) from it.
|
|
25
|
+
*/
|
|
26
|
+
export function useSaveMode(input) {
|
|
27
|
+
const { canPublish, dirty, draft, source } = input;
|
|
28
|
+
const explicitSave = !canPublish;
|
|
29
|
+
const draftRef = useRef(draft);
|
|
30
|
+
draftRef.current = draft;
|
|
31
|
+
const dirtyRef = useRef(dirty);
|
|
32
|
+
dirtyRef.current = dirty;
|
|
33
|
+
const sourceRef = useRef(source);
|
|
34
|
+
sourceRef.current = source;
|
|
35
|
+
const explicitSaveRef = useRef(explicitSave);
|
|
36
|
+
explicitSaveRef.current = explicitSave;
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
if (explicitSave)
|
|
39
|
+
return; // explicit rails persist only via the Save button
|
|
40
|
+
if (!dirty)
|
|
41
|
+
return;
|
|
42
|
+
const timer = window.setTimeout(() => sourceRef.current.save(draftRef.current), 1000);
|
|
43
|
+
return () => window.clearTimeout(timer);
|
|
44
|
+
}, [dirty, draft, explicitSave]);
|
|
45
|
+
// Flush a still-pending AUTOSAVE edit on unmount (navigating away inside the
|
|
46
|
+
// debounce window) so nothing is lost. Explicit rails never flush silently —
|
|
47
|
+
// their `UnsavedChangesGuard` catches the navigation instead. Unmount-only.
|
|
48
|
+
useEffect(() => () => {
|
|
49
|
+
if (shouldPersistDraft({ explicitSave: explicitSaveRef.current, dirty: dirtyRef.current }))
|
|
50
|
+
sourceRef.current.save(draftRef.current);
|
|
51
|
+
}, []);
|
|
52
|
+
return { explicitSave };
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=use-save-mode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-save-mode.js","sourceRoot":"","sources":["../../../src/components/cms/use-save-mode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAIzC;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAgD;IACjF,OAAO,CAAC,KAAK,CAAC,YAAY,IAAI,KAAK,CAAC,KAAK,CAAA;AAC3C,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CAAC,KAK3B;IACC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,KAAK,CAAA;IAClD,MAAM,YAAY,GAAG,CAAC,UAAU,CAAA;IAChC,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,CAAA;IAC9B,QAAQ,CAAC,OAAO,GAAG,KAAK,CAAA;IACxB,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,CAAA;IAC9B,QAAQ,CAAC,OAAO,GAAG,KAAK,CAAA;IACxB,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,CAAA;IAChC,SAAS,CAAC,OAAO,GAAG,MAAM,CAAA;IAC1B,MAAM,eAAe,GAAG,MAAM,CAAC,YAAY,CAAC,CAAA;IAC5C,eAAe,CAAC,OAAO,GAAG,YAAY,CAAA;IAEtC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,YAAY;YAAE,OAAM,CAAC,kDAAkD;QAC3E,IAAI,CAAC,KAAK;YAAE,OAAM;QAClB,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC,CAAA;QACrF,OAAO,GAAG,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,CAAA;IACzC,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC,CAAA;IAEhC,6EAA6E;IAC7E,6EAA6E;IAC7E,4EAA4E;IAC5E,SAAS,CACP,GAAG,EAAE,CAAC,GAAG,EAAE;QACT,IAAI,kBAAkB,CAAC,EAAE,YAAY,EAAE,eAAe,CAAC,OAAO,EAAE,KAAK,EAAE,QAAQ,CAAC,OAAO,EAAE,CAAC;YACxF,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAA;IAC5C,CAAC,EACD,EAAE,CACH,CAAA;IAED,OAAO,EAAE,YAAY,EAAE,CAAA;AACzB,CAAC","sourcesContent":["import { useEffect, useRef } from 'react'\nimport type { CmsDocument } from '@elytracms/core/cms-core'\nimport type { CollectionViewSource } from '@elytracms/studio/lib/collection-view/collection-view-source'\n\n/**\n * EC-225 / EC-240 — the two persistence disciplines the detail editor supports,\n * as one pure decision so the autosave scheduler and the unmount flush can never\n * diverge on WHEN a dirty draft is persisted silently:\n *\n * - A **publishing** rail AUTOSAVES: the draft persists 1s after the last edit and\n * publish is the deliberate commit — so a dirty draft is flushed both on the\n * debounce timer and on unmount (navigating away inside the debounce window).\n * - A **non-publishing** rail (users, EC-240) SAVES EXPLICITLY: edits stay in the\n * draft until the Save button, and an `UnsavedChangesGuard` protects navigation —\n * so it NEVER persists silently (returns false here).\n */\nexport function shouldPersistDraft(input: { explicitSave: boolean; dirty: boolean }): boolean {\n return !input.explicitSave && input.dirty\n}\n\n/**\n * EC-225 autosave · EC-240 explicit save. Owns the debounced autosave timer and\n * the unmount flush for a publishing rail; a non-publishing rail persists only via\n * its Save button (the caller's `UnsavedChangesGuard` covers navigation). Refs let\n * the unmount flush read the latest values without re-subscribing.\n *\n * Returns `explicitSave` — the caller renders the Save button + guard (explicit)\n * or the autosave status text (autosave) from it.\n */\nexport function useSaveMode(input: {\n canPublish: boolean\n dirty: boolean\n draft: CmsDocument\n source: CollectionViewSource\n}): { explicitSave: boolean } {\n const { canPublish, dirty, draft, source } = input\n const explicitSave = !canPublish\n const draftRef = useRef(draft)\n draftRef.current = draft\n const dirtyRef = useRef(dirty)\n dirtyRef.current = dirty\n const sourceRef = useRef(source)\n sourceRef.current = source\n const explicitSaveRef = useRef(explicitSave)\n explicitSaveRef.current = explicitSave\n\n useEffect(() => {\n if (explicitSave) return // explicit rails persist only via the Save button\n if (!dirty) return\n const timer = window.setTimeout(() => sourceRef.current.save(draftRef.current), 1000)\n return () => window.clearTimeout(timer)\n }, [dirty, draft, explicitSave])\n\n // Flush a still-pending AUTOSAVE edit on unmount (navigating away inside the\n // debounce window) so nothing is lost. Explicit rails never flush silently —\n // their `UnsavedChangesGuard` catches the navigation instead. Unmount-only.\n useEffect(\n () => () => {\n if (shouldPersistDraft({ explicitSave: explicitSaveRef.current, dirty: dirtyRef.current }))\n sourceRef.current.save(draftRef.current)\n },\n [],\n )\n\n return { explicitSave }\n}\n"]}
|
|
@@ -4,17 +4,17 @@ import { ArrowRight, ChevronDown, ChevronLeft, ChevronRight, Image as ImageIcon,
|
|
|
4
4
|
import { diffDocuments } from '@elytracms/core/content';
|
|
5
5
|
import { assetSubfieldsOf, isAssetNode, readAssetId, readDocumentRef } from '@elytracms/core/cms-core';
|
|
6
6
|
import { Badge, Button, Code, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, Row, Spacer, Stack, Text, } from '@elytracms/studio/ui';
|
|
7
|
-
import { useOperationCache } from '@elytracms/studio/lib/query';
|
|
8
7
|
import { translate } from '@elytracms/studio/lib/i18n';
|
|
9
8
|
import { localeChipLabel } from '@elytracms/studio/lib/locale';
|
|
10
9
|
import { cn } from '@elytracms/studio/lib/utils';
|
|
11
10
|
import { readDiffViewMode, storeDiffViewMode } from '@elytracms/studio/lib/diff-view-preference';
|
|
12
11
|
import { useCollectionView } from '../collection-view/collection-view-context';
|
|
13
|
-
import { fieldLabel } from './cms-
|
|
12
|
+
import { fieldLabel } from './cms-labels';
|
|
14
13
|
import { toRefArray } from './cell';
|
|
15
14
|
import { DIFF_TRUNCATE_THRESHOLD, mergeWordDiff, richTextToText, truncateText, truncateTokens, wordDiff, } from './version-compare-text';
|
|
16
15
|
import { useDocumentEditor } from './document-editor-context';
|
|
17
16
|
import { useDocumentVersionsQuery } from './use-document-versions';
|
|
17
|
+
import { useRestoreVersion } from './use-restore-version';
|
|
18
18
|
import { formatVersionTimestamp } from './versions-model';
|
|
19
19
|
/**
|
|
20
20
|
* EC-298 — the version compare view. Renders as the sticky **Compare** content tab
|
|
@@ -35,8 +35,16 @@ export function VersionCompare() {
|
|
|
35
35
|
const source = useCollectionView();
|
|
36
36
|
const { collection, docRef, draft, dirty, viewReadOnly, save, replaceDraft, compare, setCompareBase, setCompareTarget, } = useDocumentEditor();
|
|
37
37
|
const { versionsQuery, versionKey } = useDocumentVersionsQuery(docRef);
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
// EC-325 — the shared restore sequence (flush → restore → replaceDraft →
|
|
39
|
+
// invalidate). `busy` gates the restore controls below while it round-trips.
|
|
40
|
+
const { restore: restoreVersion, busy } = useRestoreVersion({
|
|
41
|
+
source,
|
|
42
|
+
docRef,
|
|
43
|
+
versionKey,
|
|
44
|
+
dirty,
|
|
45
|
+
save,
|
|
46
|
+
replaceDraft,
|
|
47
|
+
});
|
|
40
48
|
// EC-317 — the unified/side-by-side toggle is a transient, personal-local view
|
|
41
49
|
// preference (same class as the theme/rail prefs): read once at mount, written
|
|
42
50
|
// back on every change, never round-tripped through the project graph.
|
|
@@ -97,19 +105,8 @@ export function VersionCompare() {
|
|
|
97
105
|
const restore = useCallback(async (scope) => {
|
|
98
106
|
if (!compare)
|
|
99
107
|
return;
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (dirty)
|
|
103
|
-
save();
|
|
104
|
-
const restored = await source.versioning?.restore(docRef, compare.base, scope);
|
|
105
|
-
if (restored)
|
|
106
|
-
replaceDraft(restored);
|
|
107
|
-
await cache.invalidate(versionKey);
|
|
108
|
-
}
|
|
109
|
-
finally {
|
|
110
|
-
setBusy(false);
|
|
111
|
-
}
|
|
112
|
-
}, [compare, dirty, save, source, docRef, replaceDraft, cache, versionKey]);
|
|
108
|
+
await restoreVersion(compare.base, scope);
|
|
109
|
+
}, [compare, restoreVersion]);
|
|
113
110
|
if (!compare)
|
|
114
111
|
return null;
|
|
115
112
|
if (view === null)
|