@atlaskit/editor-plugin-code-block-advanced 3.0.7 → 3.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.
Files changed (42) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/cjs/codeBlockAdvancedPlugin.js +6 -2
  3. package/dist/cjs/nodeviews/codeBlockAdvanced.js +12 -4
  4. package/dist/cjs/nodeviews/codeBlockNodeWithToDOMFixed.js +5 -4
  5. package/dist/cjs/nodeviews/extensions/foldGutter.js +100 -0
  6. package/dist/cjs/ui/theme.js +31 -1
  7. package/dist/es2019/codeBlockAdvancedPlugin.js +6 -2
  8. package/dist/es2019/nodeviews/codeBlockAdvanced.js +13 -5
  9. package/dist/es2019/nodeviews/codeBlockNodeWithToDOMFixed.js +5 -4
  10. package/dist/es2019/nodeviews/extensions/foldGutter.js +97 -0
  11. package/dist/es2019/ui/theme.js +30 -0
  12. package/dist/esm/codeBlockAdvancedPlugin.js +6 -2
  13. package/dist/esm/nodeviews/codeBlockAdvanced.js +13 -5
  14. package/dist/esm/nodeviews/codeBlockNodeWithToDOMFixed.js +5 -4
  15. package/dist/esm/nodeviews/extensions/foldGutter.js +94 -0
  16. package/dist/esm/ui/theme.js +30 -0
  17. package/dist/types/codeBlockAdvancedPluginType.d.ts +5 -3
  18. package/dist/types/index.d.ts +1 -1
  19. package/dist/types/nodeviews/codeBlockAdvanced.d.ts +2 -1
  20. package/dist/types/nodeviews/codeBlockNodeWithToDOMFixed.d.ts +5 -1
  21. package/dist/types/nodeviews/extensions/foldGutter.d.ts +3 -0
  22. package/dist/types/nodeviews/lazyCodeBlockAdvanced.d.ts +1 -0
  23. package/dist/types/pm-plugins/main.d.ts +1 -0
  24. package/dist/types/ui/theme.d.ts +1 -0
  25. package/dist/types-ts4.5/codeBlockAdvancedPluginType.d.ts +5 -3
  26. package/dist/types-ts4.5/index.d.ts +1 -1
  27. package/dist/types-ts4.5/nodeviews/codeBlockAdvanced.d.ts +2 -1
  28. package/dist/types-ts4.5/nodeviews/codeBlockNodeWithToDOMFixed.d.ts +5 -1
  29. package/dist/types-ts4.5/nodeviews/extensions/foldGutter.d.ts +3 -0
  30. package/dist/types-ts4.5/nodeviews/lazyCodeBlockAdvanced.d.ts +1 -0
  31. package/dist/types-ts4.5/pm-plugins/main.d.ts +1 -0
  32. package/dist/types-ts4.5/ui/theme.d.ts +1 -0
  33. package/package.json +4 -5
  34. package/src/codeBlockAdvancedPlugin.tsx +7 -2
  35. package/src/codeBlockAdvancedPluginType.ts +6 -5
  36. package/src/index.ts +4 -1
  37. package/src/nodeviews/codeBlockAdvanced.ts +13 -4
  38. package/src/nodeviews/codeBlockNodeWithToDOMFixed.ts +11 -4
  39. package/src/nodeviews/extensions/foldGutter.ts +128 -0
  40. package/src/nodeviews/lazyCodeBlockAdvanced.ts +1 -0
  41. package/src/pm-plugins/main.ts +1 -0
  42. package/src/ui/theme.ts +31 -0
