UNPKG

@atlaskit/editor-common

Version:

A package that contains common classes and components for editor and renderer

166 lines (155 loc) 6.09 kB
import _classCallCheck from "@babel/runtime/helpers/classCallCheck"; import _createClass from "@babel/runtime/helpers/createClass"; import _defineProperty from "@babel/runtime/helpers/defineProperty"; import { isEmptyDocument } from '../utils'; import { DynamicBitArray } from './dynamic-bit-array'; import { LIMITED_MODE_DEFAULT_DOC_SIZE_THRESHOLD, LIMITED_MODE_DEFAULT_NODE_COUNT_THRESHOLD } from './limited-mode-document-thresholds'; export var NodeAnchorProvider = /*#__PURE__*/function () { function NodeAnchorProvider() { var limitedMode = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; var emptyDoc = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; _classCallCheck(this, NodeAnchorProvider); _defineProperty(this, "cache", new WeakMap()); _defineProperty(this, "count", BigInt(0)); _defineProperty(this, "existingPos", new DynamicBitArray()); _defineProperty(this, "limitedMode", false); _defineProperty(this, "emptyDoc", false); this.limitedMode = limitedMode; this.emptyDoc = emptyDoc; } return _createClass(NodeAnchorProvider, [{ key: "isEmptyDoc", value: function isEmptyDoc() { return this.emptyDoc; } }, { key: "setEmptyDoc", value: function setEmptyDoc(isEmpty) { this.emptyDoc = isEmpty; } }, { key: "isLimitedMode", value: function isLimitedMode() { return this.limitedMode; } // We use pos to generate unique ids for each node at a specific position // This is to ensure the same ADF will always generate the same DOM initially }, { key: "getOrGenerateId", value: function getOrGenerateId(node, pos) { if (this.limitedMode) { return undefined; } if (this.cache.has(node)) { return this.cache.get(node); } var idSuffix = ''; if (this.existingPos.get(pos)) { idSuffix = "-".concat((this.count++).toString(36)); } else { this.existingPos.set(pos, true); } var anchorName = "--anchor-".concat(node.type.name, "-").concat(pos).concat(idSuffix); this.cache.set(node, anchorName); return anchorName; } }, { key: "getIdForNode", value: function getIdForNode(node) { if (this.limitedMode) { return undefined; } return this.cache.get(node); } }, { key: "setIdForNode", value: function setIdForNode(node, id) { if (this.limitedMode) { return; } this.cache.set(node, id); } // After set to limited mode, we clear the cache to free up memory // and prevent further ids from being generated // Once in limited mode, we won't exit it }, { key: "setLimitedMode", value: function setLimitedMode() { this.limitedMode = true; this.cache = new WeakMap(); this.existingPos = new DynamicBitArray(); this.count = BigInt(0); } }]); }(); var nodeIdProviderMap = new WeakMap(); /** * Determines whether limited mode should be enabled. * This logic mirrors the limited mode plugin implementation, but lives here to avoid a circular dependency. * If it changes, update the matching logic in `editor-plugin-limited-mode/src/pm-plugins/main.ts`. * * Limited mode is activated when ANY of the following conditions are met: * 1. Document size exceeds `LIMITED_MODE_DEFAULT_DOC_SIZE_THRESHOLD` — checked first as O(1) * 2. Node count exceeds `LIMITED_MODE_DEFAULT_NODE_COUNT_THRESHOLD` * 3. Document contains a legacy-content macro (LCM) * * Performance optimisations: * - Doc size is checked first (O(1)) - if it exceeds threshold, we skip traversal entirely. * - If we find an LCM during traversal, we exit early since limited mode will be enabled. */ var isLimitedModeEnabled = function isLimitedModeEnabled(editorView) { var doc = editorView.state.doc; var nodeCountThreshold = LIMITED_MODE_DEFAULT_NODE_COUNT_THRESHOLD; var docSizeThreshold = LIMITED_MODE_DEFAULT_DOC_SIZE_THRESHOLD; // Early exit: doc size exceeds threshold - O(1), no traversal needed if (doc.nodeSize > docSizeThreshold) { return true; } // Single traversal for node count and LCM detection var nodeCount = 0; var hasLcm = false; doc.descendants(function (node) { var _node$attrs; nodeCount += 1; if (((_node$attrs = node.attrs) === null || _node$attrs === void 0 ? void 0 : _node$attrs.extensionKey) === 'legacy-content') { hasLcm = true; // Early exit: LCM found — limited mode will be enabled return false; } }); // LCM condition takes precedence (if we early exited traversal, this is why) if (hasLcm) { return true; } // Check node count threshold if (nodeCount > nodeCountThreshold) { return true; } return false; }; // Get the NodeIdProvider for a specific EditorView instance. // This allows access to the node ids anywhere. export var getNodeIdProvider = function getNodeIdProvider(editorView) { if (!nodeIdProviderMap.has(editorView)) { // if the limited mode flag is on, enable limited mode based on the threshold // only for the first time var limitedMode = isLimitedModeEnabled(editorView); var isEmptyDoc = isEmptyDocument(editorView.state.doc); var provider = new NodeAnchorProvider(limitedMode, isEmptyDoc); nodeIdProviderMap.set(editorView, provider); return provider; } var nodeIdProvider = nodeIdProviderMap.get(editorView); // in some cases we need to re-check limited mode state // Confluence editor can start with an empty doc and then load content later // so we need to check first time from an empty doc to a non-empty doc if (nodeIdProvider.isEmptyDoc() && !isEmptyDocument(editorView.state.doc)) { // set empty doc to false regardless of limited mode state nodeIdProvider.setEmptyDoc(false); if (!nodeIdProvider.isLimitedMode() && isLimitedModeEnabled(editorView)) { nodeIdProvider.setLimitedMode(); } } // This is based on the fact that editorView is a singleton. return nodeIdProviderMap.get(editorView); };