UNPKG

@atlaskit/editor-plugin-code-block

Version:

Code block plugin for @atlaskit/editor-core

131 lines (127 loc) 6.99 kB
import _slicedToArray from "@babel/runtime/helpers/slicedToArray"; import _defineProperty from "@babel/runtime/helpers/defineProperty"; 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) { _defineProperty(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; } import { ACTION, ACTION_SUBJECT, EVENT_TYPE } from '@atlaskit/editor-common/analytics'; import { fg } from '@atlaskit/platform-feature-flags'; import { ACTIONS } from '../pm-plugins/actions'; import { autoDetectPluginKey } from '../pm-plugins/auto-detect-state'; import { createAutoDetectEntry } from './auto-detect-state'; import { detectLanguage } from './language-detect'; var AUTO_DETECT_DEBOUNCE_MS = 500; // Stored positions are mapped through transactions; verify the localId before using them. var getCodeBlockFromEntry = function getCodeBlockFromEntry(view, localId, entry) { var node = view.state.doc.nodeAt(entry.pos); var codeBlockType = view.state.schema.nodes.codeBlock; if ((node === null || node === void 0 ? void 0 : node.type) === codeBlockType && node.attrs.localId === localId) { return { node: node, pos: entry.pos }; } return null; }; // Runs after debounce, so it must re-read current editor state before applying language changes. var runPendingDetection = function runPendingDetection(view, localId, api) { var _api$core; var pluginState = autoDetectPluginKey.getState(view.state); var entry = pluginState === null || pluginState === void 0 ? void 0 : pluginState.languageDetectionMap[localId]; if (!(entry !== null && entry !== void 0 && entry.isPending)) { return; } var found = getCodeBlockFromEntry(view, localId, entry); if (!found) { return; } var detectedLanguage = detectLanguage(found.node.textContent); var detectionResult = detectedLanguage ? 'detected' : 'noneDetected'; var detectionPhase = entry.detectionResult ? 'redetection' : 'initial'; // Keep a previous auto-detected language when the latest snippet is too weak to classify. var shouldPreserveAutoDetectedLanguage = !detectedLanguage && Boolean(entry.autoDetectedLanguage) && found.node.attrs.language === entry.autoDetectedLanguage; var nextEntry = _objectSpread(_objectSpread({}, createAutoDetectEntry(found.node, found.pos, false, entry)), {}, { detectionResult: detectionResult, autoDetectedLanguage: detectedLanguage !== null && detectedLanguage !== void 0 ? detectedLanguage : entry.autoDetectedLanguage }); // If there is no confident detection, record the result without clearing user-visible language. var shouldOnlyUpdateDetectionState = !detectedLanguage && (!found.node.attrs.language || shouldPreserveAutoDetectedLanguage); var hasDetectedLanguageChange = Boolean(detectedLanguage) && found.node.attrs.language !== detectedLanguage; var shouldClearStaleLanguage = !detectedLanguage && Boolean(found.node.attrs.language) && !shouldPreserveAutoDetectedLanguage; var shouldUpdateNodeLanguage = fg('platform_editor_code_block_dogfooding_patch') ? hasDetectedLanguageChange || shouldClearStaleLanguage : !shouldOnlyUpdateDetectionState; api === null || api === void 0 || (_api$core = api.core) === null || _api$core === void 0 || _api$core.actions.execute(function (_ref) { var tr = _ref.tr; if (shouldUpdateNodeLanguage) { tr.setNodeMarkup(found.pos, undefined, _objectSpread(_objectSpread({}, found.node.attrs), {}, { language: detectedLanguage }), found.node.marks); } tr.setMeta(autoDetectPluginKey, { type: ACTIONS.SET_AUTO_DETECT_ENTRY, data: { localId: localId, entry: nextEntry } }); // When platform_editor_code_block_dogfooding_patch is cleaned up, merge this with the previous shouldUpdateNodeLanguage check. if (!fg('platform_editor_code_block_dogfooding_patch') || shouldUpdateNodeLanguage) { var _api$analytics; api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 || _api$analytics.actions.attachAnalyticsEvent({ action: ACTION.LANGUAGE_AUTO_DETECTED, actionSubject: ACTION_SUBJECT.CODE_BLOCK, attributes: _objectSpread({ language: detectedLanguage !== null && detectedLanguage !== void 0 ? detectedLanguage : 'none', detectionResult: detectionResult }, fg('platform_editor_code_block_dogfooding_patch') ? { detectionPhase: detectionPhase } : {}), eventType: EVENT_TYPE.TRACK })(tr); } // Language detection runs in the background and should not move the viewport. return tr.setMeta('scrollIntoView', false); }); }; var clearTimer = function clearTimer(timers, localId) { var scheduledDetection = timers.get(localId); if (scheduledDetection) { clearTimeout(scheduledDetection.timer); timers.delete(localId); } }; // Keeps one debounce timer per pending code block and drops timers for stale entries. export var syncPendingDetectionTimers = function syncPendingDetectionTimers(view, timers, api) { var _pluginState$language; var pluginState = autoDetectPluginKey.getState(view.state); var pendingEntries = Object.entries((_pluginState$language = pluginState === null || pluginState === void 0 ? void 0 : pluginState.languageDetectionMap) !== null && _pluginState$language !== void 0 ? _pluginState$language : {}).filter(function (_ref2) { var _ref3 = _slicedToArray(_ref2, 2), entry = _ref3[1]; return entry.isPending; }); var pendingLocalIds = new Set(pendingEntries.map(function (_ref4) { var _ref5 = _slicedToArray(_ref4, 1), localId = _ref5[0]; return localId; })); pendingEntries.forEach(function (_ref6) { var _ref7 = _slicedToArray(_ref6, 2), localId = _ref7[0], entry = _ref7[1]; var scheduledDetection = timers.get(localId); if ((scheduledDetection === null || scheduledDetection === void 0 ? void 0 : scheduledDetection.lastObservedText) === entry.lastObservedText) { return; } clearTimer(timers, localId); var timer = setTimeout(function () { timers.delete(localId); runPendingDetection(view, localId, api); }, AUTO_DETECT_DEBOUNCE_MS); timers.set(localId, { lastObservedText: entry.lastObservedText, timer: timer }); }); timers.forEach(function (_, localId) { if (!pendingLocalIds.has(localId)) { clearTimer(timers, localId); } }); };