@wordpress/core-data
Version:
Access to and manipulation of core WordPress entities.
229 lines (228 loc) • 6.65 kB
JavaScript
// packages/core-data/src/utils/crdt.ts
import fastDeepEqual from "fast-deep-equal/es6";
import { __unstableSerializeAndClean } from "@wordpress/blocks";
import { Y } from "@wordpress/sync";
import {
mergeCrdtBlocks
} from "./crdt-blocks";
import {
CRDT_DOC_META_PERSISTENCE_KEY,
CRDT_RECORD_MAP_KEY,
WORDPRESS_META_KEY_FOR_CRDT_DOC_PERSISTENCE
} from "../sync";
var lastSelection = null;
var allowedPostProperties = /* @__PURE__ */ new Set([
"author",
"blocks",
"comment_status",
"date",
"excerpt",
"featured_media",
"format",
"ping_status",
"meta",
"slug",
"status",
"sticky",
"tags",
"template",
"title"
]);
var disallowedPostMetaKeys = /* @__PURE__ */ new Set([
WORDPRESS_META_KEY_FOR_CRDT_DOC_PERSISTENCE
]);
function defaultApplyChangesToCRDTDoc(ydoc, changes) {
const ymap = ydoc.getMap(CRDT_RECORD_MAP_KEY);
Object.entries(changes).forEach(([key, newValue]) => {
if ("function" === typeof newValue) {
return;
}
function setValue(updatedValue) {
ymap.set(key, updatedValue);
}
switch (key) {
// Add support for additional data types here.
default: {
const currentValue = ymap.get(key);
mergeValue(currentValue, newValue, setValue);
}
}
});
}
function applyPostChangesToCRDTDoc(ydoc, changes, _postType) {
const ymap = ydoc.getMap(CRDT_RECORD_MAP_KEY);
Object.entries(changes).forEach(([key, newValue]) => {
if (!allowedPostProperties.has(key)) {
return;
}
if ("function" === typeof newValue) {
return;
}
function setValue(updatedValue) {
ymap.set(key, updatedValue);
}
switch (key) {
case "blocks": {
let currentBlocks = ymap.get("blocks");
if (!(currentBlocks instanceof Y.Array)) {
currentBlocks = new Y.Array();
setValue(currentBlocks);
}
const newBlocks = newValue ?? [];
mergeCrdtBlocks(currentBlocks, newBlocks, lastSelection);
break;
}
case "excerpt": {
const currentValue = ymap.get("excerpt");
const rawNewValue = getRawValue(newValue);
mergeValue(currentValue, rawNewValue, setValue);
break;
}
// "Meta" is overloaded term; here, it refers to post meta.
case "meta": {
let metaMap = ymap.get("meta");
if (!(metaMap instanceof Y.Map)) {
metaMap = new Y.Map();
setValue(metaMap);
}
Object.entries(newValue ?? {}).forEach(
([metaKey, metaValue]) => {
if (disallowedPostMetaKeys.has(metaKey)) {
return;
}
mergeValue(
metaMap.get(metaKey),
// current value in CRDT
metaValue,
// new value from changes
(updatedMetaValue) => {
metaMap.set(metaKey, updatedMetaValue);
}
);
}
);
break;
}
case "slug": {
if (!newValue) {
break;
}
const currentValue = ymap.get("slug");
mergeValue(currentValue, newValue, setValue);
break;
}
case "title": {
const currentValue = ymap.get("title");
let rawNewValue = getRawValue(newValue);
if (!currentValue && "Auto Draft" === rawNewValue) {
rawNewValue = "";
}
mergeValue(currentValue, rawNewValue, setValue);
break;
}
// Add support for additional data types here.
default: {
const currentValue = ymap.get(key);
mergeValue(currentValue, newValue, setValue);
}
}
});
if ("selection" in changes) {
lastSelection = changes.selection?.selectionStart ?? null;
}
}
function defaultGetChangesFromCRDTDoc(crdtDoc) {
return crdtDoc.getMap(CRDT_RECORD_MAP_KEY).toJSON();
}
function getPostChangesFromCRDTDoc(ydoc, editedRecord, _postType) {
const ymap = ydoc.getMap(CRDT_RECORD_MAP_KEY);
let allowedMetaChanges = {};
const changes = Object.fromEntries(
Object.entries(ymap.toJSON()).filter(([key, newValue]) => {
if (!allowedPostProperties.has(key)) {
return false;
}
const currentValue = editedRecord[key];
switch (key) {
case "blocks": {
if (ydoc.meta?.get(CRDT_DOC_META_PERSISTENCE_KEY) && editedRecord.content) {
const blocks = ymap.get("blocks");
return __unstableSerializeAndClean(
blocks.toJSON()
).trim() !== editedRecord.content.raw.trim();
}
return true;
}
case "date": {
const currentDateIsFloating = ["draft", "auto-draft", "pending"].includes(
ymap.get("status")
) && (null === currentValue || editedRecord.modified === currentValue);
if (!newValue && currentDateIsFloating) {
return false;
}
return haveValuesChanged(currentValue, newValue);
}
case "meta": {
allowedMetaChanges = Object.fromEntries(
Object.entries(newValue ?? {}).filter(
([metaKey]) => !disallowedPostMetaKeys.has(metaKey)
)
);
const mergedValue = {
...currentValue,
...allowedMetaChanges
};
return haveValuesChanged(currentValue, mergedValue);
}
case "status": {
if ("auto-draft" === newValue) {
return false;
}
return haveValuesChanged(currentValue, newValue);
}
case "excerpt":
case "title": {
return haveValuesChanged(
getRawValue(currentValue),
newValue
);
}
// Add support for additional data types here.
default: {
return haveValuesChanged(currentValue, newValue);
}
}
})
);
if ("object" === typeof changes.meta) {
changes.meta = {
...editedRecord.meta,
...allowedMetaChanges
};
}
return changes;
}
function getRawValue(value) {
if ("string" === typeof value) {
return value;
}
if (value && "object" === typeof value && "raw" in value && "string" === typeof value.raw) {
return value.raw;
}
return void 0;
}
function haveValuesChanged(currentValue, newValue) {
return !fastDeepEqual(currentValue, newValue);
}
function mergeValue(currentValue, newValue, setValue) {
if (haveValuesChanged(currentValue, newValue)) {
setValue(newValue);
}
}
export {
applyPostChangesToCRDTDoc,
defaultApplyChangesToCRDTDoc,
defaultGetChangesFromCRDTDoc,
getPostChangesFromCRDTDoc
};
//# sourceMappingURL=crdt.js.map