@atlaskit/editor-plugin-code-block 13.0.0 → 13.1.0

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 (51) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/cjs/codeBlockPlugin.js +14 -2
  3. package/dist/cjs/editor-commands/index.js +182 -7
  4. package/dist/cjs/pm-plugins/actions.js +6 -3
  5. package/dist/cjs/pm-plugins/main.js +17 -1
  6. package/dist/cjs/pm-plugins/toolbar.js +20 -3
  7. package/dist/cjs/ui/CodeBlockLanguagePicker.js +2 -2
  8. package/dist/cjs/ui/FormatCodeErrorFlag.js +70 -0
  9. package/dist/cjs/ui/language-picker-options.js +2 -2
  10. package/dist/cjs/utils/format-code/format-code-state.js +81 -0
  11. package/dist/cjs/utils/format-code/formatter-impl.js +15 -0
  12. package/dist/cjs/utils/format-code/formatter.js +86 -0
  13. package/dist/es2019/codeBlockPlugin.js +13 -3
  14. package/dist/es2019/editor-commands/index.js +179 -2
  15. package/dist/es2019/pm-plugins/actions.js +6 -3
  16. package/dist/es2019/pm-plugins/main.js +18 -1
  17. package/dist/es2019/pm-plugins/toolbar.js +21 -4
  18. package/dist/es2019/ui/CodeBlockLanguagePicker.js +2 -2
  19. package/dist/es2019/ui/FormatCodeErrorFlag.js +62 -0
  20. package/dist/es2019/ui/language-picker-options.js +2 -2
  21. package/dist/es2019/utils/format-code/format-code-state.js +82 -0
  22. package/dist/es2019/utils/format-code/formatter-impl.js +10 -0
  23. package/dist/es2019/utils/format-code/formatter.js +47 -0
  24. package/dist/esm/codeBlockPlugin.js +14 -2
  25. package/dist/esm/editor-commands/index.js +181 -6
  26. package/dist/esm/pm-plugins/actions.js +6 -3
  27. package/dist/esm/pm-plugins/main.js +17 -1
  28. package/dist/esm/pm-plugins/toolbar.js +21 -4
  29. package/dist/esm/ui/CodeBlockLanguagePicker.js +2 -2
  30. package/dist/esm/ui/FormatCodeErrorFlag.js +61 -0
  31. package/dist/esm/ui/language-picker-options.js +2 -2
  32. package/dist/esm/utils/format-code/format-code-state.js +74 -0
  33. package/dist/esm/utils/format-code/formatter-impl.js +9 -0
  34. package/dist/esm/utils/format-code/formatter.js +75 -0
  35. package/dist/types/codeBlockPluginType.d.ts +3 -0
  36. package/dist/types/editor-commands/index.d.ts +6 -1
  37. package/dist/types/pm-plugins/actions.d.ts +6 -3
  38. package/dist/types/pm-plugins/main-state.d.ts +16 -0
  39. package/dist/types/ui/FormatCodeErrorFlag.d.ts +6 -0
  40. package/dist/types/utils/format-code/format-code-state.d.ts +4 -0
  41. package/dist/types/utils/format-code/formatter-impl.d.ts +5 -0
  42. package/dist/types/utils/format-code/formatter.d.ts +25 -0
  43. package/dist/types-ts4.5/codeBlockPluginType.d.ts +3 -0
  44. package/dist/types-ts4.5/editor-commands/index.d.ts +6 -1
  45. package/dist/types-ts4.5/pm-plugins/actions.d.ts +6 -3
  46. package/dist/types-ts4.5/pm-plugins/main-state.d.ts +16 -0
  47. package/dist/types-ts4.5/ui/FormatCodeErrorFlag.d.ts +6 -0
  48. package/dist/types-ts4.5/utils/format-code/format-code-state.d.ts +4 -0
  49. package/dist/types-ts4.5/utils/format-code/formatter-impl.d.ts +5 -0
  50. package/dist/types-ts4.5/utils/format-code/formatter.d.ts +32 -0
  51. package/package.json +12 -8
