From 6c2e51ea77d4a74ad283124218780f8d2179d0d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rge=20N=C3=A6ss?= Date: Mon, 6 May 2024 15:38:09 +0200 Subject: [PATCH 001/116] feat(sanity): add support for sticky query params --- packages/sanity/src/router/RouterProvider.tsx | 79 +++++++++++++++++-- packages/sanity/src/router/types.ts | 10 +++ 2 files changed, 84 insertions(+), 5 deletions(-) diff --git a/packages/sanity/src/router/RouterProvider.tsx b/packages/sanity/src/router/RouterProvider.tsx index d42c5e6ad55..dd9c912af6b 100644 --- a/packages/sanity/src/router/RouterProvider.tsx +++ b/packages/sanity/src/router/RouterProvider.tsx @@ -1,3 +1,4 @@ +import {partition} from 'lodash' import {type ReactElement, type ReactNode, useCallback, useMemo} from 'react' import {RouterContext} from 'sanity/_singletons' @@ -7,6 +8,7 @@ import { type Router, type RouterContextValue, type RouterState, + type SearchParam, } from './types' /** @@ -88,10 +90,39 @@ export function RouterProvider(props: RouterProviderProps): ReactElement { ) const resolvePathFromState = useCallback( - (nextState: Record): string => { - return routerProp.encode(nextState) + (nextState: RouterState): string => { + const currentStateParams = state._searchParams || [] + const nextStateParams = nextState._searchParams || [] + const nextParams = STICKY.reduce((acc, param) => { + return replaceStickyParam( + acc, + param, + findParam(nextStateParams, param) ?? findParam(currentStateParams, param), + ) + }, nextStateParams || []) + + return routerProp.encode({ + ...nextState, + _searchParams: nextParams, + }) }, - [routerProp], + [routerProp, state], + ) + + const handleNavigateStickyParam = useCallback( + (param: string, value: string | undefined, options: NavigateOptions = {}) => { + if (!STICKY.includes(param)) { + throw new Error('Parameter is not sticky') + } + onNavigate({ + path: resolvePathFromState({ + ...state, + _searchParams: [[param, value || '']], + }), + replace: options.replace, + }) + }, + [onNavigate, resolvePathFromState, state], ) const navigate = useCallback( @@ -108,17 +139,55 @@ export function RouterProvider(props: RouterProviderProps): ReactElement { [onNavigate, resolveIntentLink], ) + const [routerState, stickyParams] = useMemo(() => { + if (!state._searchParams) { + return [state, null] + } + const {_searchParams, ...rest} = state + const [sticky, restParams] = partition(_searchParams, ([key]) => STICKY.includes(key)) + if (sticky.length === 0) { + return [state, null] + } + return [{...rest, _searchParams: restParams}, sticky] + }, [state]) + const router: RouterContextValue = useMemo( () => ({ navigate, navigateIntent, + navigateStickyParam: handleNavigateStickyParam, navigateUrl: onNavigate, resolveIntentLink, resolvePathFromState, - state, + state: routerState, + stickyParams: Object.fromEntries(stickyParams || []), }), - [navigate, navigateIntent, onNavigate, resolveIntentLink, resolvePathFromState, state], + [ + handleNavigateStickyParam, + navigate, + navigateIntent, + onNavigate, + resolveIntentLink, + resolvePathFromState, + routerState, + stickyParams, + ], ) return {props.children} } +const STICKY = ['perspective'] + +function replaceStickyParam( + current: SearchParam[], + param: string, + value: string | undefined, +): SearchParam[] { + const filtered = current.filter(([key]) => key !== param) + return value === undefined || value == '' ? filtered : [...filtered, [param, value]] +} + +function findParam(searchParams: SearchParam[], key: string): string | undefined { + const entry = searchParams.find(([k]) => k === key) + return entry ? entry[1] : undefined +} diff --git a/packages/sanity/src/router/types.ts b/packages/sanity/src/router/types.ts index b8033b85a53..1f10f13ffa3 100644 --- a/packages/sanity/src/router/types.ts +++ b/packages/sanity/src/router/types.ts @@ -260,6 +260,11 @@ export interface RouterContextValue { */ navigateUrl: (opts: {path: string; replace?: boolean}) => void + /** + * Navigates to the current URL with the sticky url search param set to the given value + */ + navigateStickyParam: (param: string, value: string, options?: NavigateOptions) => void + /** * Navigates to the given router state. * See {@link RouterState} and {@link NavigateOptions} @@ -276,4 +281,9 @@ export interface RouterContextValue { * The current router state. See {@link RouterState} */ state: RouterState + + /** + * The current router state. See {@link RouterState} + */ + stickyParams: Record } From d5ee7699d385099077fe37bcab66560573eaad51 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 002/116] feat(sanity): add 'perspective' dropdown --- .../core/preview/components/PreviewLoader.tsx | 3 ++ .../src/core/preview/createPreviewObserver.ts | 24 ++++++++----- .../src/core/preview/documentPreviewStore.ts | 3 +- .../src/core/preview/useValuePreview.ts | 15 +++++--- .../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 | 25 ++++++++++---- .../panes/documentList/DocumentListPane.tsx | 4 +++ .../panes/documentList/listenSearchQuery.ts | 3 ++ .../panes/documentList/useDocumentList.ts | 5 ++- 15 files changed, 109 insertions(+), 33 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 08b60271829..241bbe7b589 100644 --- a/packages/sanity/src/core/preview/createPreviewObserver.ts +++ b/packages/sanity/src/core/preview/createPreviewObserver.ts @@ -33,16 +33,22 @@ export function createPreviewObserver(context: { }): ( value: Previewable, type: PreviewableType, - viewOptions?: PrepareViewOptions, - apiConfig?: ApiConfig, + options?: { + perspective?: string + viewOptions?: PrepareViewOptions + apiConfig?: ApiConfig + }, ) => Observable { const {observeDocumentTypeFromId, observePaths} = context return function observeForPreview( value: Previewable, type: PreviewableType, - viewOptions?: PrepareViewOptions, - apiConfig?: ApiConfig, + options?: { + perspective?: string + viewOptions?: PrepareViewOptions + apiConfig?: ApiConfig + }, ): Observable { if (isCrossDatasetReferenceSchemaType(type)) { // if the value is of type crossDatasetReference, but has no _ref property, we cannot prepare any value for the preview @@ -57,7 +63,7 @@ export function createPreviewObserver(context: { switchMap((typeName) => { if (typeName) { const refType = type.to.find((toType) => toType.type === typeName) - return observeForPreview(value, refType as any, {}, refApiConfig) + return observeForPreview(value, refType as any, {apiConfig: refApiConfig}) } return observableOf({snapshot: undefined}) }), @@ -88,10 +94,10 @@ export function createPreviewObserver(context: { } const paths = getPreviewPaths(type.preview) if (paths) { - return observePaths(value, paths, apiConfig).pipe( + return observePaths(value, paths, options?.apiConfig).pipe( map((snapshot) => ({ type: type, - snapshot: snapshot && prepareForPreview(snapshot, type as any, viewOptions), + snapshot: snapshot && prepareForPreview(snapshot, type as any, options?.viewOptions), })), ) } @@ -103,7 +109,9 @@ export function createPreviewObserver(context: { return observableOf({ type, snapshot: - value && isRecord(value) ? invokePrepare(type, value, viewOptions).returnValue : null, + value && isRecord(value) + ? invokePrepare(type, value, options?.viewOptions).returnValue + : null, }) } } diff --git a/packages/sanity/src/core/preview/documentPreviewStore.ts b/packages/sanity/src/core/preview/documentPreviewStore.ts index e715cee925d..9909b449dd3 100644 --- a/packages/sanity/src/core/preview/documentPreviewStore.ts +++ b/packages/sanity/src/core/preview/documentPreviewStore.ts @@ -26,8 +26,7 @@ import { export type ObserveForPreviewFn = ( value: Previewable, type: PreviewableType, - 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 5b70deb514f..5613d7c2582 100644 --- a/packages/sanity/src/core/preview/useValuePreview.ts +++ b/packages/sanity/src/core/preview/useValuePreview.ts @@ -1,7 +1,7 @@ import {type PreviewValue, type SchemaType, type SortOrdering} from '@sanity/types' import {useMemo} from 'react' import {useObservable} from 'react-rx' -import {of} from 'rxjs' +import {type Observable, of} from 'rxjs' import {catchError, map} from 'rxjs/operators' import {useDocumentPreviewStore} from '../store' @@ -28,17 +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(() => { + const observable = useMemo>(() => { if (!enabled || !previewValue || !schemaType) return of(PENDING_STATE) - return observeForPreview(previewValue as Previewable, schemaType, {ordering}).pipe( + 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, observeForPreview, ordering, previewValue, schemaType]) + }, [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 1afbd3a20c2..f3272ee9d3c 100644 --- a/packages/sanity/src/core/search/text-search/createTextSearch.ts +++ b/packages/sanity/src/core/search/text-search/createTextSearch.ts @@ -145,6 +145,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 605660351ae..1c96ee0c8ba 100644 --- a/packages/sanity/src/core/search/weighted/createSearchQuery.ts +++ b/packages/sanity/src/core/search/weighted/createSearchQuery.ts @@ -186,7 +186,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 @@ -109,13 +111,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 @@ -506,6 +515,7 @@ export const DocumentPaneProvider = memo((props: DocumentPaneProviderProps) => { const isLocked = editState.transactionSyncLock?.enabled return ( + !!perspective || !ready || revTime !== null || hasNoPermission || @@ -517,16 +527,17 @@ export const DocumentPaneProvider = memo((props: DocumentPaneProviderProps) => { isDeleted ) }, [ - connectionState, - editState.transactionSyncLock, - isNonExistent, - isDeleted, - isDeleting, isPermissionsLoading, permissions?.granted, + schemaType, + isNonExistent, + connectionState, + editState.transactionSyncLock?.enabled, + perspective, ready, revTime, - schemaType, + isDeleting, + isDeleted, ]) const formState = useFormState(schemaType!, { diff --git a/packages/sanity/src/structure/panes/documentList/DocumentListPane.tsx b/packages/sanity/src/structure/panes/documentList/DocumentListPane.tsx index 61bc05af867..26d9d25dbd6 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' @@ -50,6 +51,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) @@ -92,6 +95,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 25dd3498d83..b5ef916506c 100644 --- a/packages/sanity/src/structure/panes/documentList/listenSearchQuery.ts +++ b/packages/sanity/src/structure/panes/documentList/listenSearchQuery.ts @@ -30,6 +30,7 @@ interface ListenQueryOptions { schema: Schema searchQuery: string sort: SortOrder + perspective?: string staticTypeNames?: string[] | null maxFieldDepth?: number enableLegacySearch?: boolean @@ -40,6 +41,7 @@ export function listenSearchQuery(options: ListenQueryOptions): Observable searchQuery: string | null sortOrder?: SortOrder @@ -50,7 +51,7 @@ const INITIAL_QUERY_RESULTS: QueryResult = { * @internal */ export function useDocumentList(opts: UseDocumentListOpts): DocumentListState { - const {filter, params: paramsProp, sortOrder, searchQuery, apiVersion} = opts + const {filter, params: paramsProp, sortOrder, searchQuery, perspective, apiVersion} = opts const client = useClient({ ...DEFAULT_STUDIO_CLIENT_OPTIONS, apiVersion: apiVersion || DEFAULT_STUDIO_CLIENT_OPTIONS.apiVersion, @@ -156,6 +157,7 @@ export function useDocumentList(opts: UseDocumentListOpts): DocumentListState { limit, params: paramsProp, schema, + perspective, searchQuery: searchQuery || '', sort, staticTypeNames: typeNameFromFilter, @@ -192,6 +194,7 @@ export function useDocumentList(opts: UseDocumentListOpts): DocumentListState { filter, paramsProp, schema, + perspective, searchQuery, typeNameFromFilter, maxFieldDepth, From 53993542600e4f0d9a1367ec558f574420572fef Mon Sep 17 00:00:00 2001 From: Ash Date: Fri, 21 Jun 2024 12:38:46 +0100 Subject: [PATCH 003/116] 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 f3272ee9d3c..8f70a904bf6 100644 --- a/packages/sanity/src/core/search/text-search/createTextSearch.ts +++ b/packages/sanity/src/core/search/text-search/createTextSearch.ts @@ -157,7 +157,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 1c96ee0c8ba..1b83a0967b8 100644 --- a/packages/sanity/src/core/search/weighted/createSearchQuery.ts +++ b/packages/sanity/src/core/search/weighted/createSearchQuery.ts @@ -146,7 +146,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 e851dc24deece79bfdf5b5be943f929fcf47ea41 Mon Sep 17 00:00:00 2001 From: Ash Date: Fri, 21 Jun 2024 12:40:05 +0100 Subject: [PATCH 004/116] 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 22305e5794ac6fe55260e1f52ba0bf8abc717e39 Mon Sep 17 00:00:00 2001 From: Ash Date: Mon, 24 Jun 2024 11:50:58 +0100 Subject: [PATCH 005/116] 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 8a50a7c32d72d3942c11ccae816268d0e43f0c48 Mon Sep 17 00:00:00 2001 From: Ash Date: Mon, 24 Jun 2024 11:51:39 +0100 Subject: [PATCH 006/116] 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 85ddb5a25fd64548d4926baab2ea9c172e861ea6 Mon Sep 17 00:00:00 2001 From: Ash Date: Wed, 22 May 2024 10:53:04 +0100 Subject: [PATCH 007/116] 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 0bf28b3271c..6a314ca98e9 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 @@ -59,11 +59,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 } @@ -113,9 +115,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'), } @@ -177,12 +182,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( @@ -193,11 +199,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))), @@ -208,7 +214,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), @@ -223,12 +232,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 c78a3b91434..313fd287094 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 @@ -12,10 +12,10 @@ export const del: OperationImpl<[], 'NOTHING_TO_DELETE'> = { //the delete action requires a published doc -- discard if not present if (!snapshots.published) { return client.observable.action( - { + idPair.draftIds.map((draftId) => ({ actionType: 'sanity.action.document.discard', - draftId: idPair.draftId, - }, + draftId, + })), {tag: 'document.delete'}, ) } @@ -23,7 +23,7 @@ export const del: OperationImpl<[], 'NOTHING_TO_DELETE'> = { return 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 864c6592a53..2e21ddb8cb7 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 @@ -14,10 +14,10 @@ export const discardChanges: OperationImpl<[], DisabledReason> = { }, execute: ({client, idPair}) => { return 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 4fce51f99a3..bb99184f957 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,25 +30,30 @@ 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 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 10852fb1058..4bd28f6b61d 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 @@ -10,12 +10,15 @@ export const unpublish: OperationImpl<[], DisabledReason> = { } return snapshots.published ? false : 'NOT_PUBLISHED' }, - execute: ({client, idPair}) => - client.observable.action( + execute: ({client, idPair}) => { + // TODO: Should be dynamic + const draftIndex = 0 + + return 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, }, { @@ -25,5 +28,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 4ad8d322267..20bf8b3bb42 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 @@ -97,10 +97,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 64ed02d48cc..5d4f7dcf6f3 100644 --- a/packages/sanity/src/core/store/_legacy/document/document-store.ts +++ b/packages/sanity/src/core/store/_legacy/document/document-store.ts @@ -37,7 +37,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 0ad2cf6e0a1..360720c6496 100644 --- a/packages/sanity/src/core/store/_legacy/document/types.ts +++ b/packages/sanity/src/core/store/_legacy/document/types.ts @@ -34,7 +34,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 e18b8b8fd8a48ea62d1224dfc09306e604617ce9 Mon Sep 17 00:00:00 2001 From: Ash Date: Mon, 24 Jun 2024 16:28:55 +0100 Subject: [PATCH 008/116] feat(sanity): support versioned document ids in `getPublishedId` function --- packages/sanity/src/core/util/draftUtils.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/sanity/src/core/util/draftUtils.ts b/packages/sanity/src/core/util/draftUtils.ts index c87031b4f86..fe45fd7ba47 100644 --- a/packages/sanity/src/core/util/draftUtils.ts +++ b/packages/sanity/src/core/util/draftUtils.ts @@ -14,7 +14,8 @@ export type PublishedId = Opaque /** @internal */ export const DRAFTS_FOLDER = 'drafts' -const DRAFTS_PREFIX = `${DRAFTS_FOLDER}.` +const PATH_SEPARATOR = '.' +const DRAFTS_PREFIX = `${DRAFTS_FOLDER}${PATH_SEPARATOR}` /** * @@ -74,8 +75,16 @@ export function getDraftId(id: string): DraftId { } /** @internal */ -export function getPublishedId(id: string): PublishedId { - return (isDraftId(id) ? id.slice(DRAFTS_PREFIX.length) : id) as PublishedId +export function getPublishedId(id: string, isVersion?: boolean): PublishedId { + if (isVersion) { + return id.split(PATH_SEPARATOR).slice(1).join(PATH_SEPARATOR) as PublishedId + } + + if (isDraftId(id)) { + return id.slice(DRAFTS_PREFIX.length) as PublishedId + } + + return id as PublishedId } /** @internal */ From 18b0a6fadb01467b8ae6980e469f351ea0e64842 Mon Sep 17 00:00:00 2001 From: Ash Date: Wed, 26 Jun 2024 14:34:46 +0100 Subject: [PATCH 009/116] 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 9909b449dd3..50249244f75 100644 --- a/packages/sanity/src/core/preview/documentPreviewStore.ts +++ b/packages/sanity/src/core/preview/documentPreviewStore.ts @@ -81,7 +81,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 a3d1b6441d1..ded2a03bc6a 100644 --- a/packages/sanity/src/core/preview/utils/getPreviewPaths.ts +++ b/packages/sanity/src/core/preview/utils/getPreviewPaths.ts @@ -2,7 +2,7 @@ import {type SchemaType} from '@sanity/types' import {type PreviewPath} from '../types' -const DEFAULT_PREVIEW_PATHS: PreviewPath[] = [['_createdAt'], ['_updatedAt']] +const DEFAULT_PREVIEW_PATHS: PreviewPath[] = [['_createdAt'], ['_updatedAt'], ['_version']] /** @internal */ export function getPreviewPaths(preview: SchemaType['preview']): PreviewPath[] | undefined { diff --git a/packages/sanity/src/core/preview/utils/prepareForPreview.ts b/packages/sanity/src/core/preview/utils/prepareForPreview.ts index 416f293f365..5dfbd994f35 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 @@ -260,7 +261,7 @@ export function prepareForPreview( rawValue: unknown, type: SchemaType, 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 3fd18236715fbd1ec8b835d838a26ae5e74d76a3 Mon Sep 17 00:00:00 2001 From: Ash Date: Wed, 26 Jun 2024 14:52:07 +0100 Subject: [PATCH 010/116] feat(sanity): reflect checked out bundle in document lists and list previews --- .../documentStatus/DocumentStatus.tsx | 17 +++++-- .../DocumentStatusIndicator.tsx | 14 +++--- .../utils/getPreviewStateObservable.ts | 22 +++++++-- .../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 | 9 +++- 11 files changed, 137 insertions(+), 33 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 1ff5f791b01..05ad7485f1f 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,6 +25,7 @@ export function getPreviewStateObservable( schemaType: SchemaType, documentId: string, title: ReactNode, + bundlePerspective?: string, ): Observable { const draft$ = isLiveEditEnabled(schemaType) ? of({snapshot: null}) @@ -32,17 +34,29 @@ export function getPreviewStateObservable( 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( - {_type: 'reference', _ref: getPublishedId(documentId)}, + { + _type: 'reference', + _ref: 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 fe45fd7ba47..2eb756d9aee 100644 --- a/packages/sanity/src/core/util/draftUtils.ts +++ b/packages/sanity/src/core/util/draftUtils.ts @@ -125,23 +125,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 b5ef916506c..19c321bf663 100644 --- a/packages/sanity/src/structure/panes/documentList/listenSearchQuery.ts +++ b/packages/sanity/src/structure/panes/documentList/listenSearchQuery.ts @@ -126,7 +126,7 @@ export function listenSearchQuery(options: ListenQueryOptions): Observable (documents ? removePublishedWithDrafts(documents) : EMPTY_ARRAY), - [documents], + () => + documents + ? removePublishedWithDrafts(documents, { + bundlePerspective: (perspective ?? '').split('bundle.').at(1), + }) + : EMPTY_ARRAY, + [documents, perspective], ) // A state variable to keep track of whether we are currently lazy loading the list. From b262130b9e69917efe0a34ce6daac24079ea6810 Mon Sep 17 00:00:00 2001 From: Ash Date: Wed, 26 Jun 2024 14:53:02 +0100 Subject: [PATCH 011/116] 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 ef1d4b42eef349d82fd5584563f391cac388da81 Mon Sep 17 00:00:00 2001 From: RitaDias Date: Tue, 25 Jun 2024 14:37:37 +0200 Subject: [PATCH 012/116] 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 2eb756d9aee..fa90d063f96 100644 --- a/packages/sanity/src/core/util/draftUtils.ts +++ b/packages/sanity/src/core/util/draftUtils.ts @@ -71,6 +71,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 683de3a62d62d267322ce0084f17b5a5b0cf1dcf Mon Sep 17 00:00:00 2001 From: RitaDias Date: Wed, 26 Jun 2024 10:43:17 +0200 Subject: [PATCH 013/116] 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 52f5a01947e18b0bdb2f05b70e37cb0e67cc2c27 Mon Sep 17 00:00:00 2001 From: RitaDias Date: Wed, 26 Jun 2024 10:51:32 +0200 Subject: [PATCH 014/116] 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 e84c17ae3b76220d9161fa4cdcb4f1f36ef9c874 Mon Sep 17 00:00:00 2001 From: RitaDias Date: Wed, 26 Jun 2024 11:17:20 +0200 Subject: [PATCH 015/116] chore(sanity): update check for checking version --- packages/sanity/src/core/util/draftUtils.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/sanity/src/core/util/draftUtils.ts b/packages/sanity/src/core/util/draftUtils.ts index fa90d063f96..edc2f2ed907 100644 --- a/packages/sanity/src/core/util/draftUtils.ts +++ b/packages/sanity/src/core/util/draftUtils.ts @@ -56,6 +56,11 @@ export function isDraftId(id: string): id is DraftId { return id.startsWith(DRAFTS_PREFIX) } +/** @internal */ +export function isVersion(id: string): id is DraftId { + return id.startsWith(DRAFTS_PREFIX) || id.includes('.') +} + /** @internal */ export function getIdPair(id: string): {draftId: DraftId; publishedId: PublishedId} { return { @@ -71,12 +76,7 @@ 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) + return isDraftId(id) || isVersion(id) ? id : ((DRAFTS_PREFIX + id) as DraftId) } /** @internal */ From ba56b95bf8e18034bd35793e83215cb846c19cbe Mon Sep 17 00:00:00 2001 From: RitaDias Date: Wed, 26 Jun 2024 11:45:59 +0200 Subject: [PATCH 016/116] 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 5988427b35cf4fc2ae288c024d1a2b80adf5d597 Mon Sep 17 00:00:00 2001 From: RitaDias Date: Wed, 26 Jun 2024 15:02:47 +0200 Subject: [PATCH 017/116] 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 f4fcaa8292b036a411bb028d0bb2b6f4b55b18c1 Mon Sep 17 00:00:00 2001 From: RitaDias Date: Wed, 26 Jun 2024 15:13:44 +0200 Subject: [PATCH 018/116] 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 1bcf5dd65aca5984a5b147ea0937f17e40b148c5 Mon Sep 17 00:00:00 2001 From: RitaDias Date: Wed, 26 Jun 2024 15:28:58 +0200 Subject: [PATCH 019/116] update isVersion --- packages/sanity/src/core/util/draftUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sanity/src/core/util/draftUtils.ts b/packages/sanity/src/core/util/draftUtils.ts index edc2f2ed907..9661e89d9da 100644 --- a/packages/sanity/src/core/util/draftUtils.ts +++ b/packages/sanity/src/core/util/draftUtils.ts @@ -58,7 +58,7 @@ export function isDraftId(id: string): id is DraftId { /** @internal */ export function isVersion(id: string): id is DraftId { - return id.startsWith(DRAFTS_PREFIX) || id.includes('.') + return id.includes('.') } /** @internal */ From 64405515d268dad187cb912826b5f10bc3fc3ba8 Mon Sep 17 00:00:00 2001 From: RitaDias Date: Wed, 26 Jun 2024 15:29:48 +0200 Subject: [PATCH 020/116] 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 61bdca491b26fff6ae1f92811cd743a6a477a962 Mon Sep 17 00:00:00 2001 From: RitaDias Date: Wed, 26 Jun 2024 15:34:37 +0200 Subject: [PATCH 021/116] 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 392aedf75c3c70eb98855efadb3c76a93debbdaf Mon Sep 17 00:00:00 2001 From: RitaDias Date: Wed, 26 Jun 2024 15:42:51 +0200 Subject: [PATCH 022/116] 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 49cbc496db60ebb3389c642e94ecc822972e3a1c Mon Sep 17 00:00:00 2001 From: RitaDias Date: Wed, 26 Jun 2024 15:52:02 +0200 Subject: [PATCH 023/116] 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 5545b69d8c735a00652625663e15acfcdf2ca6f6 Mon Sep 17 00:00:00 2001 From: Ash Date: Wed, 26 Jun 2024 16:50:14 +0100 Subject: [PATCH 024/116] 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 3ee428b225c2d9e24ee45717fa92d047323a1142 Mon Sep 17 00:00:00 2001 From: Ash Date: Thu, 27 Jun 2024 09:06:47 +0100 Subject: [PATCH 025/116] 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 1a3143681d499490d54a91053823b6fb7437529e 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 026/116] 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 + + +