import {studioPath} from '@sanity/client/csm' import type {SanityNode, SanityStegaNode} from '@sanity/visual-editing-types' import {minLength, object, optional, parse, pipe, record, safeParse, string, unknown} from 'valibot' import {isValidSanityNode} from './isValidSanityNode' import {sanityNodeSchema} from './sanityNodeSchema' import {urlStringToPath} from './urlStringToPath' const lengthyStr = pipe(string(), minLength(1)) const sanityLegacyNodeSchema = object({ origin: lengthyStr, href: lengthyStr, data: optional(record(string(), unknown())), }) /** * Transforms a sanity data string into sanity node data * @param str - An encoded string of sanity data * @internal */ export function decodeSanityString(str: string): SanityNode | undefined { const segments = str.split(';') const data = segments.reduce((acc, segment) => { const [key, value] = segment.split('=') if (!key || (segment.includes('=') && !value)) return acc switch (key) { case 'id': acc.id = value break case 'type': acc.type = value break case 'path': acc.path = studioPath.toString(urlStringToPath(value)) break case 'base': acc.baseUrl = decodeURIComponent(value) break case 'perspective': acc.perspective = value break case 'tool': acc.tool = value break case 'workspace': acc.workspace = value break case 'projectId': acc.projectId = value break case 'dataset': acc.dataset = value break default: } return acc }, {} as Partial) if (!isValidSanityNode(data)) return undefined return data } /** * Transforms stringified JSON into sanity node data * @param str - JSON sanity data */ function decodeSanityObject( data: Record, ): SanityNode | SanityStegaNode | undefined { const sanityNode = safeParse(sanityNodeSchema, data) if (sanityNode.success) { return sanityNode.output } const sanityLegacyNode = safeParse(sanityLegacyNodeSchema, data) if (sanityLegacyNode.success) { try { const url = new URL( sanityLegacyNode.output.href, typeof document === 'undefined' ? 'https://example.com' : location.origin, ) if (url.searchParams.size > 0) { return parse(sanityNodeSchema, Object.fromEntries(url.searchParams.entries())) } return sanityLegacyNode.output } catch (err) { // eslint-disable-next-line no-console console.error('Failed to parse sanity node', err) return sanityLegacyNode.output } } return undefined } /** * Transforms sanity data from multiple formats into sanity node data * @param str - Sanity data as a string of unknown format * @internal */ export function decodeSanityNodeData( data: SanityStegaNode | string, ): SanityNode | SanityStegaNode | undefined { if (typeof data === 'object' && data !== null) { return decodeSanityObject(data) } // @TODO maybe check if the string starts with `{` before attempting to JSON parse it? try { const obj = JSON.parse(data) return decodeSanityObject(obj) } catch { return decodeSanityString(data) } }