UNPKG

@atlaskit/editor-plugin-show-diff

Version:

ShowDiff plugin for @atlaskit/editor-core

173 lines (165 loc) 7.72 kB
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; };