@atlaskit/adf-schema
Version:
Shared package that contains the ADF-schema (json) and ProseMirror node/mark specs
167 lines (166 loc) • 5.36 kB
JavaScript
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);