UNPKG

@wordpress/core-data

Version:
601 lines (600 loc) 17 kB
// packages/core-data/src/actions.js import fastDeepEqual from "fast-deep-equal/es6"; import { v4 as uuid } from "uuid"; import apiFetch from "@wordpress/api-fetch"; import { addQueryArgs } from "@wordpress/url"; import deprecated from "@wordpress/deprecated"; import { getNestedValue, setNestedValue } from "./utils"; import { receiveItems, removeItems, receiveQueriedItems } from "./queried-data"; import { DEFAULT_ENTITY_KEY } from "./entities"; import { createBatch } from "./batch"; import { STORE_NAME } from "./name"; import { LOCAL_EDITOR_ORIGIN, getSyncManager } from "./sync"; import logEntityDeprecation from "./utils/log-entity-deprecation"; function receiveUserQuery(queryID, users) { return { type: "RECEIVE_USER_QUERY", users: Array.isArray(users) ? users : [users], queryID }; } function receiveCurrentUser(currentUser) { return { type: "RECEIVE_CURRENT_USER", currentUser }; } function addEntities(entities) { return { type: "ADD_ENTITIES", entities }; } function receiveEntityRecords(kind, name, records, query = void 0, invalidateCache = false, edits = void 0, meta = void 0) { if (kind === "postType") { records = (Array.isArray(records) ? records : [records]).map( (record) => record.status === "auto-draft" ? { ...record, title: "" } : record ); } let action; if (query) { action = receiveQueriedItems(records, query, edits, meta); } else { action = receiveItems(records, edits, meta); } return { ...action, kind, name, invalidateCache }; } function receiveCurrentTheme(currentTheme) { return { type: "RECEIVE_CURRENT_THEME", currentTheme }; } function __experimentalReceiveCurrentGlobalStylesId(currentGlobalStylesId) { return { type: "RECEIVE_CURRENT_GLOBAL_STYLES_ID", id: currentGlobalStylesId }; } function __experimentalReceiveThemeBaseGlobalStyles(stylesheet, globalStyles) { return { type: "RECEIVE_THEME_GLOBAL_STYLES", stylesheet, globalStyles }; } function __experimentalReceiveThemeGlobalStyleVariations(stylesheet, variations) { return { type: "RECEIVE_THEME_GLOBAL_STYLE_VARIATIONS", stylesheet, variations }; } function receiveThemeSupports() { deprecated("wp.data.dispatch( 'core' ).receiveThemeSupports", { since: "5.9" }); return { type: "DO_NOTHING" }; } function receiveThemeGlobalStyleRevisions(currentId, revisions) { deprecated( "wp.data.dispatch( 'core' ).receiveThemeGlobalStyleRevisions()", { since: "6.5.0", alternative: "wp.data.dispatch( 'core' ).receiveRevisions" } ); return { type: "RECEIVE_THEME_GLOBAL_STYLE_REVISIONS", currentId, revisions }; } function receiveEmbedPreview(url, preview) { return { type: "RECEIVE_EMBED_PREVIEW", url, preview }; } var deleteEntityRecord = (kind, name, recordId, query, { __unstableFetch = apiFetch, throwOnError = false } = {}) => async ({ dispatch, resolveSelect }) => { logEntityDeprecation(kind, name, "deleteEntityRecord"); const configs = await resolveSelect.getEntitiesConfig(kind); const entityConfig = configs.find( (config) => config.kind === kind && config.name === name ); let error; let deletedRecord = false; if (!entityConfig) { return; } const lock = await dispatch.__unstableAcquireStoreLock( STORE_NAME, ["entities", "records", kind, name, recordId], { exclusive: true } ); try { dispatch({ type: "DELETE_ENTITY_RECORD_START", kind, name, recordId }); let hasError = false; let { baseURL } = entityConfig; if (kind === "postType" && name === "wp_template" && (recordId && typeof recordId === "string" && !/^\d+$/.test(recordId) || !window?.__experimentalTemplateActivate)) { baseURL = baseURL.slice(0, baseURL.lastIndexOf("/")) + "/templates"; } try { let path = `${baseURL}/${recordId}`; if (query) { path = addQueryArgs(path, query); } deletedRecord = await __unstableFetch({ path, method: "DELETE" }); await dispatch(removeItems(kind, name, recordId, true)); } catch (_error) { hasError = true; error = _error; } dispatch({ type: "DELETE_ENTITY_RECORD_FINISH", kind, name, recordId, error }); if (hasError && throwOnError) { throw error; } return deletedRecord; } finally { dispatch.__unstableReleaseStoreLock(lock); } }; var editEntityRecord = (kind, name, recordId, edits, options = {}) => ({ select, dispatch }) => { logEntityDeprecation(kind, name, "editEntityRecord"); const entityConfig = select.getEntityConfig(kind, name); if (!entityConfig) { throw new Error( `The entity being edited (${kind}, ${name}) does not have a loaded config.` ); } const { mergedEdits = {} } = entityConfig; const record = select.getRawEntityRecord(kind, name, recordId); const editedRecord = select.getEditedEntityRecord( kind, name, recordId ); const edit = { kind, name, recordId, // Clear edits when they are equal to their persisted counterparts // so that the property is not considered dirty. edits: Object.keys(edits).reduce((acc, key) => { const recordValue = record[key]; const editedRecordValue = editedRecord[key]; const value = mergedEdits[key] ? { ...editedRecordValue, ...edits[key] } : edits[key]; acc[key] = fastDeepEqual(recordValue, value) ? void 0 : value; return acc; }, {}) }; if (window.__experimentalEnableSync && entityConfig.syncConfig) { if (globalThis.IS_GUTENBERG_PLUGIN) { const objectType = `${kind}/${name}`; const objectId = recordId; getSyncManager()?.update( objectType, objectId, edit.edits, LOCAL_EDITOR_ORIGIN ); } } if (!options.undoIgnore) { select.getUndoManager().addRecord( [ { id: { kind, name, recordId }, changes: Object.keys(edits).reduce((acc, key) => { acc[key] = { from: editedRecord[key], to: edits[key] }; return acc; }, {}) } ], options.isCached ); } dispatch({ type: "EDIT_ENTITY_RECORD", ...edit }); }; var undo = () => ({ select, dispatch }) => { const undoRecord = select.getUndoManager().undo(); if (!undoRecord) { return; } dispatch({ type: "UNDO", record: undoRecord }); }; var redo = () => ({ select, dispatch }) => { const redoRecord = select.getUndoManager().redo(); if (!redoRecord) { return; } dispatch({ type: "REDO", record: redoRecord }); }; var __unstableCreateUndoLevel = () => ({ select }) => { select.getUndoManager().addRecord(); }; var saveEntityRecord = (kind, name, record, { isAutosave = false, __unstableFetch = apiFetch, throwOnError = false } = {}) => async ({ select, resolveSelect, dispatch }) => { logEntityDeprecation(kind, name, "saveEntityRecord"); const configs = await resolveSelect.getEntitiesConfig(kind); const entityConfig = configs.find( (config) => config.kind === kind && config.name === name ); if (!entityConfig) { return; } const entityIdKey = entityConfig.key ?? DEFAULT_ENTITY_KEY; const recordId = record[entityIdKey]; const isNewRecord = !!entityIdKey && !recordId; const lock = await dispatch.__unstableAcquireStoreLock( STORE_NAME, ["entities", "records", kind, name, recordId || uuid()], { exclusive: true } ); try { for (const [key, value] of Object.entries(record)) { if (typeof value === "function") { const evaluatedValue = value( select.getEditedEntityRecord(kind, name, recordId) ); dispatch.editEntityRecord( kind, name, recordId, { [key]: evaluatedValue }, { undoIgnore: true } ); record[key] = evaluatedValue; } } dispatch({ type: "SAVE_ENTITY_RECORD_START", kind, name, recordId, isAutosave }); let updatedRecord; let error; let hasError = false; let { baseURL } = entityConfig; if (kind === "postType" && name === "wp_template" && (recordId && typeof recordId === "string" && !/^\d+$/.test(recordId) || !window?.__experimentalTemplateActivate)) { baseURL = baseURL.slice(0, baseURL.lastIndexOf("/")) + "/templates"; } try { const path = `${baseURL}${recordId ? "/" + recordId : ""}`; const persistedRecord = !isNewRecord ? select.getRawEntityRecord(kind, name, recordId) : {}; if (isAutosave) { const currentUser = select.getCurrentUser(); const currentUserId = currentUser ? currentUser.id : void 0; const autosavePost = await resolveSelect.getAutosave( persistedRecord.type, persistedRecord.id, currentUserId ); let data = { ...persistedRecord, ...autosavePost, ...record }; data = Object.keys(data).reduce( (acc, key) => { if ([ "title", "excerpt", "content", "meta" ].includes(key)) { acc[key] = data[key]; } return acc; }, { // Do not update the `status` if we have edited it when auto saving. // It's very important to let the user explicitly save this change, // because it can lead to unexpected results. An example would be to // have a draft post and change the status to publish. status: data.status === "auto-draft" ? "draft" : void 0 } ); updatedRecord = await __unstableFetch({ path: `${path}/autosaves`, method: "POST", data }); if (persistedRecord.id === updatedRecord.id) { let newRecord = { ...persistedRecord, ...data, ...updatedRecord }; newRecord = Object.keys(newRecord).reduce( (acc, key) => { if (["title", "excerpt", "content"].includes( key )) { acc[key] = newRecord[key]; } else if (key === "status") { acc[key] = persistedRecord.status === "auto-draft" && newRecord.status === "draft" ? newRecord.status : persistedRecord.status; } else { acc[key] = persistedRecord[key]; } return acc; }, {} ); dispatch.receiveEntityRecords( kind, name, newRecord, void 0, true ); } else { dispatch.receiveAutosaves( persistedRecord.id, updatedRecord ); } } else { let edits = record; if (entityConfig.__unstablePrePersist) { edits = { ...edits, ...entityConfig.__unstablePrePersist( persistedRecord, edits ) }; } updatedRecord = await __unstableFetch({ path, method: recordId ? "PUT" : "POST", data: edits }); dispatch.receiveEntityRecords( kind, name, updatedRecord, void 0, true, edits ); } } catch (_error) { hasError = true; error = _error; } dispatch({ type: "SAVE_ENTITY_RECORD_FINISH", kind, name, recordId, error, isAutosave }); if (hasError && throwOnError) { throw error; } return updatedRecord; } finally { dispatch.__unstableReleaseStoreLock(lock); } }; var __experimentalBatch = (requests) => async ({ dispatch }) => { const batch = createBatch(); const api = { saveEntityRecord(kind, name, record, options) { return batch.add( (add) => dispatch.saveEntityRecord(kind, name, record, { ...options, __unstableFetch: add }) ); }, saveEditedEntityRecord(kind, name, recordId, options) { return batch.add( (add) => dispatch.saveEditedEntityRecord(kind, name, recordId, { ...options, __unstableFetch: add }) ); }, deleteEntityRecord(kind, name, recordId, query, options) { return batch.add( (add) => dispatch.deleteEntityRecord(kind, name, recordId, query, { ...options, __unstableFetch: add }) ); } }; const resultPromises = requests.map((request) => request(api)); const [, ...results] = await Promise.all([ batch.run(), ...resultPromises ]); return results; }; var saveEditedEntityRecord = (kind, name, recordId, options) => async ({ select, dispatch, resolveSelect }) => { logEntityDeprecation(kind, name, "saveEditedEntityRecord"); if (!select.hasEditsForEntityRecord(kind, name, recordId)) { return; } const configs = await resolveSelect.getEntitiesConfig(kind); const entityConfig = configs.find( (config) => config.kind === kind && config.name === name ); if (!entityConfig) { return; } const entityIdKey = entityConfig.key || DEFAULT_ENTITY_KEY; const edits = select.getEntityRecordNonTransientEdits( kind, name, recordId ); const record = { [entityIdKey]: recordId, ...edits }; return await dispatch.saveEntityRecord(kind, name, record, options); }; var __experimentalSaveSpecifiedEntityEdits = (kind, name, recordId, itemsToSave, options) => async ({ select, dispatch, resolveSelect }) => { logEntityDeprecation( kind, name, "__experimentalSaveSpecifiedEntityEdits" ); if (!select.hasEditsForEntityRecord(kind, name, recordId)) { return; } const edits = select.getEntityRecordNonTransientEdits( kind, name, recordId ); const editsToSave = {}; for (const item of itemsToSave) { setNestedValue(editsToSave, item, getNestedValue(edits, item)); } const configs = await resolveSelect.getEntitiesConfig(kind); const entityConfig = configs.find( (config) => config.kind === kind && config.name === name ); const entityIdKey = entityConfig?.key || DEFAULT_ENTITY_KEY; if (recordId) { editsToSave[entityIdKey] = recordId; } return await dispatch.saveEntityRecord( kind, name, editsToSave, options ); }; function receiveUploadPermissions(hasUploadPermissions) { deprecated("wp.data.dispatch( 'core' ).receiveUploadPermissions", { since: "5.9", alternative: "receiveUserPermission" }); return receiveUserPermission("create/media", hasUploadPermissions); } function receiveUserPermission(key, isAllowed) { return { type: "RECEIVE_USER_PERMISSION", key, isAllowed }; } function receiveUserPermissions(permissions) { return { type: "RECEIVE_USER_PERMISSIONS", permissions }; } function receiveAutosaves(postId, autosaves) { return { type: "RECEIVE_AUTOSAVES", postId, autosaves: Array.isArray(autosaves) ? autosaves : [autosaves] }; } function receiveNavigationFallbackId(fallbackId) { return { type: "RECEIVE_NAVIGATION_FALLBACK_ID", fallbackId }; } function receiveDefaultTemplateId(query, templateId) { return { type: "RECEIVE_DEFAULT_TEMPLATE", query, templateId }; } var receiveRevisions = (kind, name, recordKey, records, query, invalidateCache = false, meta) => async ({ dispatch, resolveSelect }) => { logEntityDeprecation(kind, name, "receiveRevisions"); const configs = await resolveSelect.getEntitiesConfig(kind); const entityConfig = configs.find( (config) => config.kind === kind && config.name === name ); const key = entityConfig && entityConfig?.revisionKey ? entityConfig.revisionKey : DEFAULT_ENTITY_KEY; dispatch({ type: "RECEIVE_ITEM_REVISIONS", key, items: Array.isArray(records) ? records : [records], recordKey, meta, query, kind, name, invalidateCache }); }; export { __experimentalBatch, __experimentalReceiveCurrentGlobalStylesId, __experimentalReceiveThemeBaseGlobalStyles, __experimentalReceiveThemeGlobalStyleVariations, __experimentalSaveSpecifiedEntityEdits, __unstableCreateUndoLevel, addEntities, deleteEntityRecord, editEntityRecord, receiveAutosaves, receiveCurrentTheme, receiveCurrentUser, receiveDefaultTemplateId, receiveEmbedPreview, receiveEntityRecords, receiveNavigationFallbackId, receiveRevisions, receiveThemeGlobalStyleRevisions, receiveThemeSupports, receiveUploadPermissions, receiveUserPermission, receiveUserPermissions, receiveUserQuery, redo, saveEditedEntityRecord, saveEntityRecord, undo }; //# sourceMappingURL=actions.js.map