@atlaskit/editor-plugin-code-block 12.1.6 → 12.1.8
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 +14 -0
- package/dist/cjs/editor-commands/index.js +5 -3
- package/dist/cjs/pm-plugins/toolbar.js +23 -24
- package/dist/cjs/ui/CodeBlockLanguagePicker.js +45 -0
- package/dist/cjs/ui/LanguagePicker.js +63 -10
- package/dist/cjs/ui/language-picker-options.js +21 -4
- package/dist/cjs/ui/recent-languages.js +50 -0
- package/dist/es2019/editor-commands/index.js +5 -2
- package/dist/es2019/pm-plugins/toolbar.js +168 -167
- package/dist/es2019/ui/CodeBlockLanguagePicker.js +31 -0
- package/dist/es2019/ui/LanguagePicker.js +41 -9
- package/dist/es2019/ui/language-picker-options.js +15 -4
- package/dist/es2019/ui/recent-languages.js +38 -0
- package/dist/esm/editor-commands/index.js +5 -3
- package/dist/esm/pm-plugins/toolbar.js +24 -25
- package/dist/esm/ui/CodeBlockLanguagePicker.js +36 -0
- package/dist/esm/ui/LanguagePicker.js +64 -11
- package/dist/esm/ui/language-picker-options.js +21 -4
- package/dist/esm/ui/recent-languages.js +43 -0
- package/dist/types/editor-commands/index.d.ts +2 -1
- package/dist/types/ui/CodeBlockLanguagePicker.d.ts +5 -0
- package/dist/types/ui/LanguagePicker.d.ts +7 -5
- package/dist/types/ui/language-picker-options.d.ts +2 -0
- package/dist/types/ui/recent-languages.d.ts +4 -0
- package/dist/types-ts4.5/editor-commands/index.d.ts +2 -1
- package/dist/types-ts4.5/ui/CodeBlockLanguagePicker.d.ts +5 -0
- package/dist/types-ts4.5/ui/LanguagePicker.d.ts +7 -5
- package/dist/types-ts4.5/ui/language-picker-options.d.ts +2 -0
- package/dist/types-ts4.5/ui/recent-languages.d.ts +4 -0
- package/package.json +5 -4
|
@@ -10,189 +10,190 @@ import TextWrapIcon from '@atlaskit/icon/core/text-wrap';
|
|
|
10
10
|
import { fg } from '@atlaskit/platform-feature-flags';
|
|
11
11
|
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
|
|
12
12
|
import { changeLanguage, copyContentToClipboardWithAnalytics, removeCodeBlockWithAnalytics, resetCopiedState, toggleLineNumbersForCodeBlockNode, toggleWordWrapStateForCodeBlockNode } from '../editor-commands';
|
|
13
|
+
import { CodeBlockLanguagePicker } from '../ui/CodeBlockLanguagePicker';
|
|
13
14
|
import { WrapIcon } from '../ui/icons/WrapIcon';
|
|
14
|
-
import {
|
|
15
|
-
import { LanguagePicker } from '../ui/LanguagePicker';
|
|
15
|
+
import { NONE_LANGUAGE_VALUE, PLAIN_TEXT_LANGUAGE_VALUE } from '../ui/language-picker-options';
|
|
16
16
|
import { provideVisualFeedbackForCopyButton, removeVisualFeedbackForCopyButton } from './codeBlockCopySelectionPlugin';
|
|
17
17
|
import { createLanguageList, DEFAULT_LANGUAGES, getLanguageIdentifier } from './language-list';
|
|
18
18
|
import { pluginKey } from './plugin-key';
|
|
19
|
-
export const getToolbarConfig = (allowCopyToClipboard = false, api, overrideLanguageName = undefined) =>
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const isViewMode = (api === null || api === void 0 ? void 0 : (_api$editorViewMode = api.editorViewMode) === null || _api$editorViewMode === void 0 ? void 0 : (_api$editorViewMode$s = _api$editorViewMode.sharedState.currentState()) === null || _api$editorViewMode$s === void 0 ? void 0 : _api$editorViewMode$s.mode) === 'view';
|
|
24
|
-
const {
|
|
25
|
-
hoverDecoration
|
|
26
|
-
} = (_api$decorations$acti = api === null || api === void 0 ? void 0 : (_api$decorations = api.decorations) === null || _api$decorations === void 0 ? void 0 : _api$decorations.actions) !== null && _api$decorations$acti !== void 0 ? _api$decorations$acti : {};
|
|
27
|
-
const editorAnalyticsAPI = api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions;
|
|
28
|
-
const codeBlockState = pluginKey.getState(state);
|
|
29
|
-
const pos = (_codeBlockState$pos = codeBlockState === null || codeBlockState === void 0 ? void 0 : codeBlockState.pos) !== null && _codeBlockState$pos !== void 0 ? _codeBlockState$pos : null;
|
|
30
|
-
if (!codeBlockState || pos === null) {
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
const node = state.doc.nodeAt(pos);
|
|
34
|
-
const nodeType = state.schema.nodes.codeBlock;
|
|
35
|
-
if ((node === null || node === void 0 ? void 0 : node.type) !== nodeType) {
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
const isWrapped = isCodeBlockWordWrapEnabled(node);
|
|
39
|
-
const areLineNumbersVisible = areCodeBlockLineNumbersVisible(node);
|
|
40
|
-
const language = node === null || node === void 0 ? void 0 : (_node$attrs = node.attrs) === null || _node$attrs === void 0 ? void 0 : _node$attrs.language;
|
|
41
|
-
const languageList = createLanguageList(overrideLanguageName ? DEFAULT_LANGUAGES.map(language => ({
|
|
42
|
-
...language,
|
|
43
|
-
name: overrideLanguageName(language.name)
|
|
19
|
+
export const getToolbarConfig = (allowCopyToClipboard = false, api, overrideLanguageName = undefined) => {
|
|
20
|
+
const languageList = createLanguageList(overrideLanguageName ? DEFAULT_LANGUAGES.map(languageOption => ({
|
|
21
|
+
...languageOption,
|
|
22
|
+
name: overrideLanguageName(languageOption.name)
|
|
44
23
|
})) : DEFAULT_LANGUAGES);
|
|
45
|
-
const
|
|
24
|
+
const languagePickerOptions = languageList.map(lang => ({
|
|
46
25
|
label: lang.name,
|
|
47
26
|
value: getLanguageIdentifier(lang),
|
|
48
27
|
alias: lang.alias
|
|
49
28
|
}));
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
29
|
+
return (state, {
|
|
30
|
+
formatMessage
|
|
31
|
+
}) => {
|
|
32
|
+
var _api$editorViewMode, _api$editorViewMode$s, _api$decorations$acti, _api$decorations, _api$analytics, _codeBlockState$pos, _node$attrs, _languagePicker;
|
|
33
|
+
const isViewMode = (api === null || api === void 0 ? void 0 : (_api$editorViewMode = api.editorViewMode) === null || _api$editorViewMode === void 0 ? void 0 : (_api$editorViewMode$s = _api$editorViewMode.sharedState.currentState()) === null || _api$editorViewMode$s === void 0 ? void 0 : _api$editorViewMode$s.mode) === 'view';
|
|
34
|
+
const {
|
|
35
|
+
hoverDecoration
|
|
36
|
+
} = (_api$decorations$acti = api === null || api === void 0 ? void 0 : (_api$decorations = api.decorations) === null || _api$decorations === void 0 ? void 0 : _api$decorations.actions) !== null && _api$decorations$acti !== void 0 ? _api$decorations$acti : {};
|
|
37
|
+
const editorAnalyticsAPI = api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions;
|
|
38
|
+
const codeBlockState = pluginKey.getState(state);
|
|
39
|
+
const pos = (_codeBlockState$pos = codeBlockState === null || codeBlockState === void 0 ? void 0 : codeBlockState.pos) !== null && _codeBlockState$pos !== void 0 ? _codeBlockState$pos : null;
|
|
40
|
+
if (!codeBlockState || pos === null) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const node = state.doc.nodeAt(pos);
|
|
44
|
+
const nodeType = state.schema.nodes.codeBlock;
|
|
45
|
+
if ((node === null || node === void 0 ? void 0 : node.type) !== nodeType) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const isWrapped = isCodeBlockWordWrapEnabled(node);
|
|
49
|
+
const areLineNumbersVisible = areCodeBlockLineNumbersVisible(node);
|
|
50
|
+
const language = node === null || node === void 0 ? void 0 : (_node$attrs = node.attrs) === null || _node$attrs === void 0 ? void 0 : _node$attrs.language;
|
|
51
|
+
// Keep fresh option objects for the legacy toolbar select so reopening it
|
|
52
|
+
// continues to start from the top rather than preserving the previously
|
|
53
|
+
// focused option by reference.
|
|
54
|
+
const languageSelectOptions = languagePickerOptions.map(option => ({
|
|
55
|
+
...option
|
|
56
|
+
}));
|
|
57
|
+
const defaultValue = language ? languageSelectOptions.find(option => option.value === language) || languageSelectOptions.find(option => option.alias.includes(language)) : null;
|
|
58
|
+
const languageSelect = {
|
|
59
|
+
id: 'editor.codeBlock.languageOptions',
|
|
60
|
+
type: 'select',
|
|
61
|
+
selectType: 'list',
|
|
62
|
+
onChange: option => changeLanguage(editorAnalyticsAPI)(option.value),
|
|
63
|
+
defaultValue,
|
|
64
|
+
placeholder: formatMessage(codeBlockButtonMessages.selectLanguage),
|
|
65
|
+
options: languageSelectOptions,
|
|
66
|
+
filterOption: languageListFilter
|
|
67
|
+
};
|
|
68
|
+
let languagePicker;
|
|
69
|
+
if (expValEquals('platform_editor_code_block_q4_lovability', 'isEnabled', true) && fg('platform_editor_code_block_add_line_number_button')) {
|
|
70
|
+
const defaultPickerValue = language ? languagePickerOptions.find(option => language === NONE_LANGUAGE_VALUE ? option.value === PLAIN_TEXT_LANGUAGE_VALUE : option.value === language || option.alias.includes(language)) : undefined;
|
|
71
|
+
languagePicker = {
|
|
72
|
+
type: 'custom',
|
|
73
|
+
fallback: [],
|
|
74
|
+
render: view => {
|
|
75
|
+
if (!view) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
return /*#__PURE__*/React.createElement(CodeBlockLanguagePicker, {
|
|
79
|
+
api: api,
|
|
80
|
+
defaultValue: defaultPickerValue,
|
|
81
|
+
editorView: view,
|
|
82
|
+
filterOption: languageListFilter,
|
|
83
|
+
formatMessage: formatMessage,
|
|
84
|
+
languagePickerOptions: languagePickerOptions
|
|
85
|
+
});
|
|
77
86
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
filterOption: languageListFilter,
|
|
83
|
-
formatMessage: formatMessage,
|
|
84
|
-
options: groupedOptions
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
const separator = {
|
|
90
|
+
type: 'separator'
|
|
87
91
|
};
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
if (areAnyNewToolbarFlagsEnabled) {
|
|
115
|
-
const overflowMenuOptions = [{
|
|
116
|
-
title: formatMessage(commonMessages.delete),
|
|
117
|
-
icon: DeleteIcon({
|
|
118
|
-
label: ''
|
|
119
|
-
}),
|
|
120
|
-
onMouseEnter: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(nodeType, true),
|
|
121
|
-
onMouseLeave: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(nodeType, false),
|
|
122
|
-
onFocus: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(nodeType, true),
|
|
123
|
-
onBlur: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(nodeType, false),
|
|
124
|
-
onClick: removeCodeBlockWithAnalytics(editorAnalyticsAPI)
|
|
125
|
-
}];
|
|
126
|
-
if (allowCopyToClipboard) {
|
|
127
|
-
overflowMenuOptions.unshift({
|
|
128
|
-
title: formatMessage(commonMessages.copyToClipboard),
|
|
129
|
-
onClick: copyContentToClipboardWithAnalytics(editorAnalyticsAPI),
|
|
130
|
-
icon: CopyIcon({
|
|
92
|
+
const areAnyNewToolbarFlagsEnabled = areToolbarFlagsEnabled(Boolean(api === null || api === void 0 ? void 0 : api.toolbar));
|
|
93
|
+
const copyToClipboardItems = !allowCopyToClipboard ? [] : [{
|
|
94
|
+
id: 'editor.codeBlock.copy',
|
|
95
|
+
type: 'button',
|
|
96
|
+
supportsViewMode: true,
|
|
97
|
+
appearance: 'subtle',
|
|
98
|
+
icon: CopyIcon,
|
|
99
|
+
// note: copyContentToClipboardWithAnalytics contains logic that also removes the
|
|
100
|
+
// visual feedback for the copy button
|
|
101
|
+
onClick: copyContentToClipboardWithAnalytics(editorAnalyticsAPI),
|
|
102
|
+
title: formatMessage(codeBlockState.contentCopied ? codeBlockButtonMessages.copiedCodeToClipboard : codeBlockButtonMessages.copyCodeToClipboard),
|
|
103
|
+
onMouseEnter: provideVisualFeedbackForCopyButton,
|
|
104
|
+
// note: resetCopiedState contains logic that also removes the
|
|
105
|
+
// visual feedback for the copy button
|
|
106
|
+
onMouseLeave: resetCopiedState,
|
|
107
|
+
onFocus: provideVisualFeedbackForCopyButton,
|
|
108
|
+
onBlur: removeVisualFeedbackForCopyButton,
|
|
109
|
+
hideTooltipOnClick: false,
|
|
110
|
+
disabled: codeBlockState.isNodeSelected,
|
|
111
|
+
tabIndex: null
|
|
112
|
+
}, separator];
|
|
113
|
+
let copyAndDeleteButtonMenuItems = [];
|
|
114
|
+
if (areAnyNewToolbarFlagsEnabled) {
|
|
115
|
+
const overflowMenuOptions = [{
|
|
116
|
+
title: formatMessage(commonMessages.delete),
|
|
117
|
+
icon: DeleteIcon({
|
|
131
118
|
label: ''
|
|
132
119
|
}),
|
|
133
|
-
onMouseEnter:
|
|
134
|
-
onMouseLeave:
|
|
135
|
-
onFocus:
|
|
136
|
-
onBlur:
|
|
137
|
-
|
|
138
|
-
}
|
|
120
|
+
onMouseEnter: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(nodeType, true),
|
|
121
|
+
onMouseLeave: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(nodeType, false),
|
|
122
|
+
onFocus: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(nodeType, true),
|
|
123
|
+
onBlur: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(nodeType, false),
|
|
124
|
+
onClick: removeCodeBlockWithAnalytics(editorAnalyticsAPI)
|
|
125
|
+
}];
|
|
126
|
+
if (allowCopyToClipboard) {
|
|
127
|
+
overflowMenuOptions.unshift({
|
|
128
|
+
title: formatMessage(commonMessages.copyToClipboard),
|
|
129
|
+
onClick: copyContentToClipboardWithAnalytics(editorAnalyticsAPI),
|
|
130
|
+
icon: CopyIcon({
|
|
131
|
+
label: ''
|
|
132
|
+
}),
|
|
133
|
+
onMouseEnter: provideVisualFeedbackForCopyButton,
|
|
134
|
+
onMouseLeave: resetCopiedState,
|
|
135
|
+
onFocus: provideVisualFeedbackForCopyButton,
|
|
136
|
+
onBlur: removeVisualFeedbackForCopyButton,
|
|
137
|
+
disabled: codeBlockState.isNodeSelected
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
copyAndDeleteButtonMenuItems = isViewMode ? [...copyToClipboardItems] : [{
|
|
141
|
+
type: 'separator',
|
|
142
|
+
fullHeight: true
|
|
143
|
+
}, {
|
|
144
|
+
type: 'overflow-dropdown',
|
|
145
|
+
testId: 'code-block-overflow-dropdown-trigger',
|
|
146
|
+
options: overflowMenuOptions
|
|
147
|
+
}];
|
|
148
|
+
} else {
|
|
149
|
+
const deleteButton = {
|
|
150
|
+
id: 'editor.codeBlock.delete',
|
|
151
|
+
type: 'button',
|
|
152
|
+
appearance: 'danger',
|
|
153
|
+
icon: DeleteIcon,
|
|
154
|
+
iconFallback: DeleteIcon,
|
|
155
|
+
onMouseEnter: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(nodeType, true),
|
|
156
|
+
onMouseLeave: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(nodeType, false),
|
|
157
|
+
onFocus: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(nodeType, true),
|
|
158
|
+
onBlur: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(nodeType, false),
|
|
159
|
+
onClick: removeCodeBlockWithAnalytics(editorAnalyticsAPI),
|
|
160
|
+
title: formatMessage(commonMessages.remove),
|
|
161
|
+
tabIndex: null
|
|
162
|
+
};
|
|
163
|
+
copyAndDeleteButtonMenuItems = [separator, ...copyToClipboardItems, deleteButton];
|
|
139
164
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}, {
|
|
144
|
-
type: 'overflow-dropdown',
|
|
145
|
-
testId: 'code-block-overflow-dropdown-trigger',
|
|
146
|
-
options: overflowMenuOptions
|
|
147
|
-
}];
|
|
148
|
-
} else {
|
|
149
|
-
const deleteButton = {
|
|
150
|
-
id: 'editor.codeBlock.delete',
|
|
165
|
+
const codeBlockWrapButtonTitle = expValEquals('platform_editor_code_block_q4_lovability', 'isEnabled', true) ? formatMessage(isWrapped ? codeBlockButtonMessages.unwrapCodeLabel : codeBlockButtonMessages.wrapCodeLabel) : formatMessage(codeBlockButtonMessages.wrapCode);
|
|
166
|
+
const codeBlockWrapButton = {
|
|
167
|
+
id: 'editor.codeBlock.wrap',
|
|
151
168
|
type: 'button',
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
169
|
+
// Toggling button now writes to ADF, hence it should be available in view mode
|
|
170
|
+
supportsViewMode: !expValEquals('platform_editor_code_block_q4_lovability', 'isEnabled', true),
|
|
171
|
+
icon: TextWrapIcon,
|
|
172
|
+
iconFallback: WrapIcon,
|
|
173
|
+
onClick: toggleWordWrapStateForCodeBlockNode(editorAnalyticsAPI),
|
|
174
|
+
title: codeBlockWrapButtonTitle,
|
|
175
|
+
tabIndex: null,
|
|
176
|
+
selected: isWrapped
|
|
177
|
+
};
|
|
178
|
+
const codeBlockLineNumbersButton = {
|
|
179
|
+
id: 'editor.codeBlock.lineNumbers',
|
|
180
|
+
type: 'button',
|
|
181
|
+
supportsViewMode: false,
|
|
182
|
+
icon: ListNumberedIcon,
|
|
183
|
+
onClick: toggleLineNumbersForCodeBlockNode(editorAnalyticsAPI),
|
|
184
|
+
title: formatMessage(areLineNumbersVisible ? codeBlockButtonMessages.hideLineNumbersLabel : codeBlockButtonMessages.showLineNumbersLabel),
|
|
185
|
+
tabIndex: null,
|
|
186
|
+
selected: areLineNumbersVisible
|
|
187
|
+
};
|
|
188
|
+
return {
|
|
189
|
+
title: 'CodeBlock floating controls',
|
|
190
|
+
// Ignored via go/ees005
|
|
191
|
+
// eslint-disable-next-line @atlaskit/editor/no-as-casting
|
|
192
|
+
getDomRef: view => findDomRefAtPos(pos, view.domAtPos.bind(view)),
|
|
193
|
+
nodeType,
|
|
194
|
+
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] : []), ...copyAndDeleteButtonMenuItems],
|
|
195
|
+
scrollable: true
|
|
162
196
|
};
|
|
163
|
-
copyAndDeleteButtonMenuItems = [separator, ...copyToClipboardItems, deleteButton];
|
|
164
|
-
}
|
|
165
|
-
const codeBlockWrapButtonTitle = expValEquals('platform_editor_code_block_q4_lovability', 'isEnabled', true) ? formatMessage(isWrapped ? codeBlockButtonMessages.unwrapCodeLabel : codeBlockButtonMessages.wrapCodeLabel) : formatMessage(codeBlockButtonMessages.wrapCode);
|
|
166
|
-
const codeBlockWrapButton = {
|
|
167
|
-
id: 'editor.codeBlock.wrap',
|
|
168
|
-
type: 'button',
|
|
169
|
-
// Toggling button now writes to ADF, hence it should be available in view mode
|
|
170
|
-
supportsViewMode: !expValEquals('platform_editor_code_block_q4_lovability', 'isEnabled', true),
|
|
171
|
-
icon: TextWrapIcon,
|
|
172
|
-
iconFallback: WrapIcon,
|
|
173
|
-
onClick: toggleWordWrapStateForCodeBlockNode(editorAnalyticsAPI),
|
|
174
|
-
title: codeBlockWrapButtonTitle,
|
|
175
|
-
tabIndex: null,
|
|
176
|
-
selected: isWrapped
|
|
177
|
-
};
|
|
178
|
-
const codeBlockLineNumbersButton = {
|
|
179
|
-
id: 'editor.codeBlock.lineNumbers',
|
|
180
|
-
type: 'button',
|
|
181
|
-
supportsViewMode: false,
|
|
182
|
-
icon: ListNumberedIcon,
|
|
183
|
-
onClick: toggleLineNumbersForCodeBlockNode(editorAnalyticsAPI),
|
|
184
|
-
title: formatMessage(areLineNumbersVisible ? codeBlockButtonMessages.hideLineNumbersLabel : codeBlockButtonMessages.showLineNumbersLabel),
|
|
185
|
-
tabIndex: null,
|
|
186
|
-
selected: areLineNumbersVisible
|
|
187
|
-
};
|
|
188
|
-
return {
|
|
189
|
-
title: 'CodeBlock floating controls',
|
|
190
|
-
// Ignored via go/ees005
|
|
191
|
-
// eslint-disable-next-line @atlaskit/editor/no-as-casting
|
|
192
|
-
getDomRef: view => findDomRefAtPos(pos, view.domAtPos.bind(view)),
|
|
193
|
-
nodeType,
|
|
194
|
-
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] : []), ...copyAndDeleteButtonMenuItems],
|
|
195
|
-
scrollable: true
|
|
196
197
|
};
|
|
197
198
|
};
|
|
198
199
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React, { useCallback, useState } from 'react';
|
|
2
|
+
import { LanguagePicker } from './LanguagePicker';
|
|
3
|
+
import { getRecentLanguages, saveRecentLanguage } from './recent-languages';
|
|
4
|
+
export const CodeBlockLanguagePicker = ({
|
|
5
|
+
api,
|
|
6
|
+
defaultValue,
|
|
7
|
+
editorView,
|
|
8
|
+
filterOption,
|
|
9
|
+
formatMessage,
|
|
10
|
+
languagePickerOptions
|
|
11
|
+
}) => {
|
|
12
|
+
const [recentLanguageValues, setRecentLanguageValues] = useState(() => getRecentLanguages());
|
|
13
|
+
const refreshRecentLanguages = useCallback(() => {
|
|
14
|
+
setRecentLanguageValues(getRecentLanguages());
|
|
15
|
+
}, []);
|
|
16
|
+
const handleLanguageSelect = useCallback(language => {
|
|
17
|
+
saveRecentLanguage(language);
|
|
18
|
+
setRecentLanguageValues(getRecentLanguages());
|
|
19
|
+
}, []);
|
|
20
|
+
return /*#__PURE__*/React.createElement(LanguagePicker, {
|
|
21
|
+
api: api,
|
|
22
|
+
defaultValue: defaultValue,
|
|
23
|
+
editorView: editorView,
|
|
24
|
+
filterOption: filterOption,
|
|
25
|
+
formatMessage: formatMessage,
|
|
26
|
+
languagePickerOptions: languagePickerOptions,
|
|
27
|
+
recentLanguageValues: recentLanguageValues,
|
|
28
|
+
onLanguageSelect: handleLanguageSelect,
|
|
29
|
+
onMenuOpen: refreshRecentLanguages
|
|
30
|
+
});
|
|
31
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* LanguagePicker.tsx generated by @compiled/babel-plugin v0.39.1 */
|
|
2
2
|
import "./LanguagePicker.compiled.css";
|
|
3
3
|
import { ax, ix } from "@compiled/react/runtime";
|
|
4
|
-
import React, { Fragment, useCallback, useMemo, useState } from 'react';
|
|
4
|
+
import React, { Fragment, useCallback, useMemo, useRef, useState } from 'react';
|
|
5
5
|
import Button from '@atlaskit/button/new';
|
|
6
6
|
import { codeBlockButtonMessages } from '@atlaskit/editor-common/messages';
|
|
7
7
|
import { akEditorLineHeight } from '@atlaskit/editor-shared-styles';
|
|
@@ -9,6 +9,7 @@ import ChevronDownIcon from '@atlaskit/icon/core/chevron-down';
|
|
|
9
9
|
import { Box } from '@atlaskit/primitives/compiled';
|
|
10
10
|
import { PopupSelect, components } from '@atlaskit/select';
|
|
11
11
|
import { changeLanguage } from '../editor-commands';
|
|
12
|
+
import { createGroupedLanguageOptions } from './language-picker-options';
|
|
12
13
|
const pickerOptionStyles = null;
|
|
13
14
|
const styles = {
|
|
14
15
|
divider: "_h7alglyw _179rglyw _mqm2ia51 _1bsb1osq",
|
|
@@ -47,29 +48,60 @@ const menuPopperProps = {
|
|
|
47
48
|
enabled: false
|
|
48
49
|
}]
|
|
49
50
|
};
|
|
51
|
+
const getRecentlyUsedLanguages = (recentLanguageValues, optionsByValue) => {
|
|
52
|
+
const recentlyUsedLanguages = [];
|
|
53
|
+
for (const recentLanguageValue of recentLanguageValues) {
|
|
54
|
+
const option = optionsByValue.get(recentLanguageValue);
|
|
55
|
+
if (option) {
|
|
56
|
+
recentlyUsedLanguages.push(option);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return recentlyUsedLanguages;
|
|
60
|
+
};
|
|
50
61
|
export const LanguagePicker = ({
|
|
51
62
|
api,
|
|
52
63
|
defaultValue,
|
|
53
64
|
editorView,
|
|
54
65
|
filterOption,
|
|
55
66
|
formatMessage,
|
|
56
|
-
|
|
67
|
+
languagePickerOptions,
|
|
68
|
+
recentLanguageValues = [],
|
|
69
|
+
onLanguageSelect,
|
|
70
|
+
onMenuOpen
|
|
57
71
|
}) => {
|
|
58
72
|
var _api$analytics, _defaultValue$label;
|
|
59
73
|
const editorAnalyticsAPI = api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions;
|
|
60
74
|
const label = (_defaultValue$label = defaultValue === null || defaultValue === void 0 ? void 0 : defaultValue.label) !== null && _defaultValue$label !== void 0 ? _defaultValue$label : formatMessage(codeBlockButtonMessages.selectLanguage);
|
|
61
75
|
const selectLanguageLabel = formatMessage(codeBlockButtonMessages.selectLanguage);
|
|
62
|
-
const [
|
|
76
|
+
const [hasSearchQuery, setHasSearchQuery] = useState(false);
|
|
77
|
+
const inputValueRef = useRef('');
|
|
78
|
+
const optionsByValue = useMemo(() => new Map(languagePickerOptions.map(option => [option.value, option])), [languagePickerOptions]);
|
|
79
|
+
const recentlyUsedLanguages = useMemo(() => getRecentlyUsedLanguages(recentLanguageValues, optionsByValue), [recentLanguageValues, optionsByValue]);
|
|
80
|
+
const options = useMemo(() => createGroupedLanguageOptions({
|
|
81
|
+
formatMessage,
|
|
82
|
+
languages: languagePickerOptions,
|
|
83
|
+
recentlyUsedLanguages
|
|
84
|
+
}), [formatMessage, languagePickerOptions, recentlyUsedLanguages]);
|
|
63
85
|
const searchOptions = useMemo(() => options.flatMap(group => group.options), [options]);
|
|
64
|
-
const hasSearchQuery = inputValue.trim().length > 0;
|
|
65
86
|
const handleChange = useCallback(option => {
|
|
87
|
+
var _option$selectionSour;
|
|
66
88
|
if (!option) {
|
|
67
89
|
return;
|
|
68
90
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
91
|
+
const isSearchSelection = inputValueRef.current.trim().length > 0;
|
|
92
|
+
const selectionSource = isSearchSelection ? 'search' : (_option$selectionSour = option.selectionSource) !== null && _option$selectionSour !== void 0 ? _option$selectionSour : 'all';
|
|
93
|
+
const commandSucceeded = changeLanguage(editorAnalyticsAPI)(option.value, selectionSource)(editorView.state, editorView.dispatch);
|
|
94
|
+
if (commandSucceeded) {
|
|
95
|
+
onLanguageSelect === null || onLanguageSelect === void 0 ? void 0 : onLanguageSelect(option.value);
|
|
96
|
+
}
|
|
97
|
+
}, [editorAnalyticsAPI, editorView, onLanguageSelect]);
|
|
98
|
+
const handleInputChange = useCallback((newInputValue, actionMeta) => {
|
|
99
|
+
// React-select clears the input as part of selecting a value before onChange fires.
|
|
100
|
+
// Keep the last user-typed query so handleChange can report search selections correctly.
|
|
101
|
+
if (!actionMeta || actionMeta.action === 'input-change') {
|
|
102
|
+
inputValueRef.current = newInputValue;
|
|
103
|
+
}
|
|
104
|
+
setHasSearchQuery(newInputValue.trim().length > 0);
|
|
73
105
|
return newInputValue;
|
|
74
106
|
}, []);
|
|
75
107
|
const renderTarget = useCallback(({
|
|
@@ -93,13 +125,13 @@ export const LanguagePicker = ({
|
|
|
93
125
|
return /*#__PURE__*/React.createElement(PopupSelect, {
|
|
94
126
|
components: popupSelectComponents,
|
|
95
127
|
filterOption: filterOption,
|
|
96
|
-
inputValue: inputValue,
|
|
97
128
|
label: selectLanguageLabel,
|
|
98
129
|
maxMenuHeight: 300,
|
|
99
130
|
minMenuWidth: 200,
|
|
100
131
|
menuPlacement: "auto",
|
|
101
132
|
onChange: handleChange,
|
|
102
133
|
onInputChange: handleInputChange,
|
|
134
|
+
onMenuOpen: onMenuOpen,
|
|
103
135
|
options: hasSearchQuery ? searchOptions : options,
|
|
104
136
|
popperProps: menuPopperProps,
|
|
105
137
|
searchThreshold: -1,
|
|
@@ -5,6 +5,7 @@ export const PLAIN_TEXT_LANGUAGE_VALUE = 'text';
|
|
|
5
5
|
export const getDetectLanguageOption = formatMessage => ({
|
|
6
6
|
alias: [DETECT_LANGUAGE_VALUE],
|
|
7
7
|
label: formatMessage(codeBlockButtonMessages.detectLanguage),
|
|
8
|
+
selectionSource: 'pinned',
|
|
8
9
|
value: DETECT_LANGUAGE_VALUE
|
|
9
10
|
});
|
|
10
11
|
export const createGroupedLanguageOptions = ({
|
|
@@ -12,20 +13,30 @@ export const createGroupedLanguageOptions = ({
|
|
|
12
13
|
languages,
|
|
13
14
|
recentlyUsedLanguages = []
|
|
14
15
|
}) => {
|
|
15
|
-
const
|
|
16
|
+
const recentlyUsedLanguageValues = new Set(recentlyUsedLanguages.map(language => language.value));
|
|
17
|
+
const allLanguages = languages.filter(language => language.value !== NONE_LANGUAGE_VALUE && language.value !== PLAIN_TEXT_LANGUAGE_VALUE && !recentlyUsedLanguageValues.has(language.value));
|
|
16
18
|
const plainTextOption = languages.find(language => language.value === PLAIN_TEXT_LANGUAGE_VALUE);
|
|
17
19
|
const pinnedOptions = [getDetectLanguageOption(formatMessage)];
|
|
18
20
|
if (plainTextOption) {
|
|
19
|
-
pinnedOptions.push(
|
|
21
|
+
pinnedOptions.push({
|
|
22
|
+
...plainTextOption,
|
|
23
|
+
selectionSource: 'pinned'
|
|
24
|
+
});
|
|
20
25
|
}
|
|
21
26
|
return [{
|
|
22
27
|
label: '',
|
|
23
28
|
options: pinnedOptions
|
|
24
29
|
}, ...(recentlyUsedLanguages.length > 0 ? [{
|
|
25
30
|
label: formatMessage(codeBlockButtonMessages.recentlyUsed),
|
|
26
|
-
options: recentlyUsedLanguages
|
|
31
|
+
options: recentlyUsedLanguages.map(language => ({
|
|
32
|
+
...language,
|
|
33
|
+
selectionSource: 'recentlyUsed'
|
|
34
|
+
}))
|
|
27
35
|
}] : []), {
|
|
28
36
|
label: formatMessage(codeBlockButtonMessages.all),
|
|
29
|
-
options: allLanguages
|
|
37
|
+
options: allLanguages.map(language => ({
|
|
38
|
+
...language,
|
|
39
|
+
selectionSource: 'all'
|
|
40
|
+
}))
|
|
30
41
|
}];
|
|
31
42
|
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { StorageClient } from '@atlaskit/frontend-utilities/storage-client';
|
|
2
|
+
import { DEFAULT_LANGUAGES, getLanguageIdentifier } from '../pm-plugins/language-list';
|
|
3
|
+
import { DETECT_LANGUAGE_VALUE, NONE_LANGUAGE_VALUE, PLAIN_TEXT_LANGUAGE_VALUE } from './language-picker-options';
|
|
4
|
+
export const RECENT_LANGUAGES_STORAGE_KEY = 'recently-used-languages';
|
|
5
|
+
const RECENT_LANGUAGES_STORAGE_CLIENT_KEY = '@atlaskit/editor-plugin-code-block';
|
|
6
|
+
const MAX_RECENT_LANGUAGES = 3;
|
|
7
|
+
const RECENT_LANGUAGE_BLOCKLIST = new Set([DETECT_LANGUAGE_VALUE, NONE_LANGUAGE_VALUE, PLAIN_TEXT_LANGUAGE_VALUE]);
|
|
8
|
+
const KNOWN_LANGUAGE_VALUES = new Set(DEFAULT_LANGUAGES.map(language => getLanguageIdentifier(language)));
|
|
9
|
+
const recentLanguagesStorageClient = new StorageClient(RECENT_LANGUAGES_STORAGE_CLIENT_KEY);
|
|
10
|
+
const isValidRecentLanguage = language => KNOWN_LANGUAGE_VALUES.has(language) && !RECENT_LANGUAGE_BLOCKLIST.has(language);
|
|
11
|
+
const readRecentLanguages = () => {
|
|
12
|
+
const storedValue = recentLanguagesStorageClient.getItem(RECENT_LANGUAGES_STORAGE_KEY);
|
|
13
|
+
return Array.isArray(storedValue) ? storedValue : [];
|
|
14
|
+
};
|
|
15
|
+
export const getRecentLanguages = () => {
|
|
16
|
+
try {
|
|
17
|
+
return readRecentLanguages();
|
|
18
|
+
} catch {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
export const saveRecentLanguage = language => {
|
|
23
|
+
if (!isValidRecentLanguage(language)) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const recentLanguages = readRecentLanguages();
|
|
28
|
+
const nextRecentLanguages = Array.from(new Set([language, ...recentLanguages])).slice(0, MAX_RECENT_LANGUAGES);
|
|
29
|
+
|
|
30
|
+
// StorageClient only exposes setItemWithExpiry; omitting the duration stores without expiry.
|
|
31
|
+
recentLanguagesStorageClient.setItemWithExpiry(RECENT_LANGUAGES_STORAGE_KEY, nextRecentLanguages);
|
|
32
|
+
} catch {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
export const clearRecentLanguages = () => {
|
|
37
|
+
recentLanguagesStorageClient.removeItem(RECENT_LANGUAGES_STORAGE_KEY);
|
|
38
|
+
};
|
|
@@ -40,7 +40,7 @@ export var removeCodeBlock = function removeCodeBlock(state, dispatch) {
|
|
|
40
40
|
return true;
|
|
41
41
|
};
|
|
42
42
|
export var changeLanguage = function changeLanguage(editorAnalyticsAPI) {
|
|
43
|
-
return function (language) {
|
|
43
|
+
return function (language, selectionSource) {
|
|
44
44
|
return function (state, dispatch) {
|
|
45
45
|
var _pluginKey$getState;
|
|
46
46
|
var codeBlock = state.schema.nodes.codeBlock;
|
|
@@ -58,9 +58,11 @@ export var changeLanguage = function changeLanguage(editorAnalyticsAPI) {
|
|
|
58
58
|
editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 || editorAnalyticsAPI.attachAnalyticsEvent({
|
|
59
59
|
action: ACTION.LANGUAGE_SELECTED,
|
|
60
60
|
actionSubject: ACTION_SUBJECT.CODE_BLOCK,
|
|
61
|
-
attributes: {
|
|
61
|
+
attributes: _objectSpread({
|
|
62
62
|
language: language !== null && language !== void 0 ? language : 'none'
|
|
63
|
-
},
|
|
63
|
+
}, selectionSource ? {
|
|
64
|
+
selectionSource: selectionSource
|
|
65
|
+
} : {}),
|
|
64
66
|
eventType: EVENT_TYPE.TRACK
|
|
65
67
|
})(result);
|
|
66
68
|
dispatch(result);
|