UNPKG

@atlaskit/editor-plugin-show-diff

Version:

ShowDiff plugin for @atlaskit/editor-core

142 lines (133 loc) 7.84 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.getScrollableDecorations = void 0; exports.isInlineDiffDecorationRenderableInDoc = isInlineDiffDecorationRenderableInDoc; var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray")); var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof")); var _decorationKeys = require("./decorations/decorationKeys"); var _isExtendedEnabled = require("./isExtendedEnabled"); /** * True if `fragment` contains at least one inline node (text, hardBreak, emoji, mention, etc.). * Block-only subtrees (e.g. empty paragraphs, block cards with no inline children) return false. */ function fragmentContainsInlineContent(fragment) { for (var i = 0; i < fragment.childCount; i++) { var node = fragment.child(i); if (node.isInline) { return true; } if (node.content.size > 0 && fragmentContainsInlineContent(node.content)) { return true; } } return false; } /** * Returns true when an inline decoration's [from, to) range can actually show in the document: * positions are valid, and the slice contains at least one inline node ProseMirror would paint * (not only empty block wrappers or block-only structure). */ function isInlineDiffDecorationRenderableInDoc(doc, from, to) { try { var slice = doc.slice(from, to); return fragmentContainsInlineContent(slice.content); } catch (_unused) { return false; } } /** * Checks if range1 is fully contained within range2 */ function isRangeFullyInside(range1Start, range1End, range2Start, range2End) { return range2Start <= range1Start && range1End <= range2End; } function specHasDiffKeyPrefix(spec, keyPrefix) { return Boolean(spec && (0, _typeof2.default)(spec) === 'object' && 'key' in spec && typeof spec.key === 'string' && spec.key.startsWith(keyPrefix)); } /** * Gets scrollable decorations from a DecorationSet, filtering out overlapping decorations * and applying various rules for diff visualization. * * Rules: * 1. Only includes diff-inline, diff-widget-* and diff-block decorations * 2. Excludes listItem diff-block decorations (never scrollable) * 3. Deduplicates diff-block decorations with same from, to and nodeName * 4. When `doc` is passed: excludes diff-inline decorations whose range has no inline content * (invalid positions, or block-only slices with no text/atoms — e.g. empty blocks) * 5. Excludes diff-inline decorations that are fully contained within a diff-block * 6. Excludes diff-block decorations that are fully contained within a diff-inline * 7. Results are sorted by from position, then by to position * * @param set - The DecorationSet to extract scrollable decorations from * @param doc - Current document; when set, diff-inline ranges are validated against this doc * @returns Array of scrollable decorations, sorted and deduplicated */ var getScrollableDecorations = exports.getScrollableDecorations = function getScrollableDecorations(set, doc, diffType) { if (!set) { return []; } var isBlockDecoration = function isBlockDecoration(decoration) { var _decoration$spec$key$, _decoration$spec; return (0, _isExtendedEnabled.isExtendedEnabled)(diffType) ? (0, _decorationKeys.isDiffDecoration)(decoration) && decoration.spec.decorationType === 'block' : (_decoration$spec$key$ = (_decoration$spec = decoration.spec) === null || _decoration$spec === void 0 || (_decoration$spec = _decoration$spec.key) === null || _decoration$spec === void 0 ? void 0 : _decoration$spec.startsWith(_decorationKeys.DiffDecorationKey.block)) !== null && _decoration$spec$key$ !== void 0 ? _decoration$spec$key$ : false; }; var isInlineDecoration = function isInlineDecoration(decoration) { var _decoration$spec$key$2, _decoration$spec2; return (0, _isExtendedEnabled.isExtendedEnabled)(diffType) ? (0, _decorationKeys.isDiffDecoration)(decoration) && decoration.spec.decorationType === 'inline' : (_decoration$spec$key$2 = (_decoration$spec2 = decoration.spec) === null || _decoration$spec2 === void 0 || (_decoration$spec2 = _decoration$spec2.key) === null || _decoration$spec2 === void 0 ? void 0 : _decoration$spec2.startsWith(_decorationKeys.DiffDecorationKey.inline)) !== null && _decoration$spec$key$2 !== void 0 ? _decoration$spec$key$2 : false; }; var isWidgetDecoration = function isWidgetDecoration(decoration) { var _decoration$spec$key$3, _decoration$spec3; return (0, _isExtendedEnabled.isExtendedEnabled)(diffType) ? (0, _decorationKeys.isDiffDecoration)(decoration) && decoration.spec.decorationType === 'widget' : (_decoration$spec$key$3 = (_decoration$spec3 = decoration.spec) === null || _decoration$spec3 === void 0 || (_decoration$spec3 = _decoration$spec3.key) === null || _decoration$spec3 === void 0 ? void 0 : _decoration$spec3.startsWith(_decorationKeys.DiffDecorationKey.widget)) !== null && _decoration$spec$key$3 !== void 0 ? _decoration$spec$key$3 : false; }; var seenBlockKeys = new Set(); var allDecorations = (0, _isExtendedEnabled.isExtendedEnabled)(diffType) ? set.find(undefined, undefined, _decorationKeys.isDiffDecorationSpec) : set.find(undefined, undefined, function (spec) { return specHasDiffKeyPrefix(spec, _decorationKeys.DiffDecorationKey.inline) || specHasDiffKeyPrefix(spec, _decorationKeys.DiffDecorationKey.widget) || specHasDiffKeyPrefix(spec, _decorationKeys.DiffDecorationKey.block); }); // First pass: filter out listItem blocks and deduplicates blocks var filtered = allDecorations.filter(function (dec) { if (!isBlockDecoration(dec) && !isInlineDecoration(dec) && !isWidgetDecoration(dec)) { return false; } if (isBlockDecoration(dec)) { var _dec$spec, _dec$spec$nodeName; // Skip listItem blocks as they are not scrollable if (((_dec$spec = dec.spec) === null || _dec$spec === void 0 ? void 0 : _dec$spec.nodeName) === 'listItem') return false; var key = "".concat(dec.from, "-").concat(dec.to, "-").concat((_dec$spec$nodeName = dec.spec.nodeName) !== null && _dec$spec$nodeName !== void 0 ? _dec$spec$nodeName : ''); // Skip blocks that have already been seen if (seenBlockKeys.has(key)) return false; seenBlockKeys.add(key); } return true; }); // Separate decorations by type for easier processing var blocks = filtered.filter(isBlockDecoration); var rawInlines = filtered.filter(isInlineDecoration); var inlines = doc !== undefined ? rawInlines.filter(function (d) { return isInlineDiffDecorationRenderableInDoc(doc, d.from, d.to); }) : rawInlines; var widgets = filtered.filter(isWidgetDecoration); // Second pass: exclude overlapping decorations // Rules: // - If an inline is fully inside a block, exclude the block (inline takes priority) // - If a block is fully inside an inline, exclude the block (inline takes priority) var nonOverlappingBlocks = blocks.filter(function (block) { // Exclude block if: // 1. It's fully contained within any inline, OR // 2. It fully contains any inline return !inlines.some(function (inline) { return isRangeFullyInside(block.from, block.to, inline.from, inline.to) || // block inside inline isRangeFullyInside(inline.from, inline.to, block.from, block.to); } // inline inside block ); }); // Combine all non-overlapping decorations var result = [].concat((0, _toConsumableArray2.default)(nonOverlappingBlocks), (0, _toConsumableArray2.default)(inlines), (0, _toConsumableArray2.default)(widgets)); // Sort by from position, then by to position result.sort(function (a, b) { return a.from === b.from ? a.to - b.to : a.from - b.from; }); return result; };