@atlaskit/editor-plugin-code-block-advanced 7.1.24 → 7.1.26

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.
@@ -10,8 +10,14 @@ import {
10
10
  import { EditorView as CodeMirror, lineNumbers, type ViewUpdate, gutters } from '@codemirror/view';
11
11
  import type { IntlShape } from 'react-intl-next';
12
12
 
13
+ import { getBrowserInfo } from '@atlaskit/editor-common/browser';
13
14
  import { isCodeBlockWordWrapEnabled } from '@atlaskit/editor-common/code-block';
14
- import { blockTypeMessages } from '@atlaskit/editor-common/messages';
15
+ import { messages as floatingToolbarMessages } from '@atlaskit/editor-common/floating-toolbar';
16
+ import {
17
+ blockTypeMessages,
18
+ codeBlockMessages,
19
+ roleDescriptionMessages,
20
+ } from '@atlaskit/editor-common/messages';
15
21
  import { type RelativeSelectionPos } from '@atlaskit/editor-common/selection';
16
22
  import type {
17
23
  getPosHandler,
@@ -47,6 +53,7 @@ import { lineSeparatorExtension } from './extensions/lineSeparator';
47
53
  import { manageSelectionMarker } from './extensions/manageSelectionMarker';
48
54
  import { prosemirrorDecorationPlugin } from './extensions/prosemirrorDecorations';
49
55
  import { tripleClickSelectAllExtension } from './extensions/tripleClickExtension';
56
+ import getLanguageName from './languages/getLanguageName';
50
57
  import { LanguageLoader } from './languages/loader';
51
58
 
52
59
  // Store last observed heights of code blocks
@@ -80,6 +87,8 @@ class CodeBlockAdvancedNodeView implements NodeView {
80
87
  private pmFacet = Facet.define<DecorationSource>();
81
88
  private ro?: ResizeObserver;
82
89
  private unsubscribeContentFormat: (() => void) | undefined;
90
+ private invisibleAriaDescription?: HTMLSpanElement;
91
+ private config: ConfigProps;
83
92
 
84
93
  constructor(
85
94
  node: PMNode,
@@ -88,7 +97,9 @@ class CodeBlockAdvancedNodeView implements NodeView {
88
97
  innerDecorations: DecorationSource,
89
98
  config: ConfigProps,
90
99
  ) {
100
+ this.config = config;
91
101
  this.node = node;
102
+
92
103
  this.view = view;
93
104
  this.getPos = getPos;
94
105
  const contentFormatSharedState = expValEquals(
@@ -124,6 +135,8 @@ class CodeBlockAdvancedNodeView implements NodeView {
124
135
  this.view.focus();
125
136
  };
126
137
 
138
+ const isMacOS = getBrowserInfo().mac;
139
+
127
140
  this.cm = new CodeMirror({
128
141
  doc: this.node.textContent,
129
142
  extensions: [
@@ -179,7 +192,24 @@ class CodeBlockAdvancedNodeView implements NodeView {
179
192
  prosemirrorDecorationPlugin(this.pmFacet, view, getPos),
180
193
  tripleClickSelectAllExtension(),
181
194
  firstCodeBlockInDocument(getPos),
182
- CodeMirror.contentAttributes.of({ 'aria-label': formattedAriaLabel }),
195
+ CodeMirror.contentAttributes.of({
196
+ ...(!expValEquals('editor_a11y_role_textbox', 'isEnabled', true) && {
197
+ 'aria-label': `${formattedAriaLabel}`,
198
+ }),
199
+ ...(isMacOS &&
200
+ expValEquals('editor_a11y_role_textbox', 'isEnabled', true) && {
201
+ role: 'textbox',
202
+ 'aria-roledescription': formatMessage(roleDescriptionMessages.codeSnippetTextBox),
203
+ 'aria-describedby': `codesnippet-${this.node.attrs.localId}`,
204
+ 'aria-multiline': 'true',
205
+ 'aria-label': formattedAriaLabel,
206
+ }),
207
+ ...(!isMacOS &&
208
+ expValEquals('editor_a11y_role_textbox', 'isEnabled', true) && {
209
+ 'aria-label': formattedAriaLabel,
210
+ 'aria-describedby': `codesnippet-${this.node.attrs.localId}`,
211
+ }),
212
+ }),
183
213
  config.allowCodeFolding
184
214
  ? [foldGutterExtension({ selectNode, getNode: () => this.node })]
185
215
  : [],
@@ -249,6 +279,18 @@ class CodeBlockAdvancedNodeView implements NodeView {
249
279
  this.dom = this.cm.dom;
250
280
  this.dom.appendChild(spaceContainer);
251
281
 
282
+ if (
283
+ expValEquals('editor_a11y_role_textbox', 'isEnabled', true) &&
284
+ fg('platform_editor_adf_with_localid')
285
+ ) {
286
+ this.invisibleAriaDescription = document.createElement('span');
287
+ this.invisibleAriaDescription.hidden = true;
288
+ this.invisibleAriaDescription.id = `codesnippet-${this.node.attrs.localId}`;
289
+ this.updateAriaDescription();
290
+
291
+ this.dom.appendChild(this.invisibleAriaDescription);
292
+ }
293
+
252
294
  // This flag is used to avoid an update loop between the outer and
253
295
  // inner editor
254
296
  this.updating = false;
@@ -315,6 +357,34 @@ class CodeBlockAdvancedNodeView implements NodeView {
315
357
 
316
358
  private updateLanguage() {
317
359
  this.languageLoader.updateLanguage(this.node.attrs.language);
360
+ if (
361
+ expValEquals('editor_a11y_role_textbox', 'isEnabled', true) &&
362
+ fg('platform_editor_adf_with_localid')
363
+ ) {
364
+ this.updateAriaDescription();
365
+ }
366
+ }
367
+
368
+ private updateAriaDescription() {
369
+ if (!this.invisibleAriaDescription) {
370
+ return;
371
+ }
372
+
373
+ const { formatMessage } = this.config.getIntl();
374
+ const languageName = getLanguageName(this.node.attrs.language);
375
+ if (languageName) {
376
+ this.invisibleAriaDescription.textContent = `${formatMessage(
377
+ codeBlockMessages.codeblockLanguageAriaDescription,
378
+ {
379
+ language: languageName,
380
+ },
381
+ )} ${formatMessage(floatingToolbarMessages.floatingToolbarAnnouncer)}`;
382
+ } else {
383
+ // If the lanuage is undefined provide a more human readable message
384
+ this.invisibleAriaDescription.textContent = `${formatMessage(
385
+ codeBlockMessages.codeBlockLanguageNotSet,
386
+ )} ${formatMessage(floatingToolbarMessages.floatingToolbarAnnouncer)}`;
387
+ }
318
388
  }
319
389
 
320
390
  private updateLocalIdAttribute() {
@@ -0,0 +1,28 @@
1
+ import { SUPPORTED_LANGUAGES } from '@atlaskit/code/constants';
2
+
3
+ // We expect alias[0] to be used for the ADF attribute, see ED-2813
4
+ export const DEFAULT_LANGUAGES = [
5
+ { name: '(None)', alias: ['none'], value: 'none' },
6
+ ...SUPPORTED_LANGUAGES,
7
+ ];
8
+
9
+ export type Language = (typeof DEFAULT_LANGUAGES)[number];
10
+
11
+ const getLanguageName = (languageValue: string): string | undefined => {
12
+ if (!languageValue) {
13
+ return undefined;
14
+ }
15
+
16
+ const language = (SUPPORTED_LANGUAGES as readonly Language[]).find((l) => {
17
+ return (
18
+ l.value === languageValue ||
19
+ (l.alias && (l.alias as string[]).includes(languageValue))
20
+ );
21
+ });
22
+
23
+
24
+
25
+ return language ? language.name : undefined;
26
+ };
27
+
28
+ export default getLanguageName;