@atlaskit/editor-plugin-show-diff
Version:
ShowDiff plugin for @atlaskit/editor-core
70 lines (65 loc) • 3.28 kB
JavaScript
import { getBaseNodeTypeName } from '@atlaskit/editor-common/utils/node-type-utils';
import { getDiffableAttrNames } from '../decorations/utils/diffableAttrs';
/**
* Attribute-aware token encoder for `prosemirror-changeset`. The default encoder
* reduces a node to `node.type.name`, so an attribute-only change (e.g. a table
* cell recolour) tokenises identically on both sides and goes undetected. This
* folds the diffable attributes (from the shared `diffableAttrs` map) into the
* open token so such changes register.
*/
// Stable composite token; attr names are pre-ordered by the caller.
const encodeNodeWithAttrs = (node, attrNames) => {
var _node$attrs;
const attrs = (_node$attrs = node.attrs) !== null && _node$attrs !== void 0 ? _node$attrs : {};
const parts = attrNames.map(name => {
var _attrs$name;
return `${name}=${JSON.stringify((_attrs$name = attrs[name]) !== null && _attrs$name !== void 0 ? _attrs$name : null)}`;
});
return `${node.type.name}|${parts.join('|')}`;
};
// Fold a character's marks (type + attrs) into its token so a formatting-only change
// (bold/italic/link/colour, or a link whose `href` changes) registers as a real diff.
// The default encoder returns just the char code, so marked and unmarked text tokenise
// identically. Marks and their attrs are sorted so the token is order-independent.
const encodeCharacterWithMarks = (char, marks) => {
if (marks.length === 0) {
// Fast path: unmarked text keeps the numeric char code (unchanged behaviour).
return char;
}
const markKey = marks.map(mark => {
var _mark$attrs;
const attrs = (_mark$attrs = mark.attrs) !== null && _mark$attrs !== void 0 ? _mark$attrs : {};
const attrParts = Object.keys(attrs).sort().map(key => {
var _attrs$key;
return `${key}=${JSON.stringify((_attrs$key = attrs[key]) !== null && _attrs$key !== void 0 ? _attrs$key : null)}`;
});
return attrParts.length > 0 ? `${mark.type.name}(${attrParts.join(',')})` : mark.type.name;
}).sort().join('&');
return `${char}\u0000${markKey}`;
};
// Like the library default, but node types with a `diffableAttrs` rule fold their
// attrs into the open token, and text characters fold in their marks. Node-end is
// unchanged, hence `string | number`.
export const attrAwareTokenEncoder = {
encodeCharacter: (char, marks) => encodeCharacterWithMarks(char, marks),
encodeNodeStart: node => {
var _node$attrs2;
// Normalise variants (e.g. `panel_c1` → `panel`) so `diffableAttrs` needs one entry.
const attrNames = getDiffableAttrNames(getBaseNodeTypeName(node.type), (_node$attrs2 = node.attrs) !== null && _node$attrs2 !== void 0 ? _node$attrs2 : {});
if (attrNames && attrNames.length > 0) {
return encodeNodeWithAttrs(node, attrNames);
}
return node.type.name;
},
encodeNodeEnd: node => -typeID(node.type),
compareTokens: (a, b) => a === b
};
// Mirrors the library's private (unexported) `typeID` so node-end tokens match.
function typeID(type) {
const cache = type.schema.cached.changeSetIDs || (type.schema.cached.changeSetIDs = Object.create(null));
let id = cache[type.name];
if (id == null) {
cache[type.name] = id = Object.keys(type.schema.nodes).indexOf(type.name) + 1;
}
return id;
}