UNPKG

@atlaskit/editor-plugin-synced-block

Version:

SyncedBlock plugin for @atlaskit/editor-core

138 lines (131 loc) 4.37 kB
import { ReplaceAroundStep, ReplaceStep } from '@atlaskit/editor-prosemirror/transform'; import { findParentNodeOfTypeClosestToPos } from '@atlaskit/editor-prosemirror/utils'; /** * Tracks changes to sync blocks in a transaction. * @param predicate - A function that returns true if a node is a sync block (source or reference or both). * @param tr - The transaction to track changes in. * @param state - The editor state. * @returns An object containing the removed and added sync blocks. */ export var trackSyncBlocks = function trackSyncBlocks(predicate, tr, state) { var removed = {}; var added = {}; if (!tr.docChanged) { return { removed: [], added: [] }; } // and cast to specific step types var replaceSteps = tr.steps.filter(function (step) { return step instanceof ReplaceStep || step instanceof ReplaceAroundStep; }); // this is a quick check to see if any insertion/deletion of sync block happened var hasSyncBlockChanges = replaceSteps.some(function (step) { var from = step.from, to = step.to; var docAtStep = tr.docs[tr.steps.indexOf(step)]; var hasChange = false; if (from !== to) { step.getMap().forEach(function (oldStart, oldEnd) { if (oldStart !== oldEnd && !hasChange) { var deletedSlice = docAtStep.slice(Math.max(0, oldStart), Math.min(docAtStep.content.size, oldEnd)); deletedSlice.content.forEach(function (node) { if (hasChange) { return; } // for top level nodes if (predicate(node)) { hasChange = true; } }); } }); } // no need to check insertions if we already found deletions if (step.slice.content.size > 0 && !hasChange) { step.slice.content.forEach(function (node) { if (predicate(node) && !hasChange) { hasChange = true; } }); } return hasChange; }); if (hasSyncBlockChanges) { var oldDoc = state.doc; var newDoc = tr.doc; var syncBlockMapOld = {}; var syncBlockMapNew = {}; oldDoc.content.forEach(function (node) { if (predicate(node)) { var syncBlockAttr = node.attrs; syncBlockMapOld[syncBlockAttr.localId] = { attrs: syncBlockAttr }; } }); newDoc.content.forEach(function (node, offset) { if (predicate(node)) { var syncBlockAttr = node.attrs; syncBlockMapNew[syncBlockAttr.localId] = { attrs: syncBlockAttr, node: node, from: offset, to: offset + node.nodeSize }; } }); // Find removed sync blocks for (var localId in syncBlockMapOld) { if (!syncBlockMapNew[localId]) { removed[localId] = syncBlockMapOld[localId]; } } // Find added sync blocks for (var _localId in syncBlockMapNew) { if (!syncBlockMapOld[_localId]) { added[_localId] = syncBlockMapNew[_localId]; } } } return { removed: Object.values(removed), added: Object.values(added) }; }; /** * * @returns true if steps modifies children node within bodiedSyncBlock */ export var hasEditInSyncBlock = function hasEditInSyncBlock(tr, state) { var bodiedSyncBlock = state.schema.nodes.bodiedSyncBlock; for (var i = 0; i < tr.steps.length; i++) { var _tr$docs; var step = tr.steps[i]; var map = step.getMap(); var docAfterStep = (_tr$docs = tr.docs[i + 1]) !== null && _tr$docs !== void 0 ? _tr$docs : tr.doc; var positions = []; // Extract positions from steps dynamically based on applicable properties if ('from' in step && typeof step.from === 'number' && 'to' in step && typeof step.to === 'number') { var _ref = step, from = _ref.from, to = _ref.to; positions.push(from, to); } else if ('pos' in step && typeof step.pos === 'number') { var _ref2 = step, pos = _ref2.pos; positions.push(pos); } for (var _i = 0, _positions = positions; _i < _positions.length; _i++) { var _pos = _positions[_i]; var newPos = map.map(_pos); if (newPos >= 0 && newPos <= docAfterStep.content.size) { if (findParentNodeOfTypeClosestToPos(docAfterStep.resolve(newPos), bodiedSyncBlock)) { return true; } } } } return false; };