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

1,521 lines • 78.9 kB
"use strict"; var jsxRuntime = require("react/jsx-runtime"), React = require("react"), sanity = require("sanity"), _singletons = require("sanity/_singletons"), reactIs = require("react-is"), icons = require("@sanity/icons"), camelCase = require("lodash/camelCase.js"), speakingurl = require("speakingurl"), uniqueId = require("lodash/uniqueId.js"), uniq = require("lodash/uniq.js"), kebabCase = require("lodash/kebabCase.js"), generateHelpUrl_esm = require("./generate-help-url.esm.js"), find = require("lodash/find.js"), startCase = require("lodash/startCase.js"); function _interopDefaultCompat(e) { return e && typeof e == "object" && "default" in e ? e : { default: e }; } var camelCase__default = /* @__PURE__ */ _interopDefaultCompat(camelCase), speakingurl__default = /* @__PURE__ */ _interopDefaultCompat(speakingurl), uniqueId__default = /* @__PURE__ */ _interopDefaultCompat(uniqueId), uniq__default = /* @__PURE__ */ _interopDefaultCompat(uniq), kebabCase__default = /* @__PURE__ */ _interopDefaultCompat(kebabCase), find__default = /* @__PURE__ */ _interopDefaultCompat(find), startCase__default = /* @__PURE__ */ _interopDefaultCompat(startCase); const structureLocaleNamespace = "structure", structureUsEnglishLocaleBundle = sanity.defineLocaleResourceBundle({ locale: "en-US", namespace: structureLocaleNamespace, resources: () => Promise.resolve().then(function() { return require("./resources6.js"); }) }), IMPLICIT_SCHEMA_TYPE_FIELDS = ["_id", "_type", "_createdAt", "_updatedAt", "_rev"]; function joinReferences(schemaType, path, strict = !1) { const [head, ...tail] = path; if (!("fields" in schemaType)) return ""; const schemaField = schemaType.fields.find((field) => field.name === head); if (!schemaField) { if (!IMPLICIT_SCHEMA_TYPE_FIELDS.includes(head)) { const errorMessage = `The current ordering config targeted the nonexistent field "${head}" on schema type "${schemaType.name}". It should be one of ${schemaType.fields.map((field) => field.name).join(", ")}`; if (strict) throw new Error(errorMessage); console.warn(errorMessage); } return ""; } if ("to" in schemaField.type && schemaField.type.name === "reference") { const refTypes = schemaField.type.to; return `${head}->{${refTypes.map((refType) => joinReferences(refType, tail)).join(",")}}`; } const tailFields = tail.length > 0 && joinReferences(schemaField.type, tail), tailWrapper = tailFields ? `{${tailFields}}` : ""; return tail.length > 0 ? `${head}${tailWrapper}` : head; } function getExtendedProjection(schemaType, orderBy, strict = !1) { return orderBy.map((ordering) => joinReferences(schemaType, ordering.field.split("."), strict)).join(", "); } class SerializeError extends Error { constructor(message, parentPath, pathSegment, hint) { super(message), this.name = "SerializeError"; const segment = typeof pathSegment > "u" ? "<unknown>" : `${pathSegment}`; this.path = (parentPath || []).concat(hint ? `${segment} (${hint})` : segment); } withHelpUrl(id) { return this.helpId = id, this; } } const HELP_URL = { ID_REQUIRED: "structure-node-id-required", TITLE_REQUIRED: "structure-title-required", FILTER_REQUIRED: "structure-filter-required", INVALID_LIST_ITEM: "structure-invalid-list-item", COMPONENT_REQUIRED: "structure-view-component-required", DOCUMENT_ID_REQUIRED: "structure-document-id-required", DOCUMENT_TYPE_REQUIRED: "structure-document-type-required", SCHEMA_TYPE_REQUIRED: "structure-schema-type-required", SCHEMA_TYPE_NOT_FOUND: "structure-schema-type-not-found", LIST_ITEMS_MUST_BE_ARRAY: "structure-list-items-must-be-array", QUERY_PROVIDED_FOR_FILTER: "structure-query-provided-for-filter", ACTION_OR_INTENT_REQUIRED: "structure-action-or-intent-required", LIST_ITEM_IDS_MUST_BE_UNIQUE: "structure-list-item-ids-must-be-unique", ACTION_AND_INTENT_MUTUALLY_EXCLUSIVE: "structure-action-and-intent-mutually-exclusive", API_VERSION_REQUIRED_FOR_CUSTOM_FILTER: "structure-api-version-required-for-custom-filter" }, ORDER_BY_UPDATED_AT = { title: "Last edited", i18n: { title: { key: "menu-items.sort-by.last-edited", ns: structureLocaleNamespace } }, name: "lastEditedDesc", by: [{ field: "_updatedAt", direction: "desc" }] }, ORDER_BY_CREATED_AT = { title: "Created", i18n: { title: { key: "menu-items.sort-by.created", ns: structureLocaleNamespace } }, name: "lastCreatedDesc", by: [{ field: "_createdAt", direction: "desc" }] }, DEFAULT_SELECTED_ORDERING_OPTION = ORDER_BY_UPDATED_AT, DEFAULT_ORDERING_OPTIONS = [ ORDER_BY_UPDATED_AT, // _updatedAt ORDER_BY_CREATED_AT // _createdAt ]; function maybeSerializeMenuItem(item, index, path) { return item instanceof MenuItemBuilder ? item.serialize({ path, index }) : item; } class MenuItemBuilder { /** menu item option object. See {@link PartialMenuItem} */ constructor(_context, spec) { this._context = _context, this.spec = spec || {}; } /** * Set menu item action * @param action - menu item action. See {@link MenuItemActionType} * @returns menu item builder based on action provided. See {@link MenuItemBuilder} */ action(action) { return this.clone({ action }); } /** * Get menu item action * @returns menu item builder action. See {@link PartialMenuItem} */ getAction() { return this.spec.action; } /** * Set menu item intent * @param intent - menu item intent. See {@link Intent} * @returns menu item builder based on intent provided. See {@link MenuItemBuilder} */ intent(intent) { return this.clone({ intent }); } /** * Get menu item intent * @returns menu item intent. See {@link PartialMenuItem} */ getIntent() { return this.spec.intent; } /** * Set menu item title * @param title - menu item title * @returns menu item builder based on title provided. See {@link MenuItemBuilder} */ title(title) { return this.clone({ title }); } /** * Get menu item title. Note that the `i18n` configuration will take * precedence and this title is left here for compatibility. * @returns menu item title */ getTitle() { return this.spec.title; } /** * Set the i18n key and namespace used to populate the localized title. * @param i18n - object with i18n key and related namespace * @returns menu item builder based on i18n config provided. See {@link MenuItemBuilder} */ i18n(i18n) { return this.clone({ i18n }); } /** * Get the i18n key and namespace used to populate the localized title. * @returns the i18n key and namespace used to populate the localized title. */ getI18n() { return this.spec.i18n; } /** * Set menu item group * @param group - menu item group * @returns menu item builder based on group provided. See {@link MenuItemBuilder} */ group(group) { return this.clone({ group }); } /** * Get menu item group * @returns menu item group. See {@link PartialMenuItem} */ getGroup() { return this.spec.group; } /** * Set menu item icon * @param icon - menu item icon * @returns menu item builder based on icon provided. See {@link MenuItemBuilder} */ icon(icon) { return this.clone({ icon }); } /** * Get menu item icon * @returns menu item icon. See {@link PartialMenuItem} */ getIcon() { return this.spec.icon; } /** * Set menu item parameters * @param params - menu item parameters. See {@link MenuItemParamsType} * @returns menu item builder based on parameters provided. See {@link MenuItemBuilder} */ params(params) { return this.clone({ params }); } /** * Get meny item parameters * @returns menu item parameters. See {@link PartialMenuItem} */ getParams() { return this.spec.params; } /** * Set menu item to show as action * @param showAsAction - determine if menu item should show as action * @returns menu item builder based on if it should show as action. See {@link MenuItemBuilder} */ showAsAction(showAsAction = !0) { return this.clone({ showAsAction: !!showAsAction }); } /** * Check if menu item should show as action * @returns true if menu item should show as action, false if not. See {@link PartialMenuItem} */ getShowAsAction() { return this.spec.showAsAction; } /** Serialize menu item builder * @param options - serialization options. See {@link SerializeOptions} * @returns menu item node based on path provided in options. See {@link MenuItem} */ serialize(options = { path: [] }) { const { title, action, intent } = this.spec; if (!title) { const hint = typeof action == "string" ? `action: "${action}"` : void 0; throw new SerializeError("`title` is required for menu item", options.path, options.index, hint).withHelpUrl(HELP_URL.TITLE_REQUIRED); } if (!action && !intent) throw new SerializeError(`\`action\` or \`intent\` required for menu item with title ${this.spec.title}`, options.path, options.index, `"${title}"`).withHelpUrl(HELP_URL.ACTION_OR_INTENT_REQUIRED); if (intent && action) throw new SerializeError("cannot set both `action` AND `intent`", options.path, options.index, `"${title}"`).withHelpUrl(HELP_URL.ACTION_AND_INTENT_MUTUALLY_EXCLUSIVE); return { ...this.spec, title }; } /** Clone menu item builder * @param withSpec - menu item options. See {@link PartialMenuItem} * @returns menu item builder based on context and spec provided. See {@link MenuItemBuilder} */ clone(withSpec) { const builder = new MenuItemBuilder(this._context); return builder.spec = { ...this.spec, ...withSpec }, builder; } } function getOrderingMenuItem(context, { by, title, i18n }, extendedProjection) { let builder = new MenuItemBuilder(context).group("sorting").title(context.i18n.t("default-menu-item.fallback-title", { // note this lives in the `studio` bundle because that one is loaded by default ns: "studio", replace: { title } // replaces the `{{title}}` option })).icon(icons.SortIcon).action("setSortOrder").params({ by, extendedProjection }); return i18n && (builder = builder.i18n(i18n)), builder; } function getOrderingMenuItemsForSchemaType(context, typeName) { const { schema } = context, type = typeof typeName == "string" ? schema.get(typeName) : typeName; return !type || !("orderings" in type) ? [] : (type.orderings ? type.orderings.concat(DEFAULT_ORDERING_OPTIONS) : DEFAULT_ORDERING_OPTIONS).map((ordering) => getOrderingMenuItem(context, ordering, getExtendedProjection(type, ordering.by))); } function maybeSerializeMenuItemGroup(item, index, path) { return item instanceof MenuItemGroupBuilder ? item.serialize({ path, index }) : item; } class MenuItemGroupBuilder { /** Menu item group ID */ /** Menu item group title */ constructor(_context, spec) { this._context = _context, this._id = spec ? spec.id : "", this._title = spec ? spec.title : "", this._i18n = spec ? spec.i18n : void 0; } /** * Set menu item group ID * @param id - menu item group ID * @returns menu item group builder based on ID provided. See {@link MenuItemGroupBuilder} */ id(id) { return new MenuItemGroupBuilder(this._context, { id, title: this._title, i18n: this._i18n }); } /** * Get menu item group ID * @returns menu item group ID */ getId() { return this._id; } /** * Set menu item group title * @param title - menu item group title * @returns menu item group builder based on title provided. See {@link MenuItemGroupBuilder} */ title(title) { return new MenuItemGroupBuilder(this._context, { title, id: this._id, i18n: this._i18n }); } /** * Get menu item group title * @returns menu item group title */ getTitle() { return this._title; } /** * Set the i18n key and namespace used to populate the localized title. * @param i18n - object with i18n key and related namespace * @returns menu item group builder based on i18n info provided. See {@link MenuItemGroupBuilder} */ i18n(i18n) { return new MenuItemGroupBuilder(this._context, { i18n, id: this._id, title: this._title }); } /** * Get the i18n key and namespace used to populate the localized title. * @returns the i18n key and namespace used to populate the localized title. */ getI18n() { return this._i18n; } /** * Serialize menu item group builder * @param options - serialization options (path). See {@link SerializeOptions} * @returns menu item group based on path provided in options. See {@link MenuItemGroup} */ serialize(options = { path: [] }) { if (!this._id) throw new SerializeError("`id` is required for a menu item group", options.path, options.index, this._title).withHelpUrl(HELP_URL.ID_REQUIRED); if (!this._title) throw new SerializeError("`title` is required for a menu item group", options.path, this._id).withHelpUrl(HELP_URL.TITLE_REQUIRED); return { id: this._id, title: this._title, i18n: this._i18n }; } } const disallowedPattern = /([^A-Za-z0-9-_.])/; function validateId(id, parentPath, pathSegment) { if (typeof id != "string") throw new SerializeError(`Structure node id must be of type string, got ${typeof id}`, parentPath, pathSegment); const [disallowedChar] = id.match(disallowedPattern) || []; if (disallowedChar) throw new SerializeError(`Structure node id cannot contain character "${disallowedChar}"`, parentPath, pathSegment); if (id.startsWith("__edit__")) throw new SerializeError("Structure node id cannot start with __edit__", parentPath, pathSegment); return id; } function getStructureNodeId(title, id) { if (id) return id; const camelCased = camelCase__default.default(title); return disallowedPattern.test(camelCased) ? camelCase__default.default(speakingurl__default.default(title)) : camelCased; } class ComponentBuilder { /** component builder option object */ constructor(spec) { this.spec = { options: {}, ...spec || {} }; } /** Set Component ID * @param id - component ID * @returns component builder based on ID provided */ id(id) { return this.clone({ id }); } /** Get ID * @returns ID */ getId() { return this.spec.id; } /** Set Component title * @param title - component title * @returns component builder based on title provided (and ID) */ title(title) { return this.clone({ title, id: getStructureNodeId(title, this.spec.id) }); } /** Get Component title * @returns title */ getTitle() { return this.spec.title; } /** Set the i18n key and namespace used to populate the localized title. * @param i18n - the key and namespaced used to populate the localized title. * @returns component builder based on i18n key and ns provided */ i18n(i18n) { return this.clone({ i18n }); } /** Get i18n key and namespace used to populate the localized title * @returns the i18n key and namespace used to populate the localized title */ getI18n() { return this.spec.i18n; } /** Set Component child * @param child - child component * @returns component builder based on child component provided */ child(child) { return this.clone({ child }); } /** Get Component child * @returns child component */ getChild() { return this.spec.child; } /** Set component * @param component - user built component * @returns component builder based on component provided */ component(component2) { return this.clone({ component: component2 }); } /** Get Component * @returns component */ getComponent() { return this.spec.component; } /** Set Component options * @param options - component options * @returns component builder based on options provided */ options(options) { return this.clone({ options }); } /** Get Component options * @returns component options */ getOptions() { return this.spec.options || {}; } /** Set Component menu items * @param menuItems - component menu items * @returns component builder based on menuItems provided */ menuItems(menuItems) { return this.clone({ menuItems }); } /** Get Component menu items * @returns menu items */ getMenuItems() { return this.spec.menuItems; } /** Set Component menu item groups * @param menuItemGroups - component menu item groups * @returns component builder based on menuItemGroups provided */ menuItemGroups(menuItemGroups) { return this.clone({ menuItemGroups }); } /** Get Component menu item groups * @returns menu item groups */ getMenuItemGroups() { return this.spec.menuItemGroups; } canHandleIntent(canHandleIntent) { return this.clone({ canHandleIntent }); } /** Serialize component * @param options - serialization options * @returns component object based on path provided in options * */ serialize(options = { path: [] }) { const { id, title, child, options: componentOptions, component: component2 } = this.spec; if (!id) throw new SerializeError("`id` is required for `component` structure item", options.path, options.index).withHelpUrl(HELP_URL.ID_REQUIRED); if (!component2) throw new SerializeError("`component` is required for `component` structure item", options.path, options.index).withHelpUrl(HELP_URL.ID_REQUIRED); return { id: validateId(id, options.path, options.index), title, type: "component", child, component: component2, canHandleIntent: this.spec.canHandleIntent, options: componentOptions || {}, menuItems: (this.spec.menuItems || []).map((item, i) => maybeSerializeMenuItem(item, i, options.path)), menuItemGroups: (this.spec.menuItemGroups || []).map((item, i) => maybeSerializeMenuItemGroup(item, i, options.path)) }; } /** Clone component builder (allows for options overriding) * @param withSpec - component builder options * @returns cloned builder */ clone(withSpec) { const builder = new ComponentBuilder(); return builder.spec = { ...this.spec, ...withSpec }, builder; } } class DividerBuilder { constructor(spec) { this.spec = { id: uniqueId__default.default("__divider__"), type: "divider", ...spec }; } /** Set the title of the divider * @param title - the title of the divider * @returns divider builder based on title provided */ title(title) { return this.clone({ title }); } /** Get the title of the divider * @returns the title of the divider */ getTitle() { return this.spec.title; } /** Set the i18n key and namespace used to populate the localized title. * @param i18n - the key and namespaced used to populate the localized title. * @returns divider builder based on i18n key and ns provided */ i18n(i18n) { return this.clone({ i18n }); } /** Get i18n key and namespace used to populate the localized title * @returns the i18n key and namespace used to populate the localized title */ getI18n() { return this.spec.i18n; } /** Serialize the divider * @returns the serialized divider */ serialize() { return { ...this.spec }; } /** Clone divider builder (allows for options overriding) * @param withSpec - divider builder options * @returns cloned builder */ clone(withSpec) { const builder = new DividerBuilder(); return builder.spec = { ...this.spec, ...withSpec }, builder; } } async function resolveTypeForDocument(getClient, id) { return await getClient(sanity.DEFAULT_STUDIO_CLIENT_OPTIONS).fetch("*[sanity::versionOf($publishedId)][0]._type", { publishedId: sanity.getPublishedId(id) }, { tag: "structure.resolve-type" }); } class GenericViewBuilder { /** Generic view option object */ spec = {}; /** Set generic view ID * @param id - generic view ID * @returns generic view builder based on ID provided. */ id(id) { return this.clone({ id }); } /** Get generic view ID * @returns generic view ID */ getId() { return this.spec.id; } /** Set generic view title * @param title - generic view title * @returns generic view builder based on title provided and (if provided) its ID. */ title(title) { return this.clone({ title, id: this.spec.id || kebabCase__default.default(title) }); } /** Get generic view title * @returns generic view title */ getTitle() { return this.spec.title; } /** Set generic view icon * @param icon - generic view icon * @returns generic view builder based on icon provided. */ icon(icon) { return this.clone({ icon }); } /** Get generic view icon * @returns generic view icon */ getIcon() { return this.spec.icon; } /** Serialize generic view * @param options - serialization options. See {@link SerializeOptions} * @returns generic view object based on path provided in options. See {@link BaseView} */ serialize(options = { path: [] }) { const { id, title, icon } = this.spec; if (!id) throw new SerializeError("`id` is required for view item", options.path, options.index).withHelpUrl(HELP_URL.ID_REQUIRED); if (!title) throw new SerializeError("`title` is required for view item", options.path, options.index).withHelpUrl(HELP_URL.TITLE_REQUIRED); return { id: validateId(id, options.path, options.index), title, icon }; } /** Clone generic view builder (allows for options overriding) * @param withSpec - Partial generic view builder options. See {@link BaseView} * @returns Generic view builder. */ } function isSerializable(view) { return typeof view.serialize == "function"; } function maybeSerializeView(item, index, path) { return isSerializable(item) ? item.serialize({ path, index }) : item; } const isComponentSpec = (spec) => sanity.isRecord(spec) && spec.type === "component"; class ComponentViewBuilder extends GenericViewBuilder { /** Partial Component view option object. See {@link ComponentView} */ constructor(componentOrSpec) { const spec = isComponentSpec(componentOrSpec) ? { ...componentOrSpec } : { options: {} }; super(), this.spec = spec; const userComponent = typeof componentOrSpec == "function" ? componentOrSpec : this.spec.component; userComponent && (this.spec = this.component(userComponent).spec); } /** Set view Component * @param component - component view component. See {@link UserViewComponent} * @returns component view builder based on component view provided. See {@link ComponentViewBuilder} */ component(component2) { return this.clone({ component: component2 }); } /** Get view Component * @returns Partial component view. See {@link ComponentView} */ getComponent() { return this.spec.component; } /** Set view Component options * @param options - component view options * @returns component view builder based on options provided. See {@link ComponentViewBuilder} */ options(options) { return this.clone({ options }); } /** Get view Component options * @returns component view options. See {@link ComponentView} */ getOptions() { return this.spec.options || {}; } /** Serialize view Component * @param options - serialization options. See {@link SerializeOptions} * @returns component view based on path provided in options. See {@link ComponentView} * */ serialize(options = { path: [] }) { const base = super.serialize(options), component2 = this.spec.component; if (typeof component2 != "function") throw new SerializeError("`component` is required and must be a function for `component()` view item", options.path, options.index).withHelpUrl(HELP_URL.COMPONENT_REQUIRED); return { ...base, component: component2, options: this.spec.options || {}, type: "component" }; } /** Clone Component view builder (allows for options overriding) * @param withSpec - partial for component view option. See {@link ComponentView} * @returns component view builder. See {@link ComponentViewBuilder} */ clone(withSpec) { const builder = new ComponentViewBuilder(); return builder.spec = { ...this.spec, ...withSpec }, builder; } } class FormViewBuilder extends GenericViewBuilder { /** Document list options. See {@link FormView} */ constructor(spec) { super(), this.spec = { id: "editor", title: "Editor", ...spec || {} }; } /** * Serialize Form view builder * @param options - Serialize options. See {@link SerializeOptions} * @returns form view builder based on path provided in options. See {@link FormView} */ serialize(options = { path: [] }) { return { ...super.serialize(options), type: "form" }; } /** * Clone Form view builder (allows for options overriding) * @param withSpec - Partial form view builder options. See {@link FormView} * @returns form view builder. See {@link FormViewBuilder} */ clone(withSpec) { const builder = new FormViewBuilder(); return builder.spec = { ...this.spec, ...withSpec }, builder; } } const form = (spec) => new FormViewBuilder(spec), component = (componentOrSpec) => new ComponentViewBuilder(componentOrSpec); var views = /* @__PURE__ */ Object.freeze({ __proto__: null, ComponentViewBuilder, FormViewBuilder, GenericViewBuilder, component, form, maybeSerializeView }); const createDocumentChildResolver = ({ resolveDocumentNode, getClient }) => async (itemId, { params, path }) => { let type = params.type; const parentPath = path.slice(0, path.length - 1), currentSegment = path[path.length - 1]; if (type || (type = await resolveTypeForDocument(getClient, itemId)), !type) throw new SerializeError("Failed to resolve document, and no type provided in parameters.", parentPath, currentSegment); return resolveDocumentNode({ documentId: itemId, schemaType: type }); }; class DocumentBuilder { /** Component builder option object See {@link PartialDocumentNode} */ constructor(_context, spec) { this._context = _context, this.spec = spec || {}; } /** Set Document Builder ID * @param id - document builder ID * @returns document builder based on ID provided. See {@link DocumentBuilder} */ id(id) { return this.clone({ id }); } /** Get Document Builder ID * @returns document ID. See {@link PartialDocumentNode} */ getId() { return this.spec.id; } /** Set Document title * @param title - document title * @returns document builder based on title provided (and ID). See {@link DocumentBuilder} */ title(title) { return this.clone({ title, id: getStructureNodeId(title, this.spec.id) }); } /** Get Document title * @returns document title. See {@link PartialDocumentNode} */ getTitle() { return this.spec.title; } /** Set the i18n key and namespace used to populate the localized title. * @param i18n - the key and namespaced used to populate the localized title. * @returns component builder based on i18n key and ns provided */ i18n(i18n) { return this.clone({ i18n }); } /** Get i18n key and namespace used to populate the localized title * @returns the i18n key and namespace used to populate the localized title */ getI18n() { return this.spec.i18n; } /** Set Document child * @param child - document child * @returns document builder based on child provided. See {@link DocumentBuilder} */ child(child) { return this.clone({ child }); } /** Get Document child * @returns document child. See {@link PartialDocumentNode} */ getChild() { return this.spec.child; } /** Set Document ID * @param documentId - document ID * @returns document builder with document based on ID provided. See {@link DocumentBuilder} */ documentId(documentId) { const paneId = this.spec.id || documentId; return this.clone({ id: paneId, options: { ...this.spec.options, id: documentId } }); } /** Get Document ID * @returns document ID. See {@link DocumentOptions} */ getDocumentId() { return this.spec.options?.id; } /** Set Document Type * @param documentType - document type * @returns document builder with document based on type provided. See {@link DocumentBuilder} */ schemaType(documentType) { return this.clone({ options: { ...this.spec.options, type: typeof documentType == "string" ? documentType : documentType.name } }); } /** Get Document Type * @returns document type. See {@link DocumentOptions} */ getSchemaType() { return this.spec.options?.type; } /** Set Document Template * @param templateId - document template ID * @param parameters - document template parameters * @returns document builder with document based on template provided. See {@link DocumentBuilder} */ initialValueTemplate(templateId, parameters) { return this.clone({ options: { ...this.spec.options, template: templateId, templateParameters: parameters } }); } /** Get Document Template * @returns document template. See {@link DocumentOptions} */ getInitialValueTemplate() { return this.spec.options?.template; } /** Get Document's initial value Template parameters * @returns document template parameters. See {@link DocumentOptions} */ getInitialValueTemplateParameters() { return this.spec.options?.templateParameters; } /** Set Document views * @param views - document views. See {@link ViewBuilder} and {@link View} * @returns document builder with document based on views provided. See {@link DocumentBuilder} */ views(views2) { return this.clone({ views: views2 }); } /** Get Document views * @returns document views. See {@link ViewBuilder} and {@link View} */ getViews() { return this.spec.views || []; } /** Serialize Document builder * @param options - serialization options. See {@link SerializeOptions} * @returns document node based on path, index and hint provided in options. See {@link DocumentNode} */ serialize({ path = [], index, hint } = { path: [] }) { const urlId = path[index || path.length - 1], id = this.spec.id || urlId && `${urlId}` || "", options = { id, type: void 0, template: void 0, templateParameters: void 0, ...this.spec.options }; if (typeof id != "string" || !id) throw new SerializeError("`id` is required for document nodes", path, index, hint).withHelpUrl(HELP_URL.ID_REQUIRED); if (!options || !options.id) throw new SerializeError("document id (`id`) is required for document nodes", path, id, hint).withHelpUrl(HELP_URL.DOCUMENT_ID_REQUIRED); if (!options || !options.type) throw new SerializeError("document type (`schemaType`) is required for document nodes", path, id, hint); const views2 = (this.spec.views && this.spec.views.length > 0 ? this.spec.views : [form()]).map((item, i) => maybeSerializeView(item, i, path)), viewIds = views2.map((view) => view.id), dupes = uniq__default.default(viewIds.filter((viewId, i) => viewIds.includes(viewId, i + 1))); if (dupes.length > 0) throw new SerializeError(`document node has views with duplicate IDs: ${dupes.join(", ")}`, path, id, hint); return { ...this.spec, child: this.spec.child || createDocumentChildResolver(this._context), id: validateId(id, path, index), type: "document", options: getDocumentOptions(options), views: views2 }; } /** Clone Document builder * @param withSpec - partial document node specification used to extend the cloned builder. See {@link PartialDocumentNode} * @returns document builder based on context and spec provided. See {@link DocumentBuilder} */ clone(withSpec = {}) { const builder = new DocumentBuilder(this._context), options = { ...this.spec.options, ...withSpec.options }; return builder.spec = { ...this.spec, ...withSpec, options }, builder; } } function getDocumentOptions(spec) { const opts = { id: spec.id || "", type: spec.type || "*" }; return spec.template && (opts.template = spec.template), spec.templateParameters && (opts.templateParameters = spec.templateParameters), opts; } function documentFromEditor(context, spec) { let doc = spec?.type ? ( // Use user-defined document fragment as base if possible context.resolveDocumentNode({ schemaType: spec.type }) ) : ( // Fall back to plain old document builder new DocumentBuilder(context) ); if (!spec) return doc; const { id, type, template, templateParameters } = spec.options; return doc = doc.id(spec.id).documentId(id), type && (doc = doc.schemaType(type)), template && (doc = doc.initialValueTemplate(template, templateParameters)), spec.child && (doc = doc.child(spec.child)), doc; } function documentFromEditorWithInitialValue({ resolveDocumentNode, templates }, templateId, parameters) { const template = templates.find((t) => t.id === templateId); if (!template) throw new Error(`Template with ID "${templateId}" not defined`); return resolveDocumentNode({ schemaType: template.schemaType }).initialValueTemplate(templateId, parameters); } class InitialValueTemplateItemBuilder { /** Initial Value template item option object. See {@link InitialValueTemplateItem} */ constructor(_context, spec) { this._context = _context, this.spec = spec || {}; } /** Set initial value template item builder ID * @param id - initial value template item ID * @returns initial value template item based on ID provided. See {@link InitialValueTemplateItemBuilder} */ id(id) { return this.clone({ id }); } /** Get initial value template item builder ID * @returns initial value template item ID. See {@link InitialValueTemplateItem} */ getId() { return this.spec.id; } /** Set initial value template item title * @param title - initial value template item title * @returns initial value template item based on title provided. See {@link InitialValueTemplateItemBuilder} */ title(title) { return this.clone({ title }); } /** Get initial value template item title * @returns initial value template item title. See {@link InitialValueTemplateItem} */ getTitle() { return this.spec.title; } /** Set initial value template item description * @param description - initial value template item description * @returns initial value template item builder based on description provided. See {@link InitialValueTemplateItemBuilder} */ description(description) { return this.clone({ description }); } /** Get initial value template item description * @returns initial value template item description. See {@link InitialValueTemplateItem} */ getDescription() { return this.spec.description; } /** Set initial value template ID * @param templateId - initial value template item template ID * @returns initial value template item based builder on template ID provided. See {@link InitialValueTemplateItemBuilder} */ templateId(templateId) { const paneId = this.spec.id || templateId; return this.clone({ id: paneId, templateId }); } /** Get initial value template item template ID * @returns initial value template item ID. See {@link InitialValueTemplateItem} */ getTemplateId() { return this.spec.templateId; } /** Get initial value template item template parameters * @param parameters - initial value template item parameters * @returns initial value template item builder based on parameters provided. See {@link InitialValueTemplateItemBuilder} */ parameters(parameters) { return this.clone({ parameters }); } /** Get initial value template item template parameters * @returns initial value template item parameters. See {@link InitialValueTemplateItem} */ getParameters() { return this.spec.parameters; } /** Serialize initial value template item * @param options - serialization options. See {@link SerializeOptions} * @returns initial value template item object based on the path, index and hint provided in options. See {@link InitialValueTemplateItem} */ serialize({ path = [], index, hint } = { path: [] }) { if (typeof this.spec.id != "string" || !this.spec.id) throw new SerializeError("`id` is required for initial value template item nodes", path, index, hint).withHelpUrl(HELP_URL.ID_REQUIRED); if (!this.spec.templateId) throw new SerializeError("template id (`templateId`) is required for initial value template item nodes", path, this.spec.id, hint).withHelpUrl(HELP_URL.ID_REQUIRED); const template = this._context.templates.find((t) => t.id === this.spec.templateId); if (!template) throw new SerializeError("template id (`templateId`) is required for initial value template item nodes", path, this.spec.id, hint).withHelpUrl(HELP_URL.ID_REQUIRED); return { id: this.spec.id, templateId: this.spec.id, schemaType: template.schemaType, type: "initialValueTemplateItem", description: this.spec.description || template.description, title: this.spec.title || template.title, subtitle: this.spec.subtitle, icon: this.spec.icon || template.icon, initialDocumentId: this.spec.initialDocumentId, parameters: this.spec.parameters }; } /** Clone generic view builder (allows for options overriding) * @param withSpec - initial value template item builder options. See {@link InitialValueTemplateItemBuilder} * @returns initial value template item builder based on the context and options provided. See {@link InitialValueTemplateItemBuilder} */ clone(withSpec = {}) { const builder = new InitialValueTemplateItemBuilder(this._context); return builder.spec = { ...this.spec, ...withSpec }, builder; } } function defaultInitialValueTemplateItems(context) { const { schema, getStructureBuilder, templates } = context, typeNames = schema.getTypeNames(); return templates.filter((tpl) => !tpl.parameters?.length).sort((a, b) => typeNames.indexOf(a.schemaType) - typeNames.indexOf(b.schemaType)).map((tpl) => getStructureBuilder().initialValueTemplateItem(tpl.id)); } function maybeSerializeInitialValueTemplateItem(item, index, path) { return item instanceof InitialValueTemplateItemBuilder ? item.serialize({ path, index }) : item; } function menuItemsFromInitialValueTemplateItems(context, templateItems) { const { schema, templates } = context; return templateItems.map((item) => { const template = templates.find((t) => t.id === item.templateId), title = item.title || template?.title || "Create", params = {}; template && template.schemaType && (params.type = template.schemaType), item.templateId && (params.template = item.templateId); const intentParams = item.parameters ? [params, item.parameters] : params, schemaType = template && schema.get(template.schemaType), i18n = item.i18n || template?.i18n; let builder = new MenuItemBuilder(context).title(title).icon(template && template.icon || schemaType?.icon || icons.AddIcon).intent({ type: "create", params: intentParams }); return i18n && (builder = builder.i18n(i18n)), builder.serialize(); }); } const DEFAULT_INTENT_HANDLER = Symbol("Document type list canHandleIntent"), defaultIntentChecker = (intentName, params, { pane }) => { const isEdit = intentName === "edit", isCreate = intentName === "create", typedSpec = pane, paneFilter = typedSpec.options?.filter || "", paneParams = typedSpec.options?.params || {}, typeNames = typedSpec.schemaTypeName ? [typedSpec.schemaTypeName] : getTypeNamesFromFilter(paneFilter, paneParams), initialValueTemplates = typedSpec.initialValueTemplates || []; return isCreate && params.template ? initialValueTemplates.some((tpl) => tpl.templateId === params.template) : isEdit && params.id && typeNames.includes(params.type) || isCreate && typeNames.includes(params.type); }; defaultIntentChecker.identity = DEFAULT_INTENT_HANDLER; const layoutOptions = ["default", "card", "media", "detail", "block"]; function noChildResolver() { } const shallowIntentChecker = (intentName, params, { pane, index }) => index <= 1 && defaultIntentChecker(intentName, params, { pane }); class GenericListBuilder { /** Check if initial value templates are set */ initialValueTemplatesSpecified = !1; /** Generic list option object */ spec = {}; /** Set generic list ID * @param id - generic list ID * @returns generic list builder based on ID provided. */ id(id) { return this.clone({ id }); } /** Get generic list ID * @returns generic list ID */ getId() { return this.spec.id; } /** Set generic list title * @param title - generic list title * @returns generic list builder based on title and ID provided. */ title(title) { return this.clone({ title, id: getStructureNodeId(title, this.spec.id) }); } /** Get generic list title * @returns generic list title */ getTitle() { return this.spec.title; } /** Set the i18n key and namespace used to populate the localized title. * @param i18n - the key and namespaced used to populate the localized title. * @returns component builder based on i18n key and ns provided */ i18n(i18n) { return this.clone({ i18n }); } /** Get i18n key and namespace used to populate the localized title * @returns the i18n key and namespace used to populate the localized title */ getI18n() { return this.spec.i18n; } /** Set generic list layout * @param defaultLayout - generic list layout key. * @returns generic list builder based on layout provided. */ defaultLayout(defaultLayout) { return this.clone({ defaultLayout }); } /** Get generic list layout * @returns generic list layout */ getDefaultLayout() { return this.spec.defaultLayout; } /** Set generic list menu items * @param menuItems - generic list menu items. See {@link MenuItem} and {@link MenuItemBuilder} * @returns generic list builder based on menu items provided. */ menuItems(menuItems) { return this.clone({ menuItems }); } /** Get generic list menu items * @returns generic list menu items */ getMenuItems() { return this.spec.menuItems; } /** Set generic list menu item groups * @param menuItemGroups - generic list menu item groups. See {@link MenuItemGroup} and {@link MenuItemGroupBuilder} * @returns generic list builder based on menu item groups provided. */ menuItemGroups(menuItemGroups) { return this.clone({ menuItemGroups }); } /** Get generic list menu item groups * @returns generic list menu item groups */ getMenuItemGroups() { return this.spec.menuItemGroups; } /** Set generic list child * @param child - generic list child. See {@link Child} * @returns generic list builder based on child provided (clone). */ child(child) { return this.clone({ child }); } /** Get generic list child * @returns generic list child */ getChild() { return this.spec.child; } /** Set generic list can handle intent * @param canHandleIntent - generic list intent checker. See {@link IntentChecker} * @returns generic list builder based on can handle intent provided. */ canHandleIntent(canHandleIntent) { return this.clone({ canHandleIntent }); } /** Get generic list can handle intent * @returns generic list can handle intent */ getCanHandleIntent() { return this.spec.canHandleIntent; } /** Set generic list display options * @param enabled - allow / disallow for showing icons * @returns generic list builder based on display options (showIcons) provided. */ showIcons(enabled = !0) { return this.clone({ displayOptions: { ...this.spec.displayOptions, showIcons: enabled } }); } /** Get generic list display options * @returns generic list display options (specifically showIcons) */ getShowIcons() { return this.spec.displayOptions ? this.spec.displayOptions.showIcons : void 0; } /** Set generic list initial value templates * @param templates - generic list initial value templates. See {@link InitialValueTemplateItemBuilder} * @returns generic list builder based on templates provided. */ initialValueTemplates(templates) { return this.initialValueTemplatesSpecified = !0, this.clone({ initialValueTemplates: Array.isArray(templates) ? templates : [templates] }); } /** Get generic list initial value templates * @returns generic list initial value templates */ getInitialValueTemplates() { return this.spec.initialValueTemplates; } /** Serialize generic list * @param options - serialization options. See {@link SerializeOptions} * @returns generic list object based on path provided in options. See {@link GenericList} */ serialize(options = { path: [] }) { const id = this.spec.id || "", path = options.path, defaultLayout = this.spec.defaultLayout; if (defaultLayout && !layoutOptions.includes(defaultLayout)) throw new SerializeError(`\`layout\` must be one of ${layoutOptions.map((item) => `"${item}"`).join(", ")}`, path, id || options.index, this.spec.title); const initialValueTemplates = (this.spec.initialValueTemplates || []).map((item, i) => maybeSerializeInitialValueTemplateItem(item, i, path)); return { id: validateId(id, options.path, id || options.index), title: this.spec.title, i18n: this.spec.i18n, type: "genericList", defaultLayout, child: this.spec.child || noChildResolver, canHandleIntent: this.spec.canHandleIntent || shallowIntentChecker, displayOptions: this.spec.displayOptions, initialValueTemplates, menuItems: (this.spec.menuItems || []).map((item, i) => maybeSerializeMenuItem(item, i, path)), menuItemGroups: (this.spec.menuItemGroups || []).map((item, i) => maybeSerializeMenuItemGroup(item, i, path)) }; } /** Clone generic list builder (allows for options overriding) * @param _withSpec - generic list options. * @returns generic list builder. */ } const validateFilter = (spec, options) => { const filter = spec.options?.filter.trim() || ""; if (["*", "{"].includes(filter[0])) throw new SerializeError(`\`filter\` cannot start with \`${filter[0]}\` - looks like you are providing a query, not a filter`, options.path, spec.id, spec.title).withHelpUrl(HELP_URL.QUERY_PROVIDED_FOR_FILTER); return filter; }, createDocumentChildResolverForItem = (context) => (itemId, options) => { const parentItem = options.parent, template = options.params?.template ? context.templates.find((tpl) => tpl.id === options.params.template) : void 0, type = template ? template.schemaType : parentItem.schemaTypeName || resolveTypeForDocument(context.getClient, itemId); return Promise.resolve(type).then((schemaType) => schemaType ? context.resolveDocumentNode({ schemaType, documentId: itemId }) : new DocumentBuilder(context).id("editor").documentId(itemId).schemaType("")); }; class DocumentListBuilder extends GenericListBuilder { /** Document list options. See {@link PartialDocumentList} */ constructor(_context, spec) { super(), this._context = _context, this.spec = spec || {}, this.initialValueTemplatesSpecified = !!spec?.initialValueTemplates; } /** Set API version * @param apiVersion - API version * @returns document list builder based on the options and API version provided. See {@link DocumentListBuilder} */ apiVersion(apiVersion) { return this.clone({ options: { ...this.spec.options || { filter: "" }, apiVersion } }); } /** Get API version * @returns API version */ getApiVersion() { return this.spec.options?.apiVersion; } /** Set Document list filter * @param filter - GROQ-filter used to determine which documents to display. Do not support joins, since they operate on individual documents, and will ignore order-clauses and projections. See {@link https://www.sanity.io/docs/realtime-updates} * @returns document list builder based on the options and filter provided. See {@link DocumentListBuilder} */ filter(filter) { return this.clone({ options: { ...this.spec.options, filter } }); } /** Get Document list filter * @returns filter */ getFilter() { return this.spec.options?.filter; } /** Set Document list schema type name * @param type -