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
JavaScript
"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 -