@atlaskit/editor-plugin-show-diff
Version:
ShowDiff plugin for @atlaskit/editor-core
92 lines (89 loc) • 3.76 kB
JavaScript
import { Decoration } from '@atlaskit/editor-prosemirror/view';
import { buildDiffDecorationSpec, scrollMarginTopValue } from '../decorationKeys';
import { createContentWrapper } from './wrapBlockNodeView';
/**
* U+2936 ARROW POINTING DOWNWARDS THEN CURVING LEFTWARDS — the return-key glyph.
*
* Exported so tests assert against the glyph actually rendered rather than a copy of it.
*/
export const RETURN_GLYPH = '⤶';
/**
* The DOM for a removed blank line: the return glyph, struck through in the deleted colours.
*
* An empty paragraph or heading is a line the author deliberately added, but it has no content for
* the deleted-content widget to serialize, so a deletion of one used to render nothing at all — the
* reviewer saw the surrounding text close up with no sign of what was removed. Standing the glyph
* in for the absent content keeps the deletion visible without inventing content that was not there.
*
* `createContentWrapper` supplies the highlight and the strikethrough line, so the glyph is styled
* exactly like any other run of deleted inline content, including the active and reveal states.
*
* Hidden from assistive technology: the glyph is a picture of a key, and read out it is announced as
* its Unicode name rather than as a removed line.
*/
export const createDeletedLineBreakWidget = ({
colorScheme,
count = 1,
isActive = false,
reveal
}) => {
const dom = document.createElement('span');
// The testid deleted content is matched by, so the glyph is found wherever deleted content is.
dom.setAttribute('data-testid', 'show-diff-deleted-decoration');
dom.setAttribute('aria-hidden', 'true');
dom.contentEditable = 'false';
// Scroll navigation aligns on the widget itself, as it does for every other deleted widget.
dom.style.setProperty('scroll-margin-top', scrollMarginTopValue);
for (let index = 0; index < Math.max(count, 1); index++) {
if (index > 0) {
// Each removed line gets its own line in the widget, so a run of blank lines reads as the
// number of lines it was. `br` rather than a block wrapper: the widget is inline, and the
// deleted-content wrapper is styled as an inline run.
dom.append(dom.ownerDocument.createElement('br'));
}
const wrapper = createContentWrapper(colorScheme, isActive, false, reveal);
wrapper.append(dom.ownerDocument.createTextNode(RETURN_GLYPH));
dom.append(wrapper);
}
return dom;
};
/**
* The glyph as a widget decoration anchored at `pos`.
*
* Shared by the two shapes a blank-line removal arrives in. On a forward diff the block is gone from
* the new document, so the change lands on the deleted side and the glyph is anchored where the
* block used to be. On an inverted diff — what AI suggested edits renders — the block is still in
* the displayed document and the change lands on the *inserted* side, so the glyph is anchored
* inside the surviving block, on the blank line itself.
*/
export const createDeletedLineBreakDecoration = ({
attributionKey,
colorScheme,
count,
isActive = false,
pos,
reveal,
side
}) => Decoration.widget(pos, createDeletedLineBreakWidget({
colorScheme,
count,
isActive,
reveal
}), {
...buildDiffDecorationSpec({
attributionKey,
colorScheme,
decorationType: 'widget',
diffId: crypto.randomUUID(),
isActive,
// The glyph always stands in for removed content, whichever side of the changeset the
// change itself landed on.
isInserted: false,
...(side !== undefined && {
side
})
}),
// Without an explicit mark set, prosemirror-view wraps the widget in the marks of the
// adjacent text. The glyph carries its own deleted styling and must not inherit them.
marks: []
});