@atlaskit/editor-plugin-code-block 13.0.1 → 13.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.
- package/CHANGELOG.md +19 -0
- package/dist/cjs/codeBlockPlugin.js +13 -1
- package/dist/cjs/editor-commands/index.js +179 -5
- package/dist/cjs/pm-plugins/actions.js +6 -3
- package/dist/cjs/pm-plugins/main.js +17 -1
- package/dist/cjs/pm-plugins/toolbar.js +19 -2
- package/dist/cjs/ui/FormatCodeErrorFlag.js +70 -0
- package/dist/cjs/utils/format-code/format-code-state.js +81 -0
- package/dist/cjs/utils/format-code/formatter-impl.js +15 -0
- package/dist/cjs/utils/format-code/formatter.js +86 -0
- package/dist/es2019/codeBlockPlugin.js +12 -2
- package/dist/es2019/editor-commands/index.js +176 -0
- package/dist/es2019/pm-plugins/actions.js +6 -3
- package/dist/es2019/pm-plugins/main.js +18 -1
- package/dist/es2019/pm-plugins/toolbar.js +22 -3
- package/dist/es2019/ui/FormatCodeErrorFlag.js +62 -0
- package/dist/es2019/utils/format-code/format-code-state.js +82 -0
- package/dist/es2019/utils/format-code/formatter-impl.js +10 -0
- package/dist/es2019/utils/format-code/formatter.js +47 -0
- package/dist/esm/codeBlockPlugin.js +13 -1
- package/dist/esm/editor-commands/index.js +178 -4
- package/dist/esm/pm-plugins/actions.js +6 -3
- package/dist/esm/pm-plugins/main.js +17 -1
- package/dist/esm/pm-plugins/toolbar.js +20 -3
- package/dist/esm/ui/FormatCodeErrorFlag.js +61 -0
- package/dist/esm/utils/format-code/format-code-state.js +74 -0
- package/dist/esm/utils/format-code/formatter-impl.js +9 -0
- package/dist/esm/utils/format-code/formatter.js +75 -0
- package/dist/types/codeBlockPluginType.d.ts +3 -0
- package/dist/types/editor-commands/index.d.ts +6 -1
- package/dist/types/pm-plugins/actions.d.ts +6 -3
- package/dist/types/pm-plugins/main-state.d.ts +16 -0
- package/dist/types/ui/FormatCodeErrorFlag.d.ts +6 -0
- package/dist/types/utils/format-code/format-code-state.d.ts +4 -0
- package/dist/types/utils/format-code/formatter-impl.d.ts +5 -0
- package/dist/types/utils/format-code/formatter.d.ts +25 -0
- package/dist/types-ts4.5/codeBlockPluginType.d.ts +3 -0
- package/dist/types-ts4.5/editor-commands/index.d.ts +6 -1
- package/dist/types-ts4.5/pm-plugins/actions.d.ts +6 -3
- package/dist/types-ts4.5/pm-plugins/main-state.d.ts +16 -0
- package/dist/types-ts4.5/ui/FormatCodeErrorFlag.d.ts +6 -0
- package/dist/types-ts4.5/utils/format-code/format-code-state.d.ts +4 -0
- package/dist/types-ts4.5/utils/format-code/formatter-impl.d.ts +5 -0
- package/dist/types-ts4.5/utils/format-code/formatter.d.ts +32 -0
- package/package.json +9 -5
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.preloadFormatterOnIntent = exports.preloadFormatterModule = exports.isSupportedFormatLanguage = exports.formatCode = void 0;
|
|
8
|
+
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
|
|
9
|
+
var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));
|
|
10
|
+
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
|
|
11
|
+
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != (0, _typeof2.default)(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t3 in e) "default" !== _t3 && {}.hasOwnProperty.call(e, _t3) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t3)) && (i.get || i.set) ? o(f, _t3, i) : f[_t3] = e[_t3]); return f; })(e, t); }
|
|
12
|
+
var supportedFormatLanguages = ['json', 'javascript', 'jsx', 'typescript', 'tsx', 'sql'];
|
|
13
|
+
var isSupportedFormatLanguage = exports.isSupportedFormatLanguage = function isSupportedFormatLanguage(language) {
|
|
14
|
+
return supportedFormatLanguages.includes(language);
|
|
15
|
+
};
|
|
16
|
+
var formatterModulePromise;
|
|
17
|
+
var preloadFormatterModule = exports.preloadFormatterModule = function preloadFormatterModule() {
|
|
18
|
+
if (!formatterModulePromise) {
|
|
19
|
+
formatterModulePromise = Promise.resolve().then(function () {
|
|
20
|
+
return _interopRequireWildcard(require( /* webpackChunkName: "@atlaskit-internal_editor-plugin-code-block-formatter" */'./formatter-impl'));
|
|
21
|
+
}).catch(function (error) {
|
|
22
|
+
formatterModulePromise = undefined;
|
|
23
|
+
throw error;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
return formatterModulePromise;
|
|
27
|
+
};
|
|
28
|
+
var preloadFormatterOnIntent = exports.preloadFormatterOnIntent = function preloadFormatterOnIntent() {
|
|
29
|
+
return function (_state, dispatch) {
|
|
30
|
+
if (!dispatch) {
|
|
31
|
+
// Hover/focus handlers are command-shaped; keep dry-runs side-effect free.
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
void preloadFormatterModule();
|
|
35
|
+
return false;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
var formatCode = exports.formatCode = /*#__PURE__*/function () {
|
|
39
|
+
var _ref2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(_ref) {
|
|
40
|
+
var content, language, formatterModule, _t, _t2;
|
|
41
|
+
return _regenerator.default.wrap(function (_context) {
|
|
42
|
+
while (1) switch (_context.prev = _context.next) {
|
|
43
|
+
case 0:
|
|
44
|
+
content = _ref.content, language = _ref.language;
|
|
45
|
+
_context.prev = 1;
|
|
46
|
+
_context.next = 2;
|
|
47
|
+
return preloadFormatterModule();
|
|
48
|
+
case 2:
|
|
49
|
+
formatterModule = _context.sent;
|
|
50
|
+
_context.next = 4;
|
|
51
|
+
break;
|
|
52
|
+
case 3:
|
|
53
|
+
_context.prev = 3;
|
|
54
|
+
_t = _context["catch"](1);
|
|
55
|
+
return _context.abrupt("return", {
|
|
56
|
+
errorType: 'formatter-load-failed',
|
|
57
|
+
language: language,
|
|
58
|
+
status: 'failed'
|
|
59
|
+
});
|
|
60
|
+
case 4:
|
|
61
|
+
_context.prev = 4;
|
|
62
|
+
_context.next = 5;
|
|
63
|
+
return formatterModule.formatCode({
|
|
64
|
+
content: content,
|
|
65
|
+
language: language
|
|
66
|
+
});
|
|
67
|
+
case 5:
|
|
68
|
+
return _context.abrupt("return", _context.sent);
|
|
69
|
+
case 6:
|
|
70
|
+
_context.prev = 6;
|
|
71
|
+
_t2 = _context["catch"](4);
|
|
72
|
+
return _context.abrupt("return", {
|
|
73
|
+
errorType: 'formatter-execution-failed',
|
|
74
|
+
language: language,
|
|
75
|
+
status: 'failed'
|
|
76
|
+
});
|
|
77
|
+
case 7:
|
|
78
|
+
case "end":
|
|
79
|
+
return _context.stop();
|
|
80
|
+
}
|
|
81
|
+
}, _callee, null, [[1, 3], [4, 6]]);
|
|
82
|
+
}));
|
|
83
|
+
return function formatCode(_x) {
|
|
84
|
+
return _ref2.apply(this, arguments);
|
|
85
|
+
};
|
|
86
|
+
}();
|
|
@@ -6,6 +6,7 @@ import { blockTypeMessages } from '@atlaskit/editor-common/messages';
|
|
|
6
6
|
import { IconCode } from '@atlaskit/editor-common/quick-insert';
|
|
7
7
|
import { fg } from '@atlaskit/platform-feature-flags';
|
|
8
8
|
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
|
|
9
|
+
import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
|
|
9
10
|
import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
|
|
10
11
|
import { createInsertCodeBlockTransaction, insertCodeBlockWithAnalytics } from './editor-commands';
|
|
11
12
|
import { createAutoDetectPlugin } from './pm-plugins/auto-detect';
|
|
@@ -15,9 +16,11 @@ import ideUX from './pm-plugins/ide-ux';
|
|
|
15
16
|
import { createCodeBlockInputRule } from './pm-plugins/input-rule';
|
|
16
17
|
import keymap from './pm-plugins/keymaps';
|
|
17
18
|
import { createPlugin } from './pm-plugins/main';
|
|
19
|
+
import { pluginKey } from './pm-plugins/plugin-key';
|
|
18
20
|
import refreshBrowserSelectionOnChange from './pm-plugins/refresh-browser-selection';
|
|
19
21
|
import { getToolbarConfig } from './pm-plugins/toolbar';
|
|
20
22
|
import { createCodeBlockMenuItem } from './ui/CodeBlockMenuItem';
|
|
23
|
+
import { FormatCodeErrorFlag } from './ui/FormatCodeErrorFlag';
|
|
21
24
|
const CODE_BLOCK_NODE_NAME = 'codeBlock';
|
|
22
25
|
const codeBlockPlugin = ({
|
|
23
26
|
config: options,
|
|
@@ -49,11 +52,15 @@ const codeBlockPlugin = ({
|
|
|
49
52
|
}];
|
|
50
53
|
},
|
|
51
54
|
getSharedState(state) {
|
|
55
|
+
var _codeBlockState$forma, _codeBlockState$pendi;
|
|
52
56
|
if (!state) {
|
|
53
57
|
return undefined;
|
|
54
58
|
}
|
|
59
|
+
const codeBlockState = pluginKey.getState(state);
|
|
55
60
|
return {
|
|
56
|
-
copyButtonHoverNode: copySelectionPluginKey.getState(state).codeBlockNode
|
|
61
|
+
copyButtonHoverNode: copySelectionPluginKey.getState(state).codeBlockNode,
|
|
62
|
+
formatCodeErrors: (_codeBlockState$forma = codeBlockState === null || codeBlockState === void 0 ? void 0 : codeBlockState.formatCodeErrors) !== null && _codeBlockState$forma !== void 0 ? _codeBlockState$forma : {},
|
|
63
|
+
pendingFormats: (_codeBlockState$pendi = codeBlockState === null || codeBlockState === void 0 ? void 0 : codeBlockState.pendingFormats) !== null && _codeBlockState$pendi !== void 0 ? _codeBlockState$pendi : {}
|
|
57
64
|
};
|
|
58
65
|
},
|
|
59
66
|
pmPlugins() {
|
|
@@ -136,7 +143,10 @@ const codeBlockPlugin = ({
|
|
|
136
143
|
}
|
|
137
144
|
}],
|
|
138
145
|
floatingToolbar: getToolbarConfig(options === null || options === void 0 ? void 0 : options.allowCopyToClipboard, api, options === null || options === void 0 ? void 0 : options.overrideLanguageName)
|
|
139
|
-
}
|
|
146
|
+
},
|
|
147
|
+
contentComponent: () => expValEqualsNoExposure('platform_editor_code_block_q4_lovability', 'isEnabled', true) ? /*#__PURE__*/React.createElement(FormatCodeErrorFlag, {
|
|
148
|
+
api: api
|
|
149
|
+
}) : null
|
|
140
150
|
};
|
|
141
151
|
};
|
|
142
152
|
export default codeBlockPlugin;
|
|
@@ -15,6 +15,7 @@ import { copySelectionPluginKey } from '../pm-plugins/codeBlockCopySelectionPlug
|
|
|
15
15
|
import { pluginKey } from '../pm-plugins/plugin-key';
|
|
16
16
|
import { transformToCodeBlockAction } from '../pm-plugins/transform-to-code-block';
|
|
17
17
|
import { createAutoDetectEntry, getLocalId, hasEnoughTextForAutoDetection } from '../utils/auto-detect-state';
|
|
18
|
+
import { formatCode, isSupportedFormatLanguage } from '../utils/format-code/formatter';
|
|
18
19
|
export const removeCodeBlockWithAnalytics = editorAnalyticsAPI => {
|
|
19
20
|
return withAnalytics(editorAnalyticsAPI, {
|
|
20
21
|
action: ACTION.DELETED,
|
|
@@ -123,6 +124,181 @@ export const detectLanguage = () => (state, dispatch) => {
|
|
|
123
124
|
}
|
|
124
125
|
return true;
|
|
125
126
|
};
|
|
127
|
+
const setResolveFormatCodeMeta = (tr, {
|
|
128
|
+
languageSource,
|
|
129
|
+
localId,
|
|
130
|
+
outcome,
|
|
131
|
+
requestId,
|
|
132
|
+
errorType
|
|
133
|
+
}) => tr.setMeta(pluginKey, {
|
|
134
|
+
type: ACTIONS.RESOLVE_FORMAT_CODE,
|
|
135
|
+
data: {
|
|
136
|
+
languageSource,
|
|
137
|
+
localId,
|
|
138
|
+
outcome,
|
|
139
|
+
requestId,
|
|
140
|
+
...(errorType ? {
|
|
141
|
+
errorType
|
|
142
|
+
} : {})
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
const replaceCodeBlockText = ({
|
|
146
|
+
codeBlockNode,
|
|
147
|
+
content,
|
|
148
|
+
pos,
|
|
149
|
+
tr
|
|
150
|
+
}) => {
|
|
151
|
+
const from = pos + 1;
|
|
152
|
+
const 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
|
+
const attachFormatCodeAnalytics = ({
|
|
162
|
+
editorAnalyticsAPI,
|
|
163
|
+
languageSource,
|
|
164
|
+
result,
|
|
165
|
+
tr
|
|
166
|
+
}) => {
|
|
167
|
+
if (result.status === 'failed') {
|
|
168
|
+
editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 ? void 0 : editorAnalyticsAPI.attachAnalyticsEvent({
|
|
169
|
+
action: ACTION.ERRORED,
|
|
170
|
+
actionSubject: ACTION_SUBJECT.CODE_BLOCK,
|
|
171
|
+
attributes: {
|
|
172
|
+
errorType: result.errorType,
|
|
173
|
+
language: result.language,
|
|
174
|
+
languageSource
|
|
175
|
+
},
|
|
176
|
+
eventType: EVENT_TYPE.TRACK
|
|
177
|
+
})(tr);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 ? void 0 : editorAnalyticsAPI.attachAnalyticsEvent({
|
|
181
|
+
action: ACTION.FORMATTED,
|
|
182
|
+
actionSubject: ACTION_SUBJECT.CODE_BLOCK,
|
|
183
|
+
attributes: {
|
|
184
|
+
language: result.language,
|
|
185
|
+
languageSource,
|
|
186
|
+
outcome: result.status
|
|
187
|
+
},
|
|
188
|
+
eventType: EVENT_TYPE.TRACK
|
|
189
|
+
})(tr);
|
|
190
|
+
};
|
|
191
|
+
const createResolveFormatCodeTransaction = ({
|
|
192
|
+
editorAnalyticsAPI,
|
|
193
|
+
localId,
|
|
194
|
+
pendingFormat,
|
|
195
|
+
result,
|
|
196
|
+
tr
|
|
197
|
+
}) => {
|
|
198
|
+
const {
|
|
199
|
+
languageSource,
|
|
200
|
+
requestId
|
|
201
|
+
} = pendingFormat;
|
|
202
|
+
const codeBlockNode = tr.doc.nodeAt(pendingFormat.pos);
|
|
203
|
+
const 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;
|
|
204
|
+
if (!hasMatchingCodeBlock) {
|
|
205
|
+
// Keep failure telemetry even when the target block is no longer available.
|
|
206
|
+
if (result.status === 'failed') {
|
|
207
|
+
attachFormatCodeAnalytics({
|
|
208
|
+
editorAnalyticsAPI,
|
|
209
|
+
languageSource,
|
|
210
|
+
result,
|
|
211
|
+
tr
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
return setResolveFormatCodeMeta(tr, {
|
|
215
|
+
languageSource,
|
|
216
|
+
localId,
|
|
217
|
+
outcome: 'unchanged',
|
|
218
|
+
requestId
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
let resultTransaction = tr;
|
|
222
|
+
if (result.status === 'formatted') {
|
|
223
|
+
resultTransaction = replaceCodeBlockText({
|
|
224
|
+
codeBlockNode,
|
|
225
|
+
content: result.content,
|
|
226
|
+
pos: pendingFormat.pos,
|
|
227
|
+
tr
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
attachFormatCodeAnalytics({
|
|
231
|
+
editorAnalyticsAPI,
|
|
232
|
+
languageSource,
|
|
233
|
+
result,
|
|
234
|
+
tr: resultTransaction
|
|
235
|
+
});
|
|
236
|
+
return setResolveFormatCodeMeta(resultTransaction, {
|
|
237
|
+
errorType: result.status === 'failed' ? result.errorType : undefined,
|
|
238
|
+
languageSource,
|
|
239
|
+
localId,
|
|
240
|
+
outcome: result.status,
|
|
241
|
+
requestId
|
|
242
|
+
});
|
|
243
|
+
};
|
|
244
|
+
export const createFormatCodeOnClick = ({
|
|
245
|
+
api,
|
|
246
|
+
editorAnalyticsAPI
|
|
247
|
+
}) => (state, dispatch) => {
|
|
248
|
+
var _autoDetectPluginKey$2, _api$core;
|
|
249
|
+
const currentCodeBlockState = pluginKey.getState(state);
|
|
250
|
+
const currentPos = currentCodeBlockState === null || currentCodeBlockState === void 0 ? void 0 : currentCodeBlockState.pos;
|
|
251
|
+
if (!currentCodeBlockState || typeof currentPos !== 'number') {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
const currentNode = state.doc.nodeAt(currentPos);
|
|
255
|
+
if (!currentNode || currentNode.type !== state.schema.nodes.codeBlock) {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
const currentLanguage = currentNode.attrs.language;
|
|
259
|
+
if (!isSupportedFormatLanguage(currentLanguage)) {
|
|
260
|
+
return true;
|
|
261
|
+
}
|
|
262
|
+
const currentLocalId = currentNode.attrs.localId;
|
|
263
|
+
if (currentCodeBlockState.pendingFormats[currentLocalId]) {
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
const autoDetectEntry = (_autoDetectPluginKey$2 = autoDetectPluginKey.getState(state)) === null || _autoDetectPluginKey$2 === void 0 ? void 0 : _autoDetectPluginKey$2.languageDetectionMap[currentLocalId];
|
|
267
|
+
const languageSource = (autoDetectEntry === null || autoDetectEntry === void 0 ? void 0 : autoDetectEntry.autoDetectedLanguage) === currentLanguage ? 'auto-detected' : 'selected';
|
|
268
|
+
const content = currentNode.textContent;
|
|
269
|
+
const requestId = crypto.randomUUID();
|
|
270
|
+
api === null || api === void 0 ? void 0 : (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute(({
|
|
271
|
+
tr
|
|
272
|
+
}) => tr.setMeta(pluginKey, {
|
|
273
|
+
type: ACTIONS.START_FORMAT_CODE,
|
|
274
|
+
data: {
|
|
275
|
+
languageSource,
|
|
276
|
+
localId: currentLocalId,
|
|
277
|
+
pos: currentPos,
|
|
278
|
+
requestId
|
|
279
|
+
}
|
|
280
|
+
}));
|
|
281
|
+
void formatCode({
|
|
282
|
+
content,
|
|
283
|
+
language: currentLanguage
|
|
284
|
+
}).then(result => {
|
|
285
|
+
var _api$codeBlock, _api$codeBlock$shared, _api$core2;
|
|
286
|
+
const pendingFormat = api === null || api === void 0 ? void 0 : (_api$codeBlock = api.codeBlock) === null || _api$codeBlock === void 0 ? void 0 : (_api$codeBlock$shared = _api$codeBlock.sharedState.currentState()) === null || _api$codeBlock$shared === void 0 ? void 0 : _api$codeBlock$shared.pendingFormats[currentLocalId];
|
|
287
|
+
if (!pendingFormat || pendingFormat.requestId !== requestId) {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
api === null || api === void 0 ? void 0 : (_api$core2 = api.core) === null || _api$core2 === void 0 ? void 0 : _api$core2.actions.execute(({
|
|
291
|
+
tr
|
|
292
|
+
}) => createResolveFormatCodeTransaction({
|
|
293
|
+
editorAnalyticsAPI,
|
|
294
|
+
localId: currentLocalId,
|
|
295
|
+
pendingFormat,
|
|
296
|
+
result,
|
|
297
|
+
tr
|
|
298
|
+
}));
|
|
299
|
+
});
|
|
300
|
+
return true;
|
|
301
|
+
};
|
|
126
302
|
export const copyContentToClipboardWithAnalytics = editorAnalyticsAPI => (state, dispatch) => {
|
|
127
303
|
const {
|
|
128
304
|
schema: {
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
export const 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
|
-
|
|
6
|
-
|
|
8
|
+
SET_SHOULD_IGNORE_FOLLOWING_MUTATIONS: 'SET_SHOULD_IGNORE_FOLLOWING_MUTATIONS',
|
|
9
|
+
START_FORMAT_CODE: 'START_FORMAT_CODE'
|
|
7
10
|
};
|
|
@@ -10,6 +10,7 @@ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
|
|
|
10
10
|
import { ignoreFollowingMutations, resetShouldIgnoreFollowingMutations } from '../editor-commands';
|
|
11
11
|
import { codeBlockNodeView } from '../nodeviews/code-block';
|
|
12
12
|
import { codeBlockClassNames } from '../ui/class-names';
|
|
13
|
+
import { applyFormatCodeMeta, mapPendingFormats } from '../utils/format-code/format-code-state';
|
|
13
14
|
import { ACTIONS } from './actions';
|
|
14
15
|
import { generateInitialDecorations, updateCodeBlockDecorations, updateDecorationSetWithWordWrappedDecorator } from './decorators';
|
|
15
16
|
import { pluginKey } from './plugin-key';
|
|
@@ -81,7 +82,9 @@ export const createPlugin = ({
|
|
|
81
82
|
return {
|
|
82
83
|
pos: node ? node.pos : null,
|
|
83
84
|
contentCopied: false,
|
|
85
|
+
formatCodeErrors: {},
|
|
84
86
|
isNodeSelected: false,
|
|
87
|
+
pendingFormats: {},
|
|
85
88
|
shouldIgnoreFollowingMutations: false,
|
|
86
89
|
decorations: DecorationSet.create(state.doc, initialDecorations)
|
|
87
90
|
};
|
|
@@ -116,7 +119,17 @@ export const createPlugin = ({
|
|
|
116
119
|
isNodeSelected: tr.selection instanceof NodeSelection,
|
|
117
120
|
decorations: updatedDecorationSet
|
|
118
121
|
};
|
|
119
|
-
|
|
122
|
+
if (!expValEquals('platform_editor_code_block_q4_lovability', 'isEnabled', true)) {
|
|
123
|
+
return newPluginState;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Successful format results change the doc and carry format meta.
|
|
127
|
+
const formatCodePluginState = applyFormatCodeMeta(newPluginState, meta);
|
|
128
|
+
return {
|
|
129
|
+
...formatCodePluginState,
|
|
130
|
+
// Pending format requests can outlive unrelated document edits.
|
|
131
|
+
pendingFormats: mapPendingFormats(formatCodePluginState.pendingFormats, tr, newState)
|
|
132
|
+
};
|
|
120
133
|
}
|
|
121
134
|
if (tr.selectionSet) {
|
|
122
135
|
const node = findCodeBlock(newState, tr.selection);
|
|
@@ -138,6 +151,10 @@ export const createPlugin = ({
|
|
|
138
151
|
shouldIgnoreFollowingMutations: meta.data
|
|
139
152
|
};
|
|
140
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
|
+
}
|
|
141
158
|
return pluginState;
|
|
142
159
|
}
|
|
143
160
|
},
|
|
@@ -3,16 +3,18 @@ import { areCodeBlockLineNumbersVisible, isCodeBlockWordWrapEnabled } from '@atl
|
|
|
3
3
|
import commonMessages, { codeBlockButtonMessages } from '@atlaskit/editor-common/messages';
|
|
4
4
|
import { areToolbarFlagsEnabled } from '@atlaskit/editor-common/toolbar-flag-check';
|
|
5
5
|
import { findDomRefAtPos } from '@atlaskit/editor-prosemirror/utils';
|
|
6
|
+
import AngleBracketsIcon from '@atlaskit/icon/core/angle-brackets';
|
|
6
7
|
import CopyIcon from '@atlaskit/icon/core/copy';
|
|
7
8
|
import DeleteIcon from '@atlaskit/icon/core/delete';
|
|
8
9
|
import ListNumberedIcon from '@atlaskit/icon/core/list-numbered';
|
|
9
10
|
import TextWrapIcon from '@atlaskit/icon/core/text-wrap';
|
|
10
11
|
import { fg } from '@atlaskit/platform-feature-flags';
|
|
11
12
|
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
|
|
12
|
-
import { changeLanguage, copyContentToClipboardWithAnalytics, removeCodeBlockWithAnalytics, resetCopiedState, toggleLineNumbersForCodeBlockNode, toggleWordWrapStateForCodeBlockNode } from '../editor-commands';
|
|
13
|
+
import { changeLanguage, copyContentToClipboardWithAnalytics, createFormatCodeOnClick, removeCodeBlockWithAnalytics, resetCopiedState, toggleLineNumbersForCodeBlockNode, toggleWordWrapStateForCodeBlockNode } from '../editor-commands';
|
|
13
14
|
import { CodeBlockLanguagePicker } from '../ui/CodeBlockLanguagePicker';
|
|
14
15
|
import { WrapIcon } from '../ui/icons/WrapIcon';
|
|
15
16
|
import { NONE_LANGUAGE_VALUE, PLAIN_TEXT_LANGUAGE_VALUE } from '../ui/language-picker-options';
|
|
17
|
+
import { isSupportedFormatLanguage, preloadFormatterOnIntent } from '../utils/format-code/formatter';
|
|
16
18
|
import { autoDetectPluginKey } from './auto-detect-state';
|
|
17
19
|
import { provideVisualFeedbackForCopyButton, removeVisualFeedbackForCopyButton } from './codeBlockCopySelectionPlugin';
|
|
18
20
|
import { createLanguageList, DEFAULT_LANGUAGES, getLanguageIdentifier } from './language-list';
|
|
@@ -79,7 +81,7 @@ export const getToolbarConfig = (allowCopyToClipboard = false, api, overrideLang
|
|
|
79
81
|
const language = node === null || node === void 0 ? void 0 : (_node$attrs = node.attrs) === null || _node$attrs === void 0 ? void 0 : _node$attrs.language;
|
|
80
82
|
const localId = node === null || node === void 0 ? void 0 : (_node$attrs2 = node.attrs) === null || _node$attrs2 === void 0 ? void 0 : _node$attrs2.localId;
|
|
81
83
|
const autoDetectState = autoDetectPluginKey.getState(state);
|
|
82
|
-
|
|
84
|
+
const isFormatCodePending = typeof localId === 'string' && Boolean(codeBlockState.pendingFormats[localId]);
|
|
83
85
|
// Keep fresh option objects for the legacy toolbar select so reopening it
|
|
84
86
|
// continues to start from the top rather than preserving the previously
|
|
85
87
|
// focused option by reference.
|
|
@@ -223,13 +225,30 @@ export const getToolbarConfig = (allowCopyToClipboard = false, api, overrideLang
|
|
|
223
225
|
tabIndex: null,
|
|
224
226
|
selected: areLineNumbersVisible
|
|
225
227
|
};
|
|
228
|
+
const canFormatCode = node.textContent.length > 0 && isSupportedFormatLanguage(language);
|
|
229
|
+
const formatCodeButton = {
|
|
230
|
+
id: 'editor.codeBlock.formatCode',
|
|
231
|
+
type: 'button',
|
|
232
|
+
supportsViewMode: false,
|
|
233
|
+
disabled: !canFormatCode || isFormatCodePending,
|
|
234
|
+
icon: AngleBracketsIcon,
|
|
235
|
+
onClick: createFormatCodeOnClick({
|
|
236
|
+
api,
|
|
237
|
+
editorAnalyticsAPI
|
|
238
|
+
}),
|
|
239
|
+
onFocus: preloadFormatterOnIntent(),
|
|
240
|
+
onMouseEnter: preloadFormatterOnIntent(),
|
|
241
|
+
title: formatMessage(canFormatCode ? codeBlockButtonMessages.formatCode : codeBlockButtonMessages.formatCodeUnavailable)
|
|
242
|
+
};
|
|
226
243
|
return {
|
|
227
244
|
title: 'CodeBlock floating controls',
|
|
228
245
|
// Ignored via go/ees005
|
|
229
246
|
// eslint-disable-next-line @atlaskit/editor/no-as-casting
|
|
230
247
|
getDomRef: view => findDomRefAtPos(pos, view.domAtPos.bind(view)),
|
|
231
248
|
nodeType,
|
|
232
|
-
items: [(_languagePicker = languagePicker) !== null && _languagePicker !== void 0 ? _languagePicker : languageSelect, ...(areAnyNewToolbarFlagsEnabled ? [] : [separator]), codeBlockWrapButton, ...(expValEquals('platform_editor_code_block_q4_lovability', 'isEnabled', true) && fg('platform_editor_code_block_add_line_number_button') ? [codeBlockLineNumbersButton] : []),
|
|
249
|
+
items: [(_languagePicker = languagePicker) !== null && _languagePicker !== void 0 ? _languagePicker : languageSelect, ...(areAnyNewToolbarFlagsEnabled ? [] : [separator]), codeBlockWrapButton, ...(expValEquals('platform_editor_code_block_q4_lovability', 'isEnabled', true) && fg('platform_editor_code_block_add_line_number_button') ? [codeBlockLineNumbersButton] : []),
|
|
250
|
+
// eslint-disable-next-line @atlaskit/platform/no-preconditioning
|
|
251
|
+
...(expValEquals('platform_editor_code_block_q4_lovability', 'isEnabled', true) && fg('platform_editor_code_block_add_line_number_button') && fg('platform_editor_code_block_formatting') ? [formatCodeButton] : []), ...copyAndDeleteButtonMenuItems],
|
|
233
252
|
scrollable: true
|
|
234
253
|
};
|
|
235
254
|
};
|
|
@@ -0,0 +1,62 @@
|
|
|
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
|
+
const FormatCodeErrorFlagItem = ({
|
|
10
|
+
formatCodeError
|
|
11
|
+
}) => {
|
|
12
|
+
const {
|
|
13
|
+
formatMessage
|
|
14
|
+
} = useIntl();
|
|
15
|
+
return /*#__PURE__*/React.createElement(AkFlag, {
|
|
16
|
+
description: formatMessage(formatCodeError.languageSource === 'auto-detected' ? codeBlockButtonMessages.formatCodeFailedAutoDetectedDescription : codeBlockButtonMessages.formatCodeFailedDescription),
|
|
17
|
+
icon: /*#__PURE__*/React.createElement(StatusErrorIcon, {
|
|
18
|
+
color: "var(--ds-icon-danger, #C9372C)",
|
|
19
|
+
label: ""
|
|
20
|
+
}),
|
|
21
|
+
id: formatCodeError.localId,
|
|
22
|
+
title: formatMessage(codeBlockButtonMessages.formatCodeFailed)
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
export const FormatCodeErrorFlag = ({
|
|
26
|
+
api
|
|
27
|
+
}) => {
|
|
28
|
+
const {
|
|
29
|
+
formatCodeErrors
|
|
30
|
+
} = useSharedPluginStateWithSelector(api, ['codeBlock'], states => {
|
|
31
|
+
var _states$codeBlockStat, _states$codeBlockStat2;
|
|
32
|
+
return {
|
|
33
|
+
formatCodeErrors: (_states$codeBlockStat = (_states$codeBlockStat2 = states.codeBlockState) === null || _states$codeBlockStat2 === void 0 ? void 0 : _states$codeBlockStat2.formatCodeErrors) !== null && _states$codeBlockStat !== void 0 ? _states$codeBlockStat : {}
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
const onDismissed = useCallback(localId => {
|
|
37
|
+
var _api$core, _api$core2;
|
|
38
|
+
api === null || api === void 0 ? void 0 : (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute(({
|
|
39
|
+
tr
|
|
40
|
+
}) => {
|
|
41
|
+
tr.setMeta(pluginKey, {
|
|
42
|
+
type: ACTIONS.CLEAR_FORMAT_CODE_ERROR,
|
|
43
|
+
data: {
|
|
44
|
+
localId
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
return tr;
|
|
48
|
+
});
|
|
49
|
+
api === null || api === void 0 ? void 0 : (_api$core2 = api.core) === null || _api$core2 === void 0 ? void 0 : _api$core2.actions.focus();
|
|
50
|
+
}, [api]);
|
|
51
|
+
const onFlagGroupDismissed = useCallback(localId => onDismissed(String(localId)), [onDismissed]);
|
|
52
|
+
const activeFormatCodeErrors = Object.values(formatCodeErrors);
|
|
53
|
+
if (activeFormatCodeErrors.length === 0) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
return /*#__PURE__*/React.createElement(FlagGroup, {
|
|
57
|
+
onDismissed: onFlagGroupDismissed
|
|
58
|
+
}, activeFormatCodeErrors.map(formatCodeError => /*#__PURE__*/React.createElement(FormatCodeErrorFlagItem, {
|
|
59
|
+
formatCodeError: formatCodeError,
|
|
60
|
+
key: formatCodeError.localId
|
|
61
|
+
})));
|
|
62
|
+
};
|
|
@@ -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,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
|
+
};
|