@atlaskit/editor-plugin-code-block-advanced 7.0.0 → 7.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.
@@ -17,6 +17,7 @@ import type {
17
17
  getPosHandler,
18
18
  getPosHandlerNode,
19
19
  ExtractInjectionAPI,
20
+ EditorContentMode,
20
21
  } from '@atlaskit/editor-common/types';
21
22
  import { ZERO_WIDTH_SPACE } from '@atlaskit/editor-common/whitespace';
22
23
  import { type EditorSelectionAPI } from '@atlaskit/editor-plugin-selection';
@@ -37,9 +38,6 @@ import type { CodeBlockAdvancedPlugin } from '../codeBlockAdvancedPluginType';
37
38
  import { highlightStyle } from '../ui/syntaxHighlightingTheme';
38
39
  import { cmTheme, codeFoldingTheme } from '../ui/theme';
39
40
 
40
- // Store last observed heights of code blocks
41
- const codeBlockHeights = new WeakMap<HTMLElement, number>();
42
-
43
41
  import { syncCMWithPM } from './codemirrorSync/syncCMWithPM';
44
42
  import { getCMSelectionChanges } from './codemirrorSync/updateCMSelection';
45
43
  import { firstCodeBlockInDocument } from './extensions/firstCodeBlockInDocument';
@@ -50,6 +48,9 @@ import { prosemirrorDecorationPlugin } from './extensions/prosemirrorDecorations
50
48
  import { tripleClickSelectAllExtension } from './extensions/tripleClickExtension';
51
49
  import { LanguageLoader } from './languages/loader';
52
50
 
