@atlaskit/editor-plugin-code-block 14.0.6 → 14.1.1

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 (32) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/cjs/codeBlockPlugin.js +2 -3
  3. package/dist/cjs/editor-commands/index.js +3 -0
  4. package/dist/cjs/pm-plugins/main.js +3 -2
  5. package/dist/cjs/pm-plugins/toolbar.js +6 -4
  6. package/dist/cjs/ui/CodeBlockLanguagePicker.js +13 -3
  7. package/dist/cjs/ui/CodeBlockMenuItem.js +7 -2
  8. package/dist/cjs/ui/LanguagePicker.js +72 -12
  9. package/dist/cjs/utils/auto-detect-view.js +21 -11
  10. package/dist/cjs/utils/language-detect.js +5 -1
  11. package/dist/es2019/codeBlockPlugin.js +2 -3
  12. package/dist/es2019/editor-commands/index.js +3 -0
  13. package/dist/es2019/pm-plugins/main.js +3 -2
  14. package/dist/es2019/pm-plugins/toolbar.js +6 -4
  15. package/dist/es2019/ui/CodeBlockLanguagePicker.js +13 -3
  16. package/dist/es2019/ui/CodeBlockMenuItem.js +8 -3
  17. package/dist/es2019/ui/LanguagePicker.js +61 -7
  18. package/dist/es2019/utils/auto-detect-view.js +22 -11
  19. package/dist/es2019/utils/language-detect.js +3 -2
  20. package/dist/esm/codeBlockPlugin.js +2 -3
  21. package/dist/esm/editor-commands/index.js +3 -0
  22. package/dist/esm/pm-plugins/main.js +3 -2
  23. package/dist/esm/pm-plugins/toolbar.js +6 -4
  24. package/dist/esm/ui/CodeBlockLanguagePicker.js +13 -3
  25. package/dist/esm/ui/CodeBlockMenuItem.js +7 -2
  26. package/dist/esm/ui/LanguagePicker.js +72 -12
  27. package/dist/esm/utils/auto-detect-view.js +21 -11
  28. package/dist/esm/utils/language-detect.js +5 -2
  29. package/dist/types/ui/CodeBlockLanguagePicker.d.ts +1 -1
  30. package/dist/types/ui/CodeBlockMenuItem.d.ts +2 -1
  31. package/dist/types/ui/LanguagePicker.d.ts +2 -1
  32. package/package.json +11 -8
@@ -6,7 +6,8 @@ import { ToolbarDropdownItem } from '@atlaskit/editor-toolbar';
6
6
  import AngleBracketsIcon from '@atlaskit/icon/core/angle-brackets';
7
7
  const NODE_NAME = 'codeBlock';