@@ -0,0 +1,82 @@
1
+ import { ACTIONS } from '../../pm-plugins/actions';
2
+ export const mapPendingFormats = (pendingFormats, tr, newState) => {
3
+ const entries = Object.entries(pendingFormats);
4
+ if (entries.length === 0) {
5
+ return pendingFormats;
6
+ }
7
+ let nextPendingFormats = pendingFormats;
8
+ entries.forEach(([localId, pendingFormat]) => {
9
+ const {
10
+ deleted,
11
+ pos
12
+ } = tr.mapping.mapResult(pendingFormat.pos, 1);
13
+ const codeBlockNode = newState.doc.nodeAt(pos);
14
+ const shouldRemovePendingFormat = deleted || (codeBlockNode === null || codeBlockNode === void 0 ? void 0 : codeBlockNode.type) !== newState.schema.nodes.codeBlock || (codeBlockNode === null || codeBlockNode === void 0 ? void 0 : codeBlockNode.attrs.localId) !== localId;
15
+ const shouldUpdatePendingFormat = pos !== pendingFormat.pos;
16
+ if (shouldRemovePendingFormat || shouldUpdatePendingFormat) {
17
+ if (nextPendingFormats === pendingFormats) {
18
+ nextPendingFormats = {
19
+ ...pendingFormats
20
+ };
21
+ }
22
+ }
23
+ if (shouldRemovePendingFormat) {
24
+ delete nextPendingFormats[localId];
25
+ return;
26
+ }
27
+ if (shouldUpdatePendingFormat) {
28
+ nextPendingFormats[localId] = {
29
+ ...pendingFormat,
30
+ pos
31
+ };
32
+ }
33
+ });
34
+ return nextPendingFormats;
35
+ };
36
+ function removeRecordEntry(record, key) {
37
+ const nextRecord = {
38
+ ...record
39
+ };
40
+ delete nextRecord[key];
41
+ return nextRecord;
42
+ }
43
+ export const applyFormatCodeMeta = (pluginState, meta) => {
44
+ switch (meta === null || meta === void 0 ? void 0 : meta.type) {
45
+ case ACTIONS.START_FORMAT_CODE:
46
+ return {
47
+ ...pluginState,
48
+ pendingFormats: {
49
+ ...pluginState.pendingFormats,
50
+ [meta.data.localId]: {
51
+ languageSource: meta.data.languageSource,
52
+ pos: meta.data.pos,
53
+ requestId: meta.data.requestId
54
+ }
55
+ }
56
+ };
57
+ case ACTIONS.RESOLVE_FORMAT_CODE:
58
+ {
59
+ const pendingFormats = removeRecordEntry(pluginState.pendingFormats, meta.data.localId);
60
+ const formatCodeErrors = removeRecordEntry(pluginState.formatCodeErrors, meta.data.localId);
61
+ return {
62
+ ...pluginState,
63
+ formatCodeErrors: meta.data.outcome === 'failed' ? {
64
+ ...formatCodeErrors,
65
+ [meta.data.localId]: {
66
+ errorType: meta.data.errorType,
67
+ localId: meta.data.localId,
68
+ languageSource: meta.data.languageSource
69
+ }
70
+ } : formatCodeErrors,
71
+ pendingFormats
72
+ };
73
+ }
74
+ case ACTIONS.CLEAR_FORMAT_CODE_ERROR:
75
+ return {
76
+ ...pluginState,
77
+ formatCodeErrors: removeRecordEntry(pluginState.formatCodeErrors, meta.data.localId)
78
+ };
79
+ default:
80
+ return pluginState;
81
+ }
82
+ };
@@ -0,0 +1,10 @@
1
+ export const formatCode = ({
2
+ content,
3
+ language
4
+ }) => {
5
+ return Promise.resolve({
6
+ content,
7
+ language,
8
+ status: 'unchanged'
9
+ });
10
+ };
@@ -0,0 +1,47 @@
1
+ const supportedFormatLanguages = ['json', 'javascript', 'jsx', 'typescript', 'tsx', 'sql'];
2
+ export const isSupportedFormatLanguage = language => supportedFormatLanguages.includes(language);
3
+ let formatterModulePromise;
4
+ export const preloadFormatterModule = () => {
5
+ if (!formatterModulePromise) {
6
+ formatterModulePromise = import( /* webpackChunkName: "@atlaskit-internal_editor-plugin-code-block-formatter" */'./formatter-impl').catch(error => {
7
+ formatterModulePromise = undefined;
8
+ throw error;
9
+ });
10
+ }
11
+ return formatterModulePromise;
12
+ };
13
+ export const preloadFormatterOnIntent = () => (_state, dispatch) => {
14
+ if (!dispatch) {
15
+ // Hover/focus handlers are command-shaped; keep dry-runs side-effect free.
16
+ return false;
17
+ }
18
+ void preloadFormatterModule();
19
+ return false;
20
+ };
21
+ export const formatCode = async ({
22
+ content,
23
+ language
24
+ }) => {
25
+ let formatterModule;
26
+ try {
27
+ formatterModule = await preloadFormatterModule();
28
+ } catch {
29
+ return {
30
+ errorType: 'formatter-load-failed',
31
+ language,
32
+ status: 'failed'
33
+ };
34
+ }
35
+ try {
36
+ return await formatterModule.formatCode({
37
+ content,
38
+ language
39
+ });
40
+ } catch {
41
+ return {
42
+ errorType: 'formatter-execution-failed',
43
+ language,
44
+ status: 'failed'
45
+ };
46
+ }
47
+ };
@@ -10,6 +10,7 @@ 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
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 { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
14
15
  import { createInsertCodeBlockTransaction, insertCodeBlockWithAnalytics } from './editor-commands';
15
16
  import { createAutoDetectPlugin } from './pm-plugins/auto-detect';
@@ -19,9 +20,11 @@ import ideUX from './pm-plugins/ide-ux';
19
20
  import { createCodeBlockInputRule } from './pm-plugins/input-rule';
20
21
  import keymap from './pm-plugins/keymaps';
21
22
  import { createPlugin } from './pm-plugins/main';
23
+ import { pluginKey } from './pm-plugins/plugin-key';
22
24
  import refreshBrowserSelectionOnChange from './pm-plugins/refresh-browser-selection';
23
25
  import { getToolbarConfig } from './pm-plugins/toolbar';
24
26
  import { createCodeBlockMenuItem } from './ui/CodeBlockMenuItem';
27
+ import { FormatCodeErrorFlag } from './ui/FormatCodeErrorFlag';
25
28
  var CODE_BLOCK_NODE_NAME = 'codeBlock';
26
29
  var codeBlockPlugin = function codeBlockPlugin(_ref) {
27
30
  var options = _ref.config,
@@ -52,11 +55,15 @@ var codeBlockPlugin = function codeBlockPlugin(_ref) {
52
55
  }];
53
56
  },