51
+ // Store last observed heights of code blocks
52
+ const codeBlockHeights = new WeakMap<HTMLElement, number>();
53
+
53
54
  export interface ConfigProps {
54
55
  allowCodeFolding: boolean;
55
56
  api: ExtractInjectionAPI<CodeBlockAdvancedPlugin> | undefined;
@@ -66,15 +67,18 @@ class CodeBlockAdvancedNodeView implements NodeView {
66
67
  private languageCompartment = new Compartment();
67
68
  private readOnlyCompartment = new Compartment();
68
69
  private pmDecorationsCompartment = new Compartment();
70
+ private themeCompartment = new Compartment();
69
71
  private node: PMNode;
70
72
  private getPos: getPosHandlerNode;
71
73
  private cm: CodeMirror;
74
+ private contentMode: EditorContentMode | undefined;
72
75
  private selectionAPI: EditorSelectionAPI | undefined;
73
76
  private maybeTryingToReachNodeSelection = false;
74
77
  private cleanupDisabledState: (() => void) | undefined;
75
78
  private languageLoader: LanguageLoader;
76
79
  private pmFacet = Facet.define<DecorationSource>();
77
80
  private ro?: ResizeObserver;
81
+ private unsubscribeContentFormat: (() => void) | undefined;
78
82
 
79
83
  constructor(
80
84
  node: PMNode,
@@ -86,12 +90,24 @@ class CodeBlockAdvancedNodeView implements NodeView {
86
90
  this.node = node;
87
91
  this.view = view;
88
92
  this.getPos = getPos;
93
+ const contentFormatSharedState = expValEquals(
94
+ 'confluence_compact_text_format',
95
+ 'isEnabled',
96
+ true,
97
+ )
98
+ ? config.api?.contentFormat?.sharedState
99
+ : undefined;
100
+ this.contentMode = expValEquals('confluence_compact_text_format', 'isEnabled', true)
101
+ ? contentFormatSharedState?.currentState?.()?.contentMode
102
+ : undefined;
103
+
89
104
  this.selectionAPI = config.api?.selection?.actions;
90
105
  const getNode = () => this.node;
91
106
  const onMaybeNodeSelection = () => (this.maybeTryingToReachNodeSelection = true);
92
107
  this.cleanupDisabledState = config.api?.editorDisabled?.sharedState.onChange(() => {
93
108
  this.updateReadonlyState();
94
109
  });
110
+
95
111
  this.languageLoader = new LanguageLoader((lang) => {
96
112
  this.updating = true;
97
113
  this.cm.dispatch({
@@ -126,7 +142,13 @@ class CodeBlockAdvancedNodeView implements NodeView {
126
142
  }),
127
143
  // Goes before cmTheme to override styles
128
144
  config.allowCodeFolding ? [codeFoldingTheme] : [],
129
- cmTheme,
145
+ this.themeCompartment.of(
146
+ cmTheme({
147
+ contentMode: expValEquals('confluence_compact_text_format', 'isEnabled', true)
148
+ ? this.contentMode
149
+ : undefined,
150
+ }),
151
+ ),
130
152
  syntaxHighlighting(highlightStyle),
131
153
  bracketMatching(),
132
154
  lineNumbers({
@@ -163,20 +185,31 @@ class CodeBlockAdvancedNodeView implements NodeView {
163
185
  ],
164
186
  });
165
187
 
166
- // We append an additional element that fixes a selection bug on chrome if the code block
167
- // is the first element followed by subsequent code blocks
168
- const spaceContainer = document.createElement('span');
169
- spaceContainer.innerText = ZERO_WIDTH_SPACE;
170
- spaceContainer.style.height = '0';
171
- // The editor's outer node is our DOM representation
172
- this.dom = this.cm.dom;
173
- this.dom.appendChild(spaceContainer);
188
+ if (contentFormatSharedState) {
189
+ this.unsubscribeContentFormat = contentFormatSharedState.onChange(
190
+ ({ nextSharedState, prevSharedState }) => {
191
+ const prevMode = prevSharedState?.contentMode;
192
+ const nextMode = nextSharedState?.contentMode;
193
+ if (nextMode === prevMode) {
194
+ return;
195
+ }
196
+
197
+ this.applyContentModeTheme(nextMode);
198
+
199
+ if (this.updating || this.cm.hasFocus) {
200
+ return;
201
+ }
202
+
203
+ this.cm.requestMeasure();
204
+ },
205
+ );
206
+ }
174
207
 
175
208
  // Observe size changes of the CodeMirror DOM and request a measurement pass
176
209
  if (
177
- expValEquals('confluence_compact_text_format', 'isEnabled', true) ||
178
- (expValEquals('cc_editor_ai_content_mode', 'variant', 'test') &&
179
- fg('platform_editor_content_mode_button_mvp'))
210
+ !expValEquals('confluence_compact_text_format', 'isEnabled', true) &&
211
+ expValEquals('cc_editor_ai_content_mode', 'variant', 'test') &&
212
+ fg('platform_editor_content_mode_button_mvp')
180
213
  ) {
181
214
  this.ro = new ResizeObserver((entries) => {
182
215
  // Skip measurements when:
@@ -195,12 +228,22 @@ class CodeBlockAdvancedNodeView implements NodeView {
195
228
  }
196
229
  codeBlockHeights.set(this.cm.contentDOM, currentHeight);
197
230
  }
231
+
198
232
  // CodeMirror to re-measure when its content size changes
199
233
  this.cm.requestMeasure();
200
234
  });
201
235
  this.ro.observe(this.cm.contentDOM);
202
236
  }
203
237
 
238
+ // We append an additional element that fixes a selection bug on chrome if the code block
239
+ // is the first element followed by subsequent code blocks
240
+ const spaceContainer = document.createElement('span');
241
+ spaceContainer.innerText = ZERO_WIDTH_SPACE;
242
+ spaceContainer.style.height = '0';
243
+ // The editor's outer node is our DOM representation
244
+ this.dom = this.cm.dom;
245
+ this.dom.appendChild(spaceContainer);
246
+
204
247
  // This flag is used to avoid an update loop between the outer and
205
248
  // inner editor
206
249
  this.updating = false;
@@ -220,10 +263,13 @@ class CodeBlockAdvancedNodeView implements NodeView {
220
263
  // codemirror
221
264
  this.clearProseMirrorDecorations();
222
265
  this.cleanupDisabledState?.();
266
+ if (expValEquals('confluence_compact_text_format', 'isEnabled', true)) {
267
+ this.unsubscribeContentFormat?.();
268
+ }
223
269
  if (
224
- expValEquals('confluence_compact_text_format', 'isEnabled', true) ||
225
- (expValEquals('cc_editor_ai_content_mode', 'variant', 'test') &&
226
- fg('platform_editor_content_mode_button_mvp'))
270
+ !expValEquals('confluence_compact_text_format', 'isEnabled', true) &&
271
+ expValEquals('cc_editor_ai_content_mode', 'variant', 'test') &&
272
+ fg('platform_editor_content_mode_button_mvp')
227
273
  ) {
228
274
  this.ro?.disconnect();
229
275
  }
@@ -308,6 +354,18 @@ class CodeBlockAdvancedNodeView implements NodeView {
308
354
  this.updating = false;
309
355
  }
310
356
 
357
+ private applyContentModeTheme(contentMode: EditorContentMode | undefined) {
358
+ if (contentMode === this.contentMode) {
359
+ return;
360
+ }
361
+ this.contentMode = contentMode;
362
+ this.updating = true;
363
+ this.cm.dispatch({
364
+ effects: this.themeCompartment.reconfigure(cmTheme({ contentMode })),
365
+ });
366
+ this.updating = false;
367
+ }
368
+
311
369
  update(node: PMNode, _: readonly Decoration[], innerDecorations: DecorationSource) {
312
370
  this.maybeTryingToReachNodeSelection = false;
313
371
 
package/src/ui/theme.ts CHANGED
@@ -1,91 +1,94 @@
1
1
  import { EditorView as CodeMirror } from '@codemirror/view';
2
2
 
3
+ import type { EditorContentMode } from '@atlaskit/editor-common/types';
3
4
  import { fg } from '@atlaskit/platform-feature-flags';
4
5
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
5
6
  import { token } from '@atlaskit/tokens';
6
7
 
7
- const getLineHeight = () =>
8
- expValEquals('confluence_compact_text_format', 'isEnabled', true) ||
8
+ const shouldUseCompactTypography = (contentMode?: EditorContentMode) =>
9
+ contentMode === 'compact' ||
9
10
  (expValEquals('cc_editor_ai_content_mode', 'variant', 'test') &&
10
- fg('platform_editor_content_mode_button_mvp'))
11
- ? '1.5em'
12
- : '1.5rem';
11
+ fg('platform_editor_content_mode_button_mvp'));
13
12
 
14
- const getFontSize = () =>
15
- expValEquals('confluence_compact_text_format', 'isEnabled', true) ||
16
- (expValEquals('cc_editor_ai_content_mode', 'variant', 'test') &&
17
- fg('platform_editor_content_mode_button_mvp'))
18
- ? '0.875em'
19
- : '0.875rem';
13
+ const getLineHeight = (contentMode?: EditorContentMode) =>
14
+ shouldUseCompactTypography(contentMode) ? '1.5em' : '1.5rem';
20
15
 
21
- export const cmTheme = CodeMirror.theme({
22
- '&': {
23
- backgroundColor: token('color.background.neutral'),
24
- padding: '0',
25
- marginTop: token('space.100'),
26
- marginBottom: token('space.100'),
27
- // eslint-disable-next-line @atlaskit/design-system/use-tokens-typography
28
- fontSize: getFontSize(),
29
- // Custom syntax styling to match existing styling
30
- // eslint-disable-next-line @atlaskit/design-system/use-tokens-typography
31
- lineHeight: getLineHeight(),
32
- },
33
- '&.cm-focused': {
34
- outline: 'none',
35
- },
36
- '.cm-line': {
37
- padding: '0',
38
- },
39
- '&.cm-editor.code-block.danger': {
40
- backgroundColor: token('color.background.danger'),
41
- },
42
- '.cm-content[aria-readonly="true"]': {
43
- caretColor: 'transparent',
44
- },
45
- '.cm-content': {
46
- cursor: 'text',
47
- caretColor: token('color.text'),
48
- margin: token('space.100'),
49
- padding: token('space.0'),
50
- },
51
- '.cm-scroller': {
52
- backgroundColor: token('color.background.neutral'),
53
- // Custom syntax styling to match existing styling
54
- // eslint-disable-next-line @atlaskit/design-system/use-tokens-typography
55
- lineHeight: 'unset',
56
- fontFamily: token('font.family.code'),
57
- borderRadius: token('radius.small'),
58
- backgroundImage: overflowShadow({
59
- leftCoverWidth: token('space.300'),
60
- }),
61
- backgroundAttachment: 'local, local, local, local, scroll, scroll, scroll, scroll',
62
- },
63
- '&.cm-focused .cm-cursor': {
64
- borderLeftColor: token('color.text'),
65
- },
66
- '.cm-gutter': {
67
- padding: token('space.100'),
68
- },
69
- '.cm-gutters': {
70
- backgroundColor: token('color.background.neutral'),
71
- border: 'none',
72
- padding: token('space.0'),
73
- color: token('color.text.subtlest'),
74
- },
75
- '.cm-lineNumbers .cm-gutterElement': {
76
- paddingLeft: token('space.0'),
77
- paddingRight: token('space.0'),
78
- minWidth: 'unset',
79
- },
80
- // Set the gutter element min height to prevent flicker of styling while
81
- // codemirror is calculating (which happens after an animation frame).
82
- // Example problem: https://github.com/codemirror/dev/issues/1076
83
- // Ignore the first gutter element as it is a special hidden element.
84
- '.cm-gutterElement:not(:first-child)': {
85
- // eslint-disable-next-line @atlaskit/design-system/use-tokens-typography
86
- minHeight: getLineHeight(),
87
- },
88
- });
16
+ const getFontSize = (contentMode?: EditorContentMode) =>
17
+ shouldUseCompactTypography(contentMode) ? '0.875em' : '0.875rem';
18
+
19
+ type ThemeOptions = {
20
+ contentMode?: EditorContentMode;
21
+ };
22
+
23
+ export const cmTheme = (options?: ThemeOptions) =>
24
+ CodeMirror.theme({
25
+ '&': {
26
+ backgroundColor: token('color.background.neutral'),
27
+ padding: '0',
28
+ marginTop: token('space.100'),
29
+ marginBottom: token('space.100'),
30
+ // eslint-disable-next-line @atlaskit/design-system/use-tokens-typography
31
+ fontSize: getFontSize(options?.contentMode),
32
+ // Custom syntax styling to match existing styling
33
+ // eslint-disable-next-line @atlaskit/design-system/use-tokens-typography
34
+ lineHeight: getLineHeight(options?.contentMode),
35
+ },
36
+ '&.cm-focused': {
37
+ outline: 'none',
38
+ },
39
+ '.cm-line': {
40
+ padding: '0',
41
+ },
42
+ '&.cm-editor.code-block.danger': {
43
+ backgroundColor: token('color.background.danger'),
44
+ },
45
+ '.cm-content[aria-readonly="true"]': {
46
+ caretColor: 'transparent',
47
+ },
48
+ '.cm-content': {
49
+ cursor: 'text',
50
+ caretColor: token('color.text'),
51
+ margin: token('space.100'),
52
+ padding: token('space.0'),
53
+ },
54
+ '.cm-scroller': {
55
+ backgroundColor: token('color.background.neutral'),
56
+ // Custom syntax styling to match existing styling
57
+ // eslint-disable-next-line @atlaskit/design-system/use-tokens-typography
58
+ lineHeight: 'unset',
59
+ fontFamily: token('font.family.code'),
60
+ borderRadius: token('radius.small'),
61
+ backgroundImage: overflowShadow({
62
+ leftCoverWidth: token('space.300'),
63
+ }),
64
+ backgroundAttachment: 'local, local, local, local, scroll, scroll, scroll, scroll',
65
+ },
66
+ '&.cm-focused .cm-cursor': {
67
+ borderLeftColor: token('color.text'),
68
+ },
69
+ '.cm-gutter': {
70
+ padding: token('space.100'),
71
+ },
72
+ '.cm-gutters': {
73
+ backgroundColor: token('color.background.neutral'),
74
+ border: 'none',
75
+ padding: token('space.0'),
76
+ color: token('color.text.subtlest'),
77
+ },
78
+ '.cm-lineNumbers .cm-gutterElement': {
79
+ paddingLeft: token('space.0'),
80
+ paddingRight: token('space.0'),
81
+ minWidth: 'unset',
82
+ },
83
+ // Set the gutter element min height to prevent flicker of styling while
84
+ // codemirror is calculating (which happens after an animation frame).
85
+ // Example problem: https://github.com/codemirror/dev/issues/1076
86
+ // Ignore the first gutter element as it is a special hidden element.
87
+ '.cm-gutterElement:not(:first-child)': {
88
+ // eslint-disable-next-line @atlaskit/design-system/use-tokens-typography
89
+ minHeight: getLineHeight(options?.contentMode),
90
+ },
91
+ });
89
92
 
90
93
  export const codeFoldingTheme = CodeMirror.theme({
91
94
  '.cm-gutter': {