UNPKG

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

187 lines (154 loc) • 4.96 kB
import { isIndexSegment, isIndexTuple, isKeySegment, type KeyedSegment, type Path, } from '@sanity/types' import {type UserColor, type UserColorManager} from '../../../user-color' import {pathToString, stringToPath} from '../../paths/helpers' import { type Annotation, type ArrayDiff, type Diff, type ItemDiff, type ObjectDiff, type StringDiff, type StringDiffSegment, } from '../../types' /** @internal */ export function getAnnotationColor( colorManager: UserColorManager, annotation?: Annotation | null, ): UserColor { return colorManager.get(annotation?.author || null) } /** @internal */ export function getAnnotationAtPath(diff: Diff, diffPath: string | Path): Annotation | undefined { const path: Path = Array.isArray(diffPath) ? diffPath : stringToPath(diffPath) return getAnnotationAt(diff, path) } /** @internal */ export function getDiffAtPath(diff: Diff, diffPath: string | Path): Diff | undefined { const path: Path = Array.isArray(diffPath) ? diffPath : stringToPath(diffPath) return getDiffAt(diff, path) } function getAnnotationAt(diff: Diff, path: Path): Annotation | undefined { const diffAt = getDiffAt(diff, path) if (!diffAt) { return undefined } if (diffAt.action === 'unchanged') { return undefined } return diffAt.annotation || undefined } // eslint-disable-next-line complexity function getDiffAt(diff: Diff, path: Path, parentPath: Path = []): Diff | undefined { if (path.length === 0) { return diff } const segment = path[0] const tail = path.slice(1) if (isIndexTuple(segment)) { throw new Error('Index tuples are not supported in diff paths') } if (isIndexSegment(segment) || isKeySegment(segment)) { const location = isIndexSegment(segment) ? `at index ${segment}` : `with key ${segment._key}` if (diff.type !== 'array') { warn(`Failed to get item ${location} at path ${pathToString(parentPath)} (not an array)`) return undefined } const itemDiff = diff.items.find( isIndexSegment(segment) ? (item) => item.toIndex === segment : (item) => itemMatchesKey(item, segment), ) if (!itemDiff) { warn(`Failed to get item ${location} at path ${pathToString(parentPath)} (item missing)`) return undefined } return getDiffAt(itemDiff.diff, tail, parentPath.concat(segment)) } if (diff.type !== 'object') { warn(`Failed to get property ${segment} at path ${pathToString(parentPath)} (not an object)`) return undefined } const fieldDiff = diff.fields[segment] if (typeof fieldDiff === 'undefined') { warn( `Failed to get property ${segment} at path ${pathToString(parentPath)} (field did not exist)`, ) return undefined } return getDiffAt(fieldDiff, tail, parentPath.concat(segment)) } /* eslint-disable no-console, @typescript-eslint/no-unused-vars */ function warn(msg: string) { //console.warn(msg) } /* eslint-enable no-console, @typescript-eslint/no-unused-vars */ function itemMatchesKey(item: ItemDiff, key: KeyedSegment) { const itemDiff = item.diff return itemDiff.type !== 'object' || !itemDiff.toValue ? false : itemDiff.toValue._key === key } /** @internal */ export type DiffVisitor = (diff: Diff | StringDiffSegment, path: Path) => boolean /** * Visit all diffs in tree, until visitor returns false * * @param diff - Diff to visit * @param visitor - Visitor function, return false to stop from going deeper * * @internal */ export function visitDiff( diff: Diff | StringDiffSegment, visitor: DiffVisitor, path: Path = [], ): void { if (!visitor(diff, path)) { return } if (diff.type === 'array') { visitArrayDiff(diff, visitor, path) return } if (diff.type === 'object') { visitObjectDiff(diff, visitor, path) return } if (diff.type === 'string') { visitStringDiff(diff, visitor, path) } } function visitArrayDiff(diff: ArrayDiff, visitor: DiffVisitor, path: Path) { if (diff.action === 'unchanged') { return } diff.items.forEach((itemDiff) => { const _key = itemDiff.diff.type === 'object' && (itemDiff.diff.toValue?._key as string) const segment = _key ? {_key} : getItemDiffIndex(itemDiff) visitDiff(itemDiff.diff, visitor, path.concat(segment)) }) } function visitObjectDiff(diff: ObjectDiff, visitor: DiffVisitor, path: Path) { if (diff.action === 'unchanged') { return } Object.keys(diff.fields).forEach((fieldName) => { const fieldDiff = diff.fields[fieldName] visitDiff(fieldDiff, visitor, path.concat(fieldName)) }) } function visitStringDiff(diff: StringDiff, visitor: DiffVisitor, path: Path) { if (diff.action === 'unchanged') { return } diff.segments.forEach((segment) => { visitDiff(segment, visitor, path) }) } function getItemDiffIndex(itemDiff: ItemDiff): number { return typeof itemDiff.toIndex === 'undefined' ? itemDiff.fromIndex || 0 : itemDiff.toIndex }