UNPKG

@atlaskit/editor-plugin-code-block

Version:

Code block plugin for @atlaskit/editor-core

197 lines (193 loc) 10.1 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.updateAutoDetectState = exports.shouldTriggerLargeChangeDetection = exports.removeAutoDetection = exports.queueAutoDetection = exports.hasEnoughTextForAutoDetection = exports.getLocalId = exports.getFirstLine = exports.createAutoDetectEntry = void 0; var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray")); var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _codeBlock = require("@atlaskit/editor-common/code-block"); var _utils = require("@atlaskit/editor-common/utils"); var _utils2 = require("../pm-plugins/utils"); function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } var MIN_AUTO_DETECT_TEXT_LENGTH = 20; var shouldTriggerLargeChangeDetection = exports.shouldTriggerLargeChangeDetection = function shouldTriggerLargeChangeDetection(lastObservedText, text) { if (!lastObservedText) { return text.length > 0; } return Math.abs(text.length - lastObservedText.length) > lastObservedText.length / 2; }; var getFirstLine = exports.getFirstLine = function getFirstLine(text) { var _text$split$; return (_text$split$ = text.split('\n')[0]) !== null && _text$split$ !== void 0 ? _text$split$ : ''; }; var hasEnoughTextForAutoDetection = exports.hasEnoughTextForAutoDetection = function hasEnoughTextForAutoDetection(text) { return text.trim().length >= MIN_AUTO_DETECT_TEXT_LENGTH; }; var getLocalId = exports.getLocalId = function getLocalId(node) { return typeof node.attrs.localId === 'string' ? node.attrs.localId : null; }; var createAutoDetectEntry = exports.createAutoDetectEntry = function createAutoDetectEntry(node, pos, isPending, previous) { var options = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {}; return { lastObservedText: node.textContent, lastObservedFirstLine: getFirstLine(node.textContent), isPending: isPending, detectionResult: options.preserveDetectionResult === false ? undefined : previous === null || previous === void 0 ? void 0 : previous.detectionResult, autoDetectedLanguage: previous === null || previous === void 0 ? void 0 : previous.autoDetectedLanguage, pos: pos }; }; var queueAutoDetection = exports.queueAutoDetection = function queueAutoDetection(languageDetectionMap, node, pos, isPending) { var localId = getLocalId(node); if (!localId) { return languageDetectionMap; } return _objectSpread(_objectSpread({}, languageDetectionMap), {}, (0, _defineProperty2.default)({}, localId, createAutoDetectEntry(node, pos, isPending, languageDetectionMap[localId]))); }; var removeAutoDetection = exports.removeAutoDetection = function removeAutoDetection(languageDetectionMap, localId) { if (!languageDetectionMap[localId]) { return languageDetectionMap; } var nextLanguageDetectionMap = _objectSpread({}, languageDetectionMap); delete nextLanguageDetectionMap[localId]; return nextLanguageDetectionMap; }; var getCodeBlockLocalIdsRemovedFromChangedRanges = function getCodeBlockLocalIdsRemovedFromChangedRanges(tr, codeBlockType) { var localIds = new Set(); tr.steps.forEach(function (step, stepIndex) { var docAtStep = tr.docs[stepIndex]; step.getMap().forEach(function (oldStart, oldEnd) { if (oldStart === oldEnd) { return; } var clampedOldEnd = Math.min(oldEnd, docAtStep.content.size); docAtStep.nodesBetween(oldStart, clampedOldEnd, function (node, pos) { if (node.type !== codeBlockType) { return true; } var isWholeCodeBlockRemoved = pos >= oldStart && pos + node.nodeSize <= clampedOldEnd; if (!isWholeCodeBlockRemoved) { return false; } var localId = getLocalId(node); if (localId) { localIds.add(localId); } return false; }); }); }); return localIds; }; var isHistoryMeta = function isHistoryMeta(meta) { return (0, _typeof2.default)(meta) === 'object' && meta !== null && 'redo' in meta; }; var getCodeBlockTransactionChanges = function getCodeBlockTransactionChanges(tr, codeBlockType) { var insertedNodesWithPos = (0, _codeBlock.getInsertedCodeBlocksInTransaction)(tr, codeBlockType); var removedFromChangedRangesLocalIds = getCodeBlockLocalIdsRemovedFromChangedRanges(tr, codeBlockType); var insertedLocalIds = new Set(); var insertedCodeBlocks = []; insertedNodesWithPos.forEach(function (_ref) { var node = _ref.node, pos = _ref.pos; var localId = getLocalId(node); if (!localId) { return; } insertedLocalIds.add(localId); if (!removedFromChangedRangesLocalIds.has(localId)) { insertedCodeBlocks.push({ localId: localId, node: node, pos: pos }); } }); var deletedLocalIds = new Set(); removedFromChangedRangesLocalIds.forEach(function (localId) { if (!insertedLocalIds.has(localId)) { deletedLocalIds.add(localId); } }); return { deletedLocalIds: deletedLocalIds, insertedCodeBlocks: insertedCodeBlocks }; }; var updateAutoDetectState = exports.updateAutoDetectState = function updateAutoDetectState(tr, pluginState) { var codeBlock = tr.doc.type.schema.nodes.codeBlock; var isPaste = tr.getMeta('paste') === true || tr.getMeta('uiEvent') === 'paste'; var isExternalContentChange = tr.getMeta('replaceDocument') === true || tr.getMeta('isRemote') === true; var historyMeta = tr.getMeta(_utils.pmHistoryPluginKey); var isUndo = isHistoryMeta(historyMeta) && historyMeta.redo === false; var hasTrackedEntries = Object.keys(pluginState.languageDetectionMap).length > 0; // Page loads and remote edits should not start auto-detection for code blocks. if (!hasTrackedEntries && isExternalContentChange) { return pluginState.languageDetectionMap; } // Existing entries still need mapping/deletion cleanup, but external edits should not refresh text. var changedCodeBlockNodes = isExternalContentChange ? [] : (0, _utils2.getAllChangedCodeBlocksInTransaction)(tr); if (!hasTrackedEntries && !changedCodeBlockNodes.length) { return pluginState.languageDetectionMap; } var _getCodeBlockTransact = getCodeBlockTransactionChanges(tr, codeBlock), deletedLocalIds = _getCodeBlockTransact.deletedLocalIds, insertedCodeBlocks = _getCodeBlockTransact.insertedCodeBlocks; var languageDetectionMap = hasTrackedEntries ? Object.fromEntries(Object.entries(pluginState.languageDetectionMap).map(function (_ref2) { var _ref3 = (0, _slicedToArray2.default)(_ref2, 2), localId = _ref3[0], entry = _ref3[1]; return [localId, _objectSpread(_objectSpread({}, entry), {}, { pos: tr.mapping.map(entry.pos) })]; })) : pluginState.languageDetectionMap; if (!isExternalContentChange) { insertedCodeBlocks.forEach(function (_ref4) { var localId = _ref4.localId, node = _ref4.node, pos = _ref4.pos; if (node.attrs.language) { languageDetectionMap = removeAutoDetection(languageDetectionMap, localId); return; } languageDetectionMap = queueAutoDetection(languageDetectionMap, node, pos, hasEnoughTextForAutoDetection(node.textContent)); }); changedCodeBlockNodes.forEach(function (_ref5) { var _tr$before$nodeAt; var node = _ref5.node, pos = _ref5.pos; var localId = getLocalId(node); if (!localId || !languageDetectionMap[localId]) { return; } var currentLanguage = node.attrs.language; var previousEntry = languageDetectionMap[localId]; // Undo metadata does not identify the changed node; compare the pre/post language // so text undo inside auto-detection mode keeps the entry. var previousLanguage = isUndo ? (_tr$before$nodeAt = tr.before.nodeAt(tr.mapping.invert().map(pos))) === null || _tr$before$nodeAt === void 0 ? void 0 : _tr$before$nodeAt.attrs.language : undefined; if (isUndo && previousLanguage && !currentLanguage || currentLanguage && currentLanguage !== previousEntry.autoDetectedLanguage) { languageDetectionMap = removeAutoDetection(languageDetectionMap, localId); return; } var text = node.textContent; var firstLine = getFirstLine(text); var shouldTriggerDetection = previousEntry.isPending || isPaste || firstLine !== previousEntry.lastObservedFirstLine || shouldTriggerLargeChangeDetection(previousEntry.lastObservedText, text); var isPending = hasEnoughTextForAutoDetection(text) && shouldTriggerDetection; // Only pending detection refreshes the text snapshot; otherwise gradual typing // should continue comparing against the last attempted detection. languageDetectionMap = isPending ? queueAutoDetection(languageDetectionMap, node, pos, true) : _objectSpread(_objectSpread({}, languageDetectionMap), {}, (0, _defineProperty2.default)({}, localId, _objectSpread(_objectSpread({}, previousEntry), {}, { isPending: false, pos: pos }))); }); } deletedLocalIds.forEach(function (localId) { if (languageDetectionMap[localId]) { languageDetectionMap = removeAutoDetection(languageDetectionMap, localId); } }); return languageDetectionMap; };