@@ -0,0 +1,94 @@
1
+ import { foldGutter, codeFolding } from '@codemirror/language';
2
+ import { convertToInlineCss } from '@atlaskit/editor-common/lazy-node-view';
3
+ import { DOMSerializer } from '@atlaskit/editor-prosemirror/model';
4
+ // Based on platform/packages/design-system/icon/svgs/utility/add.svg
5
+ var chevronDown = ['http://www.w3.org/2000/svg svg', {
6
+ width: '12',
7
+ height: '12',
8
+ fill: 'none',
9
+ viewBox: '0 0 16 16',
10
+ style: 'pointer-events: none;'
11
+ }, ['http://www.w3.org/2000/svg path', {
12
+ fill: 'currentcolor',
13
+ 'fill-rule': 'evenodd',
14
+ d: 'm14.53 6.03-6 6a.75.75 0 0 1-1.004.052l-.056-.052-6-6 1.06-1.06L8 10.44l5.47-5.47z',
15
+ 'clip-rule': 'evenodd',
16
+ style: 'pointer-events: none;'
17
+ }]];
18
+ var chevronRight = ['http://www.w3.org/2000/svg svg', {
19
+ width: '12',
20
+ height: '12',
21
+ fill: 'none',
22
+ viewBox: '0 0 16 16',
23
+ style: 'pointer-events: none;'
24
+ }, ['http://www.w3.org/2000/svg path', {
25
+ fill: 'currentcolor',
26
+ 'fill-rule': 'evenodd',
27
+ d: 'm8.28 1.47 6 6a.75.75 0 0 1 .052 1.004l-.052.056-6 6-1.06-1.06L12.69 8 7.22 2.53z',
28
+ 'clip-rule': 'evenodd',
29
+ style: 'pointer-events: none;'
30
+ }]];
31
+ export function foldGutterExtension(_ref) {
32
+ var selectNode = _ref.selectNode;
33
+ return [foldGutter({
34
+ domEventHandlers: {
35
+ click: function click(_view, _, event) {
36
+ // If we're trying to click the button, don't select
37
+ if (event.target instanceof HTMLButtonElement && event.target.getAttribute('data-marker-dom-element')) {
38
+ return false;
39
+ }
40
+ selectNode();
41
+ return false;
42
+ }
43
+ },
44
+ markerDOM: function markerDOM(open) {
45
+ var _DOMSerializer$render = DOMSerializer.renderSpec(document, chevronDown),
46
+ downElement = _DOMSerializer$render.dom;
47
+ var _DOMSerializer$render2 = DOMSerializer.renderSpec(document, chevronRight),
48
+ rightElement = _DOMSerializer$render2.dom;
49
+ var htmlElement = document.createElement('button');
50
+ htmlElement.setAttribute('data-marker-dom-element', 'true');
51
+ htmlElement.setAttribute('data-testid', "code-block-fold-button-".concat(open ? 'open' : 'closed'));
52
+ htmlElement.setAttribute('style', convertToInlineCss({
53
+ background: 'none',
54
+ color: 'inherit',
55
+ border: 'none',
56
+ padding: 0,
57
+ paddingRight: "var(--ds-space-050, 4px)",
58
+ font: 'inherit',
59
+ outline: 'inherit',
60
+ cursor: 'pointer'
61
+ }));
62
+ if (open) {
63
+ if (downElement) {
64
+ htmlElement.appendChild(downElement);
65
+ } else {
66
+ // Fallback - never called
67
+ htmlElement.textContent = '⌄';
68
+ }
69
+ } else {
70
+ if (rightElement) {
71
+ htmlElement.appendChild(rightElement);
72
+ } else {
73
+ // Fallback - never called
74
+ htmlElement.textContent = '>';
75
+ }
76
+ }
77
+ return htmlElement;
78
+ }
79
+ }), codeFolding({
80
+ placeholderDOM: function placeholderDOM(view, onclick, prepared) {
81
+ var htmlElement = document.createElement('button');
82
+ htmlElement.setAttribute('data-marker-dom-element', 'true');
83
+ htmlElement.setAttribute('style', convertToInlineCss({
84
+ color: 'inherit',
85
+ font: 'inherit',
86
+ cursor: 'pointer'
87
+ }));
88
+ htmlElement.textContent = '…';
89
+ htmlElement.className = 'cm-foldPlaceholder';
90
+ htmlElement.onclick = onclick;
91
+ return htmlElement;
92
+ }
93
+ })];
94
+ }
@@ -68,6 +68,36 @@ export var cmTheme = CodeMirror.theme({
68
68
  minHeight: lineHeight
69
69
  }
70
70
  });