8
8
  const CodeBlockMenuItem = ({
9
- api
9
+ api,
10
+ isSuggested
10
11
  }) => {
11
12
  const {
12
13
  formatMessage
@@ -20,6 +21,7 @@ const CodeBlockMenuItem = ({
20
21
  var _api$blockMenu;
21
22
  const command = api === null || api === void 0 ? void 0 : (_api$blockMenu = api.blockMenu) === null || _api$blockMenu === void 0 ? void 0 : _api$blockMenu.commands.transformNode(tr.doc.type.schema.nodes.codeBlock, {
22
23
  inputMethod,
24
+ isSuggested,
23
25
  triggeredFrom,
24
26
  targetTypeName: NODE_NAME
25
27
  });
@@ -37,7 +39,10 @@ const CodeBlockMenuItem = ({
37
39
  }, formatMessage(blockMenuMessages.codeBlock));
38
40
  };
39
41
  export const createCodeBlockMenuItem = api => {
40
- return () => /*#__PURE__*/React.createElement(CodeBlockMenuItem, {
41
- api: api
42
+ return ({
43
+ isSuggested
44
+ } = {}) => /*#__PURE__*/React.createElement(CodeBlockMenuItem, {
45
+ api: api,
46
+ isSuggested: isSuggested
42
47
  });
43
48
  };
@@ -6,6 +6,7 @@ import Button from '@atlaskit/button/new';
6
6
  import { codeBlockButtonMessages } from '@atlaskit/editor-common/messages';
7
7
  import { akEditorLineHeight } from '@atlaskit/editor-shared-styles';
8
8
  import ChevronDownIcon from '@atlaskit/icon/core/chevron-down';
9
+ import { fg } from '@atlaskit/platform-feature-flags';
9
10
  import { Box } from '@atlaskit/primitives/compiled';
10
11
  import { PopupSelect, components } from '@atlaskit/select';
11
12
  import { createGroupedLanguageOptions } from './language-picker-options';
@@ -47,6 +48,11 @@ const menuPopperProps = {
47
48
  enabled: false
48
49
  }]
49
50
  };
51
+ const focusWithoutScrolling = element => {
52
+ element.focus({
53
+ preventScroll: true
54
+ });
55
+ };
50
56
  const getRecentlyUsedLanguages = (recentLanguageValues, optionsByValue) => {
51
57
  const recentlyUsedLanguages = [];
52
58
  for (const recentLanguageValue of recentLanguageValues) {
@@ -64,12 +70,14 @@ export const LanguagePicker = ({
64
70
  languagePickerOptions,
65
71
  recentLanguageValues = [],
66
72
  onMenuOpen,
67
- onSelection
73
+ onSelection,
74
+ triggerSpacing = 'default'
68
75
  }) => {
69
76
  var _defaultValue$label;
70
77
  const label = (_defaultValue$label = defaultValue === null || defaultValue === void 0 ? void 0 : defaultValue.label) !== null && _defaultValue$label !== void 0 ? _defaultValue$label : formatMessage(codeBlockButtonMessages.selectLanguage);
71
78
  const selectLanguageLabel = formatMessage(codeBlockButtonMessages.selectLanguage);
72
79
  const [hasSearchQuery, setHasSearchQuery] = useState(false);
80
+ const [lockedPopperPlacement, setLockedPopperPlacement] = useState();
73
81
  const inputValueRef = useRef('');
74
82
  const optionsByValue = useMemo(() => new Map(languagePickerOptions.map(option => [option.value, option])), [languagePickerOptions]);
75
83
  const recentlyUsedLanguages = useMemo(() => getRecentlyUsedLanguages(recentLanguageValues, optionsByValue), [recentLanguageValues, optionsByValue]);
@@ -79,6 +87,28 @@ export const LanguagePicker = ({
79
87
  recentlyUsedLanguages
80
88
  }), [formatMessage, languagePickerOptions, recentlyUsedLanguages]);
81
89
  const searchOptions = useMemo(() => options.flatMap(group => group.options), [options]);
90
+ const stableMenuPopperProps = useMemo(() => ({
91
+ // Allow Popper to choose top on the first open when bottom has no room, then lock the
92
+ // chosen placement so later hover/search renders do not move the open picker.
93
+ placement: lockedPopperPlacement !== null && lockedPopperPlacement !== void 0 ? lockedPopperPlacement : 'bottom-start',
94
+ modifiers: [{
95
+ name: 'offset',
96
+ options: {
97
+ offset: [0, 8]
98
+ }
99
+ }, {
100
+ name: 'preventOverflow',
101
+ enabled: false
102
+ }, ...(lockedPopperPlacement ? [{
103
+ name: 'flip',
104
+ enabled: false
105
+ }] : [])],
106
+ onFirstUpdate: ({
107
+ placement
108
+ }) => {
109
+ setLockedPopperPlacement(placement);
110
+ }
111
+ }), [lockedPopperPlacement]);
82
112
  const handleChange = useCallback(option => {
83
113
  var _option$selectionSour;
84
114
  if (!option) {
@@ -91,11 +121,33 @@ export const LanguagePicker = ({
91
121
  const handleInputChange = useCallback((newInputValue, actionMeta) => {
92
122
  // React-select clears the input as part of selecting a value before onChange fires.
93
123
  // Keep the last user-typed query so handleChange can report search selections correctly.
94
- if (!actionMeta || actionMeta.action === 'input-change') {
124
+ const isInputChange = !actionMeta || actionMeta.action === 'input-change';
125
+ if (isInputChange) {
95
126
  inputValueRef.current = newInputValue;
96
127
  }
97
- setHasSearchQuery(newInputValue.trim().length > 0);
98
- return newInputValue;
128
+ if (isInputChange || !fg('platform_editor_code_block_dogfooding_patch')) {
129
+ setHasSearchQuery(newInputValue.trim().length > 0);
130
+ return newInputValue;
131
+ }
132
+ return inputValueRef.current;
133
+ }, []);
134
+ const handleMenuOpen = useCallback(() => {
135
+ if (fg('platform_editor_code_block_dogfooding_patch')) {
136
+ inputValueRef.current = '';
137
+ setHasSearchQuery(false);
138
+ setLockedPopperPlacement(undefined);
139
+ }
140
+ onMenuOpen === null || onMenuOpen === void 0 ? void 0 : onMenuOpen();
141
+ }, [onMenuOpen]);
142
+ const handleTriggerMouseDown = useCallback(event => {
143
+ if (fg('platform_editor_code_block_dogfooding_patch')) {
144
+ // PopupSelect's FocusLock returns focus to the element that was focused before the
145
+ // picker opened. If that is the editor/code block, closing the picker can scroll the
146
+ // whole code block into view. Focus the trigger first without scrolling so FocusLock
147
+ // returns to the trigger; see CodeBlockLanguagePicker's handleSelection for restoring
148
+ // editor focus.
149
+ focusWithoutScrolling(event.currentTarget);
150
+ }
99
151
  }, []);
100
152
  const renderTarget = useCallback(({
101
153
  isOpen,
@@ -105,7 +157,9 @@ export const LanguagePicker = ({
105
157
  }) => /*#__PURE__*/React.createElement("div", {
106
158
  className: ax([styles.trigger])
107
159
  }, /*#__PURE__*/React.createElement(Button, {
160
+ spacing: triggerSpacing,
108
161
  shouldFitContainer: true,
162
+ onMouseDown: handleTriggerMouseDown,
109
163
  onKeyDown: onKeyDown,
110
164
  ref: ref,
111
165
  iconAfter: ChevronDownIcon,
@@ -113,7 +167,7 @@ export const LanguagePicker = ({
113
167
  isSelected: isOpen,
114
168
  "aria-controls": ariaControls,
115
169
  testId: "code-block-language-picker-trigger"
116
- }, label)), [label]);
170
+ }, label)), [label, triggerSpacing, handleTriggerMouseDown]);
117
171
  return /*#__PURE__*/React.createElement(PopupSelect, {
118
172
  components: popupSelectComponents,
119
173
  filterOption: filterOption,
@@ -123,9 +177,9 @@ export const LanguagePicker = ({
123
177
  menuPlacement: "auto",
124
178
  onChange: handleChange,
125
179
  onInputChange: handleInputChange,
126
- onMenuOpen: onMenuOpen,
180
+ onMenuOpen: handleMenuOpen,
127
181
  options: hasSearchQuery ? searchOptions : options,
128
- popperProps: menuPopperProps,
182
+ popperProps: fg('platform_editor_code_block_dogfooding_patch') ? stableMenuPopperProps : menuPopperProps,
129
183
  searchThreshold: -1,
130
184
  target: renderTarget,
131
185
  testId: "code-block-language-picker",
@@ -1,4 +1,5 @@
1
1
  import { ACTION, ACTION_SUBJECT, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
2
+ import { fg } from '@atlaskit/platform-feature-flags';
2
3
  import { ACTIONS } from '../pm-plugins/actions';
3
4
  import { autoDetectPluginKey } from '../pm-plugins/auto-detect-state';
4
5
  import { createAutoDetectEntry } from './auto-detect-state';
@@ -32,6 +33,7 @@ const runPendingDetection = (view, localId, api) => {
32
33
  }
33
34
  const detectedLanguage = detectLanguage(found.node.textContent);
34
35
  const detectionResult = detectedLanguage ? 'detected' : 'noneDetected';
36
+ const detectionPhase = entry.detectionResult ? 'redetection' : 'initial';
35
37
  // Keep a previous auto-detected language when the latest snippet is too weak to classify.
36
38
  const shouldPreserveAutoDetectedLanguage = !detectedLanguage && Boolean(entry.autoDetectedLanguage) && found.node.attrs.language === entry.autoDetectedLanguage;
37
39
  const nextEntry = {
@@ -42,11 +44,13 @@ const runPendingDetection = (view, localId, api) => {
42
44
 
43
45
  // If there is no confident detection, record the result without clearing user-visible language.
44
46
  const shouldOnlyUpdateDetectionState = !detectedLanguage && (!found.node.attrs.language || shouldPreserveAutoDetectedLanguage);
47
+ const hasDetectedLanguageChange = Boolean(detectedLanguage) && found.node.attrs.language !== detectedLanguage;
48
+ const shouldClearStaleLanguage = !detectedLanguage && Boolean(found.node.attrs.language) && !shouldPreserveAutoDetectedLanguage;
49
+ const shouldUpdateNodeLanguage = fg('platform_editor_code_block_dogfooding_patch') ? hasDetectedLanguageChange || shouldClearStaleLanguage : !shouldOnlyUpdateDetectionState;
45
50
  api === null || api === void 0 ? void 0 : (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute(({
46
51
  tr
47
52
  }) => {
48
- var _api$analytics;
49
- if (!shouldOnlyUpdateDetectionState) {
53
+ if (shouldUpdateNodeLanguage) {
50
54
  tr.setNodeMarkup(found.pos, undefined, {
51
55
  ...found.node.attrs,
52
56
  language: detectedLanguage
@@ -59,15 +63,22 @@ const runPendingDetection = (view, localId, api) => {
59
63
  entry: nextEntry
60
64
  }
61
65
  });
62
- api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions.attachAnalyticsEvent({
63
- action: ACTION.LANGUAGE_AUTO_DETECTED,
64
- actionSubject: ACTION_SUBJECT.CODE_BLOCK,
65
- attributes: {
66
- language: detectedLanguage !== null && detectedLanguage !== void 0 ? detectedLanguage : 'none',
67
- detectionResult
68
- },
69
- eventType: EVENT_TYPE.TRACK
70
- })(tr);
66
+ // When platform_editor_code_block_dogfooding_patch is cleaned up, merge this with the previous shouldUpdateNodeLanguage check.
67
+ if (!fg('platform_editor_code_block_dogfooding_patch') || shouldUpdateNodeLanguage) {
68
+ var _api$analytics;
69
+ api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions.attachAnalyticsEvent({
70
+ action: ACTION.LANGUAGE_AUTO_DETECTED,
71
+ actionSubject: ACTION_SUBJECT.CODE_BLOCK,
72
+ attributes: {
73
+ language: detectedLanguage !== null && detectedLanguage !== void 0 ? detectedLanguage : 'none',
74
+ detectionResult,
75
+ ...(fg('platform_editor_code_block_dogfooding_patch') ? {
76
+ detectionPhase
77
+ } : {})
78
+ },
79
+ eventType: EVENT_TYPE.TRACK
80
+ })(tr);
81
+ }
71
82
  // Language detection runs in the background and should not move the viewport.
72
83
  return tr.setMeta('scrollIntoView', false);
73
84
  });
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable require-unicode-regexp */
2
-
2
+ import { fg } from '@atlaskit/platform-feature-flags';
3
3
  // Conservative weighted-regex heuristic for common high-confidence snippets, not a full classifier.
4
4
  // Ambiguous snippets intentionally return null so users can select the language manually.
5
5
  const MAX_DETECTION_CHARS = 10_000;
@@ -12,6 +12,7 @@ const hasPattern = (code, pattern) => {
12
12
  return pattern.test(code);
13
13
  };
14
14
  const scorePatterns = (code, patterns) => patterns.reduce((score, [pattern, value]) => score + (hasPattern(code, pattern) ? value : 0), 0);
15
+ const scoreEnhancedTypeScriptPatterns = code => fg('platform_editor_code_block_dogfooding_patch') ? scorePatterns(code, [[/\b(?:private|protected|public|readonly)\s+[\w$]+\s*=/, 5], [/\([^)]{0,500}\b[\w$]+\??:\s*[A-Za-z_$][\w$<>{}\[\]|&,\s.?]{0,200}[^)]{0,500}\)\s*(?::\s*[A-Za-z_$][\w$<>{}\[\]|&,\s.?]{0,200})?\s*=>/, 5]]) : 0;
15
16
  const looksLikeHtmlTagPair = code => {
16
17
  const openTags = new Set();
17
18
  const tagPattern = /<\/?([a-z][a-z0-9-]*)\b[^<>]{0,500}>/gi;
@@ -57,7 +58,7 @@ const getLanguageScores = code => [{
57
58
  score: scorePatterns(code, [[/\bSELECT\b[\s\S]+\bFROM\b/i, 5], [/\b(?:INSERT\s+INTO|UPDATE\b[\s\S]+\bSET\b|DELETE\s+FROM|CREATE\s+TABLE|ALTER\s+TABLE)\b/i, 5], [/\b(?:JOIN|WHERE|GROUP\s+BY|ORDER\s+BY|LIMIT)\b/i, 2]])
58
59
  }, {
59
60
  language: 'typescript',
60
- score: scorePatterns(code, [[/\b(?:interface|type)\s+[A-Z_$][\w$]*(?:\s*[=<{])/, 5], [/\bimport\s+type\b|\bexport\s+type\b/, 4], [/\b(?:const|let|var)\s+[\w$]+\s*:\s*[A-Za-z_$][\w$<>|\[\],\s]*/, 4], [/\)\s*:\s*(?:Promise<)?[A-Za-z_$][\w$<>|\[\],\s]*(?:>|\s)?\s*=>?\s*[{;]/, 3], [/\b(?:as\s+const|implements\s+[A-Z_$]|enum\s+[A-Z_$])/, 3]])
61
+ score: scorePatterns(code, [[/\b(?:interface|type)\s+[A-Z_$][\w$]*(?:\s*[=<{])/, 5], [/\bimport\s+type\b|\bexport\s+type\b/, 4], [/\b(?:const|let|var)\s+[\w$]+\s*:\s*[A-Za-z_$][\w$<>|\[\],\s]*/, 4], [/\)\s*:\s*(?:Promise<)?[A-Za-z_$][\w$<>|\[\],\s]*(?:>|\s)?\s*=>?\s*[{;]/, 3], [/\b(?:as\s+const|implements\s+[A-Z_$]|enum\s+[A-Z_$])/, 3]]) + scoreEnhancedTypeScriptPatterns(code)
61
62
  }, {
62
63
  language: 'javascript',
63
64
  score: scorePatterns(code, [[/\b(?:const|let|var)\s+[\w$]+\s*=/, 3], [/\bfunction\s*[\w$]*\s*\([^)]*\)\s*\{/, 3], [/=>\s*(?:\{|[\w$'"`(])/, 3], [/\b(?:import|export)\s+(?:[\w${}*,\s]+\s+from\s+)?['"][^'"]+['"]/, 3], [/\b(?:console\.log|document\.|window\.|require\()|\bmodule\.exports\b/, 3]])
@@ -9,7 +9,6 @@ import { TRANSFORM_STRUCTURE_MENU_SECTION, TRANSFORM_STRUCTURE_CODE_BLOCK_MENU_I
9
9
  import { blockTypeMessages } from '@atlaskit/editor-common/messages';
10
10
  import { IconCode } from '@atlaskit/editor-common/quick-insert';
11
11
  import { fg } from '@atlaskit/platform-feature-flags';
12
- import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
13
12
  import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
14
13
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
15
14
  import { createInsertCodeBlockTransaction, insertCodeBlockWithAnalytics } from './editor-commands';
@@ -51,7 +50,7 @@ var codeBlockPlugin = function codeBlockPlugin(_ref) {
51
50
  nodes: function nodes() {
52
51
  return [{
53
52
  name: 'codeBlock',
54
- node: expValEquals('platform_editor_code_block_q4_lovability', 'isEnabled', true) ? codeBlock : fg('platform_editor_adf_with_localid') ? codeBlockWithLocalId : codeBlock
53
+ node: expValEqualsNoExposure('platform_editor_code_block_q4_lovability', 'isEnabled', true) ? codeBlock : fg('platform_editor_adf_with_localid') ? codeBlockWithLocalId : codeBlock
55
54
  }];
56
55
  },
57
56
  getSharedState: function getSharedState(state) {
@@ -83,7 +82,7 @@ var codeBlockPlugin = function codeBlockPlugin(_ref) {
83
82
  var schema = _ref3.schema;
84
83
  return createCodeBlockInputRule(schema, api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions);
85
84
  }
86
- }].concat(_toConsumableArray(expValEquals('platform_editor_code_block_q4_lovability', 'isEnabled', true) && fg('platform_editor_code_block_language_detection_flow') ? [{
85
+ }].concat(_toConsumableArray(expValEqualsNoExposure('platform_editor_code_block_q4_lovability', 'isEnabled', true) && fg('platform_editor_code_block_language_detection_flow') ? [{
87
86
  name: 'codeBlockAutoDetect',
88
87
  plugin: function plugin() {
89
88
  return createAutoDetectPlugin(api);
@@ -472,6 +472,9 @@ export var toggleWordWrapStateForCodeBlockNode = function toggleWordWrapStateFor
472
472
  tr.setNodeMarkup(codeBlock.pos, undefined, _objectSpread(_objectSpread({}, codeBlockNode.attrs), {}, {
473
473
  wrap: updatedToggleState
474
474
  }));
475
+ if (fg('platform_editor_code_block_dogfooding_patch')) {
476
+ tr.setMeta('scrollIntoView', false);
477
+ }
475
478
  } else {
476
479
  codeBlockWrappedStates.set(codeBlockNode, updatedToggleState);
477
480
  }
@@ -10,6 +10,7 @@ import { findCodeBlock } from '@atlaskit/editor-common/transforms';
10
10
  import { NodeSelection } from '@atlaskit/editor-prosemirror/state';
11
11
  import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
12
12
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
13
+ import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
13
14
  import { ignoreFollowingMutations, resetShouldIgnoreFollowingMutations } from '../editor-commands';
14
15
  import { codeBlockNodeView } from '../nodeviews/code-block';
15
16
  import { codeBlockClassNames } from '../ui/class-names';
@@ -123,7 +124,7 @@ export var createPlugin = function createPlugin(_ref) {
123
124
  isNodeSelected: tr.selection instanceof NodeSelection,
124
125
  decorations: updatedDecorationSet
125
126
  });
126
- if (!expValEquals('platform_editor_code_block_q4_lovability', 'isEnabled', true)) {
127
+ if (!expValEqualsNoExposure('platform_editor_code_block_q4_lovability', 'isEnabled', true)) {
127
128
  return newPluginState;
128
129
  }
129
130
 
@@ -151,7 +152,7 @@ export var createPlugin = function createPlugin(_ref) {
151
152
  shouldIgnoreFollowingMutations: meta.data
152
153
  });
153
154
  }
154
- if (expValEquals('platform_editor_code_block_q4_lovability', 'isEnabled', true)) {
155
+ if (expValEqualsNoExposure('platform_editor_code_block_q4_lovability', 'isEnabled', true)) {
155
156
  // Failed/unchanged format results and dismissals are meta-only.
156
157
  return applyFormatCodeMeta(pluginState, meta);
157
158
  }
@@ -14,6 +14,7 @@ import ListNumberedIcon from '@atlaskit/icon/core/list-numbered';
14
14
  import TextWrapIcon from '@atlaskit/icon/core/text-wrap';
15
15
  import { fg } from '@atlaskit/platform-feature-flags';
16
16
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
17
+ import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
17
18
  import { changeLanguage, copyContentToClipboardWithAnalytics, createFormatCodeOnClick, removeCodeBlockWithAnalytics, resetCopiedState, toggleLineNumbersForCodeBlockNode, toggleWordWrapStateForCodeBlockNode } from '../editor-commands';
18
19
  import { CodeBlockLanguagePicker } from '../ui/CodeBlockLanguagePicker';
19
20
  import { WrapIcon } from '../ui/icons/WrapIcon';
@@ -134,6 +135,7 @@ export var getToolbarConfig = function getToolbarConfig() {
134
135
  options: languageSelectOptions,
135
136
  filterOption: languageListFilter
136
137
  };
138
+ var areAnyNewToolbarFlagsEnabled = areToolbarFlagsEnabled(Boolean(api === null || api === void 0 ? void 0 : api.toolbar));
137
139
  var languagePicker;
138
140
  if (expValEquals('platform_editor_code_block_q4_lovability', 'isEnabled', true) && fg('platform_editor_code_block_add_line_number_button')) {
139
141
  var autoDetectEntry = typeof localId === 'string' && fg('platform_editor_code_block_language_detection_flow') ? autoDetectState === null || autoDetectState === void 0 ? void 0 : autoDetectState.languageDetectionMap[localId] : undefined;
@@ -156,7 +158,8 @@ export var getToolbarConfig = function getToolbarConfig() {
156
158
  editorView: view,
157
159
  filterOption: languageListFilter,
158
160
  formatMessage: formatMessage,
159
- languagePickerOptions: languagePickerOptions
161
+ languagePickerOptions: languagePickerOptions,
162
+ triggerSpacing: !areAnyNewToolbarFlagsEnabled && fg('platform_editor_code_block_dogfooding_patch') ? 'compact' : 'default'
160
163
  });
161
164
  }
162
165
  };
@@ -164,7 +167,6 @@ export var getToolbarConfig = function getToolbarConfig() {
164
167
  var separator = {
165
168
  type: 'separator'
166
169
  };
167
- var areAnyNewToolbarFlagsEnabled = areToolbarFlagsEnabled(Boolean(api === null || api === void 0 ? void 0 : api.toolbar));
168
170
  var copyToClipboardItems = !allowCopyToClipboard ? [] : [{
169
171
  id: 'editor.codeBlock.copy',
170
172
  type: 'button',
@@ -237,12 +239,12 @@ export var getToolbarConfig = function getToolbarConfig() {
237
239
  };
238
240
  copyAndDeleteButtonMenuItems = [separator].concat(copyToClipboardItems, [deleteButton]);
239
241
  }
240
- var codeBlockWrapButtonTitle = expValEquals('platform_editor_code_block_q4_lovability', 'isEnabled', true) ? formatMessage(isWrapped ? codeBlockButtonMessages.unwrapCodeLabel : codeBlockButtonMessages.wrapCodeLabel) : formatMessage(codeBlockButtonMessages.wrapCode);
242
+ var codeBlockWrapButtonTitle = expValEqualsNoExposure('platform_editor_code_block_q4_lovability', 'isEnabled', true) ? formatMessage(isWrapped ? codeBlockButtonMessages.unwrapCodeLabel : codeBlockButtonMessages.wrapCodeLabel) : formatMessage(codeBlockButtonMessages.wrapCode);
241
243
  var codeBlockWrapButton = {
242
244
  id: 'editor.codeBlock.wrap',
243
245
  type: 'button',
244
246
  // Toggling button now writes to ADF, hence it should be available in view mode
245
- supportsViewMode: !expValEquals('platform_editor_code_block_q4_lovability', 'isEnabled', true),
247
+ supportsViewMode: !expValEqualsNoExposure('platform_editor_code_block_q4_lovability', 'isEnabled', true),
246
248
  icon: TextWrapIcon,
247
249
  iconFallback: WrapIcon,
248
250
  onClick: toggleWordWrapStateForCodeBlockNode(editorAnalyticsAPI),
@@ -12,7 +12,8 @@ export var CodeBlockLanguagePicker = function CodeBlockLanguagePicker(_ref) {
12
12
  editorView = _ref.editorView,
13
13
  filterOption = _ref.filterOption,
14
14
  formatMessage = _ref.formatMessage,
15
- languagePickerOptions = _ref.languagePickerOptions;
15
+ languagePickerOptions = _ref.languagePickerOptions,
16
+ triggerSpacing = _ref.triggerSpacing;
16
17
  var _useState = useState(function () {
17
18
  return getRecentLanguages();
18
19
  }),
@@ -26,11 +27,19 @@ export var CodeBlockLanguagePicker = function CodeBlockLanguagePicker(_ref) {
26
27
  var _api$analytics;
27
28
  var command = option.value === DETECT_LANGUAGE_VALUE && fg('platform_editor_code_block_language_detection_flow') ? 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
29
  var commandSucceeded = command(editorView.state, editorView.dispatch);
30
+ if (fg('platform_editor_code_block_dogfooding_patch')) {
31
+ requestAnimationFrame(function () {
32
+ // Let PopupSelect/FocusLock finish returning focus to the trigger, then restore the editor.
33
+ api === null || api === void 0 || api.core.actions.focus({
34
+ scrollIntoView: false
35
+ });
36
+ });
37
+ }
29
38
  if (commandSucceeded && option.value !== DETECT_LANGUAGE_VALUE) {
30
39
  saveRecentLanguage(option.value);
31
40
  setRecentLanguageValues(getRecentLanguages());
32
41
  }
33
- }, [api === null || api === void 0 || (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 ? void 0 : _api$analytics2.actions, editorView]);
42
+ }, [api === null || api === void 0 || (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 ? void 0 : _api$analytics2.actions, api === null || api === void 0 ? void 0 : api.core.actions, editorView]);
34
43
  return /*#__PURE__*/React.createElement(LanguagePicker, {
35
44
  defaultValue: defaultValue,
36
45
  filterOption: filterOption,
@@ -38,6 +47,7 @@ export var CodeBlockLanguagePicker = function CodeBlockLanguagePicker(_ref) {
38
47
  languagePickerOptions: languagePickerOptions,
39
48
  recentLanguageValues: recentLanguageValues,
40
49
  onMenuOpen: refreshRecentLanguages,
41
- onSelection: handleSelection
50
+ onSelection: handleSelection,
51
+ triggerSpacing: triggerSpacing
42
52
  });
43
53
  };
@@ -6,7 +6,8 @@ import { ToolbarDropdownItem } from '@atlaskit/editor-toolbar';
6
6
  import AngleBracketsIcon from '@atlaskit/icon/core/angle-brackets';
7
7
  var NODE_NAME = 'codeBlock';
8
8
  var CodeBlockMenuItem = function CodeBlockMenuItem(_ref) {
9
- var api = _ref.api;
9
+ var api = _ref.api,
10
+ isSuggested = _ref.isSuggested;
10
11
  var _useIntl = useIntl(),
11
12
  formatMessage = _useIntl.formatMessage;
12
13
  var handleClick = function handleClick(event) {
@@ -17,6 +18,7 @@ var CodeBlockMenuItem = function CodeBlockMenuItem(_ref) {
17
18
  var tr = _ref2.tr;
18
19
  var command = api === null || api === void 0 || (_api$blockMenu = api.blockMenu) === null || _api$blockMenu === void 0 ? void 0 : _api$blockMenu.commands.transformNode(tr.doc.type.schema.nodes.codeBlock, {
19
20
  inputMethod: inputMethod,
21
+ isSuggested: isSuggested,
20
22
  triggeredFrom: triggeredFrom,
21
23
  targetTypeName: NODE_NAME
22
24
  });
@@ -35,8 +37,11 @@ var CodeBlockMenuItem = function CodeBlockMenuItem(_ref) {
35
37
  };
36
38
  export var createCodeBlockMenuItem = function createCodeBlockMenuItem(api) {
37
39
  return function () {
40
+ var _ref3 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
41
+ isSuggested = _ref3.isSuggested;
38
42
  return /*#__PURE__*/React.createElement(CodeBlockMenuItem, {
39
- api: api
43
+ api: api,
44
+ isSuggested: isSuggested
40
45
  });
41
46
  };
42
47
  };
@@ -1,4 +1,5 @@
1
1
  /* LanguagePicker.tsx generated by @compiled/babel-plugin v0.39.1 */
2
+ import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
2
3
  import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
3
4
  import "./LanguagePicker.compiled.css";
4
5
  import { ax, ix } from "@compiled/react/runtime";
@@ -10,6 +11,7 @@ import Button from '@atlaskit/button/new';
10
11
  import { codeBlockButtonMessages } from '@atlaskit/editor-common/messages';
11
12
  import { akEditorLineHeight } from '@atlaskit/editor-shared-styles';
12
13
  import ChevronDownIcon from '@atlaskit/icon/core/chevron-down';
14
+ import { fg } from '@atlaskit/platform-feature-flags';
13
15
  import { Box } from '@atlaskit/primitives/compiled';
14
16
  import { PopupSelect, components } from '@atlaskit/select';
15
17
  import { createGroupedLanguageOptions } from './language-picker-options';
@@ -51,6 +53,11 @@ var menuPopperProps = {
51
53
  enabled: false
52
54
  }]
53
55
  };
56
+ var focusWithoutScrolling = function focusWithoutScrolling(element) {
57
+ element.focus({
58
+ preventScroll: true
59
+ });
60
+ };
54
61
  var getRecentlyUsedLanguages = function getRecentlyUsedLanguages(recentLanguageValues, optionsByValue) {
55
62
  var recentlyUsedLanguages = [];
56
63
  var _iterator = _createForOfIteratorHelper(recentLanguageValues),
@@ -79,13 +86,19 @@ export var LanguagePicker = function LanguagePicker(_ref) {
79
86
  _ref$recentLanguageVa = _ref.recentLanguageValues,
80
87
  recentLanguageValues = _ref$recentLanguageVa === void 0 ? [] : _ref$recentLanguageVa,
81
88
  onMenuOpen = _ref.onMenuOpen,
82
- onSelection = _ref.onSelection;
89
+ onSelection = _ref.onSelection,
90
+ _ref$triggerSpacing = _ref.triggerSpacing,
91
+ triggerSpacing = _ref$triggerSpacing === void 0 ? 'default' : _ref$triggerSpacing;
83
92
  var label = (_defaultValue$label = defaultValue === null || defaultValue === void 0 ? void 0 : defaultValue.label) !== null && _defaultValue$label !== void 0 ? _defaultValue$label : formatMessage(codeBlockButtonMessages.selectLanguage);
84
93
  var selectLanguageLabel = formatMessage(codeBlockButtonMessages.selectLanguage);
85
94
  var _useState = useState(false),
86
95
  _useState2 = _slicedToArray(_useState, 2),
87
96
  hasSearchQuery = _useState2[0],
88
97
  setHasSearchQuery = _useState2[1];
98
+ var _useState3 = useState(),
99
+ _useState4 = _slicedToArray(_useState3, 2),
100
+ lockedPopperPlacement = _useState4[0],
101
+ setLockedPopperPlacement = _useState4[1];
89
102
  var inputValueRef = useRef('');
90
103
  var optionsByValue = useMemo(function () {
91
104
  return new Map(languagePickerOptions.map(function (option) {
@@ -107,6 +120,29 @@ export var LanguagePicker = function LanguagePicker(_ref) {
107
120
  return group.options;
108
121
  });
109
122
  }, [options]);
123
+ var stableMenuPopperProps = useMemo(function () {
124
+ return {
125
+ // Allow Popper to choose top on the first open when bottom has no room, then lock the
126
+ // chosen placement so later hover/search renders do not move the open picker.
127
+ placement: lockedPopperPlacement !== null && lockedPopperPlacement !== void 0 ? lockedPopperPlacement : 'bottom-start',
128
+ modifiers: [{
129
+ name: 'offset',
130
+ options: {
131
+ offset: [0, 8]
132
+ }
133
+ }, {
134
+ name: 'preventOverflow',
135
+ enabled: false
136
+ }].concat(_toConsumableArray(lockedPopperPlacement ? [{
137
+ name: 'flip',
138
+ enabled: false
139
+ }] : [])),
140
+ onFirstUpdate: function onFirstUpdate(_ref2) {
141
+ var placement = _ref2.placement;
142
+ setLockedPopperPlacement(placement);
143
+ }
144
+ };
145
+ }, [lockedPopperPlacement]);
110
146
  var handleChange = useCallback(function (option) {
111
147
  var _option$selectionSour;
112
148
  if (!option) {
@@ -119,21 +155,45 @@ export var LanguagePicker = function LanguagePicker(_ref) {
119
155
  var handleInputChange = useCallback(function (newInputValue, actionMeta) {
120
156
  // React-select clears the input as part of selecting a value before onChange fires.
121
157
  // Keep the last user-typed query so handleChange can report search selections correctly.
122
- if (!actionMeta || actionMeta.action === 'input-change') {
158
+ var isInputChange = !actionMeta || actionMeta.action === 'input-change';
159
+ if (isInputChange) {
123
160
  inputValueRef.current = newInputValue;
124
161
  }
125
- setHasSearchQuery(newInputValue.trim().length > 0);
126
- return newInputValue;
162
+ if (isInputChange || !fg('platform_editor_code_block_dogfooding_patch')) {
163
+ setHasSearchQuery(newInputValue.trim().length > 0);
164
+ return newInputValue;
165
+ }
166
+ return inputValueRef.current;
167
+ }, []);
168
+ var handleMenuOpen = useCallback(function () {
169
+ if (fg('platform_editor_code_block_dogfooding_patch')) {
170
+ inputValueRef.current = '';
171
+ setHasSearchQuery(false);
172
+ setLockedPopperPlacement(undefined);
173
+ }
174
+ onMenuOpen === null || onMenuOpen === void 0 || onMenuOpen();
175
+ }, [onMenuOpen]);
176
+ var handleTriggerMouseDown = useCallback(function (event) {
177
+ if (fg('platform_editor_code_block_dogfooding_patch')) {
178
+ // PopupSelect's FocusLock returns focus to the element that was focused before the
179
+ // picker opened. If that is the editor/code block, closing the picker can scroll the
180
+ // whole code block into view. Focus the trigger first without scrolling so FocusLock
181
+ // returns to the trigger; see CodeBlockLanguagePicker's handleSelection for restoring
182
+ // editor focus.
183
+ focusWithoutScrolling(event.currentTarget);
184
+ }
127
185
  }, []);
128
- var renderTarget = useCallback(function (_ref2) {
129
- var isOpen = _ref2.isOpen,
130
- ref = _ref2.ref,
131
- onKeyDown = _ref2.onKeyDown,
132
- ariaControls = _ref2['aria-controls'];
186
+ var renderTarget = useCallback(function (_ref3) {
187
+ var isOpen = _ref3.isOpen,
188
+ ref = _ref3.ref,
189
+ onKeyDown = _ref3.onKeyDown,
190
+ ariaControls = _ref3['aria-controls'];
133
191
  return /*#__PURE__*/React.createElement("div", {
134
192
  className: ax([styles.trigger])
135
193
  }, /*#__PURE__*/React.createElement(Button, {
194
+ spacing: triggerSpacing,
136
195
  shouldFitContainer: true,
196
+ onMouseDown: handleTriggerMouseDown,
137
197
  onKeyDown: onKeyDown,
138
198
  ref: ref,
139
199
  iconAfter: ChevronDownIcon,
@@ -142,7 +202,7 @@ export var LanguagePicker = function LanguagePicker(_ref) {
142
202
  "aria-controls": ariaControls,
143
203
  testId: "code-block-language-picker-trigger"
144
204
  }, label));
145
- }, [label]);
205
+ }, [label, triggerSpacing, handleTriggerMouseDown]);
146
206
  return /*#__PURE__*/React.createElement(PopupSelect, {
147
207
  components: popupSelectComponents,
148
208
  filterOption: filterOption,
@@ -152,9 +212,9 @@ export var LanguagePicker = function LanguagePicker(_ref) {
152
212
  menuPlacement: "auto",
153
213
  onChange: handleChange,
154
214
  onInputChange: handleInputChange,
155
- onMenuOpen: onMenuOpen,
215
+ onMenuOpen: handleMenuOpen,
156
216
  options: hasSearchQuery ? searchOptions : options,
157
- popperProps: menuPopperProps,
217
+ popperProps: fg('platform_editor_code_block_dogfooding_patch') ? stableMenuPopperProps : menuPopperProps,
158
218
  searchThreshold: -1,
159
219
  target: renderTarget,
160
220
  testId: "code-block-language-picker",
@@ -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 { ACTION, ACTION_SUBJECT, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
6
+ import { fg } from '@atlaskit/platform-feature-flags';
6
7
  import { ACTIONS } from '../pm-plugins/actions';
7
8
  import { autoDetectPluginKey } from '../pm-plugins/auto-detect-state';
8
9
  import { createAutoDetectEntry } from './auto-detect-state';
@@ -36,6 +37,7 @@ var runPendingDetection = function runPendingDetection(view, localId, api) {
36
37
  }
37
38
  var detectedLanguage = detectLanguage(found.node.textContent);
38
39
  var detectionResult = detectedLanguage ? 'detected' : 'noneDetected';
40
+ var detectionPhase = entry.detectionResult ? 'redetection' : 'initial';
39
41
  // Keep a previous auto-detected language when the latest snippet is too weak to classify.
40
42
  var shouldPreserveAutoDetectedLanguage = !detectedLanguage && Boolean(entry.autoDetectedLanguage) && found.node.attrs.language === entry.autoDetectedLanguage;
41
43
  var nextEntry = _objectSpread(_objectSpread({}, createAutoDetectEntry(found.node, found.pos, false, entry)), {}, {
@@ -45,10 +47,12 @@ var runPendingDetection = function runPendingDetection(view, localId, api) {
45
47
 
46
48
  // If there is no confident detection, record the result without clearing user-visible language.
47
49
  var shouldOnlyUpdateDetectionState = !detectedLanguage && (!found.node.attrs.language || shouldPreserveAutoDetectedLanguage);
50
+ var hasDetectedLanguageChange = Boolean(detectedLanguage) && found.node.attrs.language !== detectedLanguage;
51
+ var shouldClearStaleLanguage = !detectedLanguage && Boolean(found.node.attrs.language) && !shouldPreserveAutoDetectedLanguage;
52
+ var shouldUpdateNodeLanguage = fg('platform_editor_code_block_dogfooding_patch') ? hasDetectedLanguageChange || shouldClearStaleLanguage : !shouldOnlyUpdateDetectionState;
48
53
  api === null || api === void 0 || (_api$core = api.core) === null || _api$core === void 0 || _api$core.actions.execute(function (_ref) {
49
- var _api$analytics;
50
54
  var tr = _ref.tr;
51
- if (!shouldOnlyUpdateDetectionState) {
55
+ if (shouldUpdateNodeLanguage) {
52
56
  tr.setNodeMarkup(found.pos, undefined, _objectSpread(_objectSpread({}, found.node.attrs), {}, {
53
57
  language: detectedLanguage
54
58
  }), found.node.marks);
@@ -60,15 +64,21 @@ var runPendingDetection = function runPendingDetection(view, localId, api) {
60
64
  entry: nextEntry
61
65
  }
62
66
  });
63
- api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 || _api$analytics.actions.attachAnalyticsEvent({
64
- action: ACTION.LANGUAGE_AUTO_DETECTED,
65
- actionSubject: ACTION_SUBJECT.CODE_BLOCK,
66
- attributes: {
67
- language: detectedLanguage !== null && detectedLanguage !== void 0 ? detectedLanguage : 'none',
68
- detectionResult: detectionResult
69
- },
70
- eventType: EVENT_TYPE.TRACK
71
- })(tr);
67
+ // When platform_editor_code_block_dogfooding_patch is cleaned up, merge this with the previous shouldUpdateNodeLanguage check.
68
+ if (!fg('platform_editor_code_block_dogfooding_patch') || shouldUpdateNodeLanguage) {
69
+ var _api$analytics;
70
+ api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 || _api$analytics.actions.attachAnalyticsEvent({
71
+ action: ACTION.LANGUAGE_AUTO_DETECTED,
72
+ actionSubject: ACTION_SUBJECT.CODE_BLOCK,
73
+ attributes: _objectSpread({
74
+ language: detectedLanguage !== null && detectedLanguage !== void 0 ? detectedLanguage : 'none',
75
+ detectionResult: detectionResult
76
+ }, fg('platform_editor_code_block_dogfooding_patch') ? {
77
+ detectionPhase: detectionPhase
78
+ } : {}),
79
+ eventType: EVENT_TYPE.TRACK
80
+ })(tr);
81
+ }
72
82
  // Language detection runs in the background and should not move the viewport.
73
83
  return tr.setMeta('scrollIntoView', false);
74
84
  });