54
57
  getSharedState: function getSharedState(state) {
58
+ var _codeBlockState$forma, _codeBlockState$pendi;
55
59
  if (!state) {
56
60
  return undefined;
57
61
  }
62
+ var codeBlockState = pluginKey.getState(state);
58
63
  return {
59
- copyButtonHoverNode: copySelectionPluginKey.getState(state).codeBlockNode
64
+ copyButtonHoverNode: copySelectionPluginKey.getState(state).codeBlockNode,
65
+ formatCodeErrors: (_codeBlockState$forma = codeBlockState === null || codeBlockState === void 0 ? void 0 : codeBlockState.formatCodeErrors) !== null && _codeBlockState$forma !== void 0 ? _codeBlockState$forma : {},
66
+ pendingFormats: (_codeBlockState$pendi = codeBlockState === null || codeBlockState === void 0 ? void 0 : codeBlockState.pendingFormats) !== null && _codeBlockState$pendi !== void 0 ? _codeBlockState$pendi : {}
60
67
  };
61
68
  },
62
69
  pmPlugins: function pmPlugins() {
@@ -76,7 +83,7 @@ var codeBlockPlugin = function codeBlockPlugin(_ref) {
76
83
  var schema = _ref3.schema;
77
84
  return createCodeBlockInputRule(schema, api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions);
78
85
  }
79
- }].concat(_toConsumableArray(expValEquals('platform_editor_code_block_auto_detection', 'isEnabled', true) ? [{
86
+ }].concat(_toConsumableArray(expValEquals('platform_editor_code_block_q4_lovability', 'isEnabled', true) && fg('platform_editor_code_block_language_detection_flow') ? [{
80
87
  name: 'codeBlockAutoDetect',
81
88
  plugin: function plugin() {
82
89
  return createAutoDetectPlugin(api);
@@ -150,6 +157,11 @@ var codeBlockPlugin = function codeBlockPlugin(_ref) {
150
157
  }];
151
158
  },
152
159
  floatingToolbar: getToolbarConfig(options === null || options === void 0 ? void 0 : options.allowCopyToClipboard, api, options === null || options === void 0 ? void 0 : options.overrideLanguageName)
160
+ },
161
+ contentComponent: function contentComponent() {
162
+ return expValEqualsNoExposure('platform_editor_code_block_q4_lovability', 'isEnabled', true) ? /*#__PURE__*/React.createElement(FormatCodeErrorFlag, {
163
+ api: api
164
+ }) : null;
153
165
  }
154
166
  };
155
167
  };
@@ -10,6 +10,7 @@ import { editorCommandToPMCommand } from '@atlaskit/editor-common/preset';
10
10
  import { findCodeBlock } from '@atlaskit/editor-common/transforms';
11
11
  import { NodeSelection } from '@atlaskit/editor-prosemirror/state';
12
12
  import { findParentNodeOfType, findSelectedNodeOfType, isNodeSelection, removeParentNodeOfType, removeSelectedNode, safeInsert } from '@atlaskit/editor-prosemirror/utils';
13
+ import { fg } from '@atlaskit/platform-feature-flags';
13
14
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
14
15
  import { ACTIONS } from '../pm-plugins/actions';
15
16
  import { autoDetectPluginKey } from '../pm-plugins/auto-detect-state';
@@ -17,6 +18,7 @@ import { copySelectionPluginKey } from '../pm-plugins/codeBlockCopySelectionPlug
17
18
  import { pluginKey } from '../pm-plugins/plugin-key';
18
19
  import { transformToCodeBlockAction } from '../pm-plugins/transform-to-code-block';
19
20
  import { createAutoDetectEntry, getLocalId, hasEnoughTextForAutoDetection } from '../utils/auto-detect-state';
21
+ import { formatCode, isSupportedFormatLanguage } from '../utils/format-code/formatter';
20
22
  export var removeCodeBlockWithAnalytics = function removeCodeBlockWithAnalytics(editorAnalyticsAPI) {
21
23
  return withAnalytics(editorAnalyticsAPI, {
22
24
  action: ACTION.DELETED,
@@ -52,11 +54,11 @@ export var changeLanguage = function changeLanguage(editorAnalyticsAPI) {
52
54
  }
53
55
  var node = state.doc.nodeAt(pos);
54
56
  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;
57
+ var previousAutoDetectEntry = expValEquals('platform_editor_code_block_q4_lovability', 'isEnabled', true) && fg('platform_editor_code_block_language_detection_flow') ? (_autoDetectPluginKey$ = autoDetectPluginKey.getState(state)) === null || _autoDetectPluginKey$ === void 0 ? void 0 : _autoDetectPluginKey$.languageDetectionMap[localId] : undefined;
56
58
  var tr = state.tr.setNodeMarkup(pos, codeBlock, _objectSpread(_objectSpread({}, node === null || node === void 0 ? void 0 : node.attrs), {}, {
57
59
  language: language
58
60
  })).setMeta('scrollIntoView', false);
59
- if (expValEquals('platform_editor_code_block_auto_detection', 'isEnabled', true)) {
61
+ if (expValEquals('platform_editor_code_block_q4_lovability', 'isEnabled', true) && fg('platform_editor_code_block_language_detection_flow')) {
60
62
  tr.setMeta(autoDetectPluginKey, {
61
63
  type: ACTIONS.REMOVE_AUTO_DETECT_ENTRY,
62
64
  data: {
@@ -123,6 +125,179 @@ export var detectLanguage = function detectLanguage() {
123
125
  return true;
124
126
  };
125
127
  };
128
+ var setResolveFormatCodeMeta = function setResolveFormatCodeMeta(tr, _ref) {
129
+ var languageSource = _ref.languageSource,
130
+ localId = _ref.localId,
131
+ outcome = _ref.outcome,
132
+ requestId = _ref.requestId,
133
+ errorType = _ref.errorType;
134
+ return tr.setMeta(pluginKey, {
135
+ type: ACTIONS.RESOLVE_FORMAT_CODE,
136
+ data: _objectSpread({
137
+ languageSource: languageSource,
138
+ localId: localId,
139
+ outcome: outcome,
140
+ requestId: requestId
141
+ }, errorType ? {
142
+ errorType: errorType
143
+ } : {})
144
+ });
145
+ };
146
+ var replaceCodeBlockText = function replaceCodeBlockText(_ref2) {
147
+ var codeBlockNode = _ref2.codeBlockNode,
148
+ content = _ref2.content,
149
+ pos = _ref2.pos,
150
+ tr = _ref2.tr;
151
+ var from = pos + 1;
152
+ var to = pos + codeBlockNode.nodeSize - 1;
153
+ tr.delete(from, to);
154
+ if (content) {
155
+ tr.insertText(content, from);
156
+ }
157
+
158
+ // The editor scroll plugin scrolls doc-changing transactions by default.
159
+ return tr.setMeta('scrollIntoView', false);
160
+ };
161
+ var attachFormatCodeAnalytics = function attachFormatCodeAnalytics(_ref3) {
162
+ var editorAnalyticsAPI = _ref3.editorAnalyticsAPI,
163
+ languageSource = _ref3.languageSource,
164
+ result = _ref3.result,
165
+ tr = _ref3.tr;
166
+ if (result.status === 'failed') {
167
+ editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 || editorAnalyticsAPI.attachAnalyticsEvent({
168
+ action: ACTION.ERRORED,
169
+ actionSubject: ACTION_SUBJECT.CODE_BLOCK,
170
+ attributes: {
171
+ errorType: result.errorType,
172
+ language: result.language,
173
+ languageSource: languageSource
174
+ },
175
+ eventType: EVENT_TYPE.TRACK
176
+ })(tr);
177
+ return;
178
+ }
179
+ editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 || editorAnalyticsAPI.attachAnalyticsEvent({
180
+ action: ACTION.FORMATTED,
181
+ actionSubject: ACTION_SUBJECT.CODE_BLOCK,
182
+ attributes: {
183
+ language: result.language,
184
+ languageSource: languageSource,
185
+ outcome: result.status
186
+ },
187
+ eventType: EVENT_TYPE.TRACK
188
+ })(tr);
189
+ };
190
+ var createResolveFormatCodeTransaction = function createResolveFormatCodeTransaction(_ref4) {
191
+ var editorAnalyticsAPI = _ref4.editorAnalyticsAPI,
192
+ localId = _ref4.localId,
193
+ pendingFormat = _ref4.pendingFormat,
194
+ result = _ref4.result,
195
+ tr = _ref4.tr;
196
+ var languageSource = pendingFormat.languageSource,
197
+ requestId = pendingFormat.requestId;
198
+ var codeBlockNode = tr.doc.nodeAt(pendingFormat.pos);
199
+ var hasMatchingCodeBlock = (codeBlockNode === null || codeBlockNode === void 0 ? void 0 : codeBlockNode.type) === tr.doc.type.schema.nodes.codeBlock && (codeBlockNode === null || codeBlockNode === void 0 ? void 0 : codeBlockNode.attrs.localId) === localId;
200
+ if (!hasMatchingCodeBlock) {
201
+ // Keep failure telemetry even when the target block is no longer available.
202
+ if (result.status === 'failed') {
203
+ attachFormatCodeAnalytics({
204
+ editorAnalyticsAPI: editorAnalyticsAPI,
205
+ languageSource: languageSource,
206
+ result: result,
207
+ tr: tr
208
+ });
209
+ }
210
+ return setResolveFormatCodeMeta(tr, {
211
+ languageSource: languageSource,
212
+ localId: localId,
213
+ outcome: 'unchanged',
214
+ requestId: requestId
215
+ });
216
+ }
217
+ var resultTransaction = tr;
218
+ if (result.status === 'formatted') {
219
+ resultTransaction = replaceCodeBlockText({
220
+ codeBlockNode: codeBlockNode,
221
+ content: result.content,
222
+ pos: pendingFormat.pos,
223
+ tr: tr
224
+ });
225
+ }
226
+ attachFormatCodeAnalytics({
227
+ editorAnalyticsAPI: editorAnalyticsAPI,
228
+ languageSource: languageSource,
229
+ result: result,
230
+ tr: resultTransaction
231
+ });
232
+ return setResolveFormatCodeMeta(resultTransaction, {
233
+ errorType: result.status === 'failed' ? result.errorType : undefined,
234
+ languageSource: languageSource,
235
+ localId: localId,
236
+ outcome: result.status,
237
+ requestId: requestId
238
+ });
239
+ };
240
+ export var createFormatCodeOnClick = function createFormatCodeOnClick(_ref5) {
241
+ var api = _ref5.api,
242
+ editorAnalyticsAPI = _ref5.editorAnalyticsAPI;
243
+ return function (state, dispatch) {
244
+ var _autoDetectPluginKey$2, _api$core;
245
+ var currentCodeBlockState = pluginKey.getState(state);
246
+ var currentPos = currentCodeBlockState === null || currentCodeBlockState === void 0 ? void 0 : currentCodeBlockState.pos;
247
+ if (!currentCodeBlockState || typeof currentPos !== 'number') {
248
+ return false;
249
+ }
250
+ var currentNode = state.doc.nodeAt(currentPos);
251
+ if (!currentNode || currentNode.type !== state.schema.nodes.codeBlock) {
252
+ return false;
253
+ }
254
+ var currentLanguage = currentNode.attrs.language;
255
+ if (!isSupportedFormatLanguage(currentLanguage)) {
256
+ return true;
257
+ }
258
+ var currentLocalId = currentNode.attrs.localId;
259
+ if (currentCodeBlockState.pendingFormats[currentLocalId]) {
260
+ return true;
261
+ }
262
+ var autoDetectEntry = (_autoDetectPluginKey$2 = autoDetectPluginKey.getState(state)) === null || _autoDetectPluginKey$2 === void 0 ? void 0 : _autoDetectPluginKey$2.languageDetectionMap[currentLocalId];
263
+ var languageSource = (autoDetectEntry === null || autoDetectEntry === void 0 ? void 0 : autoDetectEntry.autoDetectedLanguage) === currentLanguage ? 'auto-detected' : 'selected';
264
+ var content = currentNode.textContent;
265
+ var requestId = crypto.randomUUID();
266
+ api === null || api === void 0 || (_api$core = api.core) === null || _api$core === void 0 || _api$core.actions.execute(function (_ref6) {
267
+ var tr = _ref6.tr;
268
+ return tr.setMeta(pluginKey, {
269
+ type: ACTIONS.START_FORMAT_CODE,
270
+ data: {
271
+ languageSource: languageSource,
272
+ localId: currentLocalId,
273
+ pos: currentPos,
274
+ requestId: requestId
275
+ }
276
+ });
277
+ });
278
+ void formatCode({
279
+ content: content,
280
+ language: currentLanguage
281
+ }).then(function (result) {
282
+ var _api$codeBlock, _api$core2;
283
+ var pendingFormat = api === null || api === void 0 || (_api$codeBlock = api.codeBlock) === null || _api$codeBlock === void 0 || (_api$codeBlock = _api$codeBlock.sharedState.currentState()) === null || _api$codeBlock === void 0 ? void 0 : _api$codeBlock.pendingFormats[currentLocalId];
284
+ if (!pendingFormat || pendingFormat.requestId !== requestId) {
285
+ return;
286
+ }
287
+ api === null || api === void 0 || (_api$core2 = api.core) === null || _api$core2 === void 0 || _api$core2.actions.execute(function (_ref7) {
288
+ var tr = _ref7.tr;
289
+ return createResolveFormatCodeTransaction({
290
+ editorAnalyticsAPI: editorAnalyticsAPI,
291
+ localId: currentLocalId,
292
+ pendingFormat: pendingFormat,
293
+ result: result,
294
+ tr: tr
295
+ });
296
+ });
297
+ });
298
+ return true;
299
+ };
300
+ };
126
301
  export var copyContentToClipboardWithAnalytics = function copyContentToClipboardWithAnalytics(editorAnalyticsAPI) {
127
302
  return function (state, dispatch) {
128
303
  var nodes = state.schema.nodes,
@@ -226,8 +401,8 @@ export var resetShouldIgnoreFollowingMutations = function resetShouldIgnoreFollo
226
401
  * if there is text selected it will wrap the current selection if not it will
227
402
  * append the codeblock to the end of the document.
228
403
  */
229
- export function createInsertCodeBlockTransaction(_ref) {
230
- var state = _ref.state;
404
+ export function createInsertCodeBlockTransaction(_ref8) {
405
+ var state = _ref8.state;
231
406
  var tr = state.tr;
232
407
  var from = state.selection.from;
233
408
  var codeBlock = state.schema.nodes.codeBlock;
@@ -314,8 +489,8 @@ export var toggleWordWrapStateForCodeBlockNode = function toggleWordWrapStateFor
314
489
  };
315
490
  };
316
491
  export var toggleLineNumbersForCodeBlockNodeEditorCommand = function toggleLineNumbersForCodeBlockNodeEditorCommand(editorAnalyticsAPI) {
317
- return function (_ref2) {
318
- var tr = _ref2.tr;
492
+ return function (_ref9) {
493
+ var tr = _ref9.tr;
319
494
  var codeBlockType = tr.doc.type.schema.nodes.codeBlock;
320
495
  var codeBlock = findSelectedNodeOfType(codeBlockType)(tr.selection) || findParentNodeOfType(codeBlockType)(tr.selection);
321
496
  if (!codeBlock) {
@@ -1,7 +1,10 @@
1
1
  export var ACTIONS = {
2
+ CLEAR_FORMAT_CODE_ERROR: 'CLEAR_FORMAT_CODE_ERROR',
3
+ REMOVE_AUTO_DETECT_ENTRY: 'REMOVE_AUTO_DETECT_ENTRY',
4
+ RESOLVE_FORMAT_CODE: 'RESOLVE_FORMAT_CODE',
5
+ SET_AUTO_DETECT_ENTRY: 'SET_AUTO_DETECT_ENTRY',
2
6
  SET_COPIED_TO_CLIPBOARD: 'SET_COPIED_TO_CLIPBOARD',
3
- SET_SHOULD_IGNORE_FOLLOWING_MUTATIONS: 'SET_SHOULD_IGNORE_FOLLOWING_MUTATIONS',
4
7
  SET_IS_WRAPPED: 'SET_IS_WRAPPED',
5
- SET_AUTO_DETECT_ENTRY: 'SET_AUTO_DETECT_ENTRY',
6
- REMOVE_AUTO_DETECT_ENTRY: 'REMOVE_AUTO_DETECT_ENTRY'
8
+ SET_SHOULD_IGNORE_FOLLOWING_MUTATIONS: 'SET_SHOULD_IGNORE_FOLLOWING_MUTATIONS',
9
+ START_FORMAT_CODE: 'START_FORMAT_CODE'
7
10
  };
@@ -13,6 +13,7 @@ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
13
13
  import { ignoreFollowingMutations, resetShouldIgnoreFollowingMutations } from '../editor-commands';
14
14
  import { codeBlockNodeView } from '../nodeviews/code-block';
15
15
  import { codeBlockClassNames } from '../ui/class-names';
16
+ import { applyFormatCodeMeta, mapPendingFormats } from '../utils/format-code/format-code-state';
16
17
  import { ACTIONS } from './actions';
17
18
  import { generateInitialDecorations, updateCodeBlockDecorations, updateDecorationSetWithWordWrappedDecorator } from './decorators';
18
19
  import { pluginKey } from './plugin-key';
@@ -87,7 +88,9 @@ export var createPlugin = function createPlugin(_ref) {
87
88
  return {
88
89
  pos: node ? node.pos : null,
89
90
  contentCopied: false,
91
+ formatCodeErrors: {},
90
92
  isNodeSelected: false,
93
+ pendingFormats: {},
91
94
  shouldIgnoreFollowingMutations: false,
92
95
  decorations: DecorationSet.create(state.doc, initialDecorations)
93
96
  };
@@ -120,7 +123,16 @@ export var createPlugin = function createPlugin(_ref) {
120
123
  isNodeSelected: tr.selection instanceof NodeSelection,
121
124
  decorations: updatedDecorationSet
122
125
  });
123
- return newPluginState;
126
+ if (!expValEquals('platform_editor_code_block_q4_lovability', 'isEnabled', true)) {
127
+ return newPluginState;
128
+ }
129
+
130
+ // Successful format results change the doc and carry format meta.
131
+ var formatCodePluginState = applyFormatCodeMeta(newPluginState, meta);
132
+ return _objectSpread(_objectSpread({}, formatCodePluginState), {}, {
133
+ // Pending format requests can outlive unrelated document edits.
134
+ pendingFormats: mapPendingFormats(formatCodePluginState.pendingFormats, tr, newState)
135
+ });
124
136
  }
125
137
  if (tr.selectionSet) {
126
138
  var _node2 = findCodeBlock(newState, tr.selection);
@@ -139,6 +151,10 @@ export var createPlugin = function createPlugin(_ref) {
139
151
  shouldIgnoreFollowingMutations: meta.data
140
152
  });
141
153
  }
154
+ if (expValEquals('platform_editor_code_block_q4_lovability', 'isEnabled', true)) {
155
+ // Failed/unchanged format results and dismissals are meta-only.
156
+ return applyFormatCodeMeta(pluginState, meta);
157
+ }
142
158
  return pluginState;
143
159
  }
144
160
  },
@@ -7,16 +7,18 @@ import { areCodeBlockLineNumbersVisible, isCodeBlockWordWrapEnabled } from '@atl
7
7
  import commonMessages, { codeBlockButtonMessages } from '@atlaskit/editor-common/messages';
8
8
  import { areToolbarFlagsEnabled } from '@atlaskit/editor-common/toolbar-flag-check';
9
9
  import { findDomRefAtPos } from '@atlaskit/editor-prosemirror/utils';
10
+ import AngleBracketsIcon from '@atlaskit/icon/core/angle-brackets';
10
11
  import CopyIcon from '@atlaskit/icon/core/copy';
11
12
  import DeleteIcon from '@atlaskit/icon/core/delete';
12
13
  import ListNumberedIcon from '@atlaskit/icon/core/list-numbered';
13
14
  import TextWrapIcon from '@atlaskit/icon/core/text-wrap';
14
15
  import { fg } from '@atlaskit/platform-feature-flags';
15
16
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
16
- import { changeLanguage, copyContentToClipboardWithAnalytics, removeCodeBlockWithAnalytics, resetCopiedState, toggleLineNumbersForCodeBlockNode, toggleWordWrapStateForCodeBlockNode } from '../editor-commands';
17
+ import { changeLanguage, copyContentToClipboardWithAnalytics, createFormatCodeOnClick, removeCodeBlockWithAnalytics, resetCopiedState, toggleLineNumbersForCodeBlockNode, toggleWordWrapStateForCodeBlockNode } from '../editor-commands';
17
18
  import { CodeBlockLanguagePicker } from '../ui/CodeBlockLanguagePicker';
18
19
  import { WrapIcon } from '../ui/icons/WrapIcon';
19
20
  import { NONE_LANGUAGE_VALUE, PLAIN_TEXT_LANGUAGE_VALUE } from '../ui/language-picker-options';
21
+ import { isSupportedFormatLanguage, preloadFormatterOnIntent } from '../utils/format-code/formatter';
20
22
  import { autoDetectPluginKey } from './auto-detect-state';
21
23
  import { provideVisualFeedbackForCopyButton, removeVisualFeedbackForCopyButton } from './codeBlockCopySelectionPlugin';
22
24
  import { createLanguageList, DEFAULT_LANGUAGES, getLanguageIdentifier } from './language-list';
@@ -87,8 +89,7 @@ export var getToolbarConfig = function getToolbarConfig() {
87
89
  var language = node === null || node === void 0 || (_node$attrs = node.attrs) === null || _node$attrs === void 0 ? void 0 : _node$attrs.language;
88
90
  var localId = node === null || node === void 0 || (_node$attrs2 = node.attrs) === null || _node$attrs2 === void 0 ? void 0 : _node$attrs2.localId;
89
91
  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
-
92
+ var isFormatCodePending = typeof localId === 'string' && Boolean(codeBlockState.pendingFormats[localId]);
92
93
  // Keep fresh option objects for the legacy toolbar select so reopening it
93
94
  // continues to start from the top rather than preserving the previously
94
95
  // focused option by reference.
@@ -114,6 +115,7 @@ export var getToolbarConfig = function getToolbarConfig() {
114
115
  };
115
116
  var languagePicker;
116
117
  if (expValEquals('platform_editor_code_block_q4_lovability', 'isEnabled', true) && fg('platform_editor_code_block_add_line_number_button')) {
118
+ var autoDetectEntry = typeof localId === 'string' && fg('platform_editor_code_block_language_detection_flow') ? autoDetectState === null || autoDetectState === void 0 ? void 0 : autoDetectState.languageDetectionMap[localId] : undefined;
117
119
  var autoDetectPickerValue = getAutoDetectPickerValue({
118
120
  autoDetectEntry: autoDetectEntry,
119
121
  formatMessage: formatMessage,
@@ -237,6 +239,21 @@ export var getToolbarConfig = function getToolbarConfig() {
237
239
  tabIndex: null,
238
240
  selected: areLineNumbersVisible
239
241
  };
242
+ var canFormatCode = node.textContent.length > 0 && isSupportedFormatLanguage(language);
243
+ var formatCodeButton = {
244
+ id: 'editor.codeBlock.formatCode',
245
+ type: 'button',
246
+ supportsViewMode: false,
247
+ disabled: !canFormatCode || isFormatCodePending,
248
+ icon: AngleBracketsIcon,
249
+ onClick: createFormatCodeOnClick({
250
+ api: api,
251
+ editorAnalyticsAPI: editorAnalyticsAPI
252
+ }),
253
+ onFocus: preloadFormatterOnIntent(),
254
+ onMouseEnter: preloadFormatterOnIntent(),
255
+ title: formatMessage(canFormatCode ? codeBlockButtonMessages.formatCode : codeBlockButtonMessages.formatCodeUnavailable)
256
+ };
240
257
  return {
241
258
  title: 'CodeBlock floating controls',
242
259
  // Ignored via go/ees005
@@ -245,7 +262,7 @@ export var getToolbarConfig = function getToolbarConfig() {
245
262
  return findDomRefAtPos(pos, view.domAtPos.bind(view));
246
263
  },
247
264
  nodeType: nodeType,
248
- items: [languagePicker !== null && languagePicker !== void 0 ? languagePicker : languageSelect].concat(_toConsumableArray(areAnyNewToolbarFlagsEnabled ? [] : [separator]), [codeBlockWrapButton], _toConsumableArray(expValEquals('platform_editor_code_block_q4_lovability', 'isEnabled', true) && fg('platform_editor_code_block_add_line_number_button') ? [codeBlockLineNumbersButton] : []), _toConsumableArray(copyAndDeleteButtonMenuItems)),
265
+ items: [languagePicker !== null && languagePicker !== void 0 ? languagePicker : languageSelect].concat(_toConsumableArray(areAnyNewToolbarFlagsEnabled ? [] : [separator]), [codeBlockWrapButton], _toConsumableArray(expValEquals('platform_editor_code_block_q4_lovability', 'isEnabled', true) && fg('platform_editor_code_block_add_line_number_button') ? [codeBlockLineNumbersButton, formatCodeButton] : []), _toConsumableArray(copyAndDeleteButtonMenuItems)),
249
266
  scrollable: true
250
267
  };
251
268
  };
@@ -1,6 +1,6 @@
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';
3
+ import { fg } from '@atlaskit/platform-feature-flags';
4
4
  import { changeLanguage, detectLanguage } from '../editor-commands';
5
5
  import { DETECT_LANGUAGE_VALUE } from './language-picker-options';
6
6
  import { LanguagePicker } from './LanguagePicker';
@@ -24,7 +24,7 @@ export var CodeBlockLanguagePicker = function CodeBlockLanguagePicker(_ref) {
24
24
  }, []);
25
25
  var handleSelection = useCallback(function (option, selectionSource) {
26
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);
27
+ 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
28
  var commandSucceeded = command(editorView.state, editorView.dispatch);
29
29
  if (commandSucceeded && option.value !== DETECT_LANGUAGE_VALUE) {
30
30
  saveRecentLanguage(option.value);
@@ -0,0 +1,61 @@
1
+ import React, { useCallback } from 'react';
2
+ import { useIntl } from 'react-intl';
3
+ import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
4
+ import { codeBlockButtonMessages } from '@atlaskit/editor-common/messages';
5
+ import AkFlag, { FlagGroup } from '@atlaskit/flag';
6
+ import StatusErrorIcon from '@atlaskit/icon/core/status-error';
7
+ import { ACTIONS } from '../pm-plugins/actions';
8
+ import { pluginKey } from '../pm-plugins/plugin-key';
9
+ var FormatCodeErrorFlagItem = function FormatCodeErrorFlagItem(_ref) {
10
+ var formatCodeError = _ref.formatCodeError;
11
+ var _useIntl = useIntl(),
12
+ formatMessage = _useIntl.formatMessage;
13
+ return /*#__PURE__*/React.createElement(AkFlag, {
14
+ description: formatMessage(formatCodeError.languageSource === 'auto-detected' ? codeBlockButtonMessages.formatCodeFailedAutoDetectedDescription : codeBlockButtonMessages.formatCodeFailedDescription),
15
+ icon: /*#__PURE__*/React.createElement(StatusErrorIcon, {
16
+ color: "var(--ds-icon-danger, #C9372C)",
17
+ label: ""
18
+ }),
19
+ id: formatCodeError.localId,
20
+ title: formatMessage(codeBlockButtonMessages.formatCodeFailed)
21
+ });
22
+ };
23
+ export var FormatCodeErrorFlag = function FormatCodeErrorFlag(_ref2) {
24
+ var api = _ref2.api;
25
+ var _useSharedPluginState = useSharedPluginStateWithSelector(api, ['codeBlock'], function (states) {
26
+ var _states$codeBlockStat, _states$codeBlockStat2;
27
+ return {
28
+ formatCodeErrors: (_states$codeBlockStat = (_states$codeBlockStat2 = states.codeBlockState) === null || _states$codeBlockStat2 === void 0 ? void 0 : _states$codeBlockStat2.formatCodeErrors) !== null && _states$codeBlockStat !== void 0 ? _states$codeBlockStat : {}
29
+ };
30
+ }),
31
+ formatCodeErrors = _useSharedPluginState.formatCodeErrors;
32
+ var onDismissed = useCallback(function (localId) {
33
+ var _api$core, _api$core2;
34
+ api === null || api === void 0 || (_api$core = api.core) === null || _api$core === void 0 || _api$core.actions.execute(function (_ref3) {
35
+ var tr = _ref3.tr;
36
+ tr.setMeta(pluginKey, {
37
+ type: ACTIONS.CLEAR_FORMAT_CODE_ERROR,
38
+ data: {
39
+ localId: localId
40
+ }
41
+ });
42
+ return tr;
43
+ });
44
+ api === null || api === void 0 || (_api$core2 = api.core) === null || _api$core2 === void 0 || _api$core2.actions.focus();
45
+ }, [api]);
46
+ var onFlagGroupDismissed = useCallback(function (localId) {
47
+ return onDismissed(String(localId));
48
+ }, [onDismissed]);
49
+ var activeFormatCodeErrors = Object.values(formatCodeErrors);
50
+ if (activeFormatCodeErrors.length === 0) {
51
+ return null;
52
+ }
53
+ return /*#__PURE__*/React.createElement(FlagGroup, {
54
+ onDismissed: onFlagGroupDismissed
55
+ }, activeFormatCodeErrors.map(function (formatCodeError) {
56
+ return /*#__PURE__*/React.createElement(FormatCodeErrorFlagItem, {
57
+ formatCodeError: formatCodeError,
58
+ key: formatCodeError.localId
59
+ });
60
+ }));
61
+ };
@@ -3,7 +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
+ import { fg } from '@atlaskit/platform-feature-flags';
7
7
  export var NONE_LANGUAGE_VALUE = 'none';
8
8
  export var DETECT_LANGUAGE_VALUE = 'autodetect';
9
9
  export var PLAIN_TEXT_LANGUAGE_VALUE = 'text';
@@ -29,7 +29,7 @@ export var createGroupedLanguageOptions = function createGroupedLanguageOptions(
29
29
  var plainTextOption = languages.find(function (language) {
30
30
  return language.value === PLAIN_TEXT_LANGUAGE_VALUE;
31
31
  });
32
- var pinnedOptions = expValEquals('platform_editor_code_block_auto_detection', 'isEnabled', true) ? [getDetectLanguageOption(formatMessage)] : [];
32
+ var pinnedOptions = fg('platform_editor_code_block_language_detection_flow') ? [getDetectLanguageOption(formatMessage)] : [];
33
33
  if (plainTextOption) {
34
34
  pinnedOptions.push(_objectSpread(_objectSpread({}, plainTextOption), {}, {
35
35
  selectionSource: 'pinned'