71
+ export var codeFoldingTheme = CodeMirror.theme({
72
+ '.cm-gutter': {
73
+ paddingLeft: "var(--ds-space-075, 6px)",
74
+ paddingTop: "var(--ds-space-100, 8px)",
75
+ paddingBottom: "var(--ds-space-100, 8px)",
76
+ paddingRight: "var(--ds-space-0, 0px)"
77
+ },
78
+ '.cm-foldGutter': {
79
+ paddingLeft: "var(--ds-space-050, 4px)"
80
+ },
81
+ '.cm-gutterElement:has([data-marker-dom-element="true"])': {
82
+ color: "var(--ds-icon-subtle, #626F86)"
83
+ },
84
+ '.cm-gutterElement:has([data-marker-dom-element="true"]):hover': {
85
+ color: "var(--ds-text-accent-gray-bolder, #091E42)"
86
+ },
87
+ '.cm-foldPlaceholder': {
88
+ // To give spacing between lines
89
+ height: '20px',
90
+ backgroundColor: "var(--ds-background-accent-gray-subtlest, #F1F2F4)",
91
+ border: 'none',
92
+ color: "var(--ds-text, #172B4D)",
93
+ outline: "1px solid ".concat("var(--ds-border-accent-gray, #758195)"),
94
+ paddingLeft: "var(--ds-space-025, 2px)",
95
+ paddingRight: "var(--ds-space-025, 2px)"
96
+ },
97
+ '.cm-foldPlaceholder:hover': {
98
+ backgroundColor: "var(--ds-background-accent-gray-subtlest-hovered, #DCDFE4)"
99
+ }
100
+ });
71
101
 
72
102
  /**
73
103
  * Copied directly from `packages/editor/editor-shared-styles/src/overflow-shadow/overflow-shadow.ts`
@@ -13,7 +13,9 @@ export type CodeBlockAdvancedPlugin = NextEditorPlugin<'codeBlockAdvanced', {
13
13
  OptionalPlugin<SelectionMarkerPlugin>,
14
14
  OptionalPlugin<FindReplacePlugin>
15
15
  ];
16
- pluginConfiguration: {
17
- extensions?: Extension[];
18
- } | undefined;
16
+ pluginConfiguration: CodeBlockAdvancedPluginOptions | undefined;
19
17
  }>;
18
+ export type CodeBlockAdvancedPluginOptions = {
19
+ extensions?: Extension[];
20
+ allowCodeFolding?: boolean;
21
+ };
@@ -1,2 +1,2 @@
1
1
  export { codeBlockAdvancedPlugin } from './codeBlockAdvancedPlugin';
2
- export type { CodeBlockAdvancedPlugin } from './codeBlockAdvancedPluginType';
2
+ export type { CodeBlockAdvancedPlugin, CodeBlockAdvancedPluginOptions, } from './codeBlockAdvancedPluginType';
@@ -5,10 +5,11 @@ import type { getPosHandler, getPosHandlerNode, ExtractInjectionAPI } from '@atl
5
5
  import { type Node as PMNode } from '@atlaskit/editor-prosemirror/model';
6
6
  import type { Decoration, DecorationSource, EditorView, NodeView } from '@atlaskit/editor-prosemirror/view';
7
7
  import type { CodeBlockAdvancedPlugin } from '../codeBlockAdvancedPluginType';
8
- interface ConfigProps {
8
+ export interface ConfigProps {
9
9
  api: ExtractInjectionAPI<CodeBlockAdvancedPlugin> | undefined;
10
10
  extensions: Extension[];
11
11
  getIntl: () => IntlShape;
12
+ allowCodeFolding: boolean;
12
13
  }
13
14
  declare class CodeBlockAdvancedNodeView implements NodeView {
14
15
  dom: Node;
@@ -1,2 +1,6 @@
1
1
  import type { NodeSpec } from '@atlaskit/editor-prosemirror/model';
2
- export declare const codeBlockNodeWithFixedToDOM: () => NodeSpec;
2
+ interface Config {
3
+ allowCodeFolding: boolean;
4
+ }
5
+ export declare const codeBlockNodeWithFixedToDOM: (config: Config) => NodeSpec;
6
+ export {};
@@ -0,0 +1,3 @@
1
+ export declare function foldGutterExtension({ selectNode }: {
2
+ selectNode: () => void;
3
+ }): import("@codemirror/state").Extension[];
@@ -6,6 +6,7 @@ interface Props {
6
6
  api: ExtractInjectionAPI<CodeBlockAdvancedPlugin> | undefined;
7
7
  extensions: Extension[];
8
8
  getIntl: () => IntlShape;
9
+ allowCodeFolding: boolean;
9
10
  }
10
11
  export declare const lazyCodeBlockView: (props: Props) => import("@atlaskit/editor-common/lazy-node-view").NodeViewConstructor;
11
12
  export {};
@@ -7,6 +7,7 @@ interface Props {
7
7
  api: ExtractInjectionAPI<CodeBlockAdvancedPlugin> | undefined;
8
8
  extensions: Extension[];
9
9
  getIntl: () => IntlShape;
10
+ allowCodeFolding: boolean;
10
11
  }
11
12
  export declare const createPlugin: (props: Props) => SafePlugin<any>;
12
13
  export {};
@@ -1 +1,2 @@
1
1
  export declare const cmTheme: import("@codemirror/state").Extension;
2
+ export declare const codeFoldingTheme: import("@codemirror/state").Extension;
@@ -13,7 +13,9 @@ export type CodeBlockAdvancedPlugin = NextEditorPlugin<'codeBlockAdvanced', {
13
13
  OptionalPlugin<SelectionMarkerPlugin>,
14
14
  OptionalPlugin<FindReplacePlugin>
15
15
  ];
16
- pluginConfiguration: {
17
- extensions?: Extension[];
18
- } | undefined;
16
+ pluginConfiguration: CodeBlockAdvancedPluginOptions | undefined;
19
17
  }>;
18
+ export type CodeBlockAdvancedPluginOptions = {
19
+ extensions?: Extension[];
20
+ allowCodeFolding?: boolean;
21
+ };
@@ -1,2 +1,2 @@
1
1
  export { codeBlockAdvancedPlugin } from './codeBlockAdvancedPlugin';
2
- export type { CodeBlockAdvancedPlugin } from './codeBlockAdvancedPluginType';
2
+ export type { CodeBlockAdvancedPlugin, CodeBlockAdvancedPluginOptions, } from './codeBlockAdvancedPluginType';
@@ -5,10 +5,11 @@ import type { getPosHandler, getPosHandlerNode, ExtractInjectionAPI } from '@atl
5
5
  import { type Node as PMNode } from '@atlaskit/editor-prosemirror/model';
6
6
  import type { Decoration, DecorationSource, EditorView, NodeView } from '@atlaskit/editor-prosemirror/view';
7
7
  import type { CodeBlockAdvancedPlugin } from '../codeBlockAdvancedPluginType';
8
- interface ConfigProps {
8
+ export interface ConfigProps {
9
9
  api: ExtractInjectionAPI<CodeBlockAdvancedPlugin> | undefined;
10
10
  extensions: Extension[];
11
11
  getIntl: () => IntlShape;
12
+ allowCodeFolding: boolean;
12
13
  }
13
14
  declare class CodeBlockAdvancedNodeView implements NodeView {
14
15
  dom: Node;
@@ -1,2 +1,6 @@
1
1
  import type { NodeSpec } from '@atlaskit/editor-prosemirror/model';
2
- export declare const codeBlockNodeWithFixedToDOM: () => NodeSpec;
2
+ interface Config {
3
+ allowCodeFolding: boolean;
4
+ }
5
+ export declare const codeBlockNodeWithFixedToDOM: (config: Config) => NodeSpec;
6
+ export {};
@@ -0,0 +1,3 @@
1
+ export declare function foldGutterExtension({ selectNode }: {
2
+ selectNode: () => void;
3
+ }): import("@codemirror/state").Extension[];
@@ -6,6 +6,7 @@ interface Props {
6
6
  api: ExtractInjectionAPI<CodeBlockAdvancedPlugin> | undefined;
7
7
  extensions: Extension[];
8
8
  getIntl: () => IntlShape;
9
+ allowCodeFolding: boolean;
9
10
  }
10
11
  export declare const lazyCodeBlockView: (props: Props) => import("@atlaskit/editor-common/lazy-node-view").NodeViewConstructor;
11
12
  export {};
@@ -7,6 +7,7 @@ interface Props {
7
7
  api: ExtractInjectionAPI<CodeBlockAdvancedPlugin> | undefined;
8
8
  extensions: Extension[];
9
9
  getIntl: () => IntlShape;
10
+ allowCodeFolding: boolean;
10
11
  }
11
12
  export declare const createPlugin: (props: Props) => SafePlugin<any>;
12
13
  export {};
@@ -1 +1,2 @@
1
1
  export declare const cmTheme: import("@codemirror/state").Extension;
2
+ export declare const codeFoldingTheme: import("@codemirror/state").Extension;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-code-block-advanced",
3
- "version": "3.0.7",
3
+ "version": "3.1.0",
4
4
  "description": "CodeBlockAdvanced plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -39,7 +39,7 @@
39
39
  "@atlaskit/editor-plugin-selection-marker": "^3.0.0",
40
40
  "@atlaskit/editor-prosemirror": "7.0.0",
41
41
  "@atlaskit/platform-feature-flags": "^1.1.0",
42
- "@atlaskit/tmp-editor-statsig": "^9.22.0",
42
+ "@atlaskit/tmp-editor-statsig": "^9.27.0",
43
43
  "@atlaskit/tokens": "^6.0.0",
44
44
  "@babel/runtime": "^7.0.0",
45
45
  "@codemirror/autocomplete": "6.18.4",
@@ -54,12 +54,11 @@
54
54
  "codemirror-lang-elixir": "4.0.0"
55
55
  },
56
56
  "peerDependencies": {
57
- "@atlaskit/editor-common": "^107.16.0",
57
+ "@atlaskit/editor-common": "^107.20.0",
58
58
  "react": "^18.2.0"
59
59
  },
60
60
  "devDependencies": {
61
- "@atlaskit/code": "^17.2.0",
62
- "typescript": "~5.4.2"
61
+ "@atlaskit/code": "^17.2.0"
63
62
  },
64
63
  "techstack": {
65
64
  "@atlassian/frontend": {
@@ -9,7 +9,7 @@ export const codeBlockAdvancedPlugin: CodeBlockAdvancedPlugin = ({ api, config }
9
9
  return [
10
10
  {
11
11
  name: 'codeBlock',
12
- node: codeBlockNodeWithFixedToDOM(),
12
+ node: codeBlockNodeWithFixedToDOM({ allowCodeFolding: config?.allowCodeFolding ?? false }),
13
13
  },
14
14
  ];
15
15
  },
@@ -19,7 +19,12 @@ export const codeBlockAdvancedPlugin: CodeBlockAdvancedPlugin = ({ api, config }
19
19
  {
20
20
  name: 'codeBlockAdvancedPlugin',
21
21
  plugin: ({ getIntl }) =>
22
- createPlugin({ api, extensions: config?.extensions ?? [], getIntl }),
22
+ createPlugin({
23
+ api,
24
+ extensions: config?.extensions ?? [],
25
+ allowCodeFolding: config?.allowCodeFolding ?? false,
26
+ getIntl
27
+ }),
23
28
  },
24
29
  ];
25
30
  },
@@ -17,10 +17,11 @@ export type CodeBlockAdvancedPlugin = NextEditorPlugin<
17
17
  OptionalPlugin<SelectionMarkerPlugin>,
18
18
  OptionalPlugin<FindReplacePlugin>,
19
19
  ];
20
- pluginConfiguration:
21
- | {
22
- extensions?: Extension[];
23
- }
24
- | undefined;
20
+ pluginConfiguration: CodeBlockAdvancedPluginOptions | undefined;
25
21
  }
26
22
  >;
23
+
24
+ export type CodeBlockAdvancedPluginOptions = {
25
+ extensions?: Extension[];
26
+ allowCodeFolding?: boolean;
27
+ };
package/src/index.ts CHANGED
@@ -2,4 +2,7 @@
2
2
  // Entry file in package.json
3
3
 
4
4
  export { codeBlockAdvancedPlugin } from './codeBlockAdvancedPlugin';
5
- export type { CodeBlockAdvancedPlugin } from './codeBlockAdvancedPluginType';
5
+ export type {
6
+ CodeBlockAdvancedPlugin,
7
+ CodeBlockAdvancedPluginOptions,
8
+ } from './codeBlockAdvancedPluginType';
@@ -33,21 +33,23 @@ import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equ
33
33
 
34
34
  import type { CodeBlockAdvancedPlugin } from '../codeBlockAdvancedPluginType';
35
35
  import { highlightStyle } from '../ui/syntaxHighlightingTheme';
36
- import { cmTheme } from '../ui/theme';
36
+ import { cmTheme, codeFoldingTheme } from '../ui/theme';
37
37
 
38
38
  import { syncCMWithPM } from './codemirrorSync/syncCMWithPM';
39
39
  import { getCMSelectionChanges } from './codemirrorSync/updateCMSelection';
40
40
  import { firstCodeBlockInDocument } from './extensions/firstCodeBlockInDocument';
41
+ import { foldGutterExtension } from './extensions/foldGutter';
41
42
  import { keymapExtension } from './extensions/keymap';
42
43
  import { manageSelectionMarker } from './extensions/manageSelectionMarker';
43
44
  import { prosemirrorDecorationPlugin } from './extensions/prosemirrorDecorations';
44
45
  import { tripleClickSelectAllExtension } from './extensions/tripleClickExtension';
45
46
  import { LanguageLoader } from './languages/loader';
46
47
 
47
- interface ConfigProps {
48
+ export interface ConfigProps {
48
49
  api: ExtractInjectionAPI<CodeBlockAdvancedPlugin> | undefined;
49
50
  extensions: Extension[];
50
51
  getIntl: () => IntlShape;
52
+ allowCodeFolding: boolean;
51
53
  }
52
54
 
53
55
  // Based on: https://prosemirror.net/examples/codemirror/
@@ -94,6 +96,11 @@ class CodeBlockAdvancedNodeView implements NodeView {
94
96
  const { formatMessage } = config.getIntl();
95
97
  const formattedAriaLabel = formatMessage(blockTypeMessages.codeblock);
96
98
 
99
+ const selectNode = () => {
100
+ this.selectCodeBlockNode(undefined);
101
+ this.view.focus();
102
+ };
103
+
97
104
  this.cm = new CodeMirror({
98
105
  doc: this.node.textContent,
99
106
  extensions: [
@@ -111,14 +118,15 @@ class CodeBlockAdvancedNodeView implements NodeView {
111
118
  onMaybeNodeSelection,
112
119
  customFindReplace: Boolean(config.api?.findReplace),
113
120
  }),
121
+ // Goes before cmTheme to override styles
122
+ config.allowCodeFolding ? [codeFoldingTheme] : [],
114
123
  cmTheme,
115
124
  syntaxHighlighting(highlightStyle),
116
125
  bracketMatching(),
117
126
  lineNumbers({
118
127
  domEventHandlers: {
119
128
  click: () => {
120
- this.selectCodeBlockNode(undefined);
121
- this.view.focus();
129
+ selectNode();
122
130
  return true;
123
131
  },
124
132
  },
@@ -138,6 +146,7 @@ class CodeBlockAdvancedNodeView implements NodeView {
138
146
  tripleClickSelectAllExtension(),
139
147
  firstCodeBlockInDocument(getPos),
140
148
  CodeMirror.contentAttributes.of({ 'aria-label': formattedAriaLabel }),
149
+ config.allowCodeFolding ? [foldGutterExtension({ selectNode })] : [],
141
150
  ],
142
151
  });
143
152
 
@@ -4,6 +4,10 @@ import { CodeBlockSharedCssClassName } from '@atlaskit/editor-common/styles';
4
4
  import type { NodeSpec, DOMOutputSpec, Node } from '@atlaskit/editor-prosemirror/model';
5
5
  import { token } from '@atlaskit/tokens';
6
6
 
7
+ interface Config {
8
+ allowCodeFolding: boolean;
9
+ }
10
+
7
11
  const codeBlockClassNames = {
8
12
  container: CodeBlockSharedCssClassName.CODEBLOCK_CONTAINER,
9
13
  start: CodeBlockSharedCssClassName.CODEBLOCK_START,
@@ -15,7 +19,7 @@ const codeBlockClassNames = {
15
19
  const MATCH_NEWLINES = new RegExp('\n', 'gu');
16
20
 
17
21
  // Based on: `packages/editor/editor-plugin-code-block/src/nodeviews/code-block.ts`
18
- const toDOM = (node: Node, formattedAriaLabel: string): DOMOutputSpec => {
22
+ const toDOM = (node: Node, formattedAriaLabel: string, config: Config): DOMOutputSpec => {
19
23
  let totalLineCount = 1;
20
24
 
21
25
  node.forEach((node) => {
@@ -54,7 +58,10 @@ const toDOM = (node: Node, formattedAriaLabel: string): DOMOutputSpec => {
54
58
  backgroundColor: token('color.background.neutral'),
55
59
  position: 'relative',
56
60
  width: 'var(--lineNumberGutterWidth, 2rem)',
57
- padding: token('space.100'),
61
+ /* top and bottom | left and right */
62
+ padding: config.allowCodeFolding
63
+ ? `${token('space.100')} ${token('space.250')} ${token('space.100')} ${token('space.075')}`
64
+ : token('space.100'),
58
65
  flexShrink: 0,
59
66
  fontSize: '0.875rem',
60
67
  boxSizing: 'content-box',
@@ -96,9 +103,9 @@ const toDOM = (node: Node, formattedAriaLabel: string): DOMOutputSpec => {
96
103
  ];
97
104
  };
98
105
 
99
- export const codeBlockNodeWithFixedToDOM = (): NodeSpec => {
106
+ export const codeBlockNodeWithFixedToDOM = (config: Config): NodeSpec => {
100
107
  return {
101
108
  ...codeBlock,
102
- toDOM: (node) => toDOM(node, ''),
109
+ toDOM: (node) => toDOM(node, '', config),
103
110
  };
104
111
  };
@@ -0,0 +1,128 @@
1
+ import { foldGutter, codeFolding } from '@codemirror/language';
2
+
3
+ import { convertToInlineCss } from '@atlaskit/editor-common/lazy-node-view';
4
+ import type { DOMOutputSpec } from '@atlaskit/editor-prosemirror/model';
5
+ import { DOMSerializer } from '@atlaskit/editor-prosemirror/model';
6
+ import { token } from '@atlaskit/tokens';
7
+
8
+ // Based on platform/packages/design-system/icon/svgs/utility/add.svg
9
+ const chevronDown: DOMOutputSpec = [
10
+ 'http://www.w3.org/2000/svg svg',
11
+ {
12
+ width: '12',
13
+ height: '12',
14
+ fill: 'none',
15
+ viewBox: '0 0 16 16',
16
+ style: 'pointer-events: none;',
17
+ },
18
+ [
19
+ 'http://www.w3.org/2000/svg path',
20
+ {
21
+ fill: 'currentcolor',
22
+ 'fill-rule': 'evenodd',
23
+ d: 'm14.53 6.03-6 6a.75.75 0 0 1-1.004.052l-.056-.052-6-6 1.06-1.06L8 10.44l5.47-5.47z',
24
+ 'clip-rule': 'evenodd',
25
+ style: 'pointer-events: none;',
26
+ },
27
+ ],
28
+ ];
29
+
30
+ const chevronRight: DOMOutputSpec = [
31
+ 'http://www.w3.org/2000/svg svg',
32
+ {
33
+ width: '12',
34
+ height: '12',
35
+ fill: 'none',
36
+ viewBox: '0 0 16 16',
37
+ style: 'pointer-events: none;',
38
+ },
39
+ [
40
+ 'http://www.w3.org/2000/svg path',
41
+ {
42
+ fill: 'currentcolor',
43
+ 'fill-rule': 'evenodd',
44
+ d: 'm8.28 1.47 6 6a.75.75 0 0 1 .052 1.004l-.052.056-6 6-1.06-1.06L12.69 8 7.22 2.53z',
45
+ 'clip-rule': 'evenodd',
46
+ style: 'pointer-events: none;',
47
+ },
48
+ ],
49
+ ];
50
+
51
+ export function foldGutterExtension({ selectNode }: { selectNode: () => void }) {
52
+ return [
53
+ foldGutter({
54
+ domEventHandlers: {
55
+ click: (_view, _, event) => {
56
+ // If we're trying to click the button, don't select
57
+ if (
58
+ event.target instanceof HTMLButtonElement &&
59
+ event.target.getAttribute('data-marker-dom-element')
60
+ ) {
61
+ return false;
62
+ }
63
+ selectNode();
64
+ return false;
65
+ },
66
+ },
67
+ markerDOM: (open) => {
68
+ const { dom: downElement } = DOMSerializer.renderSpec(document, chevronDown);
69
+ const { dom: rightElement } = DOMSerializer.renderSpec(document, chevronRight);
70
+ const htmlElement = document.createElement('button');
71
+ htmlElement.setAttribute('data-marker-dom-element', 'true');
72
+ htmlElement.setAttribute(
73
+ 'data-testid',
74
+ `code-block-fold-button-${open ? 'open' : 'closed'}`,
75
+ );
76
+ htmlElement.setAttribute(
77
+ 'style',
78
+ convertToInlineCss({
79
+ background: 'none',
80
+ color: 'inherit',
81
+ border: 'none',
82
+ padding: 0,
83
+ paddingRight: token('space.050'),
84
+ font: 'inherit',
85
+ outline: 'inherit',
86
+ cursor: 'pointer',
87
+ }),
88
+ );
89
+
90
+ if (open) {
91
+ if (downElement) {
92
+ htmlElement.appendChild(downElement);
93
+ } else {
94
+ // Fallback - never called
95
+ htmlElement.textContent = '⌄';
96
+ }
97
+ } else {
98
+ if (rightElement) {
99
+ htmlElement.appendChild(rightElement);
100
+ } else {
101
+ // Fallback - never called
102
+ htmlElement.textContent = '>';
103
+ }
104
+ }
105
+
106
+ return htmlElement;
107
+ },
108
+ }),
109
+ codeFolding({
110
+ placeholderDOM(view, onclick, prepared) {
111
+ const htmlElement = document.createElement('button');
112
+ htmlElement.setAttribute('data-marker-dom-element', 'true');
113
+ htmlElement.setAttribute(
114
+ 'style',
115
+ convertToInlineCss({
116
+ color: 'inherit',
117
+ font: 'inherit',
118
+ cursor: 'pointer',
119
+ }),
120
+ );
121
+ htmlElement.textContent = '…';
122
+ htmlElement.className = 'cm-foldPlaceholder';
123
+ htmlElement.onclick = onclick;
124
+ return htmlElement;
125
+ },
126
+ }),
127
+ ];
128
+ }
@@ -12,6 +12,7 @@ interface Props {
12
12
  api: ExtractInjectionAPI<CodeBlockAdvancedPlugin> | undefined;
13
13
  extensions: Extension[];
14
14
  getIntl: () => IntlShape;
15
+ allowCodeFolding: boolean;
15
16
  }
16
17
 
17
18
  export const lazyCodeBlockView = (props: Props) => {
@@ -14,6 +14,7 @@ interface Props {
14
14
  api: ExtractInjectionAPI<CodeBlockAdvancedPlugin> | undefined;
15
15
  extensions: Extension[];
16
16
  getIntl: () => IntlShape;
17
+ allowCodeFolding: boolean;
17
18
  }
18
19
 
19
20
  export const createPlugin = (props: Props) => {
package/src/ui/theme.ts CHANGED
@@ -73,6 +73,37 @@ export const cmTheme = CodeMirror.theme({
73
73
  },
74
74
  });
75
75
 
76
+ export const codeFoldingTheme = CodeMirror.theme({
77
+ '.cm-gutter': {
78
+ paddingLeft: token('space.075'),
79
+ paddingTop: token('space.100'),
80
+ paddingBottom: token('space.100'),
81
+ paddingRight: token('space.0'),
82
+ },
83
+ '.cm-foldGutter': {
84
+ paddingLeft: token('space.050'),
85
+ },
86
+ '.cm-gutterElement:has([data-marker-dom-element="true"])': {
87
+ color: token('color.icon.subtle'),
88
+ },
89
+ '.cm-gutterElement:has([data-marker-dom-element="true"]):hover': {
90
+ color: token('color.text.accent.gray.bolder'),
91
+ },
92
+ '.cm-foldPlaceholder': {
93
+ // To give spacing between lines
94
+ height: '20px',
95
+ backgroundColor: token('color.background.accent.gray.subtlest'),
96
+ border: 'none',
97
+ color: token('color.text'),
98
+ outline: `1px solid ${token('color.border.accent.gray')}`,
99
+ paddingLeft: token('space.025'),
100
+ paddingRight: token('space.025'),
101
+ },
102
+ '.cm-foldPlaceholder:hover': {
103
+ backgroundColor: token('color.background.accent.gray.subtlest.hovered'),
104
+ },
105
+ });
106
+
76
107
  /**
77
108
  * Copied directly from `packages/editor/editor-shared-styles/src/overflow-shadow/overflow-shadow.ts`
78
109
  * `CodeMirror` does not support emotion styling so this has been re-created.