sanity
Version:
Sanity is a real-time content infrastructure with a scalable, hosted backend featuring a Graph Oriented Query Language (GROQ), asset pipelines and fast edge caches
377 lines (346 loc) • 9.67 kB
text/typescript
import {type SchemaType} from '@sanity/types'
import {type Observable} from 'rxjs'
import {
type ConfigContext,
type DocumentFieldActionNode,
type DocumentStore,
type GeneralPreviewLayoutKey,
type I18nTextRecord,
type InitialValueTemplateItem,
type LocaleSource,
} from 'sanity'
import {
type DefaultDocumentNodeResolver,
type Intent,
type MenuItem as StructureToolMenuItem,
type StructureBuilder,
type StructureContext,
type UserComponent,
type View,
} from './structureBuilder'
/** @internal */
export interface StructureToolFeatures {
/**
* @hidden
* @beta
*/
backButton: boolean
resizablePanes: boolean
reviewChanges: boolean
splitPanes: boolean
splitViews: boolean
}
/** @internal */
export interface StructureToolContextValue {
features: StructureToolFeatures
layoutCollapsed: boolean
setLayoutCollapsed: (layoutCollapsed: boolean) => void
rootPaneNode: UnresolvedPaneNode
structureContext: StructureContext
}
/**
* Structure tool context. Extends from {@link ConfigContext}.
* @hidden
* @public
*/
export interface StructureResolverContext extends ConfigContext {
/**
* This can be replaced by a different API in the future.
* It is provided as-is to support common structure patterns found in V2 in V3.
* @alpha
* */
documentStore: DocumentStore
/** @alpha */
i18n: LocaleSource
}
/**
* Lets you configure how lists, documents, views, menus, and initial value templates are organized in the Sanity Studio’s structure-tool.
*
* @public
*
* @returns A structure builder, or null/undefined if no structure should be returned. See {@link StructureBuilder}
* @example Configuring structure
* ```ts
* // sanity.config.js
*
* import {defineConfig} from 'sanity'
* import {structureTool} from 'sanity/structure'
* import {schemaTypes} from './schema'
*
* export default defineConfig({
* name: 'default',
* title: 'My Cool Project',
* projectId: 'my-project-id',
* dataset: 'production',
* plugins: [
* structureTool({
* structure: (S, context) => {
* console.log(context) // returns { currentUser, dataset, projectId, schema, getClient, documentStore }
* return S.documentTypeList('post')
* },
* })
* ],
* schema: schemaTypes
* })
* ```
*
*/
export type StructureResolver = (
/**
* S - An instance of the structure builder, that can be used to build the lists/items/panes for the structure tool
* context - an object holding various context that may be used to customize the structure, for instance the current user.
* Defaults to
* ```ts
* (S) => S.defaults()
* ```
* See {@link StructureBuilder}
*/
S: StructureBuilder,
/**
* An object containing pane and index information for the current structure tool.
* See {@link StructureResolverContext}
*/
context: StructureResolverContext,
) => unknown
/** @internal */
export type StructureToolPaneActionHandler = (params: any, scope?: unknown) => void
/**
* The params for the `structureTool` api. See {@link structureTool}
*
* @public */
export interface StructureToolOptions {
/*
* React icon component for the tool, used in navigation bar. Defaults to MasterDetailIcon from @sanity/icons
*/
icon?: React.ComponentType
/*
* The name you want this structure to have (among other places, this name is used in routing,
* if name is set to “structure”, it is shown on /structure). Usually lowercase or camelcase by convention. Defaults to structure.
*/
name?: string
/**
* A workspace can have different "sources". These sources were meant to allow using multiple datasets within the same workspace, for instance.
* This is not supported yet, but the API is still here.
*
@hidden
@alpha
*/
source?: string
/**
* A structure resolver function. See {@link StructureResolver}
*/
structure?: StructureResolver
/**
* A resolver function used to return the default document node used when editing documents. See {@link DefaultDocumentNodeResolver}
*/
defaultDocumentNode?: DefaultDocumentNodeResolver
/**
* The title that will be displayed for the tool. Defaults to Structure
*/
title?: string
}
/**
* Represents the state of the `panes` inside the structure-tool router
*
* - The structure tool stores the state of the current panes inside of the router.
* - The panes are stored in groups delimited in the URL by `;`.
* - In each group, there can be one or more sibling (aka split) panes delimited
* by `|`.
* - Each item pane can contain it's own parameters and payloads
* - Per item pane in each group, if not specified separately, the ID, params,
* and payload will be inherited from the first item pane in the pane group
* (unless it's an `exclusiveParam`)
*
* E.g. `/structure/books;book-123|,view=preview` will parse to:
*
* ```js
* [
* // first pane group
* [{id: 'book'}],
*
* // second pane group
* [
* [
* // first pane item
* {id: 'book-123'},
* // second pane item
* {id: 'book-123', params: {view: 'preview'}},
* ],
* ],
* ]
* ```
*
* @hidden
* @beta
*/
export type RouterPanes = RouterPaneGroup[]
/**
* Represents a "pane group" in the router.
*
* @see RouterPanes
*
*
* @hidden
* @beta
*/
export type RouterPaneGroup = RouterPaneSibling[]
/**
* Represents a "sibling pane" or "split pane" in the router.
*
* @see RouterPanes
*
*
* @hidden
* @beta
*/
export interface RouterPaneSibling {
id: string
params?: Record<string, string | undefined>
payload?: unknown
}
// TODO: unify this with the structure builder ChildResolver type
/**
* Passed as the second argument to the item of resolving pane children
*
* @see RouterPanes
*
* @internal
*/
export interface RouterPaneSiblingContext {
id: string
parent: PaneNode | null
index: number
splitIndex: number
path: string[]
params: Record<string, string | undefined>
payload: unknown
structureContext: StructureContext
// used in structure builder
serializeOptions?: {
path: (string | number)[]
index?: number
hint?: string
}
}
/** @internal */
export {type StructureToolMenuItem}
/**
* Represents what can be passed into `menuItems` inside of structure-tool panes
*
* @see BaseResolvedPaneNode
*
* @internal
*/
export interface PaneMenuItem extends StructureToolMenuItem {
// TODO: these would be great options in the `MenuItemBuilder`
// currently, they are only used in the `DocumentPaneProvider`
isDisabled?: boolean
shortcut?: string
selected?: boolean
tone?: 'primary' | 'positive' | 'caution' | 'critical'
}
/** @internal */
export interface PaneMenuItemGroup {
id: string
title?: string
i18n?: I18nTextRecord<'title'>
}
/** @internal */
export interface BaseResolvedPaneNode<T extends PaneNode['type']> {
id: string
type: T
title: string
i18n?: I18nTextRecord<'title'>
menuItems?: PaneMenuItem[]
menuItemGroups?: PaneMenuItemGroup[]
canHandleIntent?: (
intentName: string,
params: Record<string, string | undefined>,
options: {pane: PaneNode; index: number},
) => boolean
child?: UnresolvedPaneNode
}
/** @internal */
export interface CustomComponentPaneNode extends BaseResolvedPaneNode<'component'> {
component: UserComponent
options?: Record<string, unknown>
// component: React.ComponentType<Props> | React.ReactNode
}
/** @internal */
export interface DocumentPaneNode extends BaseResolvedPaneNode<'document'> {
options: {
id: string
type: string
template?: string
templateParameters?: Record<string, unknown>
}
source?: string
views?: View[]
}
/** @internal */
export interface DocumentListPaneNode extends BaseResolvedPaneNode<'documentList'> {
defaultLayout?: GeneralPreviewLayoutKey
displayOptions?: {showIcons?: boolean}
initialValueTemplates?: InitialValueTemplateItem[]
options: {
filter: string
defaultOrdering?: Array<{field: string; direction: 'asc' | 'desc'}>
params?: Record<string, unknown>
apiVersion?: string
}
schemaTypeName: string
source?: string
}
/** @internal */
export interface PaneListItem<TParams = unknown> {
type: 'listItem'
id: string
// these are specific to `DocumentListItem`
_id?: string
schemaType?: SchemaType
title: string
i18n?: I18nTextRecord<'title'>
icon?: React.ComponentType | false
displayOptions?: {showIcon?: boolean}
action?: (t: TParams) => unknown
params?: TParams
}
/** @internal */
export interface PaneListItemDivider {
type: 'divider'
}
/** @internal */
export interface ListPaneNode extends BaseResolvedPaneNode<'list'> {
defaultLayout?: GeneralPreviewLayoutKey
displayOptions?: {showIcons?: boolean}
items?: Array<PaneListItem | PaneListItemDivider>
// TODO: mark as unstable or remove
source?: string
}
/** @internal */
export type PaneNode =
| CustomComponentPaneNode
| DocumentPaneNode
| DocumentListPaneNode
| ListPaneNode
/** @internal */
export type SerializablePaneNode = {
// TODO: unify this context with `SerializeOptions`
serialize(context: RouterPaneSiblingContext): UnresolvedPaneNode
}
/** @internal */
export type PaneNodeResolver = (id: string, context: RouterPaneSiblingContext) => UnresolvedPaneNode
// TODO: these types need to be unified with the buidlers `ListBuilder
/** @internal */
export type UnresolvedPaneNode =
| PaneNodeResolver
| SerializablePaneNode
| Observable<UnresolvedPaneNode>
| PromiseLike<UnresolvedPaneNode>
| PaneNode
/**
* @hidden
* @beta */
export type DocumentFieldMenuActionNode = DocumentFieldActionNode & {
intent?: Intent
}