UNPKG

@atlaskit/adf-schema

Version:

Shared package that contains the ADF-schema (json) and ProseMirror node/mark specs

167 lines (166 loc) 5.36 kB
import { Fragment, Slice } from '@atlaskit/editor-prosemirror/model'; import { Step, StepResult } from '@atlaskit/editor-prosemirror/transform'; const stepType = 'batchAttrs'; const isValidData = data => { if (data !== null && !Array.isArray(data)) { return false; } return !data.some(d => { const invalidPosition = typeof d.position !== 'number'; const invalidNodeType = typeof d.nodeType !== 'string'; const invalidAttrs = typeof d.attrs !== 'object'; return invalidPosition || invalidNodeType || invalidAttrs; }); }; /** * 📢 Public API: Editor FE Platform * * Represents a step that applies a batch of attribute changes to nodes in a ProseMirror document. * * This step is particularly useful when you need to update the attributes of multiple nodes in a document * in a single operation. For example, you might want to change the color of several panels or update metadata * for various sections without needing to perform multiple separate operations. * * **Use Cases:** * - **Efficiency**: Apply multiple attribute changes in a single step to reduce the number of operations. * - **Atomicity**: Ensure that a group of attribute changes are applied together, maintaining document consistency. * - **Consistency**: Use when changes are logically related, such as updating theme attributes for a document section. * * **When Not to Use:** * - **Single Changes**: If you only need to change attributes on a single node, a more straightforward step might be suitable like `AttrsStep` from prosemirror. * - **Complex Node Transformations**: This step is designed for attribute changes rather than structural changes. * - **Performance Concerns**: While efficient for batch updates, unnecessary use for single updates may add overhead. * * @example * ```typescript * import { BatchAttrsStep } from '@atlaskit/adf-schema/steps'; * * // Define the attribute changes * const changes = [ * { * position: 0, // Position of the first panel * nodeType: 'panel', * attrs: { panelType: 'error' } * }, * { * position: 7, // Position of the second panel * nodeType: 'panel', * attrs: { panelType: 'success' } * } * ]; * * // Create the step and apply it to the document * const step = new BatchAttrsStep(changes); * * const tr = editorState.tr; * * tr.step(step); * ``` * * @class * @extends {Step} */ export class BatchAttrsStep extends Step { constructor(data, inverted = false) { super(); this.data = data; this.inverted = inverted; } apply(doc) { const resultDoc = this.data.reduce((acc, value) => { var _acc$doc; if (!acc.doc || acc.failed) { return acc; } const { position, attrs } = value; if (acc.doc && position > acc.doc.nodeSize || position < 0) { return StepResult.fail(`Position ${position} out of document range.`); } const target = (_acc$doc = acc.doc) === null || _acc$doc === void 0 ? void 0 : _acc$doc.nodeAt(position); if (!target) { return StepResult.fail(`No node at given position: ${position}`); } if (target.isText) { return StepResult.fail('Target is a text node. Attributes are not allowed.'); } const mergedAttrs = { ...(target.attrs || {}), ...(attrs || {}) }; const updatedNode = target.type.create(mergedAttrs, null, target.marks); const slice = new Slice(Fragment.from(updatedNode), 0, target.isLeaf ? 0 : 1); return StepResult.fromReplace(acc.doc, position, position + 1, slice); }, StepResult.ok(doc)); return resultDoc; } invert(doc) { const previousData = this.data.reduce((acc, value) => { const { position, nodeType, attrs: nextAttrs } = value; if (position > doc.nodeSize) { return acc; } const target = doc.nodeAt(position); if (!target) { return acc; } if (target.isText) { return acc; } const previousAttrs = Object.keys(nextAttrs).reduce((attributesIterator, key) => { const oldValue = target.attrs[key]; attributesIterator[key] = oldValue; return attributesIterator; }, {}); const prev = { position, nodeType, attrs: previousAttrs }; acc.push(prev); return acc; }, []); return new BatchAttrsStep(previousData, true); } map(mapping) { const mappedData = this.data.reduce((acc, value) => { const { position } = value; const mappedPosition = mapping.mapResult(position); if (mappedPosition.deleted) { return acc; } acc.push({ ...value, position: mappedPosition.pos }); return acc; }, []); if (mappedData.length === 0) { return null; } return new BatchAttrsStep(mappedData, this.inverted); } toJSON() { return { stepType, data: this.data, inverted: this.inverted }; } static fromJSON(_schema, json) { const data = json === null || json === void 0 ? void 0 : json.data; if (!isValidData(data)) { throw new Error('Invalid input for BatchAttrsStep.fromJSON'); } return new BatchAttrsStep(data, Boolean(json.inverted)); } } Step.jsonID(stepType, BatchAttrsStep);