UNPKG

@atlaskit/editor-plugin-show-diff

Version:

ShowDiff plugin for @atlaskit/editor-core

219 lines (212 loc) 9.19 kB
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; };