@atlaskit/editor-plugin-code-block
Version:
Code block plugin for @atlaskit/editor-core
131 lines (127 loc) • 6.99 kB
JavaScript
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);
}
});
};