UNPKG

@atlaskit/editor-plugin-collab-edit

Version:

Collab Edit plugin for @atlaskit/editor-core

138 lines (133 loc) 5.54 kB
import { AnalyticsStep, InsertTypeAheadStep, LinkMetaStep, SetAttrsStep } from '@atlaskit/adf-schema/steps'; import { SafePlugin } from '@atlaskit/editor-common/safe-plugin'; import { PluginKey } from '@atlaskit/editor-prosemirror/state'; import { AddMarkStep, AddNodeMarkStep, AttrStep, DocAttrStep, RemoveMarkStep, RemoveNodeMarkStep, ReplaceAroundStep, ReplaceStep } from '@atlaskit/editor-prosemirror/transform'; var THRESHOLD = 50; // 50 milliseconds /** * Sanitizes a given ProseMirror step by extracting its type and non-UCG relevant attributes. * * @param {Step} step - The ProseMirror step to be sanitized. * @returns {SanitizedFilteredStep} - The sanitized step with only necessary information. * * @example * ``` * const step = new AttrStep(10, 'colwidth', [123, 451] ); * const sanitized = sanitizeFilteredStep(step); * * // Output: { stepType: 'attr', stepInstance: 'AttrStep', attr: 'example' } * ``` */ export var sanitizeFilteredStep = function sanitizeFilteredStep(step) { var serializedStep = step.toJSON(); var sanitizedStep = { stepType: serializedStep.stepType, stepInstance: 'unknown' }; if (step instanceof AttrStep) { sanitizedStep.attr = step.attr; sanitizedStep.stepInstance = 'AttrStep'; } else if (step instanceof DocAttrStep) { sanitizedStep.attr = step.attr; sanitizedStep.stepInstance = 'DocAttrStep'; } else if (step instanceof SetAttrsStep) { // Combines all attrs keys separated by _ to one single string sanitizedStep.attr = Object.keys(step.attrs).sort().join('_'); sanitizedStep.stepInstance = 'SetAttrsStep'; } else if (step instanceof AddMarkStep) { sanitizedStep.markType = step.mark.type.name; sanitizedStep.stepInstance = 'AddMarkStep'; } else if (step instanceof RemoveMarkStep) { sanitizedStep.markType = step.mark.type.name; sanitizedStep.stepInstance = 'RemoveMarkStep'; } else if (step instanceof RemoveNodeMarkStep) { sanitizedStep.markType = step.mark.type.name; sanitizedStep.stepInstance = 'RemoveNodeMarkStep'; } else if (step instanceof AddNodeMarkStep) { sanitizedStep.markType = step.mark.type.name; sanitizedStep.stepInstance = 'AddNodeMarkStep'; } else if (step instanceof ReplaceStep) { sanitizedStep.stepInstance = 'ReplaceStep'; } else if (step instanceof ReplaceAroundStep) { sanitizedStep.stepInstance = 'ReplaceAroundStep'; } else if (step instanceof AnalyticsStep) { sanitizedStep.stepInstance = 'AnalyticsStep'; } else if (step instanceof InsertTypeAheadStep) { sanitizedStep.stepInstance = 'InsertTypeAheadStep'; } else if (step instanceof LinkMetaStep) { sanitizedStep.stepInstance = 'LinkMetaStep'; } return sanitizedStep; }; export var createFilterTransaction = function createFilterTransaction(recentTransactionsTimestamps, trackFilteredTransaction) { return function (tr) { if (Boolean(tr.getMeta('appendTransaction'))) { return true; } var isRemote = Boolean(tr.getMeta('isRemote')); if (isRemote) { return true; } var containsExcludedSteps = tr.steps.some(function (step) { return step instanceof AnalyticsStep || step instanceof ReplaceStep || step instanceof ReplaceAroundStep || step instanceof LinkMetaStep; }); if (containsExcludedSteps) { return true; } if (tr.docChanged && tr.doc.eq(tr.before)) { var transactionKey = generateTransactionKey(tr); if (!transactionKey) { return true; } //Clean up tracked transactions when time over threshold recentTransactionsTimestamps.forEach(function (value, key) { if (tr.time - value.timestamp > THRESHOLD) { // Delete tracked transaction when over threshold recentTransactionsTimestamps.delete(key); } }); var lastTransactionEntry = recentTransactionsTimestamps.get(transactionKey); if (!lastTransactionEntry) { // If no timestamp exists for the given transaction, allow transaction and add an entry recentTransactionsTimestamps.set(transactionKey, { timestamp: tr.time, steps: tr.steps }); return true; } // Track analytics for the filtered transaction trackFilteredTransaction(tr); // Filter transaction return false; } return true; // Allow the transaction }; }; // Helper function to create a u ique transaction key export function generateTransactionKey(tr) { var stepPositions = tr.steps.map(function (step) { if (step instanceof RemoveNodeMarkStep || step instanceof AddNodeMarkStep || step instanceof SetAttrsStep || step instanceof AttrStep) { if (step.pos !== undefined) { return "".concat(step.pos); } } else if (step instanceof AddMarkStep || step instanceof RemoveMarkStep) { return "from_".concat(step.from, "_to_").concat(step.to); } else if (step instanceof InsertTypeAheadStep) { return "insertTypeAheadStep"; } return ''; }); if (stepPositions.some(function (step) { return Boolean(step); })) { return stepPositions.join('_'); } return ''; } export var trackSpammingStepsPluginKey = new PluginKey('trackAndFilterSpammingStepsPluginKey'); export var createPlugin = function createPlugin(trackFilteredTransaction) { var recentTransactionsTimestamps = new Map(); return new SafePlugin({ key: trackSpammingStepsPluginKey, filterTransaction: createFilterTransaction(recentTransactionsTimestamps, trackFilteredTransaction) }); };