@atlaskit/editor-plugin-code-block-advanced 7.0.0 → 7.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.
@@ -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,19 +38,20 @@ 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';
46
44
  import { foldGutterExtension, getCodeBlockFoldStateEffects } from './extensions/foldGutter';
47
45
  import { keymapExtension } from './extensions/keymap';
46
+ import { lineSeparatorExtension } from './extensions/lineSeparator';
48
47
  import { manageSelectionMarker } from './extensions/manageSelectionMarker';
49
48
  import { prosemirrorDecorationPlugin } from './extensions/prosemirrorDecorations';
50
49
  import { tripleClickSelectAllExtension } from './extensions/tripleClickExtension';
51
50
  import { LanguageLoader } from './languages/loader';
52
51
 
52
+ // Store last observed heights of code blocks
53
+ const codeBlockHeights = new WeakMap<HTMLElement, number>();
54
+
53
55
  export interface ConfigProps {
54
56
  allowCodeFolding: boolean;
55
57
  api: ExtractInjectionAPI<CodeBlockAdvancedPlugin> | undefined;
@@ -66,15 +68,18 @@ class CodeBlockAdvancedNodeView implements NodeView {
66
68
  private languageCompartment = new Compartment();
67
69
  private readOnlyCompartment = new Compartment();
68
70
  private pmDecorationsCompartment = new Compartment();
71
+ private themeCompartment = new Compartment();
69
72
  private node: PMNode;
70
73
  private getPos: getPosHandlerNode;
71
74
  private cm: CodeMirror;
75
+ private contentMode: EditorContentMode | undefined;
72
76
  private selectionAPI: EditorSelectionAPI | undefined;
73
77
  private maybeTryingToReachNodeSelection = false;
74
78
  private cleanupDisabledState: (() => void) | undefined;
75
79
  private languageLoader: LanguageLoader;
76
80
  private pmFacet = Facet.define<DecorationSource>();
77
81
  private ro?: ResizeObserver;
82
+ private unsubscribeContentFormat: (() => void) | undefined;
78
83
 
79
84
  constructor(
80
85
  node: PMNode,
@@ -86,12 +91,24 @@ class CodeBlockAdvancedNodeView implements NodeView {
86
91
  this.node = node;
87
92
  this.view = view;
88
93
  this.getPos = getPos;
94
+ const contentFormatSharedState = expValEquals(
95
+ 'confluence_compact_text_format',
96
+ 'isEnabled',
97
+ true,
98
+ )
99
+ ? config.api?.contentFormat?.sharedState
100
+ : undefined;
101
+ this.contentMode = expValEquals('confluence_compact_text_format', 'isEnabled', true)
102
+ ? contentFormatSharedState?.currentState?.()?.contentMode
103
+ : undefined;
104
+
89
105
  this.selectionAPI = config.api?.selection?.actions;
90
106
  const getNode = () => this.node;
91
107
  const onMaybeNodeSelection = () => (this.maybeTryingToReachNodeSelection = true);
92
108
  this.cleanupDisabledState = config.api?.editorDisabled?.sharedState.onChange(() => {
93
109
  this.updateReadonlyState();
94
110
  });
111
+
95
112
  this.languageLoader = new LanguageLoader((lang) => {
96
113
  this.updating = true;
97
114
  this.cm.dispatch({
@@ -126,7 +143,13 @@ class CodeBlockAdvancedNodeView implements NodeView {
126
143
  }),
127
144
  // Goes before cmTheme to override styles
128
145
  config.allowCodeFolding ? [codeFoldingTheme] : [],
129
- cmTheme,
146
+ this.themeCompartment.of(
147
+ cmTheme({
148
+ contentMode: expValEquals('confluence_compact_text_format', 'isEnabled', true)
149
+ ? this.contentMode
150
+ : undefined,
151
+ }),
152
+ ),
130
153
  syntaxHighlighting(highlightStyle),
131
154
  bracketMatching(),
132
155
  lineNumbers({
@@ -160,23 +183,35 @@ class CodeBlockAdvancedNodeView implements NodeView {
160
183
  config.allowCodeFolding
161
184
  ? [foldGutterExtension({ selectNode, getNode: () => this.node })]
162
185
  : [],
186
+ lineSeparatorExtension(),
163
187
  ],
164
188
  });
165
189
 
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);
190
+ if (contentFormatSharedState) {
191
+ this.unsubscribeContentFormat = contentFormatSharedState.onChange(
192
+ ({ nextSharedState, prevSharedState }) => {
193
+ const prevMode = prevSharedState?.contentMode;
194
+ const nextMode = nextSharedState?.contentMode;
195
+ if (nextMode === prevMode) {
196
+ return;
197
+ }
198
+
199
+ this.applyContentModeTheme(nextMode);
200
+
201
+ if (this.updating || this.cm.hasFocus) {
202
+ return;
203
+ }
204
+
205
+ this.cm.requestMeasure();
206
+ },
207
+ );
208
+ }
174
209
 
175
210
  // Observe size changes of the CodeMirror DOM and request a measurement pass
176
211
  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'))
212
+ !expValEquals('confluence_compact_text_format', 'isEnabled', true) &&
213
+ expValEquals('cc_editor_ai_content_mode', 'variant', 'test') &&
214
+ fg('platform_editor_content_mode_button_mvp')
180
215
  ) {
181
216
  this.ro = new ResizeObserver((entries) => {
182
217
  // Skip measurements when:
@@ -195,12 +230,22 @@ class CodeBlockAdvancedNodeView implements NodeView {
195
230
  }
196
231
  codeBlockHeights.set(this.cm.contentDOM, currentHeight);
197
232
  }
233
+
198
234
  // CodeMirror to re-measure when its content size changes
199
235
  this.cm.requestMeasure();
200
236
  });
201
237
  this.ro.observe(this.cm.contentDOM);
202
238
  }
203
239
 
240
+ // We append an additional element that fixes a selection bug on chrome if the code block
241
+ // is the first element followed by subsequent code blocks
242
+ const spaceContainer = document.createElement('span');
243
+ spaceContainer.innerText = ZERO_WIDTH_SPACE;
244
+ spaceContainer.style.height = '0';
245
+ // The editor's outer node is our DOM representation
246
+ this.dom = this.cm.dom;
247
+ this.dom.appendChild(spaceContainer);
248
+
204
249
  // This flag is used to avoid an update loop between the outer and
205
250
  // inner editor
206
251
  this.updating = false;
@@ -220,10 +265,13 @@ class CodeBlockAdvancedNodeView implements NodeView {
220
265
  // codemirror
221
266
  this.clearProseMirrorDecorations();
222
267
  this.cleanupDisabledState?.();
268
+ if (expValEquals('confluence_compact_text_format', 'isEnabled', true)) {
269
+ this.unsubscribeContentFormat?.();
270
+ }
223
271
  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'))
272
+ !expValEquals('confluence_compact_text_format', 'isEnabled', true) &&
273
+ expValEquals('cc_editor_ai_content_mode', 'variant', 'test') &&
274
+ fg('platform_editor_content_mode_button_mvp')
227
275
  ) {
228
276
  this.ro?.disconnect();
229
277
  }
@@ -308,7 +356,19 @@ class CodeBlockAdvancedNodeView implements NodeView {
308
356
  this.updating = false;
309
357
  }
310
358
 
311
- update(node: PMNode, _: readonly Decoration[], innerDecorations: DecorationSource) {
359
+ private applyContentModeTheme(contentMode: EditorContentMode | undefined) {
360
+ if (contentMode === this.contentMode) {
361
+ return;
362
+ }
363
+ this.contentMode = contentMode;
364
+ this.updating = true;
365
+ this.cm.dispatch({
366
+ effects: this.themeCompartment.reconfigure(cmTheme({ contentMode })),
367
+ });
368
+ this.updating = false;
369
+ }
370
+
371
+ update(node: PMNode, _: readonly Decoration[], innerDecorations: DecorationSource): boolean {
312
372
  this.maybeTryingToReachNodeSelection = false;
313
373
 
314
374
  if (node.type !== this.node.type) {
@@ -361,7 +421,7 @@ class CodeBlockAdvancedNodeView implements NodeView {
361
421
  this.updating = false;
362
422
  }
363
423
 
364
- stopEvent(e: Event) {
424
+ stopEvent(e: Event): boolean {
365
425
  // If we have selected the node we should not stop these events
366
426
  if (
367
427
  (e instanceof KeyboardEvent || e instanceof ClipboardEvent) &&
@@ -12,7 +12,7 @@ interface BackspaceProps {
12
12
  view: EditorView;
13
13
  }
14
14
 
15
- export const backspaceKeymap = ({ cm, view, getPos, getNode }: BackspaceProps) => {
15
+ export const backspaceKeymap = ({ cm, view, getPos, getNode }: BackspaceProps): boolean => {
16
16
  const ranges = cm.state.selection.ranges;
17
17
 
18
18
  if (ranges.length > 1) {
@@ -0,0 +1,14 @@
1
+ import { EditorState as CodeMirrorState } from '@codemirror/state';
2
+
3
+ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
4
+
5
+ /**
6
+ * To avoid issues with CRLF (\r\n) syncing with ProseMirror,
7
+ * we only consider \n for line separators.
8
+ */
9
+ export const lineSeparatorExtension = () => {
10
+ if (expValEquals('platform_editor_fix_advanced_codeblocks_crlf', 'isEnabled', true)) {
11
+ return CodeMirrorState.lineSeparator.of('\n');
12
+ }
13
+ return [];
14
+ };
@@ -1,7 +1,7 @@
1
1
  import { TextSelection } from '@atlaskit/editor-prosemirror/state';
2
2
  import type { EditorView } from '@atlaskit/editor-prosemirror/view';
3
3
 
4
- export const shiftArrowUpWorkaround = (view: EditorView, event: KeyboardEvent) => {
4
+ export const shiftArrowUpWorkaround = (view: EditorView, event: KeyboardEvent): boolean => {
5
5
  const {
6
6
  doc,
7
7
  selection: { $head, $anchor },
@@ -28,7 +28,7 @@ export const shiftArrowUpWorkaround = (view: EditorView, event: KeyboardEvent) =
28
28
  return false;
29
29
  };
30
30
 
31
- export const shiftArrowDownWorkaround = (view: EditorView, event: KeyboardEvent) => {
31
+ export const shiftArrowDownWorkaround = (view: EditorView, event: KeyboardEvent): boolean => {
32
32
  const {
33
33
  doc,
34
34
  selection: { $head, $anchor },
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': {
package/tsconfig.app.json CHANGED
@@ -43,6 +43,9 @@
43
43
  {
44
44
  "path": "../editor-plugin-code-block/tsconfig.app.json"
45
45
  },
46
+ {
47
+ "path": "../editor-plugin-content-format/tsconfig.app.json"
48
+ },
46
49
  {
47
50
  "path": "../editor-plugin-editor-disabled/tsconfig.app.json"
48
51
  },
@@ -1,22 +0,0 @@
1
- {
2
- "extends": "../tsconfig",
3
- "compilerOptions": {
4
- "target": "es5",
5
- "paths": {}
6
- },
7
- "include": [
8
- "../src/**/*.ts",
9
- "../src/**/*.tsx"
10
- ],
11
- "exclude": [
12
- "../src/**/__tests__/*",
13
- "../src/**/*.test.*",
14
- "../src/**/test.*",
15
- "../src/**/examples.*",
16
- "../src/**/examples/*",
17
- "../src/**/examples/**/*",
18
- "../src/**/*.stories.*",
19
- "../src/**/stories/*",
20
- "../src/**/stories/**/*"
21
- ]
22
- }