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
154 lines (122 loc) • 4.03 kB
text/typescript
import {
isIndexSegment,
isKeySegment,
isReferenceSchemaType,
type ObjectField,
type ObjectFieldType,
type ObjectSchemaType,
type SanityDocumentLike,
type SchemaType,
} from '@sanity/types'
import * as PathUtils from '@sanity/util/paths'
import {collate, getPublishedId} from 'sanity'
import {type DocumentListPaneItem, type SortOrder} from './types'
export function getDocumentKey(value: DocumentListPaneItem, index: number): string {
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
return {
...doc,
hasPublished: !!entry.published,
hasDraft: !!entry.draft,
}
}) as any
}
const RE_TYPE_NAME_IN_FILTER =
/\b_type\s*==\s*(['"].*?['"]|\$.*?(?:\s|$))|\B(['"].*?['"]|\$.*?(?:\s|$))\s*==\s*_type\b/
export function getTypeNameFromSingleTypeFilter(
filter: string,
params: Record<string, unknown> = {},
): string | null {
const matches = filter.match(RE_TYPE_NAME_IN_FILTER)
if (!matches) {
return null
}
const match = (matches[1] || matches[2]).trim().replace(/^["']|["']$/g, '')
if (match[0] === '$') {
const k = match.slice(1)
const v = params[k]
return typeof v === 'string' ? v : null
}
return match
}
export function isSimpleTypeFilter(filter: string): boolean {
return /^_type\s*==\s*['"$]\w+['"]?\s*$/.test(filter.trim())
}
export function applyOrderingFunctions(order: SortOrder, schemaType: ObjectSchemaType): SortOrder {
const orderBy = order.by.map((by) => {
// Skip those that already have a mapper
if (by.mapWith) {
return by
}
const fieldType = tryResolveSchemaTypeForPath(schemaType, by.field)
if (!fieldType) {
return by
}
// Note: order matters here, since the jsonType of a date field is `string`,
// but we want to apply `datetime()`, not `lower()`
if (fieldExtendsType(fieldType, 'datetime')) {
return {...by, mapWith: 'dateTime'}
}
if (fieldType.jsonType === 'string') {
return {...by, mapWith: 'lower'}
}
return by
})
return orderBy.every((item, index) => item === order.by[index]) ? order : {...order, by: orderBy}
}
function tryResolveSchemaTypeForPath(baseType: SchemaType, path: string): SchemaType | undefined {
const pathSegments = PathUtils.fromString(path)
let current: SchemaType | undefined = baseType
for (const segment of pathSegments) {
if (!current) {
return undefined
}
if (typeof segment === 'string') {
current = getFieldTypeByName(current, segment)
continue
}
const isArrayAccessor = isKeySegment(segment) || isIndexSegment(segment)
if (!isArrayAccessor || current.jsonType !== 'array') {
return undefined
}
const [memberType, otherType] = current.of || []
if (otherType || !memberType) {
// Can't figure out the type without knowing the value
return undefined
}
if (!isReferenceSchemaType(memberType)) {
current = memberType
continue
}
const [refType, otherRefType] = memberType.to || []
if (otherRefType || !refType) {
// Can't figure out the type without knowing the value
return undefined
}
current = refType
}
return current
}
function getFieldTypeByName(type: SchemaType, fieldName: string): SchemaType | undefined {
if (!('fields' in type)) {
return undefined
}
const fieldType = type.fields.find((field) => field.name === fieldName)
return fieldType ? fieldType.type : undefined
}
export function fieldExtendsType(field: ObjectField | ObjectFieldType, ofType: string): boolean {
let current: SchemaType | undefined = field.type
while (current) {
if (current.name === ofType) {
return true
}
if (!current.type && current.jsonType === ofType) {
return true
}
current = current.type
}
return false
}