@atlaskit/editor-plugin-collab-edit
Version:
Collab Edit plugin for @atlaskit/editor-core
138 lines (133 loc) • 5.54 kB
JavaScript
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)
});
};