@atlaskit/editor-plugin-show-diff
Version:
ShowDiff plugin for @atlaskit/editor-core
173 lines (165 loc) • 7.72 kB
JavaScript
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 { isExtendedEnabled } from '../isExtendedEnabled';
import { buildAtomicInlineChangedCSSVariables, buildDeletedInlineContentStyleExtended, buildDeletedInlineStyle, buildDeletedInlineStyleStandard, buildInsertStyle, buildInsertStyleActive, buildInsertStyleExtended, buildInsertStyleExtendedActive, buildInsertStyleExtendedNoUnderline, buildInsertStyleExtendedNoUnderlineActive } from './colorSchemes/factory';
import { colorSchemeRegistry } from './colorSchemes/schemes';
import { createInlineIndicatorAnchorWidgets } from './createAnchorDecorationWidgets';
import { getAtomicInlineNodeClassNameLegacy, resolveInlineChangedStyleLegacy } from './createInlineChangedDecoration.styles.legacy';
import { buildDiffDecorationSpec } from './decorationKeys';
const displayNoneStyle = convertToInlineCss({
display: 'none'
});
const DEFAULT_COLOR_SCHEME = 'standard';
const getColorScheme = colorScheme => colorSchemeRegistry[colorScheme !== null && colorScheme !== void 0 ? colorScheme : DEFAULT_COLOR_SCHEME];
/**
* Class names for an atomic inline node decoration (date, emoji, mention, status). Scheme-agnostic
* — `buildAtomicInlineChangedCSSVariables()` supplies the colour inline on the same element.
*/
const getAtomicInlineNodeClassName = inlineNodeName => {
const classNames = ['show-diff-atomic-inline-changed'];
if (inlineNodeName) {
classNames.push(`show-diff-atomic-inline-changed-${inlineNodeName}`);
}
return classNames.join(' ');
};
/** Inline style for inserted content under the extended diff experience. */
const 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 under the extended diff experience. */
const 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);
};
/** Registry-driven inline `style` for a changed-content decoration. */
const resolveInlineChangedStyleRefactored = ({
colors,
diffType,
hideAddedDiffsUnderline,
isActive,
isInserted
}) => {
if (isExtendedEnabled(diffType)) {
return isInserted ? getExtendedInsertStyle(colors, isActive, hideAddedDiffsUnderline) : getExtendedDeletedStyle(colors, isActive);
}
return isActive ? buildInsertStyleActive(colors) : buildInsertStyle(colors);
};
/**
* 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.
*/
const resolveInlineChangedStyle = args => isExperimentEnabled('platform_editor_show_diff_color_scheme_refactor') ? resolveInlineChangedStyleRefactored(args) : resolveInlineChangedStyleLegacy(args);
/**
* 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.
*/
const resolveAtomicInlineAttrs = (inlineNodeName, colorScheme, colors) => isExperimentEnabled('platform_editor_show_diff_color_scheme_refactor') ? {
className: getAtomicInlineNodeClassName(inlineNodeName),
styleSuffix: buildAtomicInlineChangedCSSVariables(colors)
} : {
className: getAtomicInlineNodeClassNameLegacy(inlineNodeName, colorScheme),
styleSuffix: ''
};
/**
* 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 const createInlineChangedDecoration = ({
change,
colorScheme,
isActive = false,
isInserted = true,
isAtomicInlineNode = false,
shouldHideDeleted = false,
showIndicators = false,
doc,
diffType,
hideAddedDiffsUnderline = false,
inlineNodeName,
hasDeletedWidget = false,
isDeletedWidgetBelow = false
}) => {
const diffId = crypto.randomUUID();
if (shouldHideDeleted) {
return [Decoration.inline(change.fromB, change.toB, {
style: displayNoneStyle
}, buildDiffDecorationSpec({
decorationType: 'inline',
diffId,
isActive
}))];
}
const colors = getColorScheme(colorScheme);
const style = resolveInlineChangedStyle({
colors,
colorScheme,
diffType,
hideAddedDiffsUnderline,
isActive,
isInserted
});
const isAtomicInlineInsertion = isAtomicInlineNode && isInserted;
const atomicInlineAttrs = isAtomicInlineInsertion ? resolveAtomicInlineAttrs(inlineNodeName, colorScheme, colors) : undefined;
const decorations = [Decoration.inline(change.fromB, change.toB, {
style: atomicInlineAttrs ? `${style}${atomicInlineAttrs.styleSuffix}` : style,
...(atomicInlineAttrs && {
class: atomicInlineAttrs.className
}),
'data-testid': 'show-diff-changed-decoration'
}, buildDiffDecorationSpec({
decorationType: 'inline',
diffId,
isActive
}))];
if (showIndicators && doc && isExtendedEnabled(diffType)) {
// 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.
let anchorFrom = change.fromB;
let anchorTo = change.toB;
if (!hasDeletedWidget || isDeletedWidgetBelow) {
var _$from$nodeAfter;
const $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;
const $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(...createInlineIndicatorAnchorWidgets({
doc,
from: anchorFrom,
to: anchorTo,
diffId
}));
}
return decorations;
};