@atlaskit/editor-plugin-code-block 12.1.10 → 12.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/cjs/codeBlockPlugin.js +9 -2
  3. package/dist/cjs/editor-commands/index.js +56 -4
  4. package/dist/cjs/pm-plugins/actions.js +3 -1
  5. package/dist/cjs/pm-plugins/auto-detect-state.js +11 -0
  6. package/dist/cjs/pm-plugins/auto-detect.js +57 -0
  7. package/dist/cjs/pm-plugins/main.js +1 -1
  8. package/dist/cjs/pm-plugins/toolbar.js +47 -11
  9. package/dist/cjs/pm-plugins/utils.js +0 -3
  10. package/dist/cjs/ui/CodeBlockLanguagePicker.js +15 -8
  11. package/dist/cjs/ui/LanguagePicker.js +7 -15
  12. package/dist/cjs/ui/language-picker-options.js +2 -1
  13. package/dist/cjs/utils/auto-detect-state.js +185 -0
  14. package/dist/cjs/utils/auto-detect-view.js +127 -0
  15. package/dist/cjs/utils/language-detect.js +126 -0
  16. package/dist/es2019/codeBlockPlugin.js +5 -1
  17. package/dist/es2019/editor-commands/index.js +52 -2
  18. package/dist/es2019/pm-plugins/actions.js +3 -1
  19. package/dist/es2019/pm-plugins/auto-detect-state.js +3 -0
  20. package/dist/es2019/pm-plugins/auto-detect.js +47 -0
  21. package/dist/es2019/pm-plugins/main.js +1 -1
  22. package/dist/es2019/pm-plugins/toolbar.js +41 -3
  23. package/dist/es2019/pm-plugins/utils.js +0 -3
  24. package/dist/es2019/ui/CodeBlockLanguagePicker.js +15 -8
  25. package/dist/es2019/ui/LanguagePicker.js +6 -14
  26. package/dist/es2019/ui/language-picker-options.js +2 -1
  27. package/dist/es2019/utils/auto-detect-state.js +179 -0
  28. package/dist/es2019/utils/auto-detect-view.js +108 -0
  29. package/dist/es2019/utils/language-detect.js +99 -0
  30. package/dist/esm/codeBlockPlugin.js +9 -2
  31. package/dist/esm/editor-commands/index.js +55 -3
  32. package/dist/esm/pm-plugins/actions.js +3 -1
  33. package/dist/esm/pm-plugins/auto-detect-state.js +5 -0
  34. package/dist/esm/pm-plugins/auto-detect.js +50 -0
  35. package/dist/esm/pm-plugins/main.js +1 -1
  36. package/dist/esm/pm-plugins/toolbar.js +47 -11
  37. package/dist/esm/pm-plugins/utils.js +0 -3
  38. package/dist/esm/ui/CodeBlockLanguagePicker.js +15 -8
  39. package/dist/esm/ui/LanguagePicker.js +7 -15
  40. package/dist/esm/ui/language-picker-options.js +2 -1
  41. package/dist/esm/utils/auto-detect-state.js +178 -0
  42. package/dist/esm/utils/auto-detect-view.js +120 -0
  43. package/dist/esm/utils/language-detect.js +119 -0
  44. package/dist/types/editor-commands/index.d.ts +2 -0
  45. package/dist/types/pm-plugins/actions.d.ts +2 -0
  46. package/dist/types/pm-plugins/auto-detect-state.d.ts +16 -0
  47. package/dist/types/pm-plugins/auto-detect.d.ts +5 -0
  48. package/dist/types/pm-plugins/utils.d.ts +1 -1
  49. package/dist/types/ui/CodeBlockLanguagePicker.d.ts +7 -1
  50. package/dist/types/ui/LanguagePicker.d.ts +4 -8
  51. package/dist/types/utils/auto-detect-state.d.ts +11 -0
  52. package/dist/types/utils/auto-detect-view.d.ts +8 -0
  53. package/dist/types/utils/language-detect.d.ts +3 -0
  54. package/dist/types-ts4.5/editor-commands/index.d.ts +2 -0
  55. package/dist/types-ts4.5/pm-plugins/actions.d.ts +2 -0
  56. package/dist/types-ts4.5/pm-plugins/auto-detect-state.d.ts +16 -0
  57. package/dist/types-ts4.5/pm-plugins/auto-detect.d.ts +5 -0
  58. package/dist/types-ts4.5/pm-plugins/utils.d.ts +1 -1
  59. package/dist/types-ts4.5/ui/CodeBlockLanguagePicker.d.ts +7 -1
  60. package/dist/types-ts4.5/ui/LanguagePicker.d.ts +4 -8
  61. package/dist/types-ts4.5/utils/auto-detect-state.d.ts +11 -0
  62. package/dist/types-ts4.5/utils/auto-detect-view.d.ts +8 -0
  63. package/dist/types-ts4.5/utils/language-detect.d.ts +3 -0
  64. package/package.json +3 -3
@@ -12,9 +12,11 @@ import { NodeSelection } from '@atlaskit/editor-prosemirror/state';
12
12
  import { findParentNodeOfType, findSelectedNodeOfType, isNodeSelection, removeParentNodeOfType, removeSelectedNode, safeInsert } from '@atlaskit/editor-prosemirror/utils';
13
13
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
14
14
  import { ACTIONS } from '../pm-plugins/actions';
15
+ import { autoDetectPluginKey } from '../pm-plugins/auto-detect-state';
15
16
  import { copySelectionPluginKey } from '../pm-plugins/codeBlockCopySelectionPlugin';
16
17
  import { pluginKey } from '../pm-plugins/plugin-key';
17
18
  import { transformToCodeBlockAction } from '../pm-plugins/transform-to-code-block';
19
+ import { createAutoDetectEntry, getLocalId, hasEnoughTextForAutoDetection } from '../utils/auto-detect-state';
18
20
  export var removeCodeBlockWithAnalytics = function removeCodeBlockWithAnalytics(editorAnalyticsAPI) {
19
21
  return withAnalytics(editorAnalyticsAPI, {
20
22
  action: ACTION.DELETED,
@@ -42,27 +44,40 @@ export var removeCodeBlock = function removeCodeBlock(state, dispatch) {
42
44
  export var changeLanguage = function changeLanguage(editorAnalyticsAPI) {
43
45
  return function (language, selectionSource) {
44
46
  return function (state, dispatch) {
45
- var _pluginKey$getState;
47
+ var _pluginKey$getState, _autoDetectPluginKey$;
46
48
  var codeBlock = state.schema.nodes.codeBlock;
47
49
  var pos = (_pluginKey$getState = pluginKey.getState(state)) === null || _pluginKey$getState === void 0 ? void 0 : _pluginKey$getState.pos;
48
50
  if (typeof pos !== 'number') {
49
51
  return false;
50
52
  }
51
53
  var node = state.doc.nodeAt(pos);
54
+ var localId = node === null || node === void 0 ? void 0 : node.attrs.localId;
55
+ var previousAutoDetectEntry = expValEquals('platform_editor_code_block_auto_detection', 'isEnabled', true) ? (_autoDetectPluginKey$ = autoDetectPluginKey.getState(state)) === null || _autoDetectPluginKey$ === void 0 ? void 0 : _autoDetectPluginKey$.languageDetectionMap[localId] : undefined;
52
56
  var tr = state.tr.setNodeMarkup(pos, codeBlock, _objectSpread(_objectSpread({}, node === null || node === void 0 ? void 0 : node.attrs), {}, {
53
57
  language: language
54
58
  })).setMeta('scrollIntoView', false);
59
+ if (expValEquals('platform_editor_code_block_auto_detection', 'isEnabled', true)) {
60
+ tr.setMeta(autoDetectPluginKey, {
61
+ type: ACTIONS.REMOVE_AUTO_DETECT_ENTRY,
62
+ data: {
63
+ localId: localId
64
+ }
65
+ });
66
+ }
55
67
  var selection = isNodeSelection(state.selection) ? NodeSelection.create(tr.doc, pos) : tr.selection;
56
68
  var result = tr.setSelection(selection);
57
69
  if (dispatch) {
58
70
  editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 || editorAnalyticsAPI.attachAnalyticsEvent({
59
71
  action: ACTION.LANGUAGE_SELECTED,
60
72
  actionSubject: ACTION_SUBJECT.CODE_BLOCK,
61
- attributes: _objectSpread({
73
+ attributes: _objectSpread(_objectSpread({
62
74
  language: language !== null && language !== void 0 ? language : 'none'
63
75
  }, selectionSource ? {
64
76
  selectionSource: selectionSource
65
- } : {}),
77
+ } : {}), {}, {
78
+ autoDetectionResult: previousAutoDetectEntry === null || previousAutoDetectEntry === void 0 ? void 0 : previousAutoDetectEntry.detectionResult,
79
+ autoDetectedLanguage: previousAutoDetectEntry === null || previousAutoDetectEntry === void 0 ? void 0 : previousAutoDetectEntry.autoDetectedLanguage
80
+ }),
66
81
  eventType: EVENT_TYPE.TRACK
67
82
  })(result);
68
83
  dispatch(result);
@@ -71,6 +86,43 @@ export var changeLanguage = function changeLanguage(editorAnalyticsAPI) {
71
86
  };
72
87
  };
73
88
  };
89
+
90
+ /** Queue auto-detection for selected code block. */
91
+ export var detectLanguage = function detectLanguage() {
92
+ return function (state, dispatch) {
93
+ var _pluginKey$getState2;
94
+ var pos = (_pluginKey$getState2 = pluginKey.getState(state)) === null || _pluginKey$getState2 === void 0 ? void 0 : _pluginKey$getState2.pos;
95
+ if (typeof pos !== 'number') {
96
+ return false;
97
+ }
98
+ var node = state.doc.nodeAt(pos);
99
+ if (!node) {
100
+ return false;
101
+ }
102
+ var localId = getLocalId(node);
103
+ if (!localId) {
104
+ return false;
105
+ }
106
+ var autoDetectState = autoDetectPluginKey.getState(state);
107
+ var previousEntry = autoDetectState === null || autoDetectState === void 0 ? void 0 : autoDetectState.languageDetectionMap[localId];
108
+ var entry = createAutoDetectEntry(node, pos, hasEnoughTextForAutoDetection(node.textContent), previousEntry);
109
+ var tr = state.tr.setNodeMarkup(pos, state.schema.nodes.codeBlock, _objectSpread(_objectSpread({}, node.attrs), {}, {
110
+ language: null
111
+ })).setMeta(autoDetectPluginKey, {
112
+ type: ACTIONS.SET_AUTO_DETECT_ENTRY,
113
+ data: {
114
+ localId: localId,
115
+ entry: entry
116
+ }
117
+ }).setMeta('scrollIntoView', false);
118
+ var selection = isNodeSelection(state.selection) ? NodeSelection.create(tr.doc, pos) : tr.selection;
119
+ var result = tr.setSelection(selection);
120
+ if (dispatch) {
121
+ dispatch(result);
122
+ }
123
+ return true;
124
+ };
125
+ };
74
126
  export var copyContentToClipboardWithAnalytics = function copyContentToClipboardWithAnalytics(editorAnalyticsAPI) {
75
127
  return function (state, dispatch) {
76
128
  var nodes = state.schema.nodes,
@@ -1,5 +1,7 @@
1
1
  export var ACTIONS = {
2
2
  SET_COPIED_TO_CLIPBOARD: 'SET_COPIED_TO_CLIPBOARD',
3
3
  SET_SHOULD_IGNORE_FOLLOWING_MUTATIONS: 'SET_SHOULD_IGNORE_FOLLOWING_MUTATIONS',
4
- SET_IS_WRAPPED: 'SET_IS_WRAPPED'
4
+ SET_IS_WRAPPED: 'SET_IS_WRAPPED',
5
+ SET_AUTO_DETECT_ENTRY: 'SET_AUTO_DETECT_ENTRY',
6
+ REMOVE_AUTO_DETECT_ENTRY: 'REMOVE_AUTO_DETECT_ENTRY'
5
7
  };
@@ -0,0 +1,5 @@
1
+ import { PluginKey } from '@atlaskit/editor-prosemirror/state';
2
+ export var autoDetectPluginKey = new PluginKey('codeBlockAutoDetectPlugin');
3
+ export var getAutoDetectPluginState = function getAutoDetectPluginState(state) {
4
+ return autoDetectPluginKey.getState(state);
5
+ };
@@ -0,0 +1,50 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ 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; }
3
+ 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; }
4
+ import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
5
+ import { removeAutoDetection, updateAutoDetectState } from '../utils/auto-detect-state';
6
+ import { syncPendingDetectionTimers } from '../utils/auto-detect-view';
7
+ import { ACTIONS } from './actions';
8
+ import { autoDetectPluginKey } from './auto-detect-state';
9
+ export var createAutoDetectPlugin = function createAutoDetectPlugin(api) {
10
+ return new SafePlugin({
11
+ key: autoDetectPluginKey,
12
+ state: {
13
+ init: function init() {
14
+ return {
15
+ languageDetectionMap: {}
16
+ };
17
+ },
18
+ apply: function apply(tr, pluginState) {
19
+ var meta = tr.getMeta(autoDetectPluginKey);
20
+ var languageDetectionMap = tr.docChanged ? updateAutoDetectState(tr, pluginState) : pluginState.languageDetectionMap;
21
+ if ((meta === null || meta === void 0 ? void 0 : meta.type) === ACTIONS.SET_AUTO_DETECT_ENTRY) {
22
+ languageDetectionMap = _objectSpread(_objectSpread({}, languageDetectionMap), {}, _defineProperty({}, meta.data.localId, meta.data.entry));
23
+ } else if ((meta === null || meta === void 0 ? void 0 : meta.type) === ACTIONS.REMOVE_AUTO_DETECT_ENTRY) {
24
+ languageDetectionMap = removeAutoDetection(languageDetectionMap, meta.data.localId);
25
+ }
26
+ if (languageDetectionMap === pluginState.languageDetectionMap) {
27
+ return pluginState;
28
+ }
29
+ return {
30
+ languageDetectionMap: languageDetectionMap
31
+ };
32
+ }
33
+ },
34
+ view: function view(_view) {
35
+ var timers = new Map();
36
+ return {
37
+ update: function update() {
38
+ syncPendingDetectionTimers(_view, timers, api);
39
+ },
40
+ destroy: function destroy() {
41
+ timers.forEach(function (_ref) {
42
+ var timer = _ref.timer;
43
+ return clearTimeout(timer);
44
+ });
45
+ timers.clear();
46
+ }
47
+ };
48
+ }
49
+ });
50
+ };
@@ -102,11 +102,11 @@ export var createPlugin = function createPlugin(_ref) {
102
102
  }
103
103
  if (tr.docChanged) {
104
104
  var _node = findCodeBlock(newState, tr.selection);
105
+ var codeBlockNodes = getAllChangedCodeBlocksInTransaction(tr);
105
106
 
106
107
  // Updates mapping position of all existing decorations to new positions
107
108
  // specifically used for updating word wrap node decorators (does not cover drag & drop, validateWordWrappedDecorators does).
108
109
  var updatedDecorationSet = pluginState.decorations.map(tr.mapping, tr.doc);
109
- var codeBlockNodes = getAllChangedCodeBlocksInTransaction(tr);
110
110
  if (codeBlockNodes) {
111
111
  updateCodeBlockWrappedStateNodeKeys(codeBlockNodes, _oldState);
112
112
  // Disabled when using advanced code block for performance reasons
@@ -17,9 +17,38 @@ import { changeLanguage, copyContentToClipboardWithAnalytics, removeCodeBlockWit
17
17
  import { CodeBlockLanguagePicker } from '../ui/CodeBlockLanguagePicker';
18
18
  import { WrapIcon } from '../ui/icons/WrapIcon';
19
19
  import { NONE_LANGUAGE_VALUE, PLAIN_TEXT_LANGUAGE_VALUE } from '../ui/language-picker-options';
20
+ import { autoDetectPluginKey } from './auto-detect-state';
20
21
  import { provideVisualFeedbackForCopyButton, removeVisualFeedbackForCopyButton } from './codeBlockCopySelectionPlugin';
21
22
  import { createLanguageList, DEFAULT_LANGUAGES, getLanguageIdentifier } from './language-list';
22
23
  import { pluginKey } from './plugin-key';
24
+ var getAutoDetectPickerValue = function getAutoDetectPickerValue(_ref) {
25
+ var autoDetectEntry = _ref.autoDetectEntry,
26
+ formatMessage = _ref.formatMessage,
27
+ language = _ref.language,
28
+ languagePickerOptions = _ref.languagePickerOptions;
29
+ var defaultPickerValue = language ? languagePickerOptions.find(function (option) {
30
+ return language === NONE_LANGUAGE_VALUE ? option.value === PLAIN_TEXT_LANGUAGE_VALUE : option.value === language || option.alias.includes(language);
31
+ }) : undefined;
32
+
33
+ // A weak re-detection records noneDetected but can leave a previously auto-detected
34
+ // language on the node. Keep showing "(detected)" only while that preserved language
35
+ // still matches the node language, so manual language changes do not inherit the label.
36
+ if (defaultPickerValue && ((autoDetectEntry === null || autoDetectEntry === void 0 ? void 0 : autoDetectEntry.detectionResult) === 'detected' || (autoDetectEntry === null || autoDetectEntry === void 0 ? void 0 : autoDetectEntry.detectionResult) === 'noneDetected' && autoDetectEntry.autoDetectedLanguage === language)) {
37
+ return _objectSpread(_objectSpread({}, defaultPickerValue), {}, {
38
+ label: formatMessage(codeBlockButtonMessages.detectedLanguage, {
39
+ language: defaultPickerValue.label
40
+ })
41
+ });
42
+ }
43
+ if ((autoDetectEntry === null || autoDetectEntry === void 0 ? void 0 : autoDetectEntry.detectionResult) === 'noneDetected' && !language) {
44
+ return {
45
+ alias: [],
46
+ label: formatMessage(codeBlockButtonMessages.noneDetected),
47
+ value: NONE_LANGUAGE_VALUE
48
+ };
49
+ }
50
+ return defaultPickerValue;
51
+ };
23
52
  export var getToolbarConfig = function getToolbarConfig() {
24
53
  var allowCopyToClipboard = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
25
54
  var api = arguments.length > 1 ? arguments[1] : undefined;
@@ -36,12 +65,12 @@ export var getToolbarConfig = function getToolbarConfig() {
36
65
  alias: lang.alias
37
66
  };
38
67
  });
39
- return function (state, _ref) {
40
- var _api$editorViewMode, _api$decorations$acti, _api$decorations, _api$analytics, _codeBlockState$pos, _node$attrs;
41
- var formatMessage = _ref.formatMessage;
68
+ return function (state, _ref2) {
69
+ var _api$editorViewMode, _api$decorations$acti, _api$decorations, _api$analytics, _codeBlockState$pos, _node$attrs, _node$attrs2;
70
+ var formatMessage = _ref2.formatMessage;
42
71
  var isViewMode = (api === null || api === void 0 || (_api$editorViewMode = api.editorViewMode) === null || _api$editorViewMode === void 0 || (_api$editorViewMode = _api$editorViewMode.sharedState.currentState()) === null || _api$editorViewMode === void 0 ? void 0 : _api$editorViewMode.mode) === 'view';
43
- var _ref2 = (_api$decorations$acti = api === null || api === void 0 || (_api$decorations = api.decorations) === null || _api$decorations === void 0 ? void 0 : _api$decorations.actions) !== null && _api$decorations$acti !== void 0 ? _api$decorations$acti : {},
44
- hoverDecoration = _ref2.hoverDecoration;
72
+ var _ref3 = (_api$decorations$acti = api === null || api === void 0 || (_api$decorations = api.decorations) === null || _api$decorations === void 0 ? void 0 : _api$decorations.actions) !== null && _api$decorations$acti !== void 0 ? _api$decorations$acti : {},
73
+ hoverDecoration = _ref3.hoverDecoration;
45
74
  var editorAnalyticsAPI = api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions;
46
75
  var codeBlockState = pluginKey.getState(state);
47
76
  var pos = (_codeBlockState$pos = codeBlockState === null || codeBlockState === void 0 ? void 0 : codeBlockState.pos) !== null && _codeBlockState$pos !== void 0 ? _codeBlockState$pos : null;
@@ -56,6 +85,10 @@ export var getToolbarConfig = function getToolbarConfig() {
56
85
  var isWrapped = isCodeBlockWordWrapEnabled(node);
57
86
  var areLineNumbersVisible = areCodeBlockLineNumbersVisible(node);
58
87
  var language = node === null || node === void 0 || (_node$attrs = node.attrs) === null || _node$attrs === void 0 ? void 0 : _node$attrs.language;
88
+ var localId = node === null || node === void 0 || (_node$attrs2 = node.attrs) === null || _node$attrs2 === void 0 ? void 0 : _node$attrs2.localId;
89
+ var autoDetectState = autoDetectPluginKey.getState(state);
90
+ var autoDetectEntry = expValEquals('platform_editor_code_block_auto_detection', 'isEnabled', true) && typeof localId === 'string' ? autoDetectState === null || autoDetectState === void 0 ? void 0 : autoDetectState.languageDetectionMap[localId] : undefined;
91
+
59
92
  // Keep fresh option objects for the legacy toolbar select so reopening it
60
93
  // continues to start from the top rather than preserving the previously
61
94
  // focused option by reference.
@@ -81,9 +114,12 @@ export var getToolbarConfig = function getToolbarConfig() {
81
114
  };
82
115
  var languagePicker;
83
116
  if (expValEquals('platform_editor_code_block_q4_lovability', 'isEnabled', true) && fg('platform_editor_code_block_add_line_number_button')) {
84
- var defaultPickerValue = language ? languagePickerOptions.find(function (option) {
85
- return language === NONE_LANGUAGE_VALUE ? option.value === PLAIN_TEXT_LANGUAGE_VALUE : option.value === language || option.alias.includes(language);
86
- }) : undefined;
117
+ var autoDetectPickerValue = getAutoDetectPickerValue({
118
+ autoDetectEntry: autoDetectEntry,
119
+ formatMessage: formatMessage,
120
+ language: language,
121
+ languagePickerOptions: languagePickerOptions
122
+ });
87
123
  languagePicker = {
88
124
  type: 'custom',
89
125
  fallback: [],
@@ -93,7 +129,7 @@ export var getToolbarConfig = function getToolbarConfig() {
93
129
  }
94
130
  return /*#__PURE__*/React.createElement(CodeBlockLanguagePicker, {
95
131
  api: api,
96
- defaultValue: defaultPickerValue,
132
+ defaultValue: autoDetectPickerValue,
97
133
  editorView: view,
98
134
  filterOption: languageListFilter,
99
135
  formatMessage: formatMessage,
@@ -225,8 +261,8 @@ export var getToolbarConfig = function getToolbarConfig() {
225
261
  export var languageListFilter = function languageListFilter(option, rawInput) {
226
262
  // Ignored via go/ees005
227
263
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
228
- var _ref3 = option,
229
- data = _ref3.data;
264
+ var _ref4 = option,
265
+ data = _ref4.data;
230
266
  var searchString = rawInput.toLowerCase();
231
267
  return data.label.toLowerCase().includes(searchString) || data.alias.some(function (alias) {
232
268
  return alias.toLowerCase() === searchString;
@@ -34,8 +34,5 @@ export function getAllChangedCodeBlocksInTransaction(tr) {
34
34
  });
35
35
  });
36
36
  });
37
- if (changedCodeBlocks.length < 1) {
38
- return null;
39
- }
40
37
  return changedCodeBlocks;
41
38
  }
@@ -1,8 +1,12 @@
1
1
  import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
2
2
  import React, { useCallback, useState } from 'react';
3
+ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
4
+ import { changeLanguage, detectLanguage } from '../editor-commands';
5
+ import { DETECT_LANGUAGE_VALUE } from './language-picker-options';
3
6
  import { LanguagePicker } from './LanguagePicker';
4
7
  import { getRecentLanguages, saveRecentLanguage } from './recent-languages';
5
8
  export var CodeBlockLanguagePicker = function CodeBlockLanguagePicker(_ref) {
9
+ var _api$analytics2;
6
10
  var api = _ref.api,
7
11
  defaultValue = _ref.defaultValue,
8
12
  editorView = _ref.editorView,
@@ -18,19 +22,22 @@ export var CodeBlockLanguagePicker = function CodeBlockLanguagePicker(_ref) {
18
22
  var refreshRecentLanguages = useCallback(function () {
19
23
  setRecentLanguageValues(getRecentLanguages());
20
24
  }, []);
21
- var handleLanguageSelect = useCallback(function (language) {
22
- saveRecentLanguage(language);
23
- setRecentLanguageValues(getRecentLanguages());
24
- }, []);
25
+ var handleSelection = useCallback(function (option, selectionSource) {
26
+ var _api$analytics;
27
+ var command = option.value === DETECT_LANGUAGE_VALUE && expValEquals('platform_editor_code_block_auto_detection', 'isEnabled', true) ? detectLanguage() : changeLanguage(api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions)(option.value, selectionSource);
28
+ var commandSucceeded = command(editorView.state, editorView.dispatch);
29
+ if (commandSucceeded && option.value !== DETECT_LANGUAGE_VALUE) {
30
+ saveRecentLanguage(option.value);
31
+ setRecentLanguageValues(getRecentLanguages());
32
+ }
33
+ }, [api === null || api === void 0 || (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 ? void 0 : _api$analytics2.actions, editorView]);
25
34
  return /*#__PURE__*/React.createElement(LanguagePicker, {
26
- api: api,
27
35
  defaultValue: defaultValue,
28
- editorView: editorView,
29
36
  filterOption: filterOption,
30
37
  formatMessage: formatMessage,
31
38
  languagePickerOptions: languagePickerOptions,
32
39
  recentLanguageValues: recentLanguageValues,
33
- onLanguageSelect: handleLanguageSelect,
34
- onMenuOpen: refreshRecentLanguages
40
+ onMenuOpen: refreshRecentLanguages,
41
+ onSelection: handleSelection
35
42
  });
36
43
  };
@@ -12,7 +12,6 @@ import { akEditorLineHeight } from '@atlaskit/editor-shared-styles';
12
12
  import ChevronDownIcon from '@atlaskit/icon/core/chevron-down';
13
13
  import { Box } from '@atlaskit/primitives/compiled';
14
14
  import { PopupSelect, components } from '@atlaskit/select';
15
- import { changeLanguage } from '../editor-commands';
16
15
  import { createGroupedLanguageOptions } from './language-picker-options';
17
16
  var pickerOptionStyles = null;
18
17
  var styles = {
@@ -72,18 +71,15 @@ var getRecentlyUsedLanguages = function getRecentlyUsedLanguages(recentLanguageV
72
71
  return recentlyUsedLanguages;
73
72
  };
74
73
  export var LanguagePicker = function LanguagePicker(_ref) {
75
- var _api$analytics, _defaultValue$label;
76
- var api = _ref.api,
77
- defaultValue = _ref.defaultValue,
78
- editorView = _ref.editorView,
74
+ var _defaultValue$label;
75
+ var defaultValue = _ref.defaultValue,
79
76
  filterOption = _ref.filterOption,
80
77
  formatMessage = _ref.formatMessage,
81
78
  languagePickerOptions = _ref.languagePickerOptions,
82
79
  _ref$recentLanguageVa = _ref.recentLanguageValues,
83
80
  recentLanguageValues = _ref$recentLanguageVa === void 0 ? [] : _ref$recentLanguageVa,
84
- onLanguageSelect = _ref.onLanguageSelect,
85
- onMenuOpen = _ref.onMenuOpen;
86
- var editorAnalyticsAPI = api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions;
81
+ onMenuOpen = _ref.onMenuOpen,
82
+ onSelection = _ref.onSelection;
87
83
  var label = (_defaultValue$label = defaultValue === null || defaultValue === void 0 ? void 0 : defaultValue.label) !== null && _defaultValue$label !== void 0 ? _defaultValue$label : formatMessage(codeBlockButtonMessages.selectLanguage);
88
84
  var selectLanguageLabel = formatMessage(codeBlockButtonMessages.selectLanguage);
89
85
  var _useState = useState(false),
@@ -118,11 +114,8 @@ export var LanguagePicker = function LanguagePicker(_ref) {
118
114
  }
119
115
  var isSearchSelection = inputValueRef.current.trim().length > 0;
120
116
  var selectionSource = isSearchSelection ? 'search' : (_option$selectionSour = option.selectionSource) !== null && _option$selectionSour !== void 0 ? _option$selectionSour : 'all';
121
- var commandSucceeded = changeLanguage(editorAnalyticsAPI)(option.value, selectionSource)(editorView.state, editorView.dispatch);
122
- if (commandSucceeded) {
123
- onLanguageSelect === null || onLanguageSelect === void 0 || onLanguageSelect(option.value);
124
- }
125
- }, [editorAnalyticsAPI, editorView, onLanguageSelect]);
117
+ onSelection(option, selectionSource);
118
+ }, [onSelection]);
126
119
  var handleInputChange = useCallback(function (newInputValue, actionMeta) {
127
120
  // React-select clears the input as part of selecting a value before onChange fires.
128
121
  // Keep the last user-typed query so handleChange can report search selections correctly.
@@ -147,10 +140,9 @@ export var LanguagePicker = function LanguagePicker(_ref) {
147
140
  appearance: "subtle",
148
141
  isSelected: isOpen,
149
142
  "aria-controls": ariaControls,
150
- "aria-label": selectLanguageLabel,
151
143
  testId: "code-block-language-picker-trigger"
152
144
  }, label));
153
- }, [label, selectLanguageLabel]);
145
+ }, [label]);
154
146
  return /*#__PURE__*/React.createElement(PopupSelect, {
155
147
  components: popupSelectComponents,
156
148
  filterOption: filterOption,
@@ -3,6 +3,7 @@ import _defineProperty from "@babel/runtime/helpers/defineProperty";
3
3
  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; }
4
4
  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; }
5
5
  import { codeBlockButtonMessages } from '@atlaskit/editor-common/messages';
6
+ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
6
7
  export var NONE_LANGUAGE_VALUE = 'none';
7
8
  export var DETECT_LANGUAGE_VALUE = 'autodetect';
8
9
  export var PLAIN_TEXT_LANGUAGE_VALUE = 'text';
@@ -28,7 +29,7 @@ export var createGroupedLanguageOptions = function createGroupedLanguageOptions(
28
29
  var plainTextOption = languages.find(function (language) {
29
30
  return language.value === PLAIN_TEXT_LANGUAGE_VALUE;
30
31
  });
31
- var pinnedOptions = [getDetectLanguageOption(formatMessage)];
32
+ var pinnedOptions = expValEquals('platform_editor_code_block_auto_detection', 'isEnabled', true) ? [getDetectLanguageOption(formatMessage)] : [];
32
33
  if (plainTextOption) {
33
34
  pinnedOptions.push(_objectSpread(_objectSpread({}, plainTextOption), {}, {
34
35
  selectionSource: 'pinned'
@@ -0,0 +1,178 @@
1
+ import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
2
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
3
+ 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; }
4
+ 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; }
5
+ import { getInsertedCodeBlocksInTransaction } from '@atlaskit/editor-common/code-block';
6
+ import { getAllChangedCodeBlocksInTransaction } from '../pm-plugins/utils';
7
+ var MIN_AUTO_DETECT_TEXT_LENGTH = 20;
8
+ export var shouldTriggerLargeChangeDetection = function shouldTriggerLargeChangeDetection(lastObservedText, text) {
9
+ if (!lastObservedText) {
10
+ return text.length > 0;
11
+ }
12
+ return Math.abs(text.length - lastObservedText.length) > lastObservedText.length / 2;
13
+ };
14
+ export var getFirstLine = function getFirstLine(text) {
15
+ var _text$split$;
16
+ return (_text$split$ = text.split('\n')[0]) !== null && _text$split$ !== void 0 ? _text$split$ : '';
17
+ };
18
+ export var hasEnoughTextForAutoDetection = function hasEnoughTextForAutoDetection(text) {
19
+ return text.trim().length >= MIN_AUTO_DETECT_TEXT_LENGTH;
20
+ };
21
+ export var getLocalId = function getLocalId(node) {
22
+ return typeof node.attrs.localId === 'string' ? node.attrs.localId : null;
23
+ };
24
+ export var createAutoDetectEntry = function createAutoDetectEntry(node, pos, isPending, previous) {
25
+ return {
26
+ lastObservedText: node.textContent,
27
+ lastObservedFirstLine: getFirstLine(node.textContent),
28
+ isPending: isPending,
29
+ detectionResult: previous === null || previous === void 0 ? void 0 : previous.detectionResult,
30
+ autoDetectedLanguage: previous === null || previous === void 0 ? void 0 : previous.autoDetectedLanguage,
31
+ pos: pos
32
+ };
33
+ };
34
+ export var queueAutoDetection = function queueAutoDetection(languageDetectionMap, node, pos, isPending) {
35
+ var localId = getLocalId(node);
36
+ if (!localId) {
37
+ return languageDetectionMap;
38
+ }
39
+ return _objectSpread(_objectSpread({}, languageDetectionMap), {}, _defineProperty({}, localId, createAutoDetectEntry(node, pos, isPending, languageDetectionMap[localId])));
40
+ };
41
+ export var removeAutoDetection = function removeAutoDetection(languageDetectionMap, localId) {
42
+ if (!languageDetectionMap[localId]) {
43
+ return languageDetectionMap;
44
+ }
45
+ var nextLanguageDetectionMap = _objectSpread({}, languageDetectionMap);
46
+ delete nextLanguageDetectionMap[localId];
47
+ return nextLanguageDetectionMap;
48
+ };
49
+ var getCodeBlockLocalIdsRemovedFromChangedRanges = function getCodeBlockLocalIdsRemovedFromChangedRanges(tr, codeBlockType) {
50
+ var localIds = new Set();
51
+ tr.steps.forEach(function (step, stepIndex) {
52
+ var docAtStep = tr.docs[stepIndex];
53
+ step.getMap().forEach(function (oldStart, oldEnd) {
54
+ if (oldStart === oldEnd) {
55
+ return;
56
+ }
57
+ var clampedOldEnd = Math.min(oldEnd, docAtStep.content.size);
58
+ docAtStep.nodesBetween(oldStart, clampedOldEnd, function (node, pos) {
59
+ if (node.type !== codeBlockType) {
60
+ return true;
61
+ }
62
+ var isWholeCodeBlockRemoved = pos >= oldStart && pos + node.nodeSize <= clampedOldEnd;
63
+ if (!isWholeCodeBlockRemoved) {
64
+ return false;
65
+ }
66
+ var localId = getLocalId(node);
67
+ if (localId) {
68
+ localIds.add(localId);
69
+ }
70
+ return false;
71
+ });
72
+ });
73
+ });
74
+ return localIds;
75
+ };
76
+ var getCodeBlockTransactionChanges = function getCodeBlockTransactionChanges(tr, codeBlockType) {
77
+ var insertedNodesWithPos = getInsertedCodeBlocksInTransaction(tr, codeBlockType);
78
+ var removedFromChangedRangesLocalIds = getCodeBlockLocalIdsRemovedFromChangedRanges(tr, codeBlockType);
79
+ var insertedLocalIds = new Set();
80
+ var insertedCodeBlocks = [];
81
+ insertedNodesWithPos.forEach(function (_ref) {
82
+ var node = _ref.node,
83
+ pos = _ref.pos;
84
+ var localId = getLocalId(node);
85
+ if (!localId) {
86
+ return;
87
+ }
88
+ insertedLocalIds.add(localId);
89
+ if (!removedFromChangedRangesLocalIds.has(localId)) {
90
+ insertedCodeBlocks.push({
91
+ localId: localId,
92
+ node: node,
93
+ pos: pos
94
+ });
95
+ }
96
+ });
97
+ var deletedLocalIds = new Set();
98
+ removedFromChangedRangesLocalIds.forEach(function (localId) {
99
+ if (!insertedLocalIds.has(localId)) {
100
+ deletedLocalIds.add(localId);
101
+ }
102
+ });
103
+ return {
104
+ deletedLocalIds: deletedLocalIds,
105
+ insertedCodeBlocks: insertedCodeBlocks
106
+ };
107
+ };
108
+ export var updateAutoDetectState = function updateAutoDetectState(tr, pluginState) {
109
+ var codeBlock = tr.doc.type.schema.nodes.codeBlock;
110
+ var isPaste = tr.getMeta('paste') === true || tr.getMeta('uiEvent') === 'paste';
111
+ var isExternalContentChange = tr.getMeta('replaceDocument') === true || tr.getMeta('isRemote') === true;
112
+ var hasTrackedEntries = Object.keys(pluginState.languageDetectionMap).length > 0;
113
+
114
+ // Page loads and remote edits should not start auto-detection for code blocks.
115
+ if (!hasTrackedEntries && isExternalContentChange) {
116
+ return pluginState.languageDetectionMap;
117
+ }
118
+
119
+ // Existing entries still need mapping/deletion cleanup, but external edits should not refresh text.
120
+ var changedCodeBlockNodes = isExternalContentChange ? [] : getAllChangedCodeBlocksInTransaction(tr);
121
+ if (!hasTrackedEntries && !changedCodeBlockNodes.length) {
122
+ return pluginState.languageDetectionMap;
123
+ }
124
+ var _getCodeBlockTransact = getCodeBlockTransactionChanges(tr, codeBlock),
125
+ deletedLocalIds = _getCodeBlockTransact.deletedLocalIds,
126
+ insertedCodeBlocks = _getCodeBlockTransact.insertedCodeBlocks;
127
+ var languageDetectionMap = hasTrackedEntries ? Object.fromEntries(Object.entries(pluginState.languageDetectionMap).map(function (_ref2) {
128
+ var _ref3 = _slicedToArray(_ref2, 2),
129
+ localId = _ref3[0],
130
+ entry = _ref3[1];
131
+ return [localId, _objectSpread(_objectSpread({}, entry), {}, {
132
+ pos: tr.mapping.map(entry.pos)
133
+ })];
134
+ })) : pluginState.languageDetectionMap;
135
+ if (!isExternalContentChange) {
136
+ insertedCodeBlocks.forEach(function (_ref4) {
137
+ var localId = _ref4.localId,
138
+ node = _ref4.node,
139
+ pos = _ref4.pos;
140
+ if (node.attrs.language) {
141
+ languageDetectionMap = removeAutoDetection(languageDetectionMap, localId);
142
+ return;
143
+ }
144
+ languageDetectionMap = queueAutoDetection(languageDetectionMap, node, pos, hasEnoughTextForAutoDetection(node.textContent));
145
+ });
146
+ changedCodeBlockNodes.forEach(function (_ref5) {
147
+ var node = _ref5.node,
148
+ pos = _ref5.pos;
149
+ var localId = getLocalId(node);
150
+ if (!localId || !languageDetectionMap[localId]) {
151
+ return;
152
+ }
153
+ var currentLanguage = node.attrs.language;
154
+ var previousEntry = languageDetectionMap[localId];
155
+ if (currentLanguage && currentLanguage !== previousEntry.autoDetectedLanguage) {
156
+ languageDetectionMap = removeAutoDetection(languageDetectionMap, localId);
157
+ return;
158
+ }
159
+ var text = node.textContent;
160
+ var firstLine = getFirstLine(text);
161
+ var shouldTriggerDetection = previousEntry.isPending || isPaste || firstLine !== previousEntry.lastObservedFirstLine || shouldTriggerLargeChangeDetection(previousEntry.lastObservedText, text);
162
+ var isPending = hasEnoughTextForAutoDetection(text) && shouldTriggerDetection;
163
+
164
+ // Only pending detection refreshes the text snapshot; otherwise gradual typing
165
+ // should continue comparing against the last attempted detection.
166
+ languageDetectionMap = isPending ? queueAutoDetection(languageDetectionMap, node, pos, true) : _objectSpread(_objectSpread({}, languageDetectionMap), {}, _defineProperty({}, localId, _objectSpread(_objectSpread({}, previousEntry), {}, {
167
+ isPending: false,
168
+ pos: pos
169
+ })));
170
+ });
171
+ }
172
+ deletedLocalIds.forEach(function (localId) {
173
+ if (languageDetectionMap[localId]) {
174
+ languageDetectionMap = removeAutoDetection(languageDetectionMap, localId);
175
+ }
176
+ });
177
+ return languageDetectionMap;
178
+ };