@atlaskit/editor-plugin-show-diff
Version:
ShowDiff plugin for @atlaskit/editor-core
289 lines (274 loc) • 16.1 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getAtomicInlineChangedAttrs = exports.createInlineChangedDecoration = void 0;
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _lazyNodeView = require("@atlaskit/editor-common/lazy-node-view");
var _view = require("@atlaskit/editor-prosemirror/view");
var _isExperimentEnabled = require("@atlaskit/platform-feature-experiments/is-experiment-enabled");
var _fg = require("@atlaskit/platform-feature-flags/fg");
var _buildContributorTagDom = require("../../ui/ContributorTag/buildContributorTagDom");
var _isEmptyParagraphSlice = require("../utils/isEmptyParagraphSlice");
var _factory = require("./colorSchemes/factory");
var _schemes = require("./colorSchemes/schemes");
var _createAnchorDecorationWidgets = require("./createAnchorDecorationWidgets");
var _createContributorTagWidget = require("./createContributorTagWidget");
var _createInlineChangedDecorationStyles = require("./createInlineChangedDecoration.styles.legacy");
var _decorationKeys = require("./decorationKeys");
var _revealStyles = require("./revealStyles");
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) { (0, _defineProperty2.default)(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; }
var displayNoneStyle = (0, _lazyNodeView.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 = (0, _lazyNodeView.convertToInlineCss)({
position: 'relative',
zIndex: _buildContributorTagDom.CONTRIBUTOR_TAG_Z_INDEX - 1
});
var getColorScheme = function getColorScheme(colorScheme) {
return _schemes.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 ? (0, _factory.buildInsertStyleActive)(colors) : (0, _factory.buildInsertStyle)(colors);
}
if (isActive) {
return hideAddedDiffsUnderline ? (0, _factory.buildInsertStyleExtendedNoUnderlineActive)(colors) : (0, _factory.buildInsertStyleExtendedActive)(colors);
}
return hideAddedDiffsUnderline ? (0, _factory.buildInsertStyleExtendedNoUnderline)(colors) : (0, _factory.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 (0, _factory.buildDeletedInlineStyle)(colors, false);
}
return (0, _factory.buildDeletedInlineStyleStandard)(colors, isActive ? 'active' : 'default') + (0, _factory.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 (0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_show_diff_color_scheme_refactor') ? resolveInlineChangedStyleRefactored(args) : (0, _createInlineChangedDecorationStyles.resolveInlineChangedStyleLegacy)(_objectSpread(_objectSpread({}, args), {}, {
colorScheme: (0, _schemes.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 (0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_show_diff_color_scheme_refactor') ? {
className: getAtomicInlineNodeClassName(inlineNodeName),
styleSuffix: (0, _factory.buildAtomicInlineChangedCSSVariables)(colors)
} : {
className: (0, _createInlineChangedDecorationStyles.getAtomicInlineNodeClassNameLegacy)(inlineNodeName, (0, _schemes.getLegacyColorScheme)(colorScheme)),
styleSuffix: ''
};
};
var getAtomicInlineChangedAttrs = exports.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
*/
var createInlineChangedDecoration = exports.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 && (0, _createContributorTagWidget.isContributorTagWidgetEnabled)() && !(0, _isEmptyParagraphSlice.isEmptyParagraphSlice)(doc.slice(change.fromB, change.toB));
if (shouldHideDeleted) {
return [_view.Decoration.inline(change.fromB, change.toB, {
style: displayNoneStyle
}, (0, _decorationKeys.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 = (0, _revealStyles.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 && (0, _fg.fg)('platform_editor_ai_show_diff_patch_2') ? {
className: DELETED_INLINE_NODE_CLASS,
styleSuffix: (0, _factory.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 ? (0, _createContributorTagWidget.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, (0, _fg.fg)('platform_editor_ai_show_diff_patch_1') ? _decorationKeys.scrollMarginTopStyle : undefined].filter(Boolean).join('');
var decorations = [_view.Decoration.inline(change.fromB, change.toB, _objectSpread(_objectSpread(_objectSpread(_objectSpread({
style: inlineStyle
}, atomicInlineAttrs && {
class: atomicInlineAttrs.className
}), deletedInlineNodeAttrs && {
class: deletedInlineNodeAttrs.className
}), revealed && (0, _defineProperty2.default)({}, _revealStyles.REVEAL_ATTR, revealed.role)), {}, {
'data-testid': 'show-diff-changed-decoration'
}, (showContributorTags || (0, _fg.fg)('platform_editor_ai_show_diff_patch_2')) && {
'data-diff-id': diffId
}), (0, _decorationKeys.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, (0, _toConsumableArray2.default)((0, _createAnchorDecorationWidgets.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, (0, _toConsumableArray2.default)(tagWidget));
}
return decorations;
};