@atlaskit/editor-plugin-show-diff
Version:
ShowDiff plugin for @atlaskit/editor-core
219 lines (212 loc) • 9.19 kB
JavaScript
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
import { convertToInlineCss } from '@atlaskit/editor-common/lazy-node-view';
import { Decoration } from '@atlaskit/editor-prosemirror/view';
import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
import { isExtendedEnabled } from '../isExtendedEnabled';
import { buildAddedCellOverlayRoundedStyle, buildAddedCellOverlayStyle, buildDeletedBlockNodeStyle, buildDeletedCellOverlayRoundedStyle, buildDeletedCellOverlayStyle, buildInsertedBlockNodeStyle } from './colorSchemes/factory';
import { colorSchemeRegistry } from './colorSchemes/schemes';
import { createBlockIndicatorAnchorWidgets } from './createAnchorDecorationWidgets';
import { buildDiffDecorationSpec } from './decorationKeys';
import { getBlockNodeStyleLegacy, resolveCellOverlayStyleLegacy } from './createBlockChangedDecoration.styles.legacy';
var displayNoneStyle = convertToInlineCss({
display: 'none'
});
var DEFAULT_COLOR_SCHEME = 'standard';
/** Nodes that carry no decoration styling of their own — layout, lists, text blocks, media groups. */
var UNSTYLED_NODES = ['mediaSingle', 'mediaGroup', 'table',
// Handle table separately to avoid border issues
'tableRow', 'paragraph',
// Paragraph and heading nodes do not need special styling
'heading', 'hardBreak', 'decisionList', 'taskList', 'bulletList', 'orderedList', 'layoutSection'];
var CELL_NODES = ['tableCell', 'tableHeader'];
/** Positioning context for the cell overlay widget decorations. */
var cellPositionStyle = convertToInlineCss({
position: 'relative'
});
var getInsertedBlockNodeShape = function getInsertedBlockNodeShape(nodeName) {
switch (nodeName) {
case 'blockquote':
return 'quote';
case 'rule':
return 'rule';
case 'blockCard':
return 'cardBlock';
case 'extension':
case 'embedCard':
case 'listItem':
return 'marker';
default:
return 'node';
}
};
var getDeletedBlockNodeCategory = function getDeletedBlockNodeCategory(nodeName) {
switch (nodeName) {
case 'blockquote':
return 'quote';
// Media nodes inside mediaSingle should not get position:relative
// as it shifts the image outside its parent container (e.g. panel)
case 'media':
case 'panel':
return 'container';
case 'listItem':
return 'listItem';
case 'extension':
case 'embedCard':
return 'marker';
default:
return 'generic';
}
};
var getNodeClass = function getNodeClass(name) {
switch (name) {
case 'extension':
return 'show-diff-changed-decoration-node';
default:
return undefined;
}
};
var getBlockNodeStyleNext = function getBlockNodeStyleNext(_ref) {
var nodeName = _ref.nodeName,
colorScheme = _ref.colorScheme,
_ref$isInserted = _ref.isInserted,
isInserted = _ref$isInserted === void 0 ? true : _ref$isInserted,
_ref$isActive = _ref.isActive,
isActive = _ref$isActive === void 0 ? false : _ref$isActive,
diffType = _ref.diffType;
if (UNSTYLED_NODES.includes(nodeName)) {
return undefined;
}
if (CELL_NODES.includes(nodeName)) {
// When the gate is off, cells get no styling — as with UNSTYLED_NODES above.
return isExtendedEnabled(diffType) ? cellPositionStyle : undefined;
}
var colors = colorSchemeRegistry[colorScheme !== null && colorScheme !== void 0 ? colorScheme : DEFAULT_COLOR_SCHEME];
// Deleted nodes only differ under the extended experience; otherwise all are insertions.
if (!isInserted && isExtendedEnabled(diffType)) {
return buildDeletedBlockNodeStyle(colors, getDeletedBlockNodeCategory(nodeName), isActive);
}
return buildInsertedBlockNodeStyle(colors, getInsertedBlockNodeShape(nodeName), isActive);
};
/**
* Dispatches to the registry-driven implementation, or to the verbatim pre-refactor one for the
* OFF cohort of `platform_editor_show_diff_color_scheme_refactor`. Both are built to emit the
* same style strings for both shipped schemes; see EDITOR-8281.
*/
var getBlockNodeStyle = function getBlockNodeStyle(props) {
return isExperimentEnabled('platform_editor_show_diff_color_scheme_refactor') ? getBlockNodeStyleNext(props) : getBlockNodeStyleLegacy(props);
};
/**
* A table cell is "empty" only when it has no content at all — no text AND no
* non-text leaf/inline nodes (media, emoji, mention, inlineCard, status, date…).
* `textContent` alone misses those non-text nodes, so we also reject any leaf or
* inline descendant. An empty ADF cell (`tableCell > empty paragraph`) returns
* true; a cell containing only media/emoji returns false.
*/
var isCellEmpty = function isCellEmpty(cellNode) {
if (cellNode.textContent.length > 0) {
return false;
}
var hasNonTextContent = false;
cellNode.descendants(function (node) {
if (hasNonTextContent) {
return false;
}
// A non-text leaf or inline node (media, emoji, mention, etc.) = real content.
if (!node.isText && (node.isLeaf || node.isInline)) {
hasNonTextContent = true;
return false;
}
return true;
});
return !hasNonTextContent;
};
/**
* Node decoration used for block-level insertions. When isActive, uses emphasised (pressed) styling.
*
* @param change Node range and name
* @param colorScheme Optional color scheme
* @param isActive Whether this node is part of the currently active/focused change
* @returns Prosemirror node decoration or undefined
*/
export var createBlockChangedDecoration = function createBlockChangedDecoration(_ref2) {
var change = _ref2.change,
colorScheme = _ref2.colorScheme,
_ref2$isInserted = _ref2.isInserted,
isInserted = _ref2$isInserted === void 0 ? true : _ref2$isInserted,
_ref2$isActive = _ref2.isActive,
isActive = _ref2$isActive === void 0 ? false : _ref2$isActive,
_ref2$shouldHideDelet = _ref2.shouldHideDeleted,
shouldHideDeleted = _ref2$shouldHideDelet === void 0 ? false : _ref2$shouldHideDelet,
_ref2$showIndicators = _ref2.showIndicators,
showIndicators = _ref2$showIndicators === void 0 ? false : _ref2$showIndicators,
doc = _ref2.doc,
diffType = _ref2.diffType;
var decorations = [];
var diffId = crypto.randomUUID();
if (shouldHideDeleted) {
return [Decoration.node(change.from, change.to, {
style: displayNoneStyle
}, buildDiffDecorationSpec({
decorationType: 'block',
diffId: diffId,
isActive: isActive,
nodeName: change.name,
diffType: diffType
}))];
}
if (isExtendedEnabled(diffType) && CELL_NODES.includes(change.name)) {
var cellOverlay = document.createElement('div');
var colors = colorSchemeRegistry[colorScheme !== null && colorScheme !== void 0 ? colorScheme : DEFAULT_COLOR_SCHEME];
var isRoundedTable = expValEquals('platform_editor_table_diff_rounded_corners', 'isEnabled', true);
// On an inverted diff, an empty cell being filled is an addition, so give it the
// added (purple) overlay instead of the deleted (grey) one (EDITOR-8442).
var cellNode = doc === null || doc === void 0 ? void 0 : doc.nodeAt(change.from);
var isEmptyCellBeingFilled = !isInserted && !!cellNode && isCellEmpty(cellNode);
var useAddedStyle = isInserted || isEmptyCellBeingFilled;
var cellOverlayStyle = isExperimentEnabled('platform_editor_show_diff_color_scheme_refactor') ? useAddedStyle ? isRoundedTable ? buildAddedCellOverlayRoundedStyle(colors) : buildAddedCellOverlayStyle(colors) : isRoundedTable ? buildDeletedCellOverlayRoundedStyle(colors) : buildDeletedCellOverlayStyle(colors) : resolveCellOverlayStyleLegacy({
colorScheme: colorScheme,
isRoundedTable: isRoundedTable,
useAddedStyle: useAddedStyle
});
cellOverlay.setAttribute('style', cellOverlayStyle);
decorations.push(
// change.to - 1 to position the overlay inside the end of the cell
// this key doesn't use the spec / key builder since this is just for
// decorating the cells, this is part of a bigger table diff
Decoration.widget(change.to - 1, cellOverlay, {
key: 'cell-overlay-decoration'
}));
}
// isInserted is only read under the extended experience, so pass it unconditionally.
var style = getBlockNodeStyle({
nodeName: change.name,
colorScheme: colorScheme,
isInserted: isInserted,
isActive: isActive,
diffType: diffType
});
var className = getNodeClass(change.name);
if (style || className) {
decorations.push(Decoration.node(change.from, change.to, {
style: style,
'data-testid': 'show-diff-changed-decoration-node',
class: className
}, buildDiffDecorationSpec({
decorationType: 'block',
diffId: diffId,
isActive: isActive,
nodeName: change.name,
diffType: diffType
})));
}
if (decorations.length > 0 && showIndicators && doc && isExtendedEnabled(diffType)) {
decorations.push.apply(decorations, _toConsumableArray(createBlockIndicatorAnchorWidgets({
doc: doc,
from: change.from,
to: change.to,
diffId: diffId
})));
}
return decorations;
};