From 28eccf4e7a94d803ad278a81bec2547404186359 Mon Sep 17 00:00:00 2001 From: Ash Date: Tue, 3 Sep 2024 11:42:11 +0100 Subject: [PATCH 001/121] feat(sanity): add `perspective` sticky parameter --- packages/sanity/src/router/stickyParams.ts | 1 + 1 file changed, 1 insertion(+) create mode 100644 packages/sanity/src/router/stickyParams.ts diff --git a/packages/sanity/src/router/stickyParams.ts b/packages/sanity/src/router/stickyParams.ts new file mode 100644 index 00000000000..7236e2fd786 --- /dev/null +++ b/packages/sanity/src/router/stickyParams.ts @@ -0,0 +1 @@ +export const STICKY_PARAMS: string[] = ['perspective'] From 06f7219d93982d7ab8d863d593e7bb965820156b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rge=20N=C3=A6ss?= Date: Mon, 22 Jul 2024 11:26:40 +0200 Subject: [PATCH 002/121] feat(preview): add `unstable_observeDocument(s)` to preview store (#7176) * chore: remove futile circular dependency workaround * refactor(core): lift global listener out of preview store * fix(preview): cleanup code so it matches current conventions + add some docs * feat(preview): add unstable_observeDocument + unstable_observeDocuments to preview store * fix: use includeMutations: false * fix: expect ts error on includeMutations: false for now --- .../src/core/preview/createGlobalListener.ts | 1 + .../src/core/preview/createObserveDocument.ts | 86 +++++++++++++++++++ .../src/core/preview/documentPreviewStore.ts | 21 ++++- .../core/preview/utils/applyMendozaPatch.ts | 18 ++++ .../core/preview/utils/replayLatest.test.ts | 37 ++++++++ 5 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 packages/sanity/src/core/preview/createObserveDocument.ts create mode 100644 packages/sanity/src/core/preview/utils/applyMendozaPatch.ts create mode 100644 packages/sanity/src/core/preview/utils/replayLatest.test.ts diff --git a/packages/sanity/src/core/preview/createGlobalListener.ts b/packages/sanity/src/core/preview/createGlobalListener.ts index ac76417b386..7be43f93e31 100644 --- a/packages/sanity/src/core/preview/createGlobalListener.ts +++ b/packages/sanity/src/core/preview/createGlobalListener.ts @@ -19,6 +19,7 @@ export function createGlobalListener(client: SanityClient) { includePreviousRevision: false, includeMutations: false, visibility: 'query', + effectFormat: 'mendoza', tag: 'preview.global', }, ) diff --git a/packages/sanity/src/core/preview/createObserveDocument.ts b/packages/sanity/src/core/preview/createObserveDocument.ts new file mode 100644 index 00000000000..cf255f77c71 --- /dev/null +++ b/packages/sanity/src/core/preview/createObserveDocument.ts @@ -0,0 +1,86 @@ +import {type MutationEvent, type SanityClient, type WelcomeEvent} from '@sanity/client' +import {type SanityDocument} from '@sanity/types' +import {memoize, uniq} from 'lodash' +import {EMPTY, finalize, type Observable, of} from 'rxjs' +import {concatMap, map, scan, shareReplay} from 'rxjs/operators' + +import {type ApiConfig} from './types' +import {applyMendozaPatch} from './utils/applyMendozaPatch' +import {debounceCollect} from './utils/debounceCollect' + +export function createObserveDocument({ + mutationChannel, + client, +}: { + client: SanityClient + mutationChannel: Observable +}) { + const getBatchFetcher = memoize( + function getBatchFetcher(apiConfig: {dataset: string; projectId: string}) { + const _client = client.withConfig(apiConfig) + + function batchFetchDocuments(ids: [string][]) { + return _client.observable + .fetch(`*[_id in $ids]`, {ids: uniq(ids.flat())}, {tag: 'preview.observe-document'}) + .pipe( + // eslint-disable-next-line max-nested-callbacks + map((result) => ids.map(([id]) => result.find((r: {_id: string}) => r._id === id))), + ) + } + return debounceCollect(batchFetchDocuments, 100) + }, + (apiConfig) => apiConfig.dataset + apiConfig.projectId, + ) + + const MEMO: Record> = {} + + function observeDocument(id: string, apiConfig?: ApiConfig) { + const _apiConfig = apiConfig || { + dataset: client.config().dataset!, + projectId: client.config().projectId!, + } + const fetchDocument = getBatchFetcher(_apiConfig) + return mutationChannel.pipe( + concatMap((event) => { + if (event.type === 'welcome') { + return fetchDocument(id).pipe(map((document) => ({type: 'sync' as const, document}))) + } + return event.documentId === id ? of(event) : EMPTY + }), + scan((current: SanityDocument | undefined, event) => { + if (event.type === 'sync') { + return event.document + } + if (event.type === 'mutation') { + return applyMutationEvent(current, event) + } + //@ts-expect-error - this should never happen + throw new Error(`Unexpected event type: "${event.type}"`) + }, undefined), + ) + } + return function memoizedObserveDocument(id: string, apiConfig?: ApiConfig) { + const key = apiConfig ? `${id}-${JSON.stringify(apiConfig)}` : id + if (!(key in MEMO)) { + MEMO[key] = observeDocument(id, apiConfig).pipe( + finalize(() => delete MEMO[key]), + shareReplay({bufferSize: 1, refCount: true}), + ) + } + return MEMO[key] + } +} + +function applyMutationEvent(current: SanityDocument | undefined, event: MutationEvent) { + if (event.previousRev !== current?._rev) { + console.warn('Document out of sync, skipping mutation') + return current + } + if (!event.effects) { + throw new Error( + 'Mutation event is missing effects. Is the listener set up with effectFormat=mendoza?', + ) + } + const next = applyMendozaPatch(current, event.effects.apply) + return {...next, _rev: event.resultRev} +} diff --git a/packages/sanity/src/core/preview/documentPreviewStore.ts b/packages/sanity/src/core/preview/documentPreviewStore.ts index 154c7e56c26..46f97cd4fbc 100644 --- a/packages/sanity/src/core/preview/documentPreviewStore.ts +++ b/packages/sanity/src/core/preview/documentPreviewStore.ts @@ -1,11 +1,12 @@ import {type MutationEvent, type SanityClient, type WelcomeEvent} from '@sanity/client' import {type PrepareViewOptions, type SanityDocument} from '@sanity/types' -import {type Observable} from 'rxjs' +import {combineLatest, type Observable} from 'rxjs' import {distinctUntilChanged, filter, map} from 'rxjs/operators' import {isRecord} from '../util' import {createPreviewAvailabilityObserver} from './availability' import {createGlobalListener} from './createGlobalListener' +import {createObserveDocument} from './createObserveDocument' import {createPathObserver} from './createPathObserver' import {createPreviewObserver} from './createPreviewObserver' import {createObservePathsDocumentPair} from './documentPair' @@ -56,6 +57,19 @@ export interface DocumentPreviewStore { id: string, paths: PreviewPath[], ) => Observable> + + /** + * Observe a complete document with the given ID + * @hidden + * @beta + */ + unstable_observeDocument: (id: string) => Observable + /** + * Observe a list of complete documents with the given IDs + * @hidden + * @beta + */ + unstable_observeDocuments: (ids: string[]) => Observable<(SanityDocument | undefined)[]> } /** @internal */ @@ -79,6 +93,8 @@ export function createDocumentPreviewStore({ map((event) => (event.type === 'welcome' ? {type: 'connected' as const} : event)), ) + const observeDocument = createObserveDocument({client, mutationChannel: globalListener}) + const observeFields = createObserveFields({client: versionedClient, invalidationChannel}) const observePaths = createPathObserver({observeFields}) @@ -110,6 +126,9 @@ export function createDocumentPreviewStore({ observeForPreview, observeDocumentTypeFromId, + unstable_observeDocument: observeDocument, + unstable_observeDocuments: (ids: string[]) => + combineLatest(ids.map((id) => observeDocument(id))), unstable_observeDocumentPairAvailability: observeDocumentPairAvailability, unstable_observePathsDocumentPair: observePathsDocumentPair, } diff --git a/packages/sanity/src/core/preview/utils/applyMendozaPatch.ts b/packages/sanity/src/core/preview/utils/applyMendozaPatch.ts new file mode 100644 index 00000000000..0c1be69450c --- /dev/null +++ b/packages/sanity/src/core/preview/utils/applyMendozaPatch.ts @@ -0,0 +1,18 @@ +import {type SanityDocument} from '@sanity/types' +import {applyPatch, type RawPatch} from 'mendoza' + +function omitRev(document: SanityDocument | undefined) { + if (document === undefined) { + return undefined + } + const {_rev, ...doc} = document + return doc +} + +export function applyMendozaPatch( + document: SanityDocument | undefined, + patch: RawPatch, +): SanityDocument | undefined { + const next = applyPatch(omitRev(document), patch) + return next === null ? undefined : next +} diff --git a/packages/sanity/src/core/preview/utils/replayLatest.test.ts b/packages/sanity/src/core/preview/utils/replayLatest.test.ts new file mode 100644 index 00000000000..03796d77b38 --- /dev/null +++ b/packages/sanity/src/core/preview/utils/replayLatest.test.ts @@ -0,0 +1,37 @@ +import {expect, test} from '@jest/globals' +import {concat, from, lastValueFrom, of, share, timer} from 'rxjs' +import {concatMap, delay, mergeMap, take, toArray} from 'rxjs/operators' + +import {shareReplayLatest} from './shareReplayLatest' + +test('replayLatest() replays matching value to new subscribers', async () => { + const observable = from(['foo', 'bar', 'baz']).pipe( + concatMap((value) => of(value).pipe(delay(100))), + share(), + shareReplayLatest((v) => v === 'foo'), + ) + + const result = observable.pipe( + mergeMap((value) => + value === 'bar' ? concat(of(value), observable.pipe(take(1))) : of(value), + ), + toArray(), + ) + expect(await lastValueFrom(result)).toEqual(['foo', 'bar', 'foo', 'baz']) +}) + +test('replayLatest() doesnt keep the replay value after resets', async () => { + const observable = timer(0, 10).pipe( + shareReplayLatest({ + resetOnRefCountZero: true, + resetOnComplete: true, + predicate: (v) => v < 2, + }), + ) + + const result = observable.pipe(take(5), toArray()) + expect(await lastValueFrom(result)).toEqual([0, 1, 2, 3, 4]) + + const resultAfter = observable.pipe(take(5), toArray()) + expect(await lastValueFrom(resultAfter)).toEqual([0, 1, 2, 3, 4]) +}) From 92e616382f0814db3e4bc6b170ba6d7672f5864d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rge=20N=C3=A6ss?= Date: Fri, 26 Jul 2024 16:57:06 +0200 Subject: [PATCH 003/121] feat(preview): add support for live document id sets (#7253) --- packages/sanity/package.json | 1 + .../src/core/preview/createObserveDocument.ts | 3 +- .../src/core/preview/documentPreviewStore.ts | 38 +++++- .../src/core/preview/liveDocumentIdSet.ts | 112 ++++++++++++++++++ .../src/core/preview/useLiveDocumentIdSet.ts | 47 ++++++++ .../src/core/preview/useLiveDocumentSet.ts | 34 ++++++ pnpm-lock.yaml | 13 ++ 7 files changed, 246 insertions(+), 2 deletions(-) create mode 100644 packages/sanity/src/core/preview/liveDocumentIdSet.ts create mode 100644 packages/sanity/src/core/preview/useLiveDocumentIdSet.ts create mode 100644 packages/sanity/src/core/preview/useLiveDocumentSet.ts diff --git a/packages/sanity/package.json b/packages/sanity/package.json index 493d0cb910d..ac24c8cef48 100644 --- a/packages/sanity/package.json +++ b/packages/sanity/package.json @@ -252,6 +252,7 @@ "rimraf": "^3.0.2", "rxjs": "^7.8.0", "rxjs-exhaustmap-with-trailing": "^2.1.1", + "rxjs-mergemap-array": "^0.1.0", "sanity-diff-patch": "^3.0.2", "scroll-into-view-if-needed": "^3.0.3", "semver": "^7.3.5", diff --git a/packages/sanity/src/core/preview/createObserveDocument.ts b/packages/sanity/src/core/preview/createObserveDocument.ts index cf255f77c71..6b384a7fbf8 100644 --- a/packages/sanity/src/core/preview/createObserveDocument.ts +++ b/packages/sanity/src/core/preview/createObserveDocument.ts @@ -82,5 +82,6 @@ function applyMutationEvent(current: SanityDocument | undefined, event: Mutation ) } const next = applyMendozaPatch(current, event.effects.apply) - return {...next, _rev: event.resultRev} + // next will be undefined in case of deletion + return next ? {...next, _rev: event.resultRev} : undefined } diff --git a/packages/sanity/src/core/preview/documentPreviewStore.ts b/packages/sanity/src/core/preview/documentPreviewStore.ts index 46f97cd4fbc..09aa88ab575 100644 --- a/packages/sanity/src/core/preview/documentPreviewStore.ts +++ b/packages/sanity/src/core/preview/documentPreviewStore.ts @@ -1,4 +1,9 @@ -import {type MutationEvent, type SanityClient, type WelcomeEvent} from '@sanity/client' +import { + type MutationEvent, + type QueryParams, + type SanityClient, + type WelcomeEvent, +} from '@sanity/client' import {type PrepareViewOptions, type SanityDocument} from '@sanity/types' import {combineLatest, type Observable} from 'rxjs' import {distinctUntilChanged, filter, map} from 'rxjs/operators' @@ -10,6 +15,8 @@ import {createObserveDocument} from './createObserveDocument' import {createPathObserver} from './createPathObserver' import {createPreviewObserver} from './createPreviewObserver' import {createObservePathsDocumentPair} from './documentPair' +import {create_preview_documentPair} from './documentPair' +import {createDocumentIdSetObserver, type DocumentIdSetObserverState} from './liveDocumentIdSet' import {createObserveFields} from './observeFields' import { type ApiConfig, @@ -58,6 +65,30 @@ export interface DocumentPreviewStore { paths: PreviewPath[], ) => Observable> + /** + * Observes a set of document IDs that matches the given groq-filter. The document ids are returned in ascending order and will update in real-time + * Whenever a document appears or disappears from the set, a new array with the updated set of IDs will be pushed to subscribers. + * The query is performed once, initially, and thereafter the set of ids are patched based on the `appear` and `disappear` + * transitions on the received listener events. + * This provides a lightweight way of subscribing to a list of ids for simple cases where you just want to subscribe to a set of documents ids + * that matches a particular filter. + * @hidden + * @beta + * @param filter - A groq filter to use for the document set + * @param params - Parameters to use with the groq filter + * @param options - Options for the observer + */ + unstable_observeDocumentIdSet: ( + filter: string, + params?: QueryParams, + options?: { + /** + * Where to insert new items into the set. Defaults to 'sorted' which is based on the lexicographic order of the id + */ + insert?: 'sorted' | 'prepend' | 'append' + }, + ) => Observable + /** * Observe a complete document with the given ID * @hidden @@ -108,6 +139,10 @@ export function createDocumentPreviewStore({ ) } + const observeDocumentIdSet = createDocumentIdSetObserver( + versionedClient.withConfig({apiVersion: '2024-07-22'}), + ) + const observeForPreview = createPreviewObserver({observeDocumentTypeFromId, observePaths}) const observeDocumentPairAvailability = createPreviewAvailabilityObserver( versionedClient, @@ -126,6 +161,7 @@ export function createDocumentPreviewStore({ observeForPreview, observeDocumentTypeFromId, + unstable_observeDocumentIdSet: observeDocumentIdSet, unstable_observeDocument: observeDocument, unstable_observeDocuments: (ids: string[]) => combineLatest(ids.map((id) => observeDocument(id))), diff --git a/packages/sanity/src/core/preview/liveDocumentIdSet.ts b/packages/sanity/src/core/preview/liveDocumentIdSet.ts new file mode 100644 index 00000000000..49d8401cde1 --- /dev/null +++ b/packages/sanity/src/core/preview/liveDocumentIdSet.ts @@ -0,0 +1,112 @@ +import {type QueryParams, type SanityClient} from '@sanity/client' +import {sortedIndex} from 'lodash' +import {of} from 'rxjs' +import {distinctUntilChanged, filter, map, mergeMap, scan, tap} from 'rxjs/operators' + +export type DocumentIdSetObserverState = { + status: 'reconnecting' | 'connected' + documentIds: string[] +} + +interface LiveDocumentIdSetOptions { + insert?: 'sorted' | 'prepend' | 'append' +} + +export function createDocumentIdSetObserver(client: SanityClient) { + return function observe( + queryFilter: string, + params?: QueryParams, + options: LiveDocumentIdSetOptions = {}, + ) { + const {insert: insertOption = 'sorted'} = options + + const query = `*[${queryFilter}]._id` + function fetchFilter() { + return client.observable + .fetch(query, params, { + tag: 'preview.observe-document-set.fetch', + }) + .pipe( + tap((result) => { + if (!Array.isArray(result)) { + throw new Error( + `Expected query to return array of documents, but got ${typeof result}`, + ) + } + }), + ) + } + return client.observable + .listen(query, params, { + visibility: 'transaction', + events: ['welcome', 'mutation', 'reconnect'], + includeResult: false, + includeMutations: false, + tag: 'preview.observe-document-set.listen', + }) + .pipe( + mergeMap((event) => { + return event.type === 'welcome' + ? fetchFilter().pipe(map((result) => ({type: 'fetch' as const, result}))) + : of(event) + }), + scan( + ( + state: DocumentIdSetObserverState | undefined, + event, + ): DocumentIdSetObserverState | undefined => { + if (event.type === 'reconnect') { + return { + documentIds: state?.documentIds || [], + ...state, + status: 'reconnecting' as const, + } + } + if (event.type === 'fetch') { + return {...state, status: 'connected' as const, documentIds: event.result} + } + if (event.type === 'mutation') { + if (event.transition === 'update') { + // ignore updates, as we're only interested in documents appearing and disappearing from the set + return state + } + if (event.transition === 'appear') { + return { + status: 'connected', + documentIds: insert(state?.documentIds || [], event.documentId, insertOption), + } + } + if (event.transition === 'disappear') { + return { + status: 'connected', + documentIds: state?.documentIds + ? state.documentIds.filter((id) => id !== event.documentId) + : [], + } + } + } + return state + }, + undefined, + ), + distinctUntilChanged(), + filter( + (state: DocumentIdSetObserverState | undefined): state is DocumentIdSetObserverState => + state !== undefined, + ), + ) + } +} + +function insert(array: T[], element: T, strategy: 'sorted' | 'prepend' | 'append') { + let index + if (strategy === 'prepend') { + index = 0 + } else if (strategy === 'append') { + index = array.length + } else { + index = sortedIndex(array, element) + } + + return array.toSpliced(index, 0, element) +} diff --git a/packages/sanity/src/core/preview/useLiveDocumentIdSet.ts b/packages/sanity/src/core/preview/useLiveDocumentIdSet.ts new file mode 100644 index 00000000000..2fa3eff626d --- /dev/null +++ b/packages/sanity/src/core/preview/useLiveDocumentIdSet.ts @@ -0,0 +1,47 @@ +import {type QueryParams} from '@sanity/client' +import {useMemo} from 'react' +import {useObservable} from 'react-rx' +import {scan} from 'rxjs/operators' + +import {useDocumentPreviewStore} from '../store/_legacy/datastores' +import {type DocumentIdSetObserverState} from './liveDocumentIdSet' + +const INITIAL_STATE = {status: 'loading' as const, documentIds: []} + +export type LiveDocumentSetState = + | {status: 'loading'; documentIds: string[]} + | DocumentIdSetObserverState + +/** + * @internal + * @beta + * Returns document ids that matches the provided GROQ-filter, and loading state + * The document ids are returned in ascending order and will update in real-time + * Whenever a document appears or disappears from the set, a new array with the updated set of IDs will be returned. + * This provides a lightweight way of subscribing to a list of ids for simple cases where you just want the documents ids + * that matches a particular filter. + */ +export function useLiveDocumentIdSet( + filter: string, + params?: QueryParams, + options: { + // how to insert new document ids. Defaults to `sorted` + insert?: 'sorted' | 'prepend' | 'append' + } = {}, +) { + const documentPreviewStore = useDocumentPreviewStore() + const observable = useMemo( + () => + documentPreviewStore.unstable_observeDocumentIdSet(filter, params, options).pipe( + scan( + (currentState: LiveDocumentSetState, nextState) => ({ + ...currentState, + ...nextState, + }), + INITIAL_STATE, + ), + ), + [documentPreviewStore, filter, params, options], + ) + return useObservable(observable, INITIAL_STATE) +} diff --git a/packages/sanity/src/core/preview/useLiveDocumentSet.ts b/packages/sanity/src/core/preview/useLiveDocumentSet.ts new file mode 100644 index 00000000000..16c5c27be24 --- /dev/null +++ b/packages/sanity/src/core/preview/useLiveDocumentSet.ts @@ -0,0 +1,34 @@ +import {type QueryParams} from '@sanity/client' +import {type SanityDocument} from '@sanity/types' +import {useMemo} from 'react' +import {useObservable} from 'react-rx' +import {map} from 'rxjs/operators' +import {mergeMapArray} from 'rxjs-mergemap-array' + +import {useDocumentPreviewStore} from '../store' + +const INITIAL_VALUE = {loading: true, documents: []} + +/** + * @internal + * @beta + * + * Observes a set of documents matching the filter and returns an array of complete documents + * A new array will be pushed whenever a document in the set changes + * Document ids are returned in ascending order + * Any sorting beyond that must happen client side + */ +export function useLiveDocumentSet( + groqFilter: string, + params?: QueryParams, +): {loading: boolean; documents: SanityDocument[]} { + const documentPreviewStore = useDocumentPreviewStore() + const observable = useMemo(() => { + return documentPreviewStore.unstable_observeDocumentIdSet(groqFilter, params).pipe( + map((state) => (state.documentIds || []) as string[]), + mergeMapArray((id) => documentPreviewStore.unstable_observeDocument(id)), + map((docs) => ({loading: false, documents: docs as SanityDocument[]})), + ) + }, [documentPreviewStore, groqFilter, params]) + return useObservable(observable, INITIAL_VALUE) +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cdc433280f0..4e10c994a5e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1715,6 +1715,9 @@ importers: rxjs-exhaustmap-with-trailing: specifier: ^2.1.1 version: 2.1.1(rxjs@7.8.1) + rxjs-mergemap-array: + specifier: ^0.1.0 + version: 0.1.0(rxjs@7.8.1) sanity-diff-patch: specifier: ^3.0.2 version: 3.0.2 @@ -10530,6 +10533,12 @@ packages: peerDependencies: rxjs: 7.x + rxjs-mergemap-array@0.1.0: + resolution: {integrity: sha512-19fXxPXN4X8LPWu7fg/nyX+nr0G97qSNXhEvF32cdgWuoyUVQ4MrFr+UL4HGip6iO5kbZOL4puAjPeQ/D5qSlA==} + engines: {node: '>=18.0.0'} + peerDependencies: + rxjs: 7.x + rxjs@6.6.7: resolution: {integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==} engines: {npm: '>=2.0.0'} @@ -22736,6 +22745,10 @@ snapshots: dependencies: rxjs: 7.8.1 + rxjs-mergemap-array@0.1.0(rxjs@7.8.1): + dependencies: + rxjs: 7.8.1 + rxjs@6.6.7: dependencies: tslib: 1.14.1 From 9b1f27de62b04938850f71ac460f8a3b3be0d276 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rge=20N=C3=A6ss?= Date: Mon, 6 May 2024 15:46:44 +0200 Subject: [PATCH 004/121] feat(sanity): add 'perspective' dropdown --- .../core/preview/components/PreviewLoader.tsx | 3 ++ .../src/core/preview/createPreviewObserver.ts | 1 + .../src/core/preview/documentPreviewStore.ts | 2 +- .../src/core/preview/useValuePreview.ts | 6 ++-- .../utils/getPreviewValueWithFallback.tsx | 4 ++- .../sanity/src/core/search/common/types.ts | 2 ++ .../search/text-search/createTextSearch.ts | 1 + .../core/search/weighted/createSearchQuery.ts | 2 +- .../studio/components/navbar/StudioNavbar.tsx | 34 +++++++++++++++++-- .../components/paneItem/PaneItem.tsx | 12 ++++--- .../components/paneItem/PaneItemPreview.tsx | 5 +-- .../panes/document/DocumentPaneProvider.tsx | 13 ++++++- .../panes/documentList/DocumentListPane.tsx | 4 +++ .../panes/documentList/listenSearchQuery.ts | 3 ++ .../panes/documentList/useDocumentList.ts | 12 ++++++- 15 files changed, 89 insertions(+), 15 deletions(-) diff --git a/packages/sanity/src/core/preview/components/PreviewLoader.tsx b/packages/sanity/src/core/preview/components/PreviewLoader.tsx index 414ab966964..a1478761f59 100644 --- a/packages/sanity/src/core/preview/components/PreviewLoader.tsx +++ b/packages/sanity/src/core/preview/components/PreviewLoader.tsx @@ -7,6 +7,7 @@ import { useState, } from 'react' +import {useRouter} from '../../../router' import {type PreviewProps} from '../../components' import {type RenderPreviewCallbackProps} from '../../form' import {useTranslation} from '../../i18n' @@ -38,6 +39,7 @@ export function PreviewLoader( ...restProps } = props + const perspective = useRouter().stickyParams.perspective const {t} = useTranslation() const [element, setElement] = useState(null) @@ -51,6 +53,7 @@ export function PreviewLoader( const preview = useValuePreview({ enabled: skipVisibilityCheck || isVisible, schemaType, + perspective, value, }) diff --git a/packages/sanity/src/core/preview/createPreviewObserver.ts b/packages/sanity/src/core/preview/createPreviewObserver.ts index 49b294f961c..20bde34d05f 100644 --- a/packages/sanity/src/core/preview/createPreviewObserver.ts +++ b/packages/sanity/src/core/preview/createPreviewObserver.ts @@ -42,6 +42,7 @@ export function createPreviewObserver(context: { value: Previewable, type: PreviewableType, options: { + perspective?: string viewOptions?: PrepareViewOptions apiConfig?: ApiConfig } = {}, diff --git a/packages/sanity/src/core/preview/documentPreviewStore.ts b/packages/sanity/src/core/preview/documentPreviewStore.ts index 09aa88ab575..76cd46d4630 100644 --- a/packages/sanity/src/core/preview/documentPreviewStore.ts +++ b/packages/sanity/src/core/preview/documentPreviewStore.ts @@ -35,7 +35,7 @@ import { export type ObserveForPreviewFn = ( value: Previewable, type: PreviewableType, - options?: {viewOptions?: PrepareViewOptions; apiConfig?: ApiConfig}, + options?: {viewOptions?: PrepareViewOptions; apiConfig?: ApiConfig; perspective?: string}, ) => Observable /** diff --git a/packages/sanity/src/core/preview/useValuePreview.ts b/packages/sanity/src/core/preview/useValuePreview.ts index 863dc2a54e7..5613d7c2582 100644 --- a/packages/sanity/src/core/preview/useValuePreview.ts +++ b/packages/sanity/src/core/preview/useValuePreview.ts @@ -28,20 +28,22 @@ function useDocumentPreview(props: { enabled?: boolean ordering?: SortOrdering schemaType?: SchemaType + perspective?: string value: unknown | undefined }): State { - const {enabled = true, ordering, schemaType, value: previewValue} = props || {} + const {enabled = true, perspective, ordering, schemaType, value: previewValue} = props || {} const {observeForPreview} = useDocumentPreviewStore() const observable = useMemo>(() => { if (!enabled || !previewValue || !schemaType) return of(PENDING_STATE) return observeForPreview(previewValue as Previewable, schemaType, { + perspective, viewOptions: {ordering: ordering}, }).pipe( map((event) => ({isLoading: false, value: event.snapshot || undefined})), catchError((error) => of({isLoading: false, error})), ) - }, [enabled, previewValue, schemaType, observeForPreview, ordering]) + }, [enabled, previewValue, schemaType, observeForPreview, perspective, ordering]) return useObservable(observable, INITIAL_STATE) } diff --git a/packages/sanity/src/core/preview/utils/getPreviewValueWithFallback.tsx b/packages/sanity/src/core/preview/utils/getPreviewValueWithFallback.tsx index 7f41bbbd800..50a292ff5ae 100644 --- a/packages/sanity/src/core/preview/utils/getPreviewValueWithFallback.tsx +++ b/packages/sanity/src/core/preview/utils/getPreviewValueWithFallback.tsx @@ -18,12 +18,14 @@ export const getPreviewValueWithFallback = ({ value, draft, published, + perspective, }: { value: SanityDocument draft?: Partial | PreviewValue | null published?: Partial | PreviewValue | null + perspective?: string }) => { - const snapshot = draft || published + const snapshot = perspective === 'published' ? published || draft : draft || published if (!snapshot) { return getMissingDocumentFallback(value) diff --git a/packages/sanity/src/core/search/common/types.ts b/packages/sanity/src/core/search/common/types.ts index fbf28299b09..3b4784f9b5f 100644 --- a/packages/sanity/src/core/search/common/types.ts +++ b/packages/sanity/src/core/search/common/types.ts @@ -106,6 +106,7 @@ export type SearchOptions = { sort?: SearchSort[] cursor?: string limit?: number + perspective?: string isCrossDataset?: boolean queryType?: 'prefixLast' | 'prefixNone' } @@ -188,6 +189,7 @@ export type TextSearchParams = { * Result ordering. */ order?: TextSearchOrder[] + perspective?: string } export type TextSearchResponse> = { diff --git a/packages/sanity/src/core/search/text-search/createTextSearch.ts b/packages/sanity/src/core/search/text-search/createTextSearch.ts index a005d7eefb3..94ac0d3eabb 100644 --- a/packages/sanity/src/core/search/text-search/createTextSearch.ts +++ b/packages/sanity/src/core/search/text-search/createTextSearch.ts @@ -146,6 +146,7 @@ export const createTextSearch: SearchStrategyFactory = ( ].filter((baseFilter): baseFilter is string => Boolean(baseFilter)) const textSearchParams: TextSearchParams = { + perspective: searchOptions.perspective, query: { string: getQueryString(searchTerms.query, searchOptions), }, diff --git a/packages/sanity/src/core/search/weighted/createSearchQuery.ts b/packages/sanity/src/core/search/weighted/createSearchQuery.ts index 15b924da74d..b6a1430695f 100644 --- a/packages/sanity/src/core/search/weighted/createSearchQuery.ts +++ b/packages/sanity/src/core/search/weighted/createSearchQuery.ts @@ -187,7 +187,7 @@ export function createSearchQuery( __limit: limit, ...(params || {}), }, - options: {tag}, + options: {tag, perspective: searchOpts.perspective}, searchSpec: specs, terms, } diff --git a/packages/sanity/src/core/studio/components/navbar/StudioNavbar.tsx b/packages/sanity/src/core/studio/components/navbar/StudioNavbar.tsx index d0355c94a37..0246ff7d186 100644 --- a/packages/sanity/src/core/studio/components/navbar/StudioNavbar.tsx +++ b/packages/sanity/src/core/studio/components/navbar/StudioNavbar.tsx @@ -8,11 +8,20 @@ import { Layer, LayerProvider, PortalProvider, + Select, useMediaIndex, } from '@sanity/ui' -import {useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react' +import { + type ChangeEvent, + useCallback, + useContext, + useEffect, + useMemo, + useRef, + useState, +} from 'react' import {NavbarContext} from 'sanity/_singletons' -import {type RouterState, useRouterState} from 'sanity/router' +import {type RouterState, useRouter, useRouterState} from 'sanity/router' import {styled} from 'styled-components' import {Button, TooltipDelayGroupProvider} from '../../../../ui-components' @@ -68,6 +77,7 @@ export function StudioNavbar(props: Omit) { } = props const {name, tools} = useWorkspace() + const router = useRouter() const routerState = useRouterState() const mediaIndex = useMediaIndex() const activeToolName = typeof routerState.tool === 'string' ? routerState.tool : undefined @@ -158,6 +168,15 @@ export function StudioNavbar(props: Omit) { setDrawerOpen(true) }, []) + const release = useMemo(() => router.stickyParams.release, [router.stickyParams]) + + const handleReleaseChange = useCallback( + (element: ChangeEvent) => { + router.navigateStickyParam('perspective', element.currentTarget.value || '') + }, + [router], + ) + const actionNodes = useMemo(() => { if (!shouldRender.tools) return null @@ -218,6 +237,17 @@ export function StudioNavbar(props: Omit) { {!shouldRender.searchFullscreen && ( )} + + {/* TODO: Fix */} + + diff --git a/packages/sanity/src/structure/components/paneItem/PaneItem.tsx b/packages/sanity/src/structure/components/paneItem/PaneItem.tsx index ddaa8d220be..ee178d992f7 100644 --- a/packages/sanity/src/structure/components/paneItem/PaneItem.tsx +++ b/packages/sanity/src/structure/components/paneItem/PaneItem.tsx @@ -24,6 +24,7 @@ import { useDocumentPreviewStore, useSchema, } from 'sanity' +import {useRouter} from 'sanity/router' import {MissingSchemaType} from '../MissingSchemaType' import {usePaneRouter} from '../paneRouter' @@ -76,6 +77,7 @@ export function PaneItem(props: PaneItemProps) { const schema = useSchema() const documentPreviewStore = useDocumentPreviewStore() const {ChildLink} = usePaneRouter() + const perspective = useRouter().stickyParams.perspective const documentPresence = useDocumentPresence(id) const hasSchemaType = Boolean(schemaType && schemaType.name && schema.get(schemaType.name)) const [clicked, setClicked] = useState(false) @@ -88,6 +90,7 @@ export function PaneItem(props: PaneItemProps) { return ( ) }, [ - documentPreviewStore, - hasSchemaType, + value, icon, - layout, schemaType, title, - value, + hasSchemaType, + perspective, + documentPreviewStore, + layout, documentPresence, ]) diff --git a/packages/sanity/src/structure/components/paneItem/PaneItemPreview.tsx b/packages/sanity/src/structure/components/paneItem/PaneItemPreview.tsx index be72c41b740..417059b399f 100644 --- a/packages/sanity/src/structure/components/paneItem/PaneItemPreview.tsx +++ b/packages/sanity/src/structure/components/paneItem/PaneItemPreview.tsx @@ -21,6 +21,7 @@ import {TooltipDelayGroupProvider} from '../../../ui-components' export interface PaneItemPreviewProps { documentPreviewStore: DocumentPreviewStore icon: ComponentType | false + perspective?: string layout: GeneralPreviewLayoutKey presence?: DocumentPresence[] schemaType: SchemaType @@ -35,7 +36,7 @@ export interface PaneItemPreviewProps { * and are rendered by ``. */ export function PaneItemPreview(props: PaneItemPreviewProps) { - const {icon, layout, presence, schemaType, value} = props + const {icon, layout, perspective, presence, schemaType, value} = props const title = (isRecord(value.title) && isValidElement(value.title)) || isString(value.title) || @@ -66,7 +67,7 @@ export function PaneItemPreview(props: PaneItemPreviewProps) { return ( { inspectors: inspectorsResolver, }, } = useSource() + const {stickyParams} = useRouter() const presenceStore = usePresenceStore() const paneRouter = usePaneRouter() const setPaneParams = paneRouter.setParams @@ -113,13 +115,20 @@ export const DocumentPaneProvider = memo((props: DocumentPaneProviderProps) => { templateName, templateParams, }) + const initialValue = useUnique(initialValueRaw) const {patch} = useDocumentOperation(documentId, documentType) const editState = useEditState(documentId, documentType) const {validation: validationRaw} = useValidationStatus(documentId, documentType) const connectionState = useConnectionState(documentId, documentType) const schemaType = schema.get(documentType) as ObjectSchemaType | undefined - const value: SanityDocumentLike = editState?.draft || editState?.published || initialValue.value + + const perspective = stickyParams.perspective + + const value: SanityDocumentLike = + (perspective === 'published' + ? editState.published || editState.draft + : editState?.draft || editState?.published) || initialValue.value const [isDeleting, setIsDeleting] = useState(false) // Resolve document actions @@ -522,6 +531,7 @@ export const DocumentPaneProvider = memo((props: DocumentPaneProviderProps) => { const isLiveEditAndDraft = Boolean(liveEdit && editState.draft) return ( + !!perspective || !ready || revTime !== null || hasNoPermission || @@ -542,6 +552,7 @@ export const DocumentPaneProvider = memo((props: DocumentPaneProviderProps) => { editState.transactionSyncLock?.enabled, editState.draft, liveEdit, + perspective, ready, revTime, isDeleting, diff --git a/packages/sanity/src/structure/panes/documentList/DocumentListPane.tsx b/packages/sanity/src/structure/panes/documentList/DocumentListPane.tsx index d50b3562041..6f16c5b5269 100644 --- a/packages/sanity/src/structure/panes/documentList/DocumentListPane.tsx +++ b/packages/sanity/src/structure/panes/documentList/DocumentListPane.tsx @@ -10,6 +10,7 @@ import { useTranslation, useUnique, } from 'sanity' +import {useRouter} from 'sanity/router' import {keyframes, styled} from 'styled-components' import {structureLocaleNamespace} from '../../i18n' @@ -74,6 +75,8 @@ export const DocumentListPane = memo(function DocumentListPane(props: DocumentLi const {childItemId, isActive, pane, paneKey, sortOrder: sortOrderRaw, layout} = props const schema = useSchema() + const perspective = useRouter().stickyParams.perspective + const {displayOptions, options} = pane const {apiVersion, filter} = options const params = useShallowUnique(options.params || EMPTY_RECORD) @@ -103,6 +106,7 @@ export const DocumentListPane = memo(function DocumentListPane(props: DocumentLi useDocumentList({ apiVersion, filter, + perspective, params, searchQuery: searchQuery?.trim(), sortOrder, diff --git a/packages/sanity/src/structure/panes/documentList/listenSearchQuery.ts b/packages/sanity/src/structure/panes/documentList/listenSearchQuery.ts index 2c0dcbe5dc9..377b33ab404 100644 --- a/packages/sanity/src/structure/panes/documentList/listenSearchQuery.ts +++ b/packages/sanity/src/structure/panes/documentList/listenSearchQuery.ts @@ -34,6 +34,7 @@ interface ListenQueryOptions { schema: Schema searchQuery: string sort: SortOrder + perspective?: string staticTypeNames?: string[] | null maxFieldDepth?: number enableLegacySearch?: boolean @@ -51,6 +52,7 @@ export function listenSearchQuery(options: ListenQueryOptions): Observable searchQuery: string | null sortOrder?: SortOrder @@ -60,7 +61,14 @@ interface UseDocumentListHookValue extends DocumentListState { * @internal */ export function useDocumentList(opts: UseDocumentListOpts): UseDocumentListHookValue { - const {filter: searchFilter, params: paramsProp, sortOrder, searchQuery, apiVersion} = opts + const { + filter: searchFilter, + params: paramsProp, + sortOrder, + searchQuery, + perspective, + apiVersion, + } = opts const client = useClient({ ...DEFAULT_STUDIO_CLIENT_OPTIONS, apiVersion: apiVersion || DEFAULT_STUDIO_CLIENT_OPTIONS.apiVersion, @@ -85,6 +93,7 @@ export function useDocumentList(opts: UseDocumentListOpts): UseDocumentListHookV limit: PARTIAL_PAGE_LIMIT, params: paramsProp, schema, + perspective, searchQuery: searchQuery || '', sort: sortOrder || DEFAULT_ORDERING, staticTypeNames: typeNameFromFilter, @@ -166,6 +175,7 @@ export function useDocumentList(opts: UseDocumentListOpts): UseDocumentListHookV searchFilter, paramsProp, schema, + perspective, searchQuery, sortOrder, typeNameFromFilter, From 5d72b8cb26e4a7fc82f63ff0944c6c0de2d1fac5 Mon Sep 17 00:00:00 2001 From: Ash Date: Fri, 21 Jun 2024 12:38:46 +0100 Subject: [PATCH 005/121] feat(sanity): include `_version` field in search results --- .../sanity/src/core/search/text-search/createTextSearch.ts | 4 +++- packages/sanity/src/core/search/weighted/createSearchQuery.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/sanity/src/core/search/text-search/createTextSearch.ts b/packages/sanity/src/core/search/text-search/createTextSearch.ts index 94ac0d3eabb..34b249bcdc2 100644 --- a/packages/sanity/src/core/search/text-search/createTextSearch.ts +++ b/packages/sanity/src/core/search/text-search/createTextSearch.ts @@ -158,7 +158,9 @@ export const createTextSearch: SearchStrategyFactory = ( }, types: getDocumentTypeConfiguration(searchOptions, searchTerms), ...(searchOptions.sort ? {order: getOrder(searchOptions.sort)} : {}), - includeAttributes: ['_id', '_type'], + // Note: Text Search API does not currently expose fields containing an empty object, so + // we're not yet able to retrieve `_version` here. + includeAttributes: ['_id', '_type', '_version'], fromCursor: searchOptions.cursor, limit: searchOptions.limit ?? DEFAULT_LIMIT, } diff --git a/packages/sanity/src/core/search/weighted/createSearchQuery.ts b/packages/sanity/src/core/search/weighted/createSearchQuery.ts index b6a1430695f..2e7124fe0a7 100644 --- a/packages/sanity/src/core/search/weighted/createSearchQuery.ts +++ b/packages/sanity/src/core/search/weighted/createSearchQuery.ts @@ -147,7 +147,7 @@ export function createSearchQuery( // Default to `_id asc` (GROQ default) if no search sort is provided const sortOrder = toOrderClause(searchOpts?.sort || [{field: '_id', direction: 'asc'}]) - const projectionFields = ['_type', '_id'] + const projectionFields = ['_type', '_id', '_version'] const selection = selections.length > 0 ? `...select(${selections.join(',\n')})` : '' const finalProjection = projectionFields.join(', ') + (selection ? `, ${selection}` : '') From 4ec4c69e677bf236d5db329e37fb1163e0cf4607 Mon Sep 17 00:00:00 2001 From: Ash Date: Fri, 21 Jun 2024 12:40:05 +0100 Subject: [PATCH 006/121] feat(types): add `_version` field to `SanityDocument` and `SanityDocumentLike` --- packages/@sanity/types/src/documents/types.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/@sanity/types/src/documents/types.ts b/packages/@sanity/types/src/documents/types.ts index 50a87fba5a4..c6ba8e53235 100644 --- a/packages/@sanity/types/src/documents/types.ts +++ b/packages/@sanity/types/src/documents/types.ts @@ -5,6 +5,7 @@ export interface SanityDocument { _createdAt: string _updatedAt: string _rev: string + _version?: Record [key: string]: unknown } @@ -21,6 +22,7 @@ export interface SanityDocumentLike { _createdAt?: string _updatedAt?: string _rev?: string + _version?: Record [key: string]: unknown } From 55ec5e990edb0b86f340cd6d71a684bedc0e10d6 Mon Sep 17 00:00:00 2001 From: Ash Date: Mon, 24 Jun 2024 11:50:58 +0100 Subject: [PATCH 007/121] fix(sanity): global perspective state restoration --- .../sanity/src/core/studio/components/navbar/StudioNavbar.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/sanity/src/core/studio/components/navbar/StudioNavbar.tsx b/packages/sanity/src/core/studio/components/navbar/StudioNavbar.tsx index 0246ff7d186..56c814f943a 100644 --- a/packages/sanity/src/core/studio/components/navbar/StudioNavbar.tsx +++ b/packages/sanity/src/core/studio/components/navbar/StudioNavbar.tsx @@ -168,7 +168,7 @@ export function StudioNavbar(props: Omit) { setDrawerOpen(true) }, []) - const release = useMemo(() => router.stickyParams.release, [router.stickyParams]) + const perspective = useMemo(() => router.stickyParams.perspective, [router.stickyParams]) const handleReleaseChange = useCallback( (element: ChangeEvent) => { @@ -239,7 +239,7 @@ export function StudioNavbar(props: Omit) { )} {/* TODO: Fix */} - {/* eslint-disable-next-line i18next/no-literal-string */} {/* eslint-disable-next-line i18next/no-literal-string */} From e937af9f6e69391f54f1f85c0903abd974e0dcb3 Mon Sep 17 00:00:00 2001 From: Ash Date: Mon, 24 Jun 2024 11:51:39 +0100 Subject: [PATCH 008/121] feat(sanity): hardcode `summerDrop` and `autumnDrop` bundles --- .../sanity/src/core/studio/components/navbar/StudioNavbar.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/sanity/src/core/studio/components/navbar/StudioNavbar.tsx b/packages/sanity/src/core/studio/components/navbar/StudioNavbar.tsx index 56c814f943a..b9e51a7576f 100644 --- a/packages/sanity/src/core/studio/components/navbar/StudioNavbar.tsx +++ b/packages/sanity/src/core/studio/components/navbar/StudioNavbar.tsx @@ -246,6 +246,10 @@ export function StudioNavbar(props: Omit) { {/* eslint-disable-next-line i18next/no-literal-string */} + {/* eslint-disable-next-line i18next/no-literal-string */} + + {/* eslint-disable-next-line i18next/no-literal-string */} + From aa542ca2cabb71fded5a570bf9ed6154c55bfe7e Mon Sep 17 00:00:00 2001 From: Ash Date: Wed, 22 May 2024 10:53:04 +0100 Subject: [PATCH 009/121] wip: allow pairs to include multiple drafts --- .../core/form/studio/formBuilderValueStore.ts | 6 ++-- .../document/document-pair/checkoutPair.ts | 28 +++++++++++++------ .../document-pair/consistencyStatus.ts | 7 +++-- .../document/document-pair/documentEvents.ts | 4 ++- .../document/document-pair/editState.ts | 5 +++- .../document/document-pair/operationArgs.ts | 6 ++-- .../document-pair/operations/delete.ts | 4 ++- .../operations/discardChanges.ts | 4 ++- .../document-pair/operations/patch.ts | 4 ++- .../document-pair/operations/publish.ts | 4 ++- .../document-pair/operations/restore.ts | 6 +++- .../document-pair/operations/unpublish.ts | 4 ++- .../document/document-pair/remoteSnapshots.ts | 4 ++- .../document-pair/serverOperations/delete.ts | 8 +++--- .../serverOperations/discardChanges.ts | 6 ++-- .../document-pair/serverOperations/patch.ts | 9 ++++-- .../document-pair/serverOperations/publish.ts | 5 +++- .../document-pair/serverOperations/restore.ts | 8 +++++- .../serverOperations/unpublish.ts | 12 +++++--- .../document/document-pair/snapshotPair.ts | 7 +++-- .../document/document-pair/validation.ts | 4 +-- .../store/_legacy/document/document-store.ts | 2 +- .../store/_legacy/document/getPairListener.ts | 12 ++++---- .../src/core/store/_legacy/document/types.ts | 4 ++- .../_legacy/grants/documentPairPermissions.ts | 7 +++-- .../store/_legacy/history/useTimelineStore.ts | 2 +- 26 files changed, 118 insertions(+), 54 deletions(-) diff --git a/packages/sanity/src/core/form/studio/formBuilderValueStore.ts b/packages/sanity/src/core/form/studio/formBuilderValueStore.ts index 7b487ef3d22..1916cd6702c 100644 --- a/packages/sanity/src/core/form/studio/formBuilderValueStore.ts +++ b/packages/sanity/src/core/form/studio/formBuilderValueStore.ts @@ -81,6 +81,7 @@ function wrap(document: DocumentVersion) { } let hasWarned = false +// TODO: Rename -> `checkoutBundle` export function checkoutPair(documentStore: DocumentStore, idPair: IdPair) { if (!hasWarned) { // eslint-disable-next-line no-console @@ -90,10 +91,11 @@ export function checkoutPair(documentStore: DocumentStore, idPair: IdPair) { hasWarned = true } - const {draft, published} = documentStore.checkoutPair(idPair) + const {drafts, published} = documentStore.checkoutPair(idPair) return { - draft: wrap(draft), + drafts: drafts.map(wrap), + // TODO: Rename -> `public` published: wrap(published), } } diff --git a/packages/sanity/src/core/store/_legacy/document/document-pair/checkoutPair.ts b/packages/sanity/src/core/store/_legacy/document/document-pair/checkoutPair.ts index fda98a66c1f..10ef4f3f215 100644 --- a/packages/sanity/src/core/store/_legacy/document/document-pair/checkoutPair.ts +++ b/packages/sanity/src/core/store/_legacy/document/document-pair/checkoutPair.ts @@ -60,11 +60,13 @@ export interface DocumentVersion { /** * @hidden * @beta */ +// TODO: Rename -> `Bundle` export interface Pair { /** @internal */ transactionsPendingEvents$: Observable + // TODO: Rename -> `public` published: DocumentVersion - draft: DocumentVersion + drafts: DocumentVersion[] complete: () => void } @@ -114,9 +116,12 @@ function toActions(idPair: IdPair, mutationParams: Mutation['params']): Action[] } } if (mutations.patch) { + // TODO: Should be dynamic + const draftIndex = 0 return { actionType: 'sanity.action.document.edit', - draftId: idPair.draftId, + draftId: idPair.draftIds[draftIndex], + // TODO: Rename -> `publicId` publishedId: idPair.publishedId, patch: omit(mutations.patch, 'id'), } @@ -196,12 +201,13 @@ function submitCommitRequest( } /** @internal */ +// TODO: Rename -> `checkoutBundle` export function checkoutPair( client: SanityClient, idPair: IdPair, serverActionsEnabled: Observable, ): Pair { - const {publishedId, draftId} = idPair + const {publishedId, draftIds} = idPair const listenerEventsConnector = new Subject() const listenerEvents$ = getPairListener(client, idPair).pipe( @@ -212,11 +218,11 @@ export function checkoutPair( filter((ev) => ev.type === 'reconnect'), ) as Observable - const draft = createBufferedDocument( - draftId, - listenerEvents$.pipe(filter(isMutationEventForDocId(draftId))), + const drafts = draftIds.map((draftId) => + createBufferedDocument(draftId, listenerEvents$.pipe(filter(isMutationEventForDocId(draftId)))), ) + // TODO: Rename -> `public` const published = createBufferedDocument( publishedId, listenerEvents$.pipe(filter(isMutationEventForDocId(publishedId))), @@ -227,7 +233,10 @@ export function checkoutPair( filter((ev): ev is PendingMutationsEvent => ev.type === 'pending'), ) - const commits$ = merge(draft.commitRequest$, published.commitRequest$).pipe( + const commits$ = merge( + ...drafts.map((draft) => draft.commitRequest$), + published.commitRequest$, + ).pipe( mergeMap((commitRequest) => serverActionsEnabled.pipe( take(1), @@ -242,12 +251,13 @@ export function checkoutPair( return { transactionsPendingEvents$, - draft: { + drafts: drafts.map((draft) => ({ ...draft, events: merge(commits$, reconnect$, draft.events).pipe(map(setVersion('draft'))), consistency$: draft.consistency$, remoteSnapshot$: draft.remoteSnapshot$.pipe(map(setVersion('draft'))), - }, + })), + // TODO: Rename -> `public` published: { ...published, events: merge(commits$, reconnect$, published.events).pipe(map(setVersion('published'))), diff --git a/packages/sanity/src/core/store/_legacy/document/document-pair/consistencyStatus.ts b/packages/sanity/src/core/store/_legacy/document/document-pair/consistencyStatus.ts index f101dc81c2f..9b548785852 100644 --- a/packages/sanity/src/core/store/_legacy/document/document-pair/consistencyStatus.ts +++ b/packages/sanity/src/core/store/_legacy/document/document-pair/consistencyStatus.ts @@ -21,9 +21,12 @@ export const consistencyStatus: ( typeName: string, serverActionsEnabled: Observable, ) => { + // TODO: Should be dynamic + const draftIndex = 0 + // TODO: Rename -> `memoizedBundle` return memoizedPair(client, idPair, typeName, serverActionsEnabled).pipe( - switchMap(({draft, published}) => - combineLatest([draft.consistency$, published.consistency$]), + switchMap(({drafts, published}) => + combineLatest([drafts[draftIndex].consistency$, published.consistency$]), ), map( ([draftIsConsistent, publishedIsConsistent]) => draftIsConsistent && publishedIsConsistent, diff --git a/packages/sanity/src/core/store/_legacy/document/document-pair/documentEvents.ts b/packages/sanity/src/core/store/_legacy/document/document-pair/documentEvents.ts index 6e127a213cc..3026b0edc4b 100644 --- a/packages/sanity/src/core/store/_legacy/document/document-pair/documentEvents.ts +++ b/packages/sanity/src/core/store/_legacy/document/document-pair/documentEvents.ts @@ -17,8 +17,10 @@ export const documentEvents = memoize( typeName: string, serverActionsEnabled: Observable, ): Observable => { + // TODO: Should be dynamic + const draftIndex = 0 return memoizedPair(client, idPair, typeName, serverActionsEnabled).pipe( - switchMap(({draft, published}) => merge(draft.events, published.events)), + switchMap(({drafts, published}) => merge(drafts[draftIndex].events, published.events)), ) }, memoizeKeyGen, diff --git a/packages/sanity/src/core/store/_legacy/document/document-pair/editState.ts b/packages/sanity/src/core/store/_legacy/document/document-pair/editState.ts index adba9e7588f..14d09d8ec88 100644 --- a/packages/sanity/src/core/store/_legacy/document/document-pair/editState.ts +++ b/packages/sanity/src/core/store/_legacy/document/document-pair/editState.ts @@ -21,6 +21,7 @@ export interface EditStateFor { type: string transactionSyncLock: TransactionSyncLockState | null draft: SanityDocument | null + // TODO: Rename -> `public` published: SanityDocument | null liveEdit: boolean ready: boolean @@ -40,10 +41,12 @@ export const editState = memoize( typeName: string, ): Observable => { const liveEdit = isLiveEditEnabled(ctx.schema, typeName) + // TODO: Should be dynamic + const draftIndex = 0 return snapshotPair(ctx.client, idPair, typeName, ctx.serverActionsEnabled).pipe( switchMap((versions) => combineLatest([ - versions.draft.snapshots$, + versions.drafts[draftIndex].snapshots$, versions.published.snapshots$, versions.transactionsPendingEvents$.pipe( map((ev: PendingMutationsEvent) => (ev.phase === 'begin' ? LOCKED : NOT_LOCKED)), diff --git a/packages/sanity/src/core/store/_legacy/document/document-pair/operationArgs.ts b/packages/sanity/src/core/store/_legacy/document/document-pair/operationArgs.ts index de0b0b5ab29..b301fdc8412 100644 --- a/packages/sanity/src/core/store/_legacy/document/document-pair/operationArgs.ts +++ b/packages/sanity/src/core/store/_legacy/document/document-pair/operationArgs.ts @@ -24,10 +24,12 @@ export const operationArgs = memoize( idPair: IdPair, typeName: string, ): Observable => { + // TODO: Should be dynamic + const draftIndex = 0 return snapshotPair(ctx.client, idPair, typeName, ctx.serverActionsEnabled).pipe( switchMap((versions) => combineLatest([ - versions.draft.snapshots$, + versions.drafts[draftIndex].snapshots$, versions.published.snapshots$, ctx.serverActionsEnabled, ]).pipe( @@ -38,7 +40,7 @@ export const operationArgs = memoize( idPair, typeName, snapshots: {draft, published}, - draft: versions.draft, + draft: versions.drafts[draftIndex], published: versions.published, }), ), diff --git a/packages/sanity/src/core/store/_legacy/document/document-pair/operations/delete.ts b/packages/sanity/src/core/store/_legacy/document/document-pair/operations/delete.ts index f547d8e1c42..31dc7e18c99 100644 --- a/packages/sanity/src/core/store/_legacy/document/document-pair/operations/delete.ts +++ b/packages/sanity/src/core/store/_legacy/document/document-pair/operations/delete.ts @@ -10,7 +10,9 @@ export const del: OperationImpl<[], 'NOTHING_TO_DELETE'> = { return tx.commit({tag: 'document.delete'}) } - return tx.delete(idPair.draftId).commit({ + idPair.draftIds.forEach((draftId) => tx.delete(draftId)) + + return tx.commit({ tag: 'document.delete', // this disables referential integrity for cross-dataset references. we // have this set because we warn against deletes in the `ConfirmDeleteDialog` diff --git a/packages/sanity/src/core/store/_legacy/document/document-pair/operations/discardChanges.ts b/packages/sanity/src/core/store/_legacy/document/document-pair/operations/discardChanges.ts index ac698ec3718..c6ef878ecb2 100644 --- a/packages/sanity/src/core/store/_legacy/document/document-pair/operations/discardChanges.ts +++ b/packages/sanity/src/core/store/_legacy/document/document-pair/operations/discardChanges.ts @@ -13,9 +13,11 @@ export const discardChanges: OperationImpl<[], DisabledReason> = { return false }, execute: ({client, idPair}) => { + // TODO: Should be dynamic + const draftIndex = 0 return client.observable .transaction() - .delete(idPair.draftId) + .delete(idPair.draftIds[draftIndex]) .commit({tag: 'document.discard-changes'}) }, } diff --git a/packages/sanity/src/core/store/_legacy/document/document-pair/operations/patch.ts b/packages/sanity/src/core/store/_legacy/document/document-pair/operations/patch.ts index f67b96767ec..0d0d630627b 100644 --- a/packages/sanity/src/core/store/_legacy/document/document-pair/operations/patch.ts +++ b/packages/sanity/src/core/store/_legacy/document/document-pair/operations/patch.ts @@ -19,11 +19,13 @@ export const patch: OperationImpl<[patches: any[], initialDocument?: Record = { }) } - tx.delete(idPair.draftId) + // TODO: Should be dynamic + const draftIndex = 0 + tx.delete(idPair.draftIds[draftIndex]) return tx.commit({tag: 'document.publish', visibility: 'async'}) }, diff --git a/packages/sanity/src/core/store/_legacy/document/document-pair/operations/restore.ts b/packages/sanity/src/core/store/_legacy/document/document-pair/operations/restore.ts index 224de4e1a8b..9df8ff6d4b5 100644 --- a/packages/sanity/src/core/store/_legacy/document/document-pair/operations/restore.ts +++ b/packages/sanity/src/core/store/_legacy/document/document-pair/operations/restore.ts @@ -4,7 +4,11 @@ import {type OperationImpl} from './types' export const restore: OperationImpl<[fromRevision: string]> = { disabled: (): false => false, execute: ({historyStore, schema, idPair, typeName}, fromRevision: string) => { - const targetId = isLiveEditEnabled(schema, typeName) ? idPair.publishedId : idPair.draftId + // TODO: Should be dynamic + const draftIndex = 0 + const targetId = isLiveEditEnabled(schema, typeName) + ? idPair.publishedId + : idPair.draftIds[draftIndex] return historyStore.restore(idPair.publishedId, targetId, fromRevision) }, } diff --git a/packages/sanity/src/core/store/_legacy/document/document-pair/operations/unpublish.ts b/packages/sanity/src/core/store/_legacy/document/document-pair/operations/unpublish.ts index 232d070d809..c670e05d17d 100644 --- a/packages/sanity/src/core/store/_legacy/document/document-pair/operations/unpublish.ts +++ b/packages/sanity/src/core/store/_legacy/document/document-pair/operations/unpublish.ts @@ -16,9 +16,11 @@ export const unpublish: OperationImpl<[], DisabledReason> = { let tx = client.observable.transaction().delete(idPair.publishedId) if (snapshots.published) { + // TODO: Should be dynamic + const draftIndex = 0 tx = tx.createIfNotExists({ ...omit(snapshots.published, '_updatedAt'), - _id: idPair.draftId, + _id: idPair.draftIds[draftIndex], _type: snapshots.published._type, }) } diff --git a/packages/sanity/src/core/store/_legacy/document/document-pair/remoteSnapshots.ts b/packages/sanity/src/core/store/_legacy/document/document-pair/remoteSnapshots.ts index 91db8f91fde..dcb08ef24ba 100644 --- a/packages/sanity/src/core/store/_legacy/document/document-pair/remoteSnapshots.ts +++ b/packages/sanity/src/core/store/_legacy/document/document-pair/remoteSnapshots.ts @@ -17,7 +17,9 @@ export const remoteSnapshots = memoize( serverActionsEnabled: Observable, ): Observable => { return memoizedPair(client, idPair, typeName, serverActionsEnabled).pipe( - switchMap(({published, draft}) => merge(published.remoteSnapshot$, draft.remoteSnapshot$)), + switchMap(({published, drafts}) => + merge(published.remoteSnapshot$, ...drafts.map((draft) => draft.remoteSnapshot$)), + ), ) }, memoizeKeyGen, diff --git a/packages/sanity/src/core/store/_legacy/document/document-pair/serverOperations/delete.ts b/packages/sanity/src/core/store/_legacy/document/document-pair/serverOperations/delete.ts index 4cc1f1c4796..f51b04be4a9 100644 --- a/packages/sanity/src/core/store/_legacy/document/document-pair/serverOperations/delete.ts +++ b/packages/sanity/src/core/store/_legacy/document/document-pair/serverOperations/delete.ts @@ -13,10 +13,10 @@ export const del: OperationImpl<[], 'NOTHING_TO_DELETE'> = { //the delete action requires a published doc -- discard if not present if (!snapshots.published) { return actionsApiClient(client).observable.action( - { + idPair.draftIds.map((draftId) => ({ actionType: 'sanity.action.document.discard', - draftId: idPair.draftId, - }, + draftId, + })), {tag: 'document.delete'}, ) } @@ -24,7 +24,7 @@ export const del: OperationImpl<[], 'NOTHING_TO_DELETE'> = { return actionsApiClient(client).observable.action( { actionType: 'sanity.action.document.delete', - includeDrafts: snapshots.draft ? [idPair.draftId] : [], + includeDrafts: idPair.draftIds, publishedId: idPair.publishedId, }, { diff --git a/packages/sanity/src/core/store/_legacy/document/document-pair/serverOperations/discardChanges.ts b/packages/sanity/src/core/store/_legacy/document/document-pair/serverOperations/discardChanges.ts index 504c7db39a3..e2de59e8cd4 100644 --- a/packages/sanity/src/core/store/_legacy/document/document-pair/serverOperations/discardChanges.ts +++ b/packages/sanity/src/core/store/_legacy/document/document-pair/serverOperations/discardChanges.ts @@ -15,10 +15,10 @@ export const discardChanges: OperationImpl<[], DisabledReason> = { }, execute: ({client, idPair}) => { return actionsApiClient(client).observable.action( - { + idPair.draftIds.map((draftId) => ({ actionType: 'sanity.action.document.discard', - draftId: idPair.draftId, - }, + draftId, + })), {tag: 'document.discard-changes'}, ) }, diff --git a/packages/sanity/src/core/store/_legacy/document/document-pair/serverOperations/patch.ts b/packages/sanity/src/core/store/_legacy/document/document-pair/serverOperations/patch.ts index 5592de150bf..28063f783fd 100644 --- a/packages/sanity/src/core/store/_legacy/document/document-pair/serverOperations/patch.ts +++ b/packages/sanity/src/core/store/_legacy/document/document-pair/serverOperations/patch.ts @@ -30,19 +30,24 @@ export const patch: OperationImpl<[patches: any[], initialDocument?: Record = { throw new Error('cannot execute "publish" when draft is missing') } + // TODO: Should be dynamic + const draftIndex = 0 + return actionsApiClient(client).observable.action( { actionType: 'sanity.action.document.publish', - draftId: idPair.draftId, + draftId: idPair.draftIds[draftIndex], publishedId: idPair.publishedId, // Optimistic locking using `ifPublishedRevisionId` ensures that concurrent publish action // invocations do not override each other. diff --git a/packages/sanity/src/core/store/_legacy/document/document-pair/serverOperations/restore.ts b/packages/sanity/src/core/store/_legacy/document/document-pair/serverOperations/restore.ts index 3ea6ec2d3f6..2657addbb5c 100644 --- a/packages/sanity/src/core/store/_legacy/document/document-pair/serverOperations/restore.ts +++ b/packages/sanity/src/core/store/_legacy/document/document-pair/serverOperations/restore.ts @@ -4,7 +4,13 @@ import {isLiveEditEnabled} from '../utils/isLiveEditEnabled' export const restore: OperationImpl<[fromRevision: string]> = { disabled: (): false => false, execute: ({snapshots, historyStore, schema, idPair, typeName}, fromRevision: string) => { - const targetId = isLiveEditEnabled(schema, typeName) ? idPair.publishedId : idPair.draftId + // TODO: Should be dynamic + const draftIndex = 0 + + const targetId = isLiveEditEnabled(schema, typeName) + ? idPair.publishedId + : idPair.draftIds[draftIndex] + return historyStore.restore(idPair.publishedId, targetId, fromRevision, { fromDeleted: !snapshots.draft && !snapshots.published, useServerDocumentActions: true, diff --git a/packages/sanity/src/core/store/_legacy/document/document-pair/serverOperations/unpublish.ts b/packages/sanity/src/core/store/_legacy/document/document-pair/serverOperations/unpublish.ts index f67ffe05104..8a141446b25 100644 --- a/packages/sanity/src/core/store/_legacy/document/document-pair/serverOperations/unpublish.ts +++ b/packages/sanity/src/core/store/_legacy/document/document-pair/serverOperations/unpublish.ts @@ -11,12 +11,15 @@ export const unpublish: OperationImpl<[], DisabledReason> = { } return snapshots.published ? false : 'NOT_PUBLISHED' }, - execute: ({client, idPair}) => - actionsApiClient(client).observable.action( + execute: ({client, idPair}) => { + // TODO: Should be dynamic + const draftIndex = 0 + + return actionsApiClient(client).observable.action( { // This operation is run when "unpublish anyway" is clicked actionType: 'sanity.action.document.unpublish', - draftId: idPair.draftId, + draftId: idPair.draftIds[draftIndex], publishedId: idPair.publishedId, }, { @@ -26,5 +29,6 @@ export const unpublish: OperationImpl<[], DisabledReason> = { // UI. skipCrossDatasetReferenceValidation: true, }, - ), + ) + }, } diff --git a/packages/sanity/src/core/store/_legacy/document/document-pair/snapshotPair.ts b/packages/sanity/src/core/store/_legacy/document/document-pair/snapshotPair.ts index 0efc43a5461..fb9120d72b4 100644 --- a/packages/sanity/src/core/store/_legacy/document/document-pair/snapshotPair.ts +++ b/packages/sanity/src/core/store/_legacy/document/document-pair/snapshotPair.ts @@ -53,9 +53,10 @@ export interface DocumentVersionSnapshots { } /** @internal */ +// TODO: Rename interface SnapshotPair { transactionsPendingEvents$: Observable - draft: DocumentVersionSnapshots + drafts: DocumentVersionSnapshots[] published: DocumentVersionSnapshots } @@ -68,11 +69,11 @@ export const snapshotPair = memoize( serverActionsEnabled: Observable, ): Observable => { return memoizedPair(client, idPair, typeName, serverActionsEnabled).pipe( - map(({published, draft, transactionsPendingEvents$}): SnapshotPair => { + map(({published, drafts, transactionsPendingEvents$}): SnapshotPair => { return { transactionsPendingEvents$, published: withSnapshots(published), - draft: withSnapshots(draft), + drafts: drafts.map(withSnapshots), } }), publishReplay(1), diff --git a/packages/sanity/src/core/store/_legacy/document/document-pair/validation.ts b/packages/sanity/src/core/store/_legacy/document/document-pair/validation.ts index 8cf895fe80a..24c52c596a9 100644 --- a/packages/sanity/src/core/store/_legacy/document/document-pair/validation.ts +++ b/packages/sanity/src/core/store/_legacy/document/document-pair/validation.ts @@ -32,10 +32,10 @@ export const validation = memoize( i18n: LocaleSource serverActionsEnabled: Observable }, - {draftId, publishedId}: IdPair, + {draftIds, publishedId}: IdPair, typeName: string, ): Observable => { - const document$ = editState(ctx, {draftId, publishedId}, typeName).pipe( + const document$ = editState(ctx, {draftIds, publishedId}, typeName).pipe( map(({draft, published}) => draft || published), throttleTime(DOC_UPDATE_DELAY, asyncScheduler, {trailing: true}), distinctUntilChanged((prev, next) => { diff --git a/packages/sanity/src/core/store/_legacy/document/document-store.ts b/packages/sanity/src/core/store/_legacy/document/document-store.ts index 7fcc203c829..fedc0669728 100644 --- a/packages/sanity/src/core/store/_legacy/document/document-store.ts +++ b/packages/sanity/src/core/store/_legacy/document/document-store.ts @@ -41,7 +41,7 @@ function getIdPairFromPublished(publishedId: string): IdPair { throw new Error('editOpsOf does not expect a draft id.') } - return {publishedId, draftId: getDraftId(publishedId)} + return {publishedId, draftIds: [getDraftId(publishedId)]} } /** diff --git a/packages/sanity/src/core/store/_legacy/document/getPairListener.ts b/packages/sanity/src/core/store/_legacy/document/getPairListener.ts index aadbc5384c0..aef4ef6eadd 100644 --- a/packages/sanity/src/core/store/_legacy/document/getPairListener.ts +++ b/packages/sanity/src/core/store/_legacy/document/getPairListener.ts @@ -64,14 +64,16 @@ export function getPairListener( idPair: IdPair, options: PairListenerOptions = {}, ): Observable { - const {publishedId, draftId} = idPair + const {publishedId, draftIds} = idPair + // TODO: Should be dynamic + const draftIndex = 0 return defer( () => client.observable.listen( - `*[_id == $publishedId || _id == $draftId]`, + `*[_id == $publishedId || _id in($draftIds)]`, { publishedId, - draftId, + draftIds, }, { includeResult: false, @@ -85,7 +87,7 @@ export function getPairListener( event.type === 'welcome' ? fetchInitialDocumentSnapshots().pipe( concatMap((snapshots) => [ - createSnapshotEvent(draftId, snapshots.draft), + createSnapshotEvent(draftIds[draftIndex], snapshots.draft), createSnapshotEvent(publishedId, snapshots.published), ]), ) @@ -131,7 +133,7 @@ export function getPairListener( function fetchInitialDocumentSnapshots(): Observable { return client.observable - .getDocuments([draftId, publishedId], {tag: 'document.snapshots'}) + .getDocuments([...draftIds, publishedId], {tag: 'document.snapshots'}) .pipe( map(([draft, published]) => ({ draft, diff --git a/packages/sanity/src/core/store/_legacy/document/types.ts b/packages/sanity/src/core/store/_legacy/document/types.ts index 19e0b005ebe..0e07bd059d3 100644 --- a/packages/sanity/src/core/store/_legacy/document/types.ts +++ b/packages/sanity/src/core/store/_legacy/document/types.ts @@ -35,7 +35,9 @@ export interface PendingMutationsEvent { } /** @internal */ +// TODO: Rename -> `IdBundle` export interface IdPair { - draftId: string + draftIds: string[] + // TODO: Rename -> `publicId` publishedId: string } diff --git a/packages/sanity/src/core/store/_legacy/grants/documentPairPermissions.ts b/packages/sanity/src/core/store/_legacy/grants/documentPairPermissions.ts index 5c84b123398..cf1d8bc204a 100644 --- a/packages/sanity/src/core/store/_legacy/grants/documentPairPermissions.ts +++ b/packages/sanity/src/core/store/_legacy/grants/documentPairPermissions.ts @@ -199,14 +199,17 @@ export function getDocumentPairPermissions({ const liveEdit = Boolean(getSchemaType(schema, type).liveEdit) + // TODO: Should be dynamic + const draftIndex = 0 + return snapshotPair( client, - {draftId: getDraftId(id), publishedId: getPublishedId(id)}, + {draftIds: [getDraftId(id)], publishedId: getPublishedId(id)}, type, serverActionsEnabled, ).pipe( switchMap((pair) => - combineLatest([pair.draft.snapshots$, pair.published.snapshots$]).pipe( + combineLatest([pair.drafts[draftIndex].snapshots$, pair.published.snapshots$]).pipe( map(([draft, published]) => ({draft, published})), ), ), diff --git a/packages/sanity/src/core/store/_legacy/history/useTimelineStore.ts b/packages/sanity/src/core/store/_legacy/history/useTimelineStore.ts index 403a755fbb7..42dfc39d239 100644 --- a/packages/sanity/src/core/store/_legacy/history/useTimelineStore.ts +++ b/packages/sanity/src/core/store/_legacy/history/useTimelineStore.ts @@ -173,7 +173,7 @@ export function useTimelineStore({ if (!snapshotsSubscriptionRef.current) { snapshotsSubscriptionRef.current = remoteSnapshots( client, - {draftId: `drafts.${documentId}`, publishedId: documentId}, + {draftIds: [`drafts.${documentId}`], publishedId: documentId}, documentType, serverActionsEnabled, ).subscribe((ev: RemoteSnapshotVersionEvent) => { From 11148b65fa33410729ded15bc7fbe350894ee653 Mon Sep 17 00:00:00 2001 From: Ash Date: Mon, 24 Jun 2024 16:28:55 +0100 Subject: [PATCH 010/121] feat(sanity): support versioned document ids in `getPublishedId` function --- packages/sanity/src/core/util/draftUtils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/sanity/src/core/util/draftUtils.ts b/packages/sanity/src/core/util/draftUtils.ts index 763fe9c9d1b..54dab4cc995 100644 --- a/packages/sanity/src/core/util/draftUtils.ts +++ b/packages/sanity/src/core/util/draftUtils.ts @@ -14,7 +14,6 @@ export type PublishedId = Opaque /** @internal */ export const DRAFTS_FOLDER = 'drafts' -/** @internal */ export const VERSION_FOLDER = 'versions' const PATH_SEPARATOR = '.' const DRAFTS_PREFIX = `${DRAFTS_FOLDER}${PATH_SEPARATOR}` From 22db127ec738e79021a13b9ea23f34e4a853e567 Mon Sep 17 00:00:00 2001 From: Ash Date: Wed, 26 Jun 2024 14:34:46 +0100 Subject: [PATCH 011/121] feat(sanity): add `_version` field to preview observation --- packages/sanity/src/core/preview/documentPreviewStore.ts | 2 +- packages/sanity/src/core/preview/utils/getPreviewPaths.ts | 2 +- packages/sanity/src/core/preview/utils/prepareForPreview.ts | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/sanity/src/core/preview/documentPreviewStore.ts b/packages/sanity/src/core/preview/documentPreviewStore.ts index 76cd46d4630..e3dc3daaef8 100644 --- a/packages/sanity/src/core/preview/documentPreviewStore.ts +++ b/packages/sanity/src/core/preview/documentPreviewStore.ts @@ -133,7 +133,7 @@ export function createDocumentPreviewStore({ id: string, apiConfig?: ApiConfig, ): Observable { - return observePaths({_type: 'reference', _ref: id}, ['_type'], apiConfig).pipe( + return observePaths({_type: 'reference', _ref: id}, ['_type', '_version'], apiConfig).pipe( map((res) => (isRecord(res) && typeof res._type === 'string' ? res._type : undefined)), distinctUntilChanged(), ) diff --git a/packages/sanity/src/core/preview/utils/getPreviewPaths.ts b/packages/sanity/src/core/preview/utils/getPreviewPaths.ts index 5ef09e29fb2..ccb6c2af8b2 100644 --- a/packages/sanity/src/core/preview/utils/getPreviewPaths.ts +++ b/packages/sanity/src/core/preview/utils/getPreviewPaths.ts @@ -1,6 +1,6 @@ import {type PreviewableType, type PreviewPath} from '../types' -const DEFAULT_PREVIEW_PATHS: PreviewPath[] = [['_createdAt'], ['_updatedAt']] +const DEFAULT_PREVIEW_PATHS: PreviewPath[] = [['_createdAt'], ['_updatedAt'], ['_version']] /** @internal */ export function getPreviewPaths(preview: PreviewableType['preview']): PreviewPath[] | undefined { diff --git a/packages/sanity/src/core/preview/utils/prepareForPreview.ts b/packages/sanity/src/core/preview/utils/prepareForPreview.ts index f07f16f421b..aeb11dcb7d2 100644 --- a/packages/sanity/src/core/preview/utils/prepareForPreview.ts +++ b/packages/sanity/src/core/preview/utils/prepareForPreview.ts @@ -2,6 +2,7 @@ import { isTitledListValue, type PrepareViewOptions, type PreviewValue, + type SanityDocument, type SchemaType, type TitledListValue, } from '@sanity/types' @@ -13,7 +14,7 @@ import {type PreviewableType} from '../types' import {keysOf} from './keysOf' import {extractTextFromBlocks, isPortableTextPreviewValue} from './portableText' -const PRESERVE_KEYS = ['_id', '_type', '_upload', '_createdAt', '_updatedAt'] +const PRESERVE_KEYS = ['_id', '_type', '_upload', '_createdAt', '_updatedAt', '_version'] const EMPTY: never[] = [] type SelectedValue = Record @@ -263,7 +264,7 @@ export function prepareForPreview( rawValue: unknown, type: PreviewableType, viewOptions: PrepareViewOptions = {}, -): PreviewValue & {_createdAt?: string; _updatedAt?: string} { +): PreviewValue & {_createdAt?: string; _updatedAt?: string} & Pick { const hasCustomPrepare = typeof type.preview?.prepare === 'function' const selection: Record = type.preview?.select || {} const targetKeys = Object.keys(selection) From d376e7966f4f9888b9aceb8d5f4b93d82c32ef49 Mon Sep 17 00:00:00 2001 From: Ash Date: Wed, 26 Jun 2024 14:52:07 +0100 Subject: [PATCH 012/121] feat(sanity): reflect checked out bundle in document lists and list previews --- .../documentStatus/DocumentStatus.tsx | 17 +++++-- .../DocumentStatusIndicator.tsx | 14 +++--- .../utils/getPreviewStateObservable.ts | 19 ++++++-- .../utils/getPreviewValueWithFallback.tsx | 15 ++++++- packages/sanity/src/core/util/draftUtils.ts | 45 ++++++++++++++++--- .../components/paneItem/PaneItemPreview.tsx | 20 ++++++--- .../documentList/DocumentListPaneContent.tsx | 2 +- .../structure/panes/documentList/helpers.ts | 15 +++++-- .../panes/documentList/listenSearchQuery.ts | 10 ++++- .../src/structure/panes/documentList/types.ts | 1 + .../panes/documentList/useDocumentList.ts | 4 +- 11 files changed, 130 insertions(+), 32 deletions(-) diff --git a/packages/sanity/src/core/components/documentStatus/DocumentStatus.tsx b/packages/sanity/src/core/components/documentStatus/DocumentStatus.tsx index 16a5190dbf7..ba5873afc1e 100644 --- a/packages/sanity/src/core/components/documentStatus/DocumentStatus.tsx +++ b/packages/sanity/src/core/components/documentStatus/DocumentStatus.tsx @@ -9,6 +9,7 @@ interface DocumentStatusProps { absoluteDate?: boolean draft?: PreviewValue | Partial | null published?: PreviewValue | Partial | null + version?: PreviewValue | Partial | null singleLine?: boolean } @@ -26,9 +27,17 @@ const StyledText = styled(Text)` * * @internal */ -export function DocumentStatus({absoluteDate, draft, published, singleLine}: DocumentStatusProps) { +export function DocumentStatus({ + absoluteDate, + draft, + published, + version, + singleLine, +}: DocumentStatusProps) { const {t} = useTranslation() - const draftUpdatedAt = draft && '_updatedAt' in draft ? draft._updatedAt : '' + const checkedOutVersion = version ?? draft + const draftUpdatedAt = + checkedOutVersion && '_updatedAt' in checkedOutVersion ? checkedOutVersion._updatedAt : '' const publishedUpdatedAt = published && '_updatedAt' in published ? published._updatedAt : '' const intlDateFormat = useDateTimeFormat({ @@ -60,12 +69,12 @@ export function DocumentStatus({absoluteDate, draft, published, singleLine}: Doc gap={2} wrap="nowrap" > - {!publishedDate && ( + {!version && !publishedDate && ( {t('document-status.not-published')} )} - {publishedDate && ( + {!version && publishedDate && ( {t('document-status.published', {date: publishedDate})} diff --git a/packages/sanity/src/core/components/documentStatusIndicator/DocumentStatusIndicator.tsx b/packages/sanity/src/core/components/documentStatusIndicator/DocumentStatusIndicator.tsx index 86036a3a8f0..762a8349c75 100644 --- a/packages/sanity/src/core/components/documentStatusIndicator/DocumentStatusIndicator.tsx +++ b/packages/sanity/src/core/components/documentStatusIndicator/DocumentStatusIndicator.tsx @@ -7,6 +7,7 @@ import {styled} from 'styled-components' interface DocumentStatusProps { draft?: PreviewValue | Partial | null published?: PreviewValue | Partial | null + version?: PreviewValue | Partial | null } const Root = styled(Text)` @@ -29,19 +30,22 @@ const Root = styled(Text)` * * @internal */ -export function DocumentStatusIndicator({draft, published}: DocumentStatusProps) { - const $draft = !!draft - const $published = !!published +export function DocumentStatusIndicator({draft, published, version}: DocumentStatusProps) { + const $version = Boolean(version) + const $draft = Boolean(draft) + const $published = Boolean(published) const status = useMemo(() => { + if ($version) return 'version' if ($draft && !$published) return 'unpublished' return 'edited' - }, [$draft, $published]) + }, [$draft, $published, $version]) // Return null if the document is: + // - Not a version // - Published without edits // - Neither published or without edits (this shouldn't be possible) - if ((!$draft && !$published) || (!$draft && $published)) { + if (!$version && ((!$draft && !$published) || (!$draft && $published))) { return null } diff --git a/packages/sanity/src/core/preview/utils/getPreviewStateObservable.ts b/packages/sanity/src/core/preview/utils/getPreviewStateObservable.ts index f37e7490868..f8729049fa8 100644 --- a/packages/sanity/src/core/preview/utils/getPreviewStateObservable.ts +++ b/packages/sanity/src/core/preview/utils/getPreviewStateObservable.ts @@ -10,6 +10,7 @@ export interface PreviewState { isLoading?: boolean draft?: PreviewValue | Partial | null published?: PreviewValue | Partial | null + version?: PreviewValue | Partial | null } const isLiveEditEnabled = (schemaType: SchemaType) => schemaType.liveEdit === true @@ -24,22 +25,32 @@ export function getPreviewStateObservable( schemaType: SchemaType, documentId: string, title: ReactNode, + bundlePerspective?: string, ): Observable { const draft$ = isLiveEditEnabled(schemaType) ? of({snapshot: null}) : documentPreviewStore.observeForPreview({_id: getDraftId(documentId)}, schemaType) + // TODO: Create `getVersionId` abstraction + const version$ = bundlePerspective + ? documentPreviewStore.observeForPreview( + {_type: 'reference', _ref: [bundlePerspective, getPublishedId(documentId, true)].join('.')}, + schemaType, + ) + : of({snapshot: null}) + const published$ = documentPreviewStore.observeForPreview( - {_id: getPublishedId(documentId)}, + {_id: getPublishedId(documentId, documentId.startsWith(`${bundlePerspective}.`))}, schemaType, ) - return combineLatest([draft$, published$]).pipe( - map(([draft, published]) => ({ + return combineLatest([draft$, published$, version$]).pipe( + map(([draft, published, version]) => ({ draft: draft.snapshot ? {title, ...(draft.snapshot || {})} : null, isLoading: false, published: published.snapshot ? {title, ...(published.snapshot || {})} : null, + version: version.snapshot ? {title, ...(version.snapshot || {})} : null, })), - startWith({draft: null, isLoading: true, published: null}), + startWith({draft: null, isLoading: true, published: null, version: null}), ) } diff --git a/packages/sanity/src/core/preview/utils/getPreviewValueWithFallback.tsx b/packages/sanity/src/core/preview/utils/getPreviewValueWithFallback.tsx index 50a292ff5ae..ea1a9109c78 100644 --- a/packages/sanity/src/core/preview/utils/getPreviewValueWithFallback.tsx +++ b/packages/sanity/src/core/preview/utils/getPreviewValueWithFallback.tsx @@ -18,14 +18,27 @@ export const getPreviewValueWithFallback = ({ value, draft, published, + version, perspective, }: { value: SanityDocument draft?: Partial | PreviewValue | null published?: Partial | PreviewValue | null + version?: Partial | PreviewValue | null perspective?: string }) => { - const snapshot = perspective === 'published' ? published || draft : draft || published + let snapshot: Partial | PreviewValue | null | undefined + + switch (true) { + case perspective?.startsWith('bundle.'): + snapshot = version || draft || published + break + case perspective === 'published': + snapshot = published || draft + break + default: + snapshot = draft || published + } if (!snapshot) { return getMissingDocumentFallback(value) diff --git a/packages/sanity/src/core/util/draftUtils.ts b/packages/sanity/src/core/util/draftUtils.ts index 54dab4cc995..a55bdba0869 100644 --- a/packages/sanity/src/core/util/draftUtils.ts +++ b/packages/sanity/src/core/util/draftUtils.ts @@ -160,23 +160,58 @@ export interface CollatedHit { type: string draft?: T published?: T + version?: T +} + +interface CollateOptions { + bundlePerspective?: string } /** @internal */ -export function collate(documents: T[]): CollatedHit[] { +export function collate< + T extends { + _id: string + _type: string + _version?: Record + }, +>(documents: T[], {bundlePerspective}: CollateOptions = {}): CollatedHit[] { const byId = documents.reduce((res, doc) => { - const publishedId = getPublishedId(doc._id) + const isVersion = Boolean(doc._version) + const publishedId = getPublishedId(doc._id, isVersion) + const bundle = isVersion ? doc._id.split('.').at(0) : undefined + let entry = res.get(publishedId) if (!entry) { - entry = {id: publishedId, type: doc._type, published: undefined, draft: undefined} + entry = { + id: publishedId, + type: doc._type, + published: undefined, + draft: undefined, + version: undefined, + } res.set(publishedId, entry) } - entry[publishedId === doc._id ? 'published' : 'draft'] = doc + if (bundlePerspective && bundle === bundlePerspective) { + entry.version = doc + } + + if (!isVersion) { + entry[publishedId === doc._id ? 'published' : 'draft'] = doc + } + return res }, new Map()) - return Array.from(byId.values()) + return ( + Array.from(byId.values()) + // Remove entries that have no data, because all the following conditions are true: + // + // 1. They have no published version. + // 2. They have no draft version. + // 3. They have a version, but not the one that is currently checked out. + .filter((entry) => entry.published ?? entry.version ?? entry.draft) + ) } /** @internal */ diff --git a/packages/sanity/src/structure/components/paneItem/PaneItemPreview.tsx b/packages/sanity/src/structure/components/paneItem/PaneItemPreview.tsx index 417059b399f..6a996fb3d76 100644 --- a/packages/sanity/src/structure/components/paneItem/PaneItemPreview.tsx +++ b/packages/sanity/src/structure/components/paneItem/PaneItemPreview.tsx @@ -45,10 +45,18 @@ export function PaneItemPreview(props: PaneItemPreviewProps) { : null const previewStateObservable = useMemo( - () => getPreviewStateObservable(props.documentPreviewStore, schemaType, value._id, title), - [props.documentPreviewStore, schemaType, title, value._id], + () => + getPreviewStateObservable( + props.documentPreviewStore, + schemaType, + value._id, + title, + perspective?.startsWith('bundle.') ? perspective.split('bundle.').at(1) : undefined, + ), + [props.documentPreviewStore, schemaType, title, value._id, perspective], ) - const {draft, published, isLoading} = useObservable(previewStateObservable, { + + const {draft, published, version, isLoading} = useObservable(previewStateObservable, { draft: null, isLoading: true, published: null, @@ -58,16 +66,16 @@ export function PaneItemPreview(props: PaneItemPreviewProps) { {presence && presence.length > 0 && } - + ) - const tooltip = + const tooltip = return ( >( (item, {activeIndex}) => { - const publishedId = getPublishedId(item._id) + const publishedId = getPublishedId(item._id, Boolean(item._version)) const isSelected = childItemId === publishedId const pressed = !isActive && isSelected const selected = isActive && isSelected diff --git a/packages/sanity/src/structure/panes/documentList/helpers.ts b/packages/sanity/src/structure/panes/documentList/helpers.ts index 4243d3c2f32..dd25192e5c7 100644 --- a/packages/sanity/src/structure/panes/documentList/helpers.ts +++ b/packages/sanity/src/structure/panes/documentList/helpers.ts @@ -18,13 +18,20 @@ export function getDocumentKey(value: DocumentListPaneItem, index: number): stri return value._id ? getPublishedId(value._id) : `item-${index}` } -export function removePublishedWithDrafts(documents: SanityDocumentLike[]): DocumentListPaneItem[] { - return collate(documents).map((entry) => { - const doc = entry.draft || entry.published +export function removePublishedWithDrafts( + documents: SanityDocumentLike[], + {bundlePerspective}: {bundlePerspective?: string} = {}, +): DocumentListPaneItem[] { + return collate(documents, {bundlePerspective}).map((entry) => { + const doc = entry.version || entry.draft || entry.published + const isVersion = Boolean(doc?._version) + const hasDraft = Boolean(entry.draft) + return { ...doc, hasPublished: !!entry.published, - hasDraft: !!entry.draft, + hasDraft, + isVersion, } }) as any } diff --git a/packages/sanity/src/structure/panes/documentList/listenSearchQuery.ts b/packages/sanity/src/structure/panes/documentList/listenSearchQuery.ts index 377b33ab404..04766428c98 100644 --- a/packages/sanity/src/structure/panes/documentList/listenSearchQuery.ts +++ b/packages/sanity/src/structure/panes/documentList/listenSearchQuery.ts @@ -139,7 +139,7 @@ export function listenSearchQuery(options: ListenQueryOptions): Observable ({fromCache, documents: value})), ) } + +function omitBundlePerspective(perspective: string | undefined): string | undefined { + if (perspective?.startsWith('bundle.')) { + return undefined + } + + return perspective +} diff --git a/packages/sanity/src/structure/panes/documentList/types.ts b/packages/sanity/src/structure/panes/documentList/types.ts index 3d8e2496e7d..dd325d2e159 100644 --- a/packages/sanity/src/structure/panes/documentList/types.ts +++ b/packages/sanity/src/structure/panes/documentList/types.ts @@ -4,6 +4,7 @@ import {type SearchSort} from 'sanity' export interface DocumentListPaneItem extends SanityDocumentLike { hasPublished: boolean hasDraft: boolean + isVersion: boolean } export type SortOrder = { diff --git a/packages/sanity/src/structure/panes/documentList/useDocumentList.ts b/packages/sanity/src/structure/panes/documentList/useDocumentList.ts index 23c9ace3875..4b6337f8c7a 100644 --- a/packages/sanity/src/structure/panes/documentList/useDocumentList.ts +++ b/packages/sanity/src/structure/panes/documentList/useDocumentList.ts @@ -156,7 +156,9 @@ export function useDocumentList(opts: UseDocumentListOpts): UseDocumentListHookV error: null, fromCache: event.result.fromCache, isLoading: false, - items: removePublishedWithDrafts(event.result.documents), + items: removePublishedWithDrafts(event.result.documents, { + bundlePerspective: (perspective ?? '').split('bundle.').at(1), + }), isLoadingFullList: false, } } From 344671e61bc91e7d240761f1568eb2e1a4a9ffa8 Mon Sep 17 00:00:00 2001 From: Ash Date: Wed, 26 Jun 2024 14:53:02 +0100 Subject: [PATCH 013/121] debug(sanity): add version debug output to list previews --- .../DocumentStatusIndicator.tsx | 6 +++++ .../components/paneItem/PaneItemPreview.tsx | 24 ++++++++++++------- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/packages/sanity/src/core/components/documentStatusIndicator/DocumentStatusIndicator.tsx b/packages/sanity/src/core/components/documentStatusIndicator/DocumentStatusIndicator.tsx index 762a8349c75..ed45b50ebc6 100644 --- a/packages/sanity/src/core/components/documentStatusIndicator/DocumentStatusIndicator.tsx +++ b/packages/sanity/src/core/components/documentStatusIndicator/DocumentStatusIndicator.tsx @@ -10,6 +10,7 @@ interface DocumentStatusProps { version?: PreviewValue | Partial | null } +// TODO: `version` style is only for debugging. const Root = styled(Text)` &[data-status='edited'] { --card-icon-color: var(--card-badge-caution-dot-color); @@ -18,6 +19,9 @@ const Root = styled(Text)` --card-icon-color: var(--card-badge-default-dot-color); opacity: 0.5 !important; } + &[data-status='version'] { + --card-icon-color: lime; + } ` /** @@ -49,8 +53,10 @@ export function DocumentStatusIndicator({draft, published, version}: DocumentSta return null } + // TODO: Remove debug `status[0]` output. return ( + {status[0]} ) diff --git a/packages/sanity/src/structure/components/paneItem/PaneItemPreview.tsx b/packages/sanity/src/structure/components/paneItem/PaneItemPreview.tsx index 6a996fb3d76..080061ccff4 100644 --- a/packages/sanity/src/structure/components/paneItem/PaneItemPreview.tsx +++ b/packages/sanity/src/structure/components/paneItem/PaneItemPreview.tsx @@ -1,5 +1,5 @@ import {type SanityDocument, type SchemaType} from '@sanity/types' -import {Flex} from '@sanity/ui' +import {Flex, Text} from '@sanity/ui' import {isNumber, isString} from 'lodash' import {type ComponentType, isValidElement, useMemo} from 'react' import {useObservable} from 'react-rx' @@ -73,14 +73,20 @@ export function PaneItemPreview(props: PaneItemPreviewProps) { const tooltip = + // TODO: Remove debug `_id` output. return ( - + <> + + {(version ?? draft ?? published)?._id} + + + ) } From ca7b93c4b6b1c1e2c6e614ffdead9fd60672aa8d Mon Sep 17 00:00:00 2001 From: RitaDias Date: Tue, 25 Jun 2024 14:37:37 +0200 Subject: [PATCH 014/121] feat(sanity): add action to create new version based on an doc id --- .../document/document-pair/operationEvents.ts | 2 ++ .../document-pair/operations/helpers.ts | 3 ++ .../document-pair/operations/newVersion.ts | 32 +++++++++++++++++++ .../document-pair/operations/types.ts | 1 + packages/sanity/src/core/util/draftUtils.ts | 5 +++ 5 files changed, 43 insertions(+) create mode 100644 packages/sanity/src/core/store/_legacy/document/document-pair/operations/newVersion.ts diff --git a/packages/sanity/src/core/store/_legacy/document/document-pair/operationEvents.ts b/packages/sanity/src/core/store/_legacy/document/document-pair/operationEvents.ts index 61ad399b895..5deebb91f1a 100644 --- a/packages/sanity/src/core/store/_legacy/document/document-pair/operationEvents.ts +++ b/packages/sanity/src/core/store/_legacy/document/document-pair/operationEvents.ts @@ -28,6 +28,7 @@ import {commit} from './operations/commit' import {del} from './operations/delete' import {discardChanges} from './operations/discardChanges' import {duplicate} from './operations/duplicate' +import {newVersion} from './operations/newVersion' import {patch} from './operations/patch' import {publish} from './operations/publish' import {restore} from './operations/restore' @@ -60,6 +61,7 @@ const operationImpls = { unpublish, duplicate, restore, + newVersion, } as const //as we add server operations one by one, we can add them here diff --git a/packages/sanity/src/core/store/_legacy/document/document-pair/operations/helpers.ts b/packages/sanity/src/core/store/_legacy/document/document-pair/operations/helpers.ts index 4c5c970cacd..70b7bfbe21e 100644 --- a/packages/sanity/src/core/store/_legacy/document/document-pair/operations/helpers.ts +++ b/packages/sanity/src/core/store/_legacy/document/document-pair/operations/helpers.ts @@ -11,6 +11,7 @@ import {commit} from './commit' import {del} from './delete' import {discardChanges} from './discardChanges' import {duplicate} from './duplicate' +import {newVersion} from './newVersion' import {patch} from './patch' import {restore} from './restore' import {type Operation, type OperationArgs, type OperationImpl, type OperationsAPI} from './types' @@ -38,6 +39,7 @@ export const GUARDED: OperationsAPI = { unpublish: createOperationGuard('unpublish'), duplicate: createOperationGuard('duplicate'), restore: createOperationGuard('restore'), + newVersion: createOperationGuard('newVersion'), } const createEmitter = (operationName: keyof OperationsAPI, idPair: IdPair, typeName: string) => @@ -67,6 +69,7 @@ export function createOperationsAPI(args: OperationArgs): OperationsAPI { unpublish: wrap('unpublish', unpublish, args), duplicate: wrap('duplicate', duplicate, args), restore: wrap('restore', restore, args), + newVersion: wrap('newVersion', newVersion, args) as Operation<[string], 'NO_NEW_VERSION'>, } //as we add server operations one by one, we can add them here diff --git a/packages/sanity/src/core/store/_legacy/document/document-pair/operations/newVersion.ts b/packages/sanity/src/core/store/_legacy/document/document-pair/operations/newVersion.ts new file mode 100644 index 00000000000..ac362c6f8bf --- /dev/null +++ b/packages/sanity/src/core/store/_legacy/document/document-pair/operations/newVersion.ts @@ -0,0 +1,32 @@ +import {omit} from 'lodash' + +import {getDraftId} from '../../../../../util' +import {isLiveEditEnabled} from '../utils/isLiveEditEnabled' +import {type OperationImpl} from './types' + +const omitProps = ['_createdAt', '_updatedAt'] + +export const newVersion: OperationImpl<[baseDocumentId: string], 'NO_NEW_VERSION'> = { + disabled: ({snapshots}) => { + return snapshots.published || snapshots.draft ? false : 'NO_NEW_VERSION' + }, + execute: ({schema, client, snapshots, typeName}, dupeId) => { + const source = snapshots.draft || snapshots.published + + if (!source) { + throw new Error('cannot execute on empty document') + } + + return client.observable.create( + { + ...omit(source, omitProps), + _id: isLiveEditEnabled(schema, typeName) ? dupeId : getDraftId(dupeId), + _type: source._type, + _version: {}, + }, + { + tag: 'document.newVersion', + }, + ) + }, +} diff --git a/packages/sanity/src/core/store/_legacy/document/document-pair/operations/types.ts b/packages/sanity/src/core/store/_legacy/document/document-pair/operations/types.ts index 8ba378d8f67..62615742dd5 100644 --- a/packages/sanity/src/core/store/_legacy/document/document-pair/operations/types.ts +++ b/packages/sanity/src/core/store/_legacy/document/document-pair/operations/types.ts @@ -37,6 +37,7 @@ export interface OperationsAPI { unpublish: Operation<[], 'LIVE_EDIT_ENABLED' | 'NOT_PUBLISHED'> | GuardedOperation duplicate: Operation<[documentId: string], 'NOTHING_TO_DUPLICATE'> | GuardedOperation restore: Operation<[revision: string]> | GuardedOperation + newVersion: GuardedOperation | Operation<[documentId: string], 'NO_NEW_VERSION'> } /** @internal */ diff --git a/packages/sanity/src/core/util/draftUtils.ts b/packages/sanity/src/core/util/draftUtils.ts index a55bdba0869..ff15c3397fb 100644 --- a/packages/sanity/src/core/util/draftUtils.ts +++ b/packages/sanity/src/core/util/draftUtils.ts @@ -78,6 +78,11 @@ export function isPublishedId(id: string): id is PublishedId { /** @internal */ export function getDraftId(id: string): DraftId { + // meaning, it already has a version attached to it + if (id.includes('.')) { + return id as DraftId + } + return isDraftId(id) ? id : ((DRAFTS_PREFIX + id) as DraftId) } From 4621a8c322e14afcc080e7ad475700adcb064a7e Mon Sep 17 00:00:00 2001 From: RitaDias Date: Wed, 26 Jun 2024 10:43:17 +0200 Subject: [PATCH 015/121] feat(sanity): update document title + version popover - add new and from existing versions --- .../panes/document/DocumentVersionMenu.tsx | 239 ++++++++++++++++++ .../header/DocumentHeaderTitle.tsx | 42 ++- 2 files changed, 273 insertions(+), 8 deletions(-) create mode 100644 packages/sanity/src/structure/panes/document/DocumentVersionMenu.tsx diff --git a/packages/sanity/src/structure/panes/document/DocumentVersionMenu.tsx b/packages/sanity/src/structure/panes/document/DocumentVersionMenu.tsx new file mode 100644 index 00000000000..8b67aa2bcf6 --- /dev/null +++ b/packages/sanity/src/structure/panes/document/DocumentVersionMenu.tsx @@ -0,0 +1,239 @@ +/* eslint-disable i18next/no-literal-string */ +import {AddIcon, ChevronDownIcon} from '@sanity/icons' +import { + Box, + Button, + Flex, + Label, + Menu, + MenuButton, + MenuDivider, + MenuItem, + Stack, + Text, + TextInput, +} from '@sanity/ui' +import {useCallback, useState} from 'react' +import {useDocumentOperation} from 'sanity' + +function toSlug(str: string) { + return str + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-+/, '') + .replace(/-+$/, '') +} + +// dummy data +const BUNDLE_OPTIONS = [ + {name: '', title: 'Create new bundle'}, + {name: 'draft', title: 'Published + Drafts'}, + {name: 'previewDrafts', title: 'Preview drafts'}, + {name: 'published', title: 'Published'}, + {name: 'summerDrop', title: 'Summer Drop'}, + {name: 'autumnDrop', title: 'Autumn Drop'}, +] + +// dummy data +const DOC_EXISTING_VERSIONS: any[] = [] + +export function DocumentVersionMenu(props: { + documentId: string + documentType: string +}): JSX.Element { + const {documentId, documentType} = props + const {newVersion} = useDocumentOperation(documentId, documentType) + + const [addVersionTitle, setAddVersionTitle] = useState('') + + const addVersionName = toSlug(addVersionTitle) + // use to prevent adding a version when you're already in that version + const addVersionExists = BUNDLE_OPTIONS.some((r) => r.name === addVersionName) + + // list of available bundles + const bundleOptionsList = BUNDLE_OPTIONS.filter((r) => + r.title.toLowerCase().includes(addVersionTitle.toLowerCase()), + ) + + /* used for the search of bundles when writing a new version name */ + const handleAddVersionChange = useCallback((event: React.ChangeEvent) => { + setAddVersionTitle(event.target.value) + }, []) + + const handleAddVersion = useCallback( + (name: string) => () => { + const bundleId = `${name}.${documentId}` + + newVersion.execute(bundleId) + }, + [documentId, newVersion], + ) + + const handleAddNewVersion = useCallback( + (name: string) => () => { + if (!name) return + + //BUNDLE_OPTIONS.push({name: addVersionName, title: name}) + + handleAddVersion(addVersionName) + }, + [addVersionName, handleAddVersion], + ) + + const handleChangeToVersion = useCallback( + (name: string) => () => { + // eslint-disable-next-line no-console + console.log('changing to an already existing version', name) + }, + [], + ) + + return ( + <> + + } + id="version-menu" + menu={ + + {/* + + ) + } + onClick={() => { + setVersionName('draft') + setDraftVersionName('draft') + }} + // padding={2} + pressed={draftVersionName === 'draft'} + text="Latest version" + /> + */} + + + + {BUNDLE_OPTIONS.length > 0 && ( + <> + + + {/* + localize text + */} + + + + {DOC_EXISTING_VERSIONS.map((r) => ( + + + {/**/} + + + + {r.name === 'draft' ? 'Latest' : r.title} + + + + {/* + + {r.publishAt ? formatRelativeTime(r.publishAt) : 'No target date'} + + */} + + + + {/**/} + + + + + ))} + + + + + )} + + + + + + + {addVersionTitle && ( + <> + {bundleOptionsList.map((r) => ( + + + {/**/} + + + + {r.name === 'draft' ? 'Latest' : r.title} + + + + + {/* + {r.publishAt ? formatRelativeTime(r.publishAt) : 'No target date'} + */} + + + {/* + + + + */} + + + ))} + + Create version: "{addVersionTitle}"} + /> + + )} + + + } + popover={{ + constrainSize: true, + fallbackPlacements: [], + placement: 'bottom-start', + portal: true, + tone: 'default', + }} + /> + + + ) +} diff --git a/packages/sanity/src/structure/panes/document/documentPanel/header/DocumentHeaderTitle.tsx b/packages/sanity/src/structure/panes/document/documentPanel/header/DocumentHeaderTitle.tsx index 80b4e848d1f..d98e371a212 100644 --- a/packages/sanity/src/structure/panes/document/documentPanel/header/DocumentHeaderTitle.tsx +++ b/packages/sanity/src/structure/panes/document/documentPanel/header/DocumentHeaderTitle.tsx @@ -1,11 +1,21 @@ +import {DocumentIcon} from '@sanity/icons' +import {Flex, Text} from '@sanity/ui' import {type ReactElement} from 'react' import {unstable_useValuePreview as useValuePreview, useTranslation} from 'sanity' import {structureLocaleNamespace} from '../../../../i18n' +import {DocumentVersionMenu} from '../../DocumentVersionMenu' import {useDocumentPane} from '../../useDocumentPane' export function DocumentHeaderTitle(): ReactElement { - const {connectionState, schemaType, title, value: documentValue} = useDocumentPane() + const { + documentId, + documentType, + connectionState, + schemaType, + title, + value: documentValue, + } = useDocumentPane() const subscribed = Boolean(documentValue) && connectionState !== 'connecting' const {error, value} = useValuePreview({ @@ -38,12 +48,28 @@ export function DocumentHeaderTitle(): ReactElement { } return ( - <> - {value?.title || ( - - {t('panes.document-header-title.untitled.text')} - - )} - + + + + + + + {value?.title || ( + + {t('panes.document-header-title.untitled.text')} + + )} + + + + + + + ) } From f6fb3d68e73c830f354136e13d79e56a3aa2a7e2 Mon Sep 17 00:00:00 2001 From: RitaDias Date: Wed, 26 Jun 2024 10:51:32 +0200 Subject: [PATCH 016/121] chore(sanity): move versions menu to versions folder --- .../panes/document/documentPanel/header/DocumentHeaderTitle.tsx | 2 +- .../panes/document/{ => versions}/DocumentVersionMenu.tsx | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename packages/sanity/src/structure/panes/document/{ => versions}/DocumentVersionMenu.tsx (100%) diff --git a/packages/sanity/src/structure/panes/document/documentPanel/header/DocumentHeaderTitle.tsx b/packages/sanity/src/structure/panes/document/documentPanel/header/DocumentHeaderTitle.tsx index d98e371a212..37610ba86ef 100644 --- a/packages/sanity/src/structure/panes/document/documentPanel/header/DocumentHeaderTitle.tsx +++ b/packages/sanity/src/structure/panes/document/documentPanel/header/DocumentHeaderTitle.tsx @@ -4,8 +4,8 @@ import {type ReactElement} from 'react' import {unstable_useValuePreview as useValuePreview, useTranslation} from 'sanity' import {structureLocaleNamespace} from '../../../../i18n' -import {DocumentVersionMenu} from '../../DocumentVersionMenu' import {useDocumentPane} from '../../useDocumentPane' +import {DocumentVersionMenu} from '../../versions/DocumentVersionMenu' export function DocumentHeaderTitle(): ReactElement { const { diff --git a/packages/sanity/src/structure/panes/document/DocumentVersionMenu.tsx b/packages/sanity/src/structure/panes/document/versions/DocumentVersionMenu.tsx similarity index 100% rename from packages/sanity/src/structure/panes/document/DocumentVersionMenu.tsx rename to packages/sanity/src/structure/panes/document/versions/DocumentVersionMenu.tsx From 93691864fb15bb0c38f11912ed52502de98acb38 Mon Sep 17 00:00:00 2001 From: RitaDias Date: Wed, 26 Jun 2024 11:17:20 +0200 Subject: [PATCH 017/121] chore(sanity): update check for checking version --- packages/sanity/src/core/util/draftUtils.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/sanity/src/core/util/draftUtils.ts b/packages/sanity/src/core/util/draftUtils.ts index ff15c3397fb..a55bdba0869 100644 --- a/packages/sanity/src/core/util/draftUtils.ts +++ b/packages/sanity/src/core/util/draftUtils.ts @@ -78,11 +78,6 @@ export function isPublishedId(id: string): id is PublishedId { /** @internal */ export function getDraftId(id: string): DraftId { - // meaning, it already has a version attached to it - if (id.includes('.')) { - return id as DraftId - } - return isDraftId(id) ? id : ((DRAFTS_PREFIX + id) as DraftId) } From 496d333e7249c99340a6d08f04c39af14fe79c41 Mon Sep 17 00:00:00 2001 From: RitaDias Date: Wed, 26 Jun 2024 11:45:59 +0200 Subject: [PATCH 018/121] fix(sanity): ability to add new versions to doc --- .../document/versions/DocumentVersionMenu.tsx | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/packages/sanity/src/structure/panes/document/versions/DocumentVersionMenu.tsx b/packages/sanity/src/structure/panes/document/versions/DocumentVersionMenu.tsx index 8b67aa2bcf6..5fe841e9f1a 100644 --- a/packages/sanity/src/structure/panes/document/versions/DocumentVersionMenu.tsx +++ b/packages/sanity/src/structure/panes/document/versions/DocumentVersionMenu.tsx @@ -62,24 +62,14 @@ export function DocumentVersionMenu(props: { const handleAddVersion = useCallback( (name: string) => () => { - const bundleId = `${name}.${documentId}` + const nameSlugged = toSlug(name) + const bundleId = `${nameSlugged}.${documentId}` newVersion.execute(bundleId) }, [documentId, newVersion], ) - const handleAddNewVersion = useCallback( - (name: string) => () => { - if (!name) return - - //BUNDLE_OPTIONS.push({name: addVersionName, title: name}) - - handleAddVersion(addVersionName) - }, - [addVersionName, handleAddVersion], - ) - const handleChangeToVersion = useCallback( (name: string) => () => { // eslint-disable-next-line no-console @@ -217,7 +207,7 @@ export function DocumentVersionMenu(props: { Create version: "{addVersionTitle}"} /> From 44a9dd13ee0a98d1bb395207440d137cdeffeffc Mon Sep 17 00:00:00 2001 From: RitaDias Date: Wed, 26 Jun 2024 15:02:47 +0200 Subject: [PATCH 019/121] feat(sanity): add dumb bundle and version data, add icons and hues --- .../sanity/src/core/util/versions/util.ts | 86 ++++++++++++++++ .../document/versions/DocumentVersionMenu.tsx | 98 +++++++++++-------- .../panes/document/versions/ReleaseIcon.tsx | 51 ++++++++++ 3 files changed, 194 insertions(+), 41 deletions(-) create mode 100644 packages/sanity/src/core/util/versions/util.ts create mode 100644 packages/sanity/src/structure/panes/document/versions/ReleaseIcon.tsx diff --git a/packages/sanity/src/core/util/versions/util.ts b/packages/sanity/src/core/util/versions/util.ts new file mode 100644 index 00000000000..bc20b584d97 --- /dev/null +++ b/packages/sanity/src/core/util/versions/util.ts @@ -0,0 +1,86 @@ +import {type ColorHueKey} from '@sanity/color' +import {type IconSymbol} from '@sanity/icons' +import formatRelative from 'date-fns/formatRelative' +import {type SanityClient, type SanityDocument} from 'sanity' + +const RANDOM_TONES: ColorHueKey[] = [ + 'green', + 'yellow', + 'red', + 'purple', + 'blue', + 'cyan', + 'magenta', + 'orange', +] +const RANDOM_SYMBOLS = [ + 'archive', + 'edit', + 'eye-open', + 'heart', + 'info-filled', + 'circle', + 'search', + 'sun', + 'star', + 'trash', + 'user', +] + +export interface SanityReleaseIcon { + hue: ColorHueKey + icon: IconSymbol +} + +// move out of here and make it right +export interface Version { + name: string + title: string + icon: IconSymbol + hue: ColorHueKey + publishAt: Date | number +} + +// dummy data +export const BUNDLES: Version[] = [ + {name: 'draft', title: 'Published + Drafts', icon: 'archive', hue: 'green', publishAt: 0}, + {name: 'previewDrafts', title: 'Preview drafts', icon: 'edit', hue: 'yellow', publishAt: 0}, + {name: 'published', title: 'Published', icon: 'eye-open', hue: 'blue', publishAt: 0}, + {name: 'summerDrop', title: 'Summer Drop', icon: 'sun', hue: 'orange', publishAt: 0}, + {name: 'autumnDrop', title: 'Autumn Drop', icon: 'star', hue: 'red', publishAt: 0}, +] + +/** + * Returns all versions of a document + * + * @param documentId - document id + * @param client - sanity client + * @returns array of SanityDocuments versions from a specific doc + */ +export async function getAllVersionsOfDocument( + client: SanityClient, + documentId: string, +): Promise { + // remove all versions, get just id (anything before .) + const id = documentId.replace(/^[^.]*\./, '') + + const query = `*[_id match "*${id}*"]` + + return await client.fetch(query).then((documents) => { + return documents.map((doc: SanityDocument) => ({ + name: getVersionName(doc._id), + title: getVersionName(doc._id), + hue: RANDOM_TONES[Math.floor(Math.random() * RANDOM_TONES.length)], + icon: RANDOM_SYMBOLS[Math.floor(Math.random() * RANDOM_SYMBOLS.length)], + })) + }) +} + +export function formatRelativeTime(date: number | Date): string { + return formatRelative(date, new Date()) +} + +export function getVersionName(documentId: string): string { + const version = documentId.slice(0, documentId.indexOf('.')) + return version +} diff --git a/packages/sanity/src/structure/panes/document/versions/DocumentVersionMenu.tsx b/packages/sanity/src/structure/panes/document/versions/DocumentVersionMenu.tsx index 5fe841e9f1a..5477a7e0407 100644 --- a/packages/sanity/src/structure/panes/document/versions/DocumentVersionMenu.tsx +++ b/packages/sanity/src/structure/panes/document/versions/DocumentVersionMenu.tsx @@ -1,5 +1,5 @@ /* eslint-disable i18next/no-literal-string */ -import {AddIcon, ChevronDownIcon} from '@sanity/icons' +import {AddIcon, CheckmarkIcon, ChevronDownIcon} from '@sanity/icons' import { Box, Button, @@ -13,8 +13,17 @@ import { Text, TextInput, } from '@sanity/ui' -import {useCallback, useState} from 'react' -import {useDocumentOperation} from 'sanity' +import {useCallback, useEffect, useState} from 'react' +import {DEFAULT_STUDIO_CLIENT_OPTIONS, useClient, useDocumentOperation} from 'sanity' + +import { + BUNDLES, + formatRelativeTime, + getAllVersionsOfDocument, + getVersionName, + type Version, +} from '../../../../core/util/versions/util' +import {ReleaseIcon} from './ReleaseIcon' function toSlug(str: string) { return str @@ -24,37 +33,41 @@ function toSlug(str: string) { .replace(/-+$/, '') } -// dummy data -const BUNDLE_OPTIONS = [ - {name: '', title: 'Create new bundle'}, - {name: 'draft', title: 'Published + Drafts'}, - {name: 'previewDrafts', title: 'Preview drafts'}, - {name: 'published', title: 'Published'}, - {name: 'summerDrop', title: 'Summer Drop'}, - {name: 'autumnDrop', title: 'Autumn Drop'}, -] - -// dummy data -const DOC_EXISTING_VERSIONS: any[] = [] - export function DocumentVersionMenu(props: { documentId: string documentType: string }): JSX.Element { const {documentId, documentType} = props const {newVersion} = useDocumentOperation(documentId, documentType) + const client = useClient(DEFAULT_STUDIO_CLIENT_OPTIONS) + const selectedVersion = getVersionName(documentId) const [addVersionTitle, setAddVersionTitle] = useState('') + const [documentVersions, setDocumentVersions] = useState([]) const addVersionName = toSlug(addVersionTitle) // use to prevent adding a version when you're already in that version - const addVersionExists = BUNDLE_OPTIONS.some((r) => r.name === addVersionName) + const addVersionExists = BUNDLES.some((r) => r.name === addVersionName) // list of available bundles - const bundleOptionsList = BUNDLE_OPTIONS.filter((r) => + const bundleOptionsList = BUNDLES.filter((r) => r.title.toLowerCase().includes(addVersionTitle.toLowerCase()), ) + const fetchVersions = useCallback(async () => { + const response = await getAllVersionsOfDocument(client, documentId) + setDocumentVersions(response) + }, [client, documentId]) + + // DUMMY FETCH -- NEEDS TO BE REPLACED + useEffect(() => { + const fetchVersionsInner = async () => { + fetchVersions() + } + + fetchVersionsInner() + }, [fetchVersions]) + /* used for the search of bundles when writing a new version name */ const handleAddVersionChange = useCallback((event: React.ChangeEvent) => { setAddVersionTitle(event.target.value) @@ -78,12 +91,18 @@ export function DocumentVersionMenu(props: { [], ) + const onMenuOpen = useCallback(async () => { + setAddVersionTitle('') + fetchVersions() + }, [fetchVersions]) + return ( <> } id="version-menu" + onOpen={onMenuOpen} menu={ {/* @@ -107,7 +126,7 @@ export function DocumentVersionMenu(props: { - {BUNDLE_OPTIONS.length > 0 && ( + {BUNDLES.length > 0 && ( <> @@ -115,12 +134,12 @@ export function DocumentVersionMenu(props: { localize text */} - {DOC_EXISTING_VERSIONS.map((r) => ( + {documentVersions.map((r) => ( - {/**/} + {} @@ -136,17 +155,21 @@ export function DocumentVersionMenu(props: { - {/* - - {r.publishAt ? formatRelativeTime(r.publishAt) : 'No target date'} - - */} + { + + + {r.publishAt ? formatRelativeTime(r.publishAt) : 'No target date'} + + + } - {/**/} + { + + } @@ -179,7 +202,7 @@ export function DocumentVersionMenu(props: { padding={1} > - {/**/} + @@ -188,22 +211,15 @@ export function DocumentVersionMenu(props: { - {/* + {r.publishAt ? formatRelativeTime(r.publishAt) : 'No target date'} - */} - - - {/* - - - */} + ))} + {/* localize text */} & {openButton?: boolean; padding?: number; title?: string}, +) { + const {hue = 'gray', icon, openButton, padding = 3, title} = props + const {color} = useTheme_v2() + + return ( + + {icon && ( + + + + + + )} + {title && ( + + {title} + + )} + {openButton && ( + + + + + + )} + + ) +} From 64f8e3a61e881de25d8031348181ecdf2fb464fd Mon Sep 17 00:00:00 2001 From: RitaDias Date: Wed, 26 Jun 2024 15:13:44 +0200 Subject: [PATCH 020/121] feat(sanity): update styles and don't rely on random --- packages/sanity/src/core/util/versions/util.ts | 6 +++--- .../panes/document/versions/DocumentVersionMenu.tsx | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/sanity/src/core/util/versions/util.ts b/packages/sanity/src/core/util/versions/util.ts index bc20b584d97..22ddd47cef5 100644 --- a/packages/sanity/src/core/util/versions/util.ts +++ b/packages/sanity/src/core/util/versions/util.ts @@ -67,11 +67,11 @@ export async function getAllVersionsOfDocument( const query = `*[_id match "*${id}*"]` return await client.fetch(query).then((documents) => { - return documents.map((doc: SanityDocument) => ({ + return documents.map((doc: SanityDocument, index: number) => ({ name: getVersionName(doc._id), title: getVersionName(doc._id), - hue: RANDOM_TONES[Math.floor(Math.random() * RANDOM_TONES.length)], - icon: RANDOM_SYMBOLS[Math.floor(Math.random() * RANDOM_SYMBOLS.length)], + hue: RANDOM_TONES[index % RANDOM_SYMBOLS.length], + icon: RANDOM_SYMBOLS[index % RANDOM_SYMBOLS.length], })) }) } diff --git a/packages/sanity/src/structure/panes/document/versions/DocumentVersionMenu.tsx b/packages/sanity/src/structure/panes/document/versions/DocumentVersionMenu.tsx index 5477a7e0407..5c46bd56713 100644 --- a/packages/sanity/src/structure/panes/document/versions/DocumentVersionMenu.tsx +++ b/packages/sanity/src/structure/panes/document/versions/DocumentVersionMenu.tsx @@ -144,13 +144,14 @@ export function DocumentVersionMenu(props: { key={r.name} onClick={handleChangeToVersion(r.name)} padding={1} - //pressed={draftVersionName === r.name} + pressed={selectedVersion === r.name} > {} + {/* localize text */} {r.name === 'draft' ? 'Latest' : r.title} @@ -158,6 +159,7 @@ export function DocumentVersionMenu(props: { { + {/* localize text */} {r.publishAt ? formatRelativeTime(r.publishAt) : 'No target date'} From 49345e4e7298ab656e9097ca7962cfe15d2a33c8 Mon Sep 17 00:00:00 2001 From: RitaDias Date: Wed, 26 Jun 2024 15:29:48 +0200 Subject: [PATCH 021/121] docs(sanity): update comments --- packages/sanity/src/core/util/versions/util.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/sanity/src/core/util/versions/util.ts b/packages/sanity/src/core/util/versions/util.ts index 22ddd47cef5..f28eee46e75 100644 --- a/packages/sanity/src/core/util/versions/util.ts +++ b/packages/sanity/src/core/util/versions/util.ts @@ -3,6 +3,8 @@ import {type IconSymbol} from '@sanity/icons' import formatRelative from 'date-fns/formatRelative' import {type SanityClient, type SanityDocument} from 'sanity' +/* MOSTLY TEMPORARY FUNCTIONS / DUMMY DATA */ + const RANDOM_TONES: ColorHueKey[] = [ 'green', 'yellow', From 8e140e902ecf8bd6a990ee1d978e56bccf6ed5dc Mon Sep 17 00:00:00 2001 From: RitaDias Date: Wed, 26 Jun 2024 15:34:37 +0200 Subject: [PATCH 022/121] feat(sanity): add "latest version" menu button --- .../document/versions/DocumentVersionMenu.tsx | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/packages/sanity/src/structure/panes/document/versions/DocumentVersionMenu.tsx b/packages/sanity/src/structure/panes/document/versions/DocumentVersionMenu.tsx index 5c46bd56713..b6bf4dda5ce 100644 --- a/packages/sanity/src/structure/panes/document/versions/DocumentVersionMenu.tsx +++ b/packages/sanity/src/structure/panes/document/versions/DocumentVersionMenu.tsx @@ -41,6 +41,7 @@ export function DocumentVersionMenu(props: { const {newVersion} = useDocumentOperation(documentId, documentType) const client = useClient(DEFAULT_STUDIO_CLIENT_OPTIONS) const selectedVersion = getVersionName(documentId) + const isDraft = selectedVersion === 'draft' const [addVersionTitle, setAddVersionTitle] = useState('') const [documentVersions, setDocumentVersions] = useState([]) @@ -91,6 +92,14 @@ export function DocumentVersionMenu(props: { [], ) + const handleGoToLatest = useCallback( + () => () => { + // eslint-disable-next-line no-console + console.log('switching into drafts / latest') + }, + [], + ) + const onMenuOpen = useCallback(async () => { setAddVersionTitle('') fetchVersions() @@ -105,24 +114,16 @@ export function DocumentVersionMenu(props: { onOpen={onMenuOpen} menu={ - {/* + + {/* localize text */} - ) - } - onClick={() => { - setVersionName('draft') - setDraftVersionName('draft') - }} - // padding={2} - pressed={draftVersionName === 'draft'} + iconRight={isDraft ? CheckmarkIcon : } + onClick={handleGoToLatest()} + pressed={isDraft} + // eslint-disable-next-line @sanity/i18n/no-attribute-string-literals text="Latest version" /> - */} + From 960d7ab05069c44e9aba6c6d945d50480089706c Mon Sep 17 00:00:00 2001 From: RitaDias Date: Wed, 26 Jun 2024 15:42:51 +0200 Subject: [PATCH 023/121] refactor(sanity): update comments --- .../panes/document/versions/DocumentVersionMenu.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/sanity/src/structure/panes/document/versions/DocumentVersionMenu.tsx b/packages/sanity/src/structure/panes/document/versions/DocumentVersionMenu.tsx index b6bf4dda5ce..05f7c465427 100644 --- a/packages/sanity/src/structure/panes/document/versions/DocumentVersionMenu.tsx +++ b/packages/sanity/src/structure/panes/document/versions/DocumentVersionMenu.tsx @@ -43,12 +43,14 @@ export function DocumentVersionMenu(props: { const selectedVersion = getVersionName(documentId) const isDraft = selectedVersion === 'draft' - const [addVersionTitle, setAddVersionTitle] = useState('') const [documentVersions, setDocumentVersions] = useState([]) + // search + const [addVersionTitle, setAddVersionTitle] = useState('') const addVersionName = toSlug(addVersionTitle) + // use to prevent adding a version when you're already in that version - const addVersionExists = BUNDLES.some((r) => r.name === addVersionName) + const addVersionExists = BUNDLES.some((r) => r.name.toLocaleLowerCase() === addVersionName) // list of available bundles const bundleOptionsList = BUNDLES.filter((r) => @@ -60,7 +62,7 @@ export function DocumentVersionMenu(props: { setDocumentVersions(response) }, [client, documentId]) - // DUMMY FETCH -- NEEDS TO BE REPLACED + // DUMMY FETCH -- NEEDS TO BE REPLACED -- USING GROQ from utils useEffect(() => { const fetchVersionsInner = async () => { fetchVersions() From af11c3983e459d064e75b7aabb37a917cf6c144d Mon Sep 17 00:00:00 2001 From: RitaDias Date: Wed, 26 Jun 2024 15:52:02 +0200 Subject: [PATCH 024/121] chore(sanity): remove import from format date. will use something else --- packages/sanity/src/core/util/versions/util.ts | 5 ----- .../panes/document/versions/DocumentVersionMenu.tsx | 9 ++++++--- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/sanity/src/core/util/versions/util.ts b/packages/sanity/src/core/util/versions/util.ts index f28eee46e75..874977c31ae 100644 --- a/packages/sanity/src/core/util/versions/util.ts +++ b/packages/sanity/src/core/util/versions/util.ts @@ -1,6 +1,5 @@ import {type ColorHueKey} from '@sanity/color' import {type IconSymbol} from '@sanity/icons' -import formatRelative from 'date-fns/formatRelative' import {type SanityClient, type SanityDocument} from 'sanity' /* MOSTLY TEMPORARY FUNCTIONS / DUMMY DATA */ @@ -78,10 +77,6 @@ export async function getAllVersionsOfDocument( }) } -export function formatRelativeTime(date: number | Date): string { - return formatRelative(date, new Date()) -} - export function getVersionName(documentId: string): string { const version = documentId.slice(0, documentId.indexOf('.')) return version diff --git a/packages/sanity/src/structure/panes/document/versions/DocumentVersionMenu.tsx b/packages/sanity/src/structure/panes/document/versions/DocumentVersionMenu.tsx index 05f7c465427..fc451ad96c7 100644 --- a/packages/sanity/src/structure/panes/document/versions/DocumentVersionMenu.tsx +++ b/packages/sanity/src/structure/panes/document/versions/DocumentVersionMenu.tsx @@ -18,7 +18,6 @@ import {DEFAULT_STUDIO_CLIENT_OPTIONS, useClient, useDocumentOperation} from 'sa import { BUNDLES, - formatRelativeTime, getAllVersionsOfDocument, getVersionName, type Version, @@ -163,7 +162,9 @@ export function DocumentVersionMenu(props: { {/* localize text */} - {r.publishAt ? formatRelativeTime(r.publishAt) : 'No target date'} + {r.publishAt + ? `a date will be here ${r.publishAt}` + : 'No target date'} } @@ -217,7 +218,9 @@ export function DocumentVersionMenu(props: { - {r.publishAt ? formatRelativeTime(r.publishAt) : 'No target date'} + {r.publishAt + ? `a date will be here ${r.publishAt}` + : 'No target date'} From 5cb7b5bc32f485ec83c0bc22a82377f7e4d8141d Mon Sep 17 00:00:00 2001 From: Ash Date: Wed, 26 Jun 2024 16:50:14 +0100 Subject: [PATCH 025/121] fix(sanity): allow uppercase characters in version identifiers --- .../panes/document/versions/DocumentVersionMenu.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/sanity/src/structure/panes/document/versions/DocumentVersionMenu.tsx b/packages/sanity/src/structure/panes/document/versions/DocumentVersionMenu.tsx index fc451ad96c7..4a9f8c6e6d0 100644 --- a/packages/sanity/src/structure/panes/document/versions/DocumentVersionMenu.tsx +++ b/packages/sanity/src/structure/panes/document/versions/DocumentVersionMenu.tsx @@ -13,6 +13,7 @@ import { Text, TextInput, } from '@sanity/ui' +import {camelCase} from 'lodash' import {useCallback, useEffect, useState} from 'react' import {DEFAULT_STUDIO_CLIENT_OPTIONS, useClient, useDocumentOperation} from 'sanity' @@ -25,11 +26,7 @@ import { import {ReleaseIcon} from './ReleaseIcon' function toSlug(str: string) { - return str - .toLowerCase() - .replace(/[^a-z0-9]+/g, '-') - .replace(/^-+/, '') - .replace(/-+$/, '') + return camelCase(str) } export function DocumentVersionMenu(props: { From 124f794ad4de7a0e4dbbe41cbae18b744bf571b2 Mon Sep 17 00:00:00 2001 From: Ash Date: Thu, 27 Jun 2024 09:06:47 +0100 Subject: [PATCH 026/121] feat(sanity): add tag to list document versions request --- packages/sanity/src/core/util/versions/util.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sanity/src/core/util/versions/util.ts b/packages/sanity/src/core/util/versions/util.ts index 874977c31ae..96e31dc7182 100644 --- a/packages/sanity/src/core/util/versions/util.ts +++ b/packages/sanity/src/core/util/versions/util.ts @@ -67,7 +67,7 @@ export async function getAllVersionsOfDocument( const query = `*[_id match "*${id}*"]` - return await client.fetch(query).then((documents) => { + return await client.fetch(query, {}, {tag: 'document.list-versions'}).then((documents) => { return documents.map((doc: SanityDocument, index: number) => ({ name: getVersionName(doc._id), title: getVersionName(doc._id), From 034ab4a4f357c79f426f5d6914763f27edeb28e8 Mon Sep 17 00:00:00 2001 From: Pedro Bonamin <46196328+pedrobonamin@users.noreply.github.com> Date: Mon, 1 Jul 2024 12:44:59 +0200 Subject: [PATCH 027/121] feat(corel): integrate bundles store (#7040) * feat(corel): add bundles store --- .../__workshop__/BundlesStoreStory.tsx | 110 +++++++++++++ .../bundles/__workshop__/ReleaseForm.tsx | 133 ++++++++++++++++ .../core/store/bundles/__workshop__/index.ts | 14 ++ .../sanity/src/core/store/bundles/index.ts | 1 + .../sanity/src/core/store/bundles/reducer.ts | 109 +++++++++++++ .../sanity/src/core/store/bundles/types.ts | 12 ++ .../core/store/bundles/useBundleOperations.ts | 50 ++++++ .../src/core/store/bundles/useBundlesStore.ts | 149 ++++++++++++++++++ 8 files changed, 578 insertions(+) create mode 100644 packages/sanity/src/core/store/bundles/__workshop__/BundlesStoreStory.tsx create mode 100644 packages/sanity/src/core/store/bundles/__workshop__/ReleaseForm.tsx create mode 100644 packages/sanity/src/core/store/bundles/__workshop__/index.ts create mode 100644 packages/sanity/src/core/store/bundles/index.ts create mode 100644 packages/sanity/src/core/store/bundles/reducer.ts create mode 100644 packages/sanity/src/core/store/bundles/types.ts create mode 100644 packages/sanity/src/core/store/bundles/useBundleOperations.ts create mode 100644 packages/sanity/src/core/store/bundles/useBundlesStore.ts diff --git a/packages/sanity/src/core/store/bundles/__workshop__/BundlesStoreStory.tsx b/packages/sanity/src/core/store/bundles/__workshop__/BundlesStoreStory.tsx new file mode 100644 index 00000000000..cd554fe5b86 --- /dev/null +++ b/packages/sanity/src/core/store/bundles/__workshop__/BundlesStoreStory.tsx @@ -0,0 +1,110 @@ +import {Card, Flex, Stack, Text} from '@sanity/ui' +import {type ComponentType, type FormEvent, useCallback, useState} from 'react' + +import {Button} from '../../../../ui-components' +import {LoadingBlock} from '../../../components/loadingBlock/LoadingBlock' +import {AddonDatasetProvider} from '../../../studio/addonDataset/AddonDatasetProvider' +import {type BundleDocument} from '../types' +import {useBundleOperations} from '../useBundleOperations' +import {useBundlesStore} from '../useBundlesStore' +import {ReleaseForm} from './ReleaseForm' + +const WithAddonDatasetProvider =

(Component: ComponentType

): React.FC

=> { + const WrappedComponent: React.FC

= (props) => ( + + + + ) + WrappedComponent.displayName = `WithAddonDatasetProvider(${Component.displayName || Component.name || 'Component'})` + + return WrappedComponent +} + +const initialValue = {name: '', title: '', tone: undefined, publishAt: undefined} +const BundlesStoreStory = () => { + const {data, loading} = useBundlesStore() + const {createBundle, deleteBundle} = useBundleOperations() + const [creating, setCreating] = useState(false) + const [deleting, setDeleting] = useState(null) + const [value, setValue] = useState>(initialValue) + const handleCreateBundle = useCallback( + async (event: FormEvent) => { + try { + event.preventDefault() + setCreating(true) + await createBundle(value) + setValue(initialValue) + } catch (err) { + console.error(err) + } finally { + setCreating(false) + } + }, + [createBundle, value], + ) + + const handleDeleteBundle = useCallback( + async (id: string) => { + try { + setDeleting(id) + await deleteBundle(id) + } catch (err) { + console.error(err) + } finally { + setDeleting(null) + } + }, + [deleteBundle], + ) + + return ( + + + +

+ + Create a new release + + +