UNPKG

@atlaskit/editor-plugin-show-diff

Version:

ShowDiff plugin for @atlaskit/editor-core

282 lines (268 loc) • 15.5 kB
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray"; import _defineProperty from "@babel/runtime/helpers/defineProperty"; function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } 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 { fg } from '@atlaskit/platform-feature-flags/fg'; import { CONTRIBUTOR_TAG_Z_INDEX } from '../../ui/ContributorTag/buildContributorTagDom'; import { isEmptyParagraphSlice } from '../utils/isEmptyParagraphSlice'; import { buildAtomicInlineChangedCSSVariables, buildDeletedInlineContentStyleExtended, buildDeletedInlineNodeCSSVariables, buildDeletedInlineStyle, buildDeletedInlineStyleStandard, buildInsertStyle, buildInsertStyleActive, buildInsertStyleExtended, buildInsertStyleExtendedActive, buildInsertStyleExtendedNoUnderline, buildInsertStyleExtendedNoUnderlineActive } from './colorSchemes/factory'; import { colorSchemeRegistry, getLegacyColorScheme } from './colorSchemes/schemes'; import { createInlineIndicatorAnchorWidgets } from './createAnchorDecorationWidgets'; import { createContributorTagWidget, isContributorTagWidgetEnabled } from './createContributorTagWidget'; import { getAtomicInlineNodeClassNameLegacy, resolveInlineChangedStyleLegacy } from './createInlineChangedDecoration.styles.legacy'; import { buildDiffDecorationSpec, scrollMarginTopStyle } from './decorationKeys'; import { REVEAL_ATTR, resolveRevealStyle } from './revealStyles'; var displayNoneStyle = convertToInlineCss({ display: 'none' }); /** * Stacks the change's highlight below its contributor tag, so no highlight can slice through a tag: * a tag overlays the line *above* its own change, and a highlight already on that line — the tail of * this change, or another change entirely — would otherwise paint straight over it. * * The tag still reads as attached to the change rather than laid on top of it, because it cannot * paint over this highlight at all: its bottom edge is pinned to the highlight's top edge and * clipped there. See `CONTRIBUTOR_TAG_Z_INDEX`. * * prosemirror-view places the tag's host widget as a *sibling* of this decoration's span, so * painting order is decided by stacking level — and the host is absolutely positioned at * `CONTRIBUTOR_TAG_Z_INDEX`. `position: relative` is what makes `z-index` apply to an otherwise * static inline box; it adds no offset. */ var stackBelowContributorTagStyle = convertToInlineCss({ position: 'relative', zIndex: CONTRIBUTOR_TAG_Z_INDEX - 1 }); var getColorScheme = function getColorScheme(colorScheme) { return colorSchemeRegistry[colorScheme !== null && colorScheme !== void 0 ? colorScheme : 'standard']; }; /** * Class names for an atomic inline node decoration (date, emoji, mention, status). Scheme-agnostic * — `buildAtomicInlineChangedCSSVariables()` supplies the colour inline on the same element. */ /** * Lets editor-core redraw the strike on a deleted status: `line-through` from an ancestor paints * behind the lozenge's own background (EDITOR-9123). Not needed for deleted-content widgets — * `createContentWrapper` appends a real positioned line. */ var DELETED_INLINE_NODE_CLASS = 'show-diff-deleted-inline-node'; var getAtomicInlineNodeClassName = function getAtomicInlineNodeClassName(inlineNodeName) { var classNames = ['show-diff-atomic-inline-changed']; if (inlineNodeName) { classNames.push("show-diff-atomic-inline-changed-".concat(inlineNodeName)); } return classNames.join(' '); }; /** Inline style for inserted content. */ var getExtendedInsertStyle = function getExtendedInsertStyle(colors, isActive, hideAddedDiffsUnderline) { if (colors.insertedInlineTreatment === 'underline') { // hideAddedDiffsUnderline only targets the 'borderBottom' rule, so it does not apply here. return isActive ? buildInsertStyleActive(colors) : buildInsertStyle(colors); } if (isActive) { return hideAddedDiffsUnderline ? buildInsertStyleExtendedNoUnderlineActive(colors) : buildInsertStyleExtendedActive(colors); } return hideAddedDiffsUnderline ? buildInsertStyleExtendedNoUnderline(colors) : buildInsertStyleExtended(colors); }; /** Inline style for deleted content. */ var getExtendedDeletedStyle = function getExtendedDeletedStyle(colors, isActive) { if (colors.deletedInlineTreatment === 'strikethrough') { // Identical in both states — the block decoration carries the active emphasis. return buildDeletedInlineStyle(colors, false); } return buildDeletedInlineStyleStandard(colors, isActive ? 'active' : 'default') + buildDeletedInlineContentStyleExtended(colors, isActive); }; /** Registry-driven inline `style` for a changed-content decoration. */ var resolveInlineChangedStyleRefactored = function resolveInlineChangedStyleRefactored(_ref) { var colors = _ref.colors, hideAddedDiffsUnderline = _ref.hideAddedDiffsUnderline, isActive = _ref.isActive, isInserted = _ref.isInserted; return isInserted ? getExtendedInsertStyle(colors, isActive, hideAddedDiffsUnderline) : getExtendedDeletedStyle(colors, isActive); }; /** * Single gate for the inline `style` string: registry + factory when the refactor is on, the * verbatim pre-refactor per-scheme constants when it is off. */ var resolveInlineChangedStyle = function resolveInlineChangedStyle(args) { return isExperimentEnabled('platform_editor_show_diff_color_scheme_refactor') ? resolveInlineChangedStyleRefactored(args) : resolveInlineChangedStyleLegacy(_objectSpread(_objectSpread({}, args), {}, { colorScheme: getLegacyColorScheme(args.colorScheme) })); }; /** * Single gate for the atomic-inline-node decoration attributes. The two cohorts differ in the DOM: * on, the colour rides on an inline CSS variable and the class list is scheme-agnostic; off, the * colour comes from a `-traditional` class and no variable is emitted. Returned together so the * class list and style suffix can never be mixed across cohorts. */ var resolveAtomicInlineAttrs = function resolveAtomicInlineAttrs(inlineNodeName, colorScheme, colors) { return isExperimentEnabled('platform_editor_show_diff_color_scheme_refactor') ? { className: getAtomicInlineNodeClassName(inlineNodeName), styleSuffix: buildAtomicInlineChangedCSSVariables(colors) } : { className: getAtomicInlineNodeClassNameLegacy(inlineNodeName, getLegacyColorScheme(colorScheme)), styleSuffix: '' }; }; export var getAtomicInlineChangedAttrs = function getAtomicInlineChangedAttrs(inlineNodeName, colorScheme) { return resolveAtomicInlineAttrs(inlineNodeName, colorScheme, getColorScheme(colorScheme)); }; /** * Inline decoration used for insertions as the content already exists in the document * * @param change Changeset "change" containing information about the change content + range * @returns Prosemirror inline decoration */ export var createInlineChangedDecoration = function createInlineChangedDecoration(_ref2) { var _revealed$style; var attributionKey = _ref2.attributionKey, change = _ref2.change, colorScheme = _ref2.colorScheme, _ref2$isActive = _ref2.isActive, isActive = _ref2$isActive === void 0 ? false : _ref2$isActive, _ref2$isInserted = _ref2.isInserted, isInserted = _ref2$isInserted === void 0 ? true : _ref2$isInserted, leftAnchorId = _ref2.leftAnchorId, _ref2$isAtomicInlineN = _ref2.isAtomicInlineNode, isAtomicInlineNode = _ref2$isAtomicInlineN === void 0 ? false : _ref2$isAtomicInlineN, _ref2$shouldHideDelet = _ref2.shouldHideDeleted, shouldHideDeleted = _ref2$shouldHideDelet === void 0 ? false : _ref2$shouldHideDelet, _ref2$showContributor = _ref2.showContributorTags, showContributorTags = _ref2$showContributor === void 0 ? false : _ref2$showContributor, _ref2$showIndicators = _ref2.showIndicators, showIndicators = _ref2$showIndicators === void 0 ? false : _ref2$showIndicators, doc = _ref2.doc, _ref2$hideAddedDiffsU = _ref2.hideAddedDiffsUnderline, hideAddedDiffsUnderline = _ref2$hideAddedDiffsU === void 0 ? false : _ref2$hideAddedDiffsU, inlineNodeName = _ref2.inlineNodeName, _ref2$hasDeletedWidge = _ref2.hasDeletedWidget, hasDeletedWidget = _ref2$hasDeletedWidge === void 0 ? false : _ref2$hasDeletedWidge, _ref2$isDeletedWidget = _ref2.isDeletedWidgetBelow, isDeletedWidgetBelow = _ref2$isDeletedWidget === void 0 ? false : _ref2$isDeletedWidget, reveal = _ref2.reveal, tagMountContext = _ref2.tagMountContext; // Derived from the range so it survives a recalculation: stepping to the next/previous change // rebuilds decorations without touching the doc, and a fresh id would change the React key and the // tag host's key, remounting every tag mid-step. Random ids are kept when tags are off. var diffId = showContributorTags ? "inline-".concat(change.fromB, "-").concat(change.toB) : crypto.randomUUID(); // Match the rendered shape: whitespace text is eligible, while an empty paragraph is structural // content with no tag host. var canTagChange = showContributorTags && !!doc && isContributorTagWidgetEnabled() && !isEmptyParagraphSlice(doc.slice(change.fromB, change.toB)); if (shouldHideDeleted) { return [Decoration.inline(change.fromB, change.toB, { style: displayNoneStyle }, buildDiffDecorationSpec({ // No attribution: this decoration paints nothing and emits no tag host, so a tag // resolved from it would have nowhere to render. colorScheme: colorScheme, decorationType: 'inline', diffId: diffId, leftAnchorId: leftAnchorId, isActive: isActive, isInserted: isInserted }))]; } var colors = getColorScheme(colorScheme); // The reveal withholds the static highlight so the wipe has something to reveal, so it supplies // the whole style rather than adding to the usual one. var revealed = resolveRevealStyle({ colorScheme: colorScheme, hideAddedDiffsUnderline: hideAddedDiffsUnderline, isActive: isActive, isInserted: isInserted, reveal: reveal }); var style = (_revealed$style = revealed === null || revealed === void 0 ? void 0 : revealed.style) !== null && _revealed$style !== void 0 ? _revealed$style : resolveInlineChangedStyle({ colors: colors, colorScheme: colorScheme, hideAddedDiffsUnderline: hideAddedDiffsUnderline, isActive: isActive, isInserted: isInserted }); var isAtomicInlineInsertion = isAtomicInlineNode && isInserted; var atomicInlineAttrs = isAtomicInlineInsertion ? resolveAtomicInlineAttrs(inlineNodeName, colorScheme, colors) : undefined; // Every deletion: an inverted diff carries no `isAtomicInlineNode`, so editor-core's selectors do // the matching. var deletedInlineNodeAttrs = !isInserted && fg('platform_editor_ai_show_diff_patch_2') ? { className: DELETED_INLINE_NODE_CLASS, styleSuffix: buildDeletedInlineNodeCSSVariables(colors, isActive) } : undefined; // Built from the raw change range, not the indicator anchors resolved below: a tag lines up with // the changed text, while the bar's anchors are held on a block boundary when a deleted widget // renders beside them. // // Built up here, ahead of where it is pushed, so the highlight's stacking below keys off whether a // tag exists rather than re-deriving the condition and risking disagreement. var tagWidget = canTagChange && doc ? createContributorTagWidget({ doc: doc, from: change.fromB, to: change.toB, diffId: diffId, mountContext: tagMountContext }) : undefined; var inlineStyle = [style, atomicInlineAttrs === null || atomicInlineAttrs === void 0 ? void 0 : atomicInlineAttrs.styleSuffix, deletedInlineNodeAttrs === null || deletedInlineNodeAttrs === void 0 ? void 0 : deletedInlineNodeAttrs.styleSuffix, tagWidget ? stackBelowContributorTagStyle : undefined, fg('platform_editor_ai_show_diff_patch_1') ? scrollMarginTopStyle : undefined].filter(Boolean).join(''); var decorations = [Decoration.inline(change.fromB, change.toB, _objectSpread(_objectSpread(_objectSpread(_objectSpread({ style: inlineStyle }, atomicInlineAttrs && { class: atomicInlineAttrs.className }), deletedInlineNodeAttrs && { class: deletedInlineNodeAttrs.className }), revealed && _defineProperty({}, REVEAL_ATTR, revealed.role)), {}, { 'data-testid': 'show-diff-changed-decoration' }, (showContributorTags || fg('platform_editor_ai_show_diff_patch_2')) && { 'data-diff-id': diffId }), buildDiffDecorationSpec({ attributionKey: canTagChange ? attributionKey : undefined, colorScheme: colorScheme, decorationType: 'inline', diffId: diffId, leftAnchorId: leftAnchorId, isActive: isActive, isInserted: isInserted }))]; if (showIndicators && doc) { // For paragraphs, fromB/toB land on the outer block // boundary. Adjust anchor widgets into inline content so the indicator // bar doesn't extend into the preceding block's margin. // Skip when the deleted-content widget is rendered — the anchor must // stay at the boundary to keep the indicator continuous. // // The widget only pins the end of the range it is anchored to. It sits at `change.fromB` // by default, but `deletedDiffPlacement: 'bottom'` / `inlineDeletedDiffPlacement: 'after'` // move it to `change.toB` so the deleted content reads after the added content. In that // case the `from` anchor has no widget beside it any more and must be adjusted inward // again, otherwise the over-long indicator bar that CCI-18224 fixed comes back. var anchorFrom = change.fromB; var anchorTo = change.toB; if (!hasDeletedWidget || isDeletedWidgetBelow) { var _$from$nodeAfter; var $from = doc.resolve(anchorFrom); if (!$from.parent.inlineContent && (_$from$nodeAfter = $from.nodeAfter) !== null && _$from$nodeAfter !== void 0 && _$from$nodeAfter.isTextblock) { anchorFrom = anchorFrom + 1; } } if (!hasDeletedWidget) { var _$to$nodeBefore; var $to = doc.resolve(anchorTo); if (!$to.parent.inlineContent && (_$to$nodeBefore = $to.nodeBefore) !== null && _$to$nodeBefore !== void 0 && _$to$nodeBefore.isTextblock) { anchorTo = anchorTo - 1; } } decorations.push.apply(decorations, _toConsumableArray(createInlineIndicatorAnchorWidgets({ doc: doc, from: anchorFrom, to: anchorTo, diffId: diffId, leftAnchorId: leftAnchorId }))); } // Pushed after the indicator anchors so the decoration order is unchanged — see `tagWidget` above // for why it is built before them. if (tagWidget) { decorations.push.apply(decorations, _toConsumableArray(tagWidget)); } return decorations; };