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
56 lines (46 loc) • 1.75 kB
text/typescript
import {isKeyedObject} from '@sanity/types'
import * as PathUtils from '@sanity/util/paths'
import {type TrackedArea, type TrackedChange} from '../tracker'
export function findMostSpecificTarget(
targetType: 'change' | 'field',
id: string,
values: Map<string, TrackedChange | TrackedArea>,
): TrackedChange | undefined {
const pathString = id.slice(id.indexOf('-') + 1) || '[]'
const path = PathUtils.fromString(pathString)
const exactId = `${targetType}-${PathUtils.toString(path)}`
if (values.has(exactId)) {
return values.get(exactId) as TrackedChange
}
let mostSpecific: TrackedChange | undefined
for (const [targetId, target] of values) {
if (!('path' in target) || !targetId.startsWith(targetType)) {
continue
}
const numEqual = PathUtils.numEqualSegments(path, target.path)
const lastPathSegment = target.path[target.path.length - 1]
const pathOnlyDiffersByKey =
numEqual === target.path.length - 1 && isKeyedObject(lastPathSegment)
if (numEqual === 0) {
continue
} else if (numEqual !== target.path.length && !pathOnlyDiffersByKey) {
/*
* We only allow matching to shorter paths if the last element in the path is a keyed object.
* `foo.bar.portableTextField[_key=abc123]` should resolve to `foo.bar.portableTextField`.
*
* Otherwise we'll only get a connector line when you add new elements to a portable text
* field and not when you alter an existing part.
*/
continue
}
mostSpecific = target
if (numEqual === path.length) {
// On exact match, return early
break
}
}
// if (!mostSpecific) {
// throw new Error(`target not found: "${id}"`)
// }
return mostSpecific
}