UNPKG

@atlaskit/editor-plugin-synced-block

Version:

SyncedBlock plugin for @atlaskit/editor-core

141 lines (134 loc) 4.19 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 const trackSyncBlocks = (predicate, tr, state) => { const removed = {}; const added = {}; if (!tr.docChanged) { return { removed: [], added: [] }; } // and cast to specific step types const replaceSteps = tr.steps.filter(step => step instanceof ReplaceStep || step instanceof ReplaceAroundStep); // this is a quick check to see if any insertion/deletion of sync block happened const hasSyncBlockChanges = replaceSteps.some(step => { const { from, to } = step; const docAtStep = tr.docs[tr.steps.indexOf(step)]; let hasChange = false; if (from !== to) { step.getMap().forEach((oldStart, oldEnd) => { if (oldStart !== oldEnd && !hasChange) { const deletedSlice = docAtStep.slice(Math.max(0, oldStart), Math.min(docAtStep.content.size, oldEnd)); deletedSlice.content.forEach(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(node => { if (predicate(node) && !hasChange) { hasChange = true; } }); } return hasChange; }); if (hasSyncBlockChanges) { const oldDoc = state.doc; const newDoc = tr.doc; const syncBlockMapOld = {}; const syncBlockMapNew = {}; oldDoc.content.forEach(node => { if (predicate(node)) { const syncBlockAttr = node.attrs; syncBlockMapOld[syncBlockAttr.localId] = { attrs: syncBlockAttr }; } }); newDoc.content.forEach((node, offset) => { if (predicate(node)) { const syncBlockAttr = node.attrs; syncBlockMapNew[syncBlockAttr.localId] = { attrs: syncBlockAttr, node: node, from: offset, to: offset + node.nodeSize }; } }); // Find removed sync blocks for (const localId in syncBlockMapOld) { if (!syncBlockMapNew[localId]) { removed[localId] = syncBlockMapOld[localId]; } } // Find added sync blocks for (const 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 const hasEditInSyncBlock = (tr, state) => { const { bodiedSyncBlock } = state.schema.nodes; for (let i = 0; i < tr.steps.length; i++) { var _tr$docs; const step = tr.steps[i]; const map = step.getMap(); const docAfterStep = (_tr$docs = tr.docs[i + 1]) !== null && _tr$docs !== void 0 ? _tr$docs : tr.doc; const 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') { const { from, to } = step; positions.push(from, to); } else if ('pos' in step && typeof step.pos === 'number') { const { pos } = step; positions.push(pos); } for (const pos of positions) { const newPos = map.map(pos); if (newPos >= 0 && newPos <= docAfterStep.content.size) { if (findParentNodeOfTypeClosestToPos(docAfterStep.resolve(newPos), bodiedSyncBlock)) { return true; } } } } return false; };