@atlaskit/editor-plugin-code-block-advanced 1.0.0 → 1.0.2

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 (31) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/afm-jira/tsconfig.json +36 -0
  3. package/afm-post-office/tsconfig.json +36 -0
  4. package/dist/cjs/nodeviews/codeBlockAdvanced.js +29 -4
  5. package/dist/cjs/nodeviews/codeBlockNodeWithToDOMFixed.js +42 -12
  6. package/dist/cjs/nodeviews/extensions/copyButtonDecorations.js +22 -0
  7. package/dist/cjs/ui/syntaxHighlightingTheme.js +5 -2
  8. package/dist/cjs/ui/theme.js +4 -3
  9. package/dist/es2019/nodeviews/codeBlockAdvanced.js +29 -6
  10. package/dist/es2019/nodeviews/codeBlockNodeWithToDOMFixed.js +58 -28
  11. package/dist/es2019/nodeviews/extensions/copyButtonDecorations.js +16 -0
  12. package/dist/es2019/ui/syntaxHighlightingTheme.js +5 -2
  13. package/dist/es2019/ui/theme.js +4 -3
  14. package/dist/esm/nodeviews/codeBlockAdvanced.js +31 -6
  15. package/dist/esm/nodeviews/codeBlockNodeWithToDOMFixed.js +42 -12
  16. package/dist/esm/nodeviews/extensions/copyButtonDecorations.js +16 -0
  17. package/dist/esm/ui/syntaxHighlightingTheme.js +5 -2
  18. package/dist/esm/ui/theme.js +4 -3
  19. package/dist/types/nodeviews/codeBlockAdvanced.d.ts +3 -0
  20. package/dist/types/nodeviews/extensions/copyButtonDecorations.d.ts +1 -0
  21. package/dist/types-ts4.5/nodeviews/codeBlockAdvanced.d.ts +3 -0
  22. package/dist/types-ts4.5/nodeviews/extensions/copyButtonDecorations.d.ts +1 -0
  23. package/package.json +4 -4
  24. package/src/nodeviews/codeBlockAdvanced.ts +30 -2
  25. package/src/nodeviews/codeBlockNodeWithToDOMFixed.ts +75 -30
  26. package/src/nodeviews/extensions/copyButtonDecorations.ts +15 -0
  27. package/src/nodeviews/lazyCodeBlockAdvanced.ts +0 -1
  28. package/src/ui/syntaxHighlightingTheme.ts +8 -1
  29. package/src/ui/theme.ts +2 -1
  30. package/tsconfig.app.json +53 -0
  31. package/tsconfig.dev.json +47 -0
@@ -3,9 +3,9 @@ import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
3
3
  import _createClass from "@babel/runtime/helpers/createClass";
4
4
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
5
5
  import { closeBrackets } from '@codemirror/autocomplete';
6
- import { syntaxHighlighting } from '@codemirror/language';
6
+ import { syntaxHighlighting, bracketMatching } from '@codemirror/language';
7
7
  import { Compartment, EditorSelection } from '@codemirror/state';
8
- import { EditorView as CodeMirror, lineNumbers } from '@codemirror/view';
8
+ import { EditorView as CodeMirror, lineNumbers, gutters } from '@codemirror/view';
9
9
  import { isCodeBlockWordWrapEnabled } from '@atlaskit/editor-common/code-block';
10
10
  import { NodeSelection } from '@atlaskit/editor-prosemirror/state';
11
11
  import { highlightStyle } from '../ui/syntaxHighlightingTheme';
@@ -13,6 +13,7 @@ import { cmTheme } from '../ui/theme';
13
13
  import { syncCMWithPM } from './codemirrorSync/syncCMWithPM';
14
14
  import { updateCMSelection } from './codemirrorSync/updateCMSelection';
15
15
  import { bidiCharWarningExtension } from './extensions/bidiCharWarning';
16
+ import { copyButtonDecorations } from './extensions/copyButtonDecorations';
16
17
  import { keymapExtension } from './extensions/keymap';
17
18
  import { LanguageLoader } from './languages/loader';
18
19
  // Based on: https://prosemirror.net/examples/codemirror/
@@ -21,11 +22,13 @@ var CodeBlockAdvancedNodeView = /*#__PURE__*/function () {
21
22
  function CodeBlockAdvancedNodeView(node, view, getPos, config) {
22
23
  var _config$api,
23
24
  _this = this,
24
- _config$api2;
25
+ _config$api2,
26
+ _config$api3;
25
27
  _classCallCheck(this, CodeBlockAdvancedNodeView);
26
28
  _defineProperty(this, "lineWrappingCompartment", new Compartment());
27
29
  _defineProperty(this, "languageCompartment", new Compartment());
28
30
  _defineProperty(this, "readOnlyCompartment", new Compartment());
31
+ _defineProperty(this, "copyDecoCompartment", new Compartment());
29
32
  _defineProperty(this, "maybeTryingToReachNodeSelection", false);
30
33
  _defineProperty(this, "wordWrappingEnabled", false);
31
34
  this.node = node;
@@ -41,6 +44,13 @@ var CodeBlockAdvancedNodeView = /*#__PURE__*/function () {
41
44
  this.cleanupDisabledState = (_config$api2 = config.api) === null || _config$api2 === void 0 || (_config$api2 = _config$api2.editorDisabled) === null || _config$api2 === void 0 ? void 0 : _config$api2.sharedState.onChange(function () {
42
45
  _this.updateReadonlyState();
43
46
  });
47
+ this.cleanupCopyButtonDecoration = (_config$api3 = config.api) === null || _config$api3 === void 0 || (_config$api3 = _config$api3.codeBlock) === null || _config$api3 === void 0 ? void 0 : _config$api3.sharedState.onChange(function (_ref) {
48
+ var nextSharedState = _ref.nextSharedState,
49
+ prevSharedState = _ref.prevSharedState;
50
+ if ((nextSharedState === null || nextSharedState === void 0 ? void 0 : nextSharedState.copyButtonHoverNode) !== (prevSharedState === null || prevSharedState === void 0 ? void 0 : prevSharedState.copyButtonHoverNode)) {
51
+ _this.addCopyButtonDecoration(nextSharedState === null || nextSharedState === void 0 ? void 0 : nextSharedState.copyButtonHoverNode);
52
+ }
53
+ });
44
54
  this.languageLoader = new LanguageLoader(function (lang) {
45
55
  _this.updating = true;
46
56
  _this.cm.dispatch({
@@ -50,13 +60,18 @@ var CodeBlockAdvancedNodeView = /*#__PURE__*/function () {
50
60
  });
51
61
  this.cm = new CodeMirror({
52
62
  doc: this.node.textContent,
53
- extensions: [].concat(_toConsumableArray(config.extensions), [this.lineWrappingCompartment.of([]), this.languageCompartment.of([]), keymapExtension({
63
+ extensions: [].concat(_toConsumableArray(config.extensions), [this.lineWrappingCompartment.of([]), this.languageCompartment.of([]), this.copyDecoCompartment.of([]), keymapExtension({
54
64
  view: view,
55
65
  getPos: getPos,
56
66
  getNode: getNode,
57
67
  selectCodeBlockNode: this.selectCodeBlockNode.bind(this),
58
68
  onMaybeNodeSelection: onMaybeNodeSelection
59
- }), cmTheme, syntaxHighlighting(highlightStyle), lineNumbers(), CodeMirror.updateListener.of(function (update) {
69
+ }), cmTheme, syntaxHighlighting(highlightStyle), bracketMatching(), lineNumbers(),
70
+ // Explicitly disable "sticky" positioning on line numbers to match
71
+ // Renderer behaviour
72
+ gutters({
73
+ fixed: false
74
+ }), CodeMirror.updateListener.of(function (update) {
60
75
  return _this.forwardUpdate(update);
61
76
  }), this.readOnlyCompartment.of(CodeMirror.editable.of(this.view.editable)), closeBrackets(), CodeMirror.editorAttributes.of({
62
77
  class: 'code-block'
@@ -74,8 +89,9 @@ var CodeBlockAdvancedNodeView = /*#__PURE__*/function () {
74
89
  return _createClass(CodeBlockAdvancedNodeView, [{
75
90
  key: "destroy",
76
91
  value: function destroy() {
77
- var _this$cleanupDisabled;
92
+ var _this$cleanupDisabled, _this$cleanupCopyButt;
78
93
  (_this$cleanupDisabled = this.cleanupDisabledState) === null || _this$cleanupDisabled === void 0 || _this$cleanupDisabled.call(this);
94
+ (_this$cleanupCopyButt = this.cleanupCopyButtonDecoration) === null || _this$cleanupCopyButt === void 0 || _this$cleanupCopyButt.call(this);
79
95
  }
80
96
  }, {
81
97
  key: "forwardUpdate",
@@ -132,6 +148,15 @@ var CodeBlockAdvancedNodeView = /*#__PURE__*/function () {
132
148
  this.view.dispatch(tr);
133
149
  }
134
150
  }
151
+ }, {
152
+ key: "addCopyButtonDecoration",
153
+ value: function addCopyButtonDecoration(node) {
154
+ this.updating = true;
155
+ this.cm.dispatch({
156
+ effects: [this.copyDecoCompartment.reconfigure(node && node === this.node ? copyButtonDecorations : [])]
157
+ });
158
+ this.updating = false;
159
+ }
135
160
  }, {
136
161
  key: "updateWordWrap",
137
162
  value: function updateWordWrap(node) {
@@ -2,36 +2,66 @@ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
2
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
3
3
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
4
4
  import { codeBlock } from '@atlaskit/adf-schema';
5
+ import { convertToInlineCss } from '@atlaskit/editor-common/lazy-node-view';
5
6
  import { CodeBlockSharedCssClassName } from '@atlaskit/editor-common/styles';
6
7
  var codeBlockClassNames = {
7
8
  container: CodeBlockSharedCssClassName.CODEBLOCK_CONTAINER,
8
9
  start: CodeBlockSharedCssClassName.CODEBLOCK_START,
9
10
  end: CodeBlockSharedCssClassName.CODEBLOCK_END,
10
11
  contentWrapper: CodeBlockSharedCssClassName.CODEBLOCK_CONTENT_WRAPPER,
11
- contentWrapped: CodeBlockSharedCssClassName.CODEBLOCK_CONTENT_WRAPPED,
12
- gutter: CodeBlockSharedCssClassName.CODEBLOCK_LINE_NUMBER_GUTTER,
13
- content: CodeBlockSharedCssClassName.CODEBLOCK_CONTENT,
14
- lineNumberWidget: CodeBlockSharedCssClassName.CODEBLOCK_CONTAINER_LINE_NUMBER_WIDGET
12
+ content: CodeBlockSharedCssClassName.CODEBLOCK_CONTENT
15
13
  };
14
+ var MATCH_NEWLINES = new RegExp('\n', 'gu');
16
15
 
17
- // From: `packages/editor/editor-plugin-code-block/src/nodeviews/code-block.ts`
18
- var _toDOM = function toDOM(node, contentEditable, formattedAriaLabel) {
19
- return ['div', {
20
- class: codeBlockClassNames.container
16
+ // Based on: `packages/editor/editor-plugin-code-block/src/nodeviews/code-block.ts`
17
+ var _toDOM = function toDOM(node, formattedAriaLabel) {
18
+ var totalLineCount = 1;
19
+ node.forEach(function (node) {
20
+ var text = node.text;
21
+ if (text) {
22
+ totalLineCount += (node.text.match(MATCH_NEWLINES) || []).length;
23
+ }
24
+ });
25
+ var maxDigits = totalLineCount.toString().length;
26
+ var content = node.textContent.split('\n').map(function (_, i) {
27
+ return i + 1;
28
+ }).join('\n');
29
+ return ['pre', {
30
+ class: codeBlockClassNames.container,
31
+ style: "--lineNumberGutterWidth:".concat(maxDigits, "ch;"),
32
+ 'data-language': node.attrs.language || ''
21
33
  }, ['div', {
22
34
  class: codeBlockClassNames.start,
23
35
  contenteditable: 'false'
24
36
  }], ['div', {
25
37
  class: codeBlockClassNames.contentWrapper
26
38
  }, ['div', {
27
- class: codeBlockClassNames.gutter,
39
+ // Based on packages/editor/editor-common/src/styles/shared/code-block.ts
40
+ // But we can't reuse that class as it adds a ::before that intefers with this approach
41
+ style: convertToInlineCss({
42
+ backgroundColor: "var(--ds-background-neutral, #091E420F)",
43
+ position: 'relative',
44
+ width: 'var(--lineNumberGutterWidth, 2rem)',
45
+ padding: "var(--ds-space-100, 8px)",
46
+ flexShrink: 0,
47
+ fontSize: '0.875rem',
48
+ boxSizing: 'content-box'
49
+ }),
28
50
  contenteditable: 'false'
29
- }], ['div', {
51
+ }, ['div', {
52
+ class: 'code-block-gutter-pseudo-element',
53
+ style: convertToInlineCss({
54
+ textAlign: 'right',
55
+ color: "var(--ds-text-subtlest, #626F86)",
56
+ fontFamily: "var(--ds-font-family-code, ui-monospace, Menlo, \"Segoe UI Mono\", \"Ubuntu Mono\", monospace)",
57
+ whiteSpace: 'pre-wrap'
58
+ }),
59
+ 'data-label': content
60
+ }]], ['div', {
30
61
  class: codeBlockClassNames.content
31
62
  }, ['code', {
32
63
  'data-language': node.attrs.language || '',
33
64
  spellcheck: 'false',
34
- contenteditable: contentEditable ? 'true' : 'false',
35
65
  'data-testid': 'code-block--code',
36
66
  'aria-label': formattedAriaLabel
37
67
  }, 0]]], ['div', {
@@ -42,7 +72,7 @@ var _toDOM = function toDOM(node, contentEditable, formattedAriaLabel) {
42
72
  export var codeBlockNodeWithFixedToDOM = function codeBlockNodeWithFixedToDOM() {
43
73
  return _objectSpread(_objectSpread({}, codeBlock), {}, {
44
74
  toDOM: function toDOM(node) {
45
- return _toDOM(node, false, '');
75
+ return _toDOM(node, '');
46
76
  }
47
77
  });
48
78
  };
@@ -0,0 +1,16 @@
1
+ import { RangeSetBuilder } from '@codemirror/state';
2
+ import { EditorView as CodeMirror, Decoration } from '@codemirror/view';
3
+ export var copyButtonDecorations = CodeMirror.decorations.compute([], function (state) {
4
+ var allTextDecoration = Decoration.mark({
5
+ attributes: {
6
+ class: 'ProseMirror-fake-text-selection'
7
+ }
8
+ });
9
+ // Create a set of decorations for the entire document
10
+ var builder = new RangeSetBuilder();
11
+ for (var i = 0; i < state.doc.lines; i++) {
12
+ builder.add(state.doc.line(i + 1).from, state.doc.line(i + 1).to, allTextDecoration);
13
+ }
14
+ var decorations = builder.finish();
15
+ return decorations;
16
+ });
@@ -40,8 +40,11 @@ export var highlightStyle = HighlightStyle.define([{
40
40
  tag: [tags.string, tags.deleted],
41
41
  color: "var(--ds-text-accent-green, #216E4E)"
42
42
  }, {
43
- tag: [tags.regexp, tags.escape, tags.special(tags.string)],
44
- color: "var(--ds-text, #172B4D)"
43
+ tag: [tags.special(tags.string)],
44
+ color: "var(--ds-text-accent-green, #216E4E)"
45
+ }, {
46
+ tag: [tags.regexp, tags.escape],
47
+ color: "var(--ds-text-accent-teal, #206A83)"
45
48
  }, {
46
49
  tag: tags.definition(tags.variableName),
47
50
  color: "var(--ds-text, #172B4D)"
@@ -5,7 +5,6 @@ export var cmTheme = CodeMirror.theme({
5
5
  padding: '0',
6
6
  marginTop: "var(--ds-space-100, 8px)",
7
7
  marginBottom: "var(--ds-space-100, 8px)",
8
- borderRadius: "var(--ds-border-radius, 4px)",
9
8
  fontSize: '0.875rem',
10
9
  // Custom syntax styling to match existing styling
11
10
  // eslint-disable-next-line @atlaskit/design-system/use-tokens-typography
@@ -31,7 +30,8 @@ export var cmTheme = CodeMirror.theme({
31
30
  // Custom syntax styling to match existing styling
32
31
  // eslint-disable-next-line @atlaskit/design-system/use-tokens-typography
33
32
  lineHeight: 'unset',
34
- fontFamily: "var(--ds-font-family-code, ui-monospace, Menlo, \"Segoe UI Mono\", \"Ubuntu Mono\", monospace)"
33
+ fontFamily: "var(--ds-font-family-code, ui-monospace, Menlo, \"Segoe UI Mono\", \"Ubuntu Mono\", monospace)",
34
+ borderRadius: "var(--ds-border-radius, 4px)"
35
35
  },
36
36
  '&.cm-focused .cm-cursor': {
37
37
  borderLeftColor: "var(--ds-text, #172B4D)"
@@ -39,7 +39,8 @@ export var cmTheme = CodeMirror.theme({
39
39
  '.cm-gutters': {
40
40
  backgroundColor: "var(--ds-background-neutral, #091E420F)",
41
41
  border: 'none',
42
- padding: "var(--ds-space-100, 8px)"
42
+ padding: "var(--ds-space-100, 8px)",
43
+ color: "var(--ds-text-subtlest, #626F86)"
43
44
  },
44
45
  '.cm-lineNumbers .cm-gutterElement': {
45
46
  paddingLeft: "var(--ds-space-0, 0px)",
@@ -15,12 +15,14 @@ declare class CodeBlockAdvancedNodeView implements NodeView {
15
15
  private lineWrappingCompartment;
16
16
  private languageCompartment;
17
17
  private readOnlyCompartment;
18
+ private copyDecoCompartment;
18
19
  private node;
19
20
  private getPos;
20
21
  private cm;
21
22
  private selectionAPI;
22
23
  private maybeTryingToReachNodeSelection;
23
24
  private cleanupDisabledState;
25
+ private cleanupCopyButtonDecoration;
24
26
  private languageLoader;
25
27
  constructor(node: PMNode, view: EditorView, getPos: getPosHandlerNode, config: ConfigProps);
26
28
  destroy(): void;
@@ -29,6 +31,7 @@ declare class CodeBlockAdvancedNodeView implements NodeView {
29
31
  private updateReadonlyState;
30
32
  private updateLanguage;
31
33
  private selectCodeBlockNode;
34
+ private addCopyButtonDecoration;
32
35
  private wordWrappingEnabled;
33
36
  private updateWordWrap;
34
37
  update(node: PMNode): boolean;
@@ -0,0 +1 @@
1
+ export declare const copyButtonDecorations: import("@codemirror/state").Extension;
@@ -15,12 +15,14 @@ declare class CodeBlockAdvancedNodeView implements NodeView {
15
15
  private lineWrappingCompartment;
16
16
  private languageCompartment;
17
17
  private readOnlyCompartment;
18
+ private copyDecoCompartment;
18
19
  private node;
19
20
  private getPos;
20
21
  private cm;
21
22
  private selectionAPI;
22
23
  private maybeTryingToReachNodeSelection;
23
24
  private cleanupDisabledState;
25
+ private cleanupCopyButtonDecoration;
24
26
  private languageLoader;
25
27
  constructor(node: PMNode, view: EditorView, getPos: getPosHandlerNode, config: ConfigProps);
26
28
  destroy(): void;
@@ -29,6 +31,7 @@ declare class CodeBlockAdvancedNodeView implements NodeView {
29
31
  private updateReadonlyState;
30
32
  private updateLanguage;
31
33
  private selectCodeBlockNode;
34
+ private addCopyButtonDecoration;
32
35
  private wordWrappingEnabled;
33
36
  private updateWordWrap;
34
37
  update(node: PMNode): boolean;
@@ -0,0 +1 @@
1
+ export declare const copyButtonDecorations: 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": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "CodeBlockAdvanced plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -33,12 +33,12 @@
33
33
  },
34
34
  "dependencies": {
35
35
  "@atlaskit/adf-schema": "^46.1.0",
36
- "@atlaskit/editor-common": "^99.1.0",
37
- "@atlaskit/editor-plugin-code-block": "^3.5.0",
36
+ "@atlaskit/editor-common": "^99.5.0",
37
+ "@atlaskit/editor-plugin-code-block": "^3.6.0",
38
38
  "@atlaskit/editor-plugin-editor-disabled": "^1.3.0",
39
39
  "@atlaskit/editor-plugin-selection": "^1.6.0",
40
40
  "@atlaskit/editor-prosemirror": "6.2.1",
41
- "@atlaskit/tokens": "^3.0.0",
41
+ "@atlaskit/tokens": "^3.2.0",
42
42
  "@babel/runtime": "^7.0.0",
43
43
  "@codemirror/autocomplete": "6.18.4",
44
44
  "@codemirror/commands": "6.7.1",
@@ -1,7 +1,7 @@
1
1
  import { closeBrackets } from '@codemirror/autocomplete';
2
- import { syntaxHighlighting } from '@codemirror/language';
2
+ import { syntaxHighlighting, bracketMatching } from '@codemirror/language';
3
3
  import { Compartment, Extension, EditorSelection } from '@codemirror/state';
4
- import { EditorView as CodeMirror, lineNumbers, ViewUpdate } from '@codemirror/view';
4
+ import { EditorView as CodeMirror, lineNumbers, ViewUpdate, gutters } from '@codemirror/view';
5
5
 
6
6
  import { isCodeBlockWordWrapEnabled } from '@atlaskit/editor-common/code-block';
7
7
  import { RelativeSelectionPos } from '@atlaskit/editor-common/selection';
@@ -22,6 +22,7 @@ import { cmTheme } from '../ui/theme';
22
22
  import { syncCMWithPM } from './codemirrorSync/syncCMWithPM';
23
23
  import { updateCMSelection } from './codemirrorSync/updateCMSelection';
24
24
  import { bidiCharWarningExtension } from './extensions/bidiCharWarning';
25
+ import { copyButtonDecorations } from './extensions/copyButtonDecorations';
25
26
  import { keymapExtension } from './extensions/keymap';
26
27
  import { LanguageLoader } from './languages/loader';
27
28
 
@@ -38,12 +39,14 @@ class CodeBlockAdvancedNodeView implements NodeView {
38
39
  private lineWrappingCompartment = new Compartment();
39
40
  private languageCompartment = new Compartment();
40
41
  private readOnlyCompartment = new Compartment();
42
+ private copyDecoCompartment = new Compartment();
41
43
  private node: PMNode;
42
44
  private getPos: getPosHandlerNode;
43
45
  private cm: CodeMirror;
44
46
  private selectionAPI: EditorSelectionAPI | undefined;
45
47
  private maybeTryingToReachNodeSelection = false;
46
48
  private cleanupDisabledState: (() => void) | undefined;
49
+ private cleanupCopyButtonDecoration: (() => void) | undefined;
47
50
  private languageLoader: LanguageLoader;
48
51
 
49
52
  // eslint-disable-next-line @typescript-eslint/max-params
@@ -57,6 +60,13 @@ class CodeBlockAdvancedNodeView implements NodeView {
57
60
  this.cleanupDisabledState = config.api?.editorDisabled?.sharedState.onChange(() => {
58
61
  this.updateReadonlyState();
59
62
  });
63
+ this.cleanupCopyButtonDecoration = config.api?.codeBlock?.sharedState.onChange(
64
+ ({ nextSharedState, prevSharedState }) => {
65
+ if (nextSharedState?.copyButtonHoverNode !== prevSharedState?.copyButtonHoverNode) {
66
+ this.addCopyButtonDecoration(nextSharedState?.copyButtonHoverNode);
67
+ }
68
+ },
69
+ );
60
70
  this.languageLoader = new LanguageLoader((lang) => {
61
71
  this.updating = true;
62
72
  this.cm.dispatch({
@@ -71,6 +81,7 @@ class CodeBlockAdvancedNodeView implements NodeView {
71
81
  ...config.extensions,
72
82
  this.lineWrappingCompartment.of([]),
73
83
  this.languageCompartment.of([]),
84
+ this.copyDecoCompartment.of([]),
74
85
  keymapExtension({
75
86
  view,
76
87
  getPos,
@@ -80,7 +91,11 @@ class CodeBlockAdvancedNodeView implements NodeView {
80
91
  }),
81
92
  cmTheme,
82
93
  syntaxHighlighting(highlightStyle),
94
+ bracketMatching(),
83
95
  lineNumbers(),
96
+ // Explicitly disable "sticky" positioning on line numbers to match
97
+ // Renderer behaviour
98
+ gutters({ fixed: false }),
84
99
  CodeMirror.updateListener.of((update) => this.forwardUpdate(update)),
85
100
  this.readOnlyCompartment.of(CodeMirror.editable.of(this.view.editable)),
86
101
  closeBrackets(),
@@ -100,6 +115,7 @@ class CodeBlockAdvancedNodeView implements NodeView {
100
115
 
101
116
  destroy() {
102
117
  this.cleanupDisabledState?.();
118
+ this.cleanupCopyButtonDecoration?.();
103
119
  }
104
120
 
105
121
  forwardUpdate(update: ViewUpdate) {
@@ -146,6 +162,18 @@ class CodeBlockAdvancedNodeView implements NodeView {
146
162
  }
147
163
  }
148
164
 
165
+ private addCopyButtonDecoration(node: PMNode | undefined) {
166
+ this.updating = true;
167
+ this.cm.dispatch({
168
+ effects: [
169
+ this.copyDecoCompartment.reconfigure(
170
+ node && node === this.node ? copyButtonDecorations : [],
171
+ ),
172
+ ],
173
+ });
174
+ this.updating = false;
175
+ }
176
+
149
177
  private wordWrappingEnabled = false;
150
178
 
151
179
  private updateWordWrap(node: PMNode) {
@@ -1,59 +1,104 @@
1
1
  import { codeBlock } from '@atlaskit/adf-schema';
2
+ import { convertToInlineCss } from '@atlaskit/editor-common/lazy-node-view';
2
3
  import { CodeBlockSharedCssClassName } from '@atlaskit/editor-common/styles';
3
4
  import type { NodeSpec, DOMOutputSpec, Node } from '@atlaskit/editor-prosemirror/model';
5
+ import { token } from '@atlaskit/tokens';
4
6
 
5
7
  const codeBlockClassNames = {
6
8
  container: CodeBlockSharedCssClassName.CODEBLOCK_CONTAINER,
7
9
  start: CodeBlockSharedCssClassName.CODEBLOCK_START,
8
10
  end: CodeBlockSharedCssClassName.CODEBLOCK_END,
9
11
  contentWrapper: CodeBlockSharedCssClassName.CODEBLOCK_CONTENT_WRAPPER,
10
- contentWrapped: CodeBlockSharedCssClassName.CODEBLOCK_CONTENT_WRAPPED,
11
- gutter: CodeBlockSharedCssClassName.CODEBLOCK_LINE_NUMBER_GUTTER,
12
12
  content: CodeBlockSharedCssClassName.CODEBLOCK_CONTENT,
13
- lineNumberWidget: CodeBlockSharedCssClassName.CODEBLOCK_CONTAINER_LINE_NUMBER_WIDGET,
14
13
  };
15
14
 
16
- // From: `packages/editor/editor-plugin-code-block/src/nodeviews/code-block.ts`
17
- const toDOM = (node: Node, contentEditable: boolean, formattedAriaLabel: string): DOMOutputSpec => [
18
- 'div',
19
- { class: codeBlockClassNames.container },
20
- ['div', { class: codeBlockClassNames.start, contenteditable: 'false' }],
21
- [
22
- 'div',
15
+ const MATCH_NEWLINES = new RegExp('\n', 'gu');
16
+
17
+ // Based on: `packages/editor/editor-plugin-code-block/src/nodeviews/code-block.ts`
18
+ const toDOM = (node: Node, formattedAriaLabel: string): DOMOutputSpec => {
19
+ let totalLineCount = 1;
20
+
21
+ node.forEach((node) => {
22
+ const text = node.text;
23
+ if (text) {
24
+ totalLineCount += (node.text.match(MATCH_NEWLINES) || []).length;
25
+ }
26
+ });
27
+
28
+ const maxDigits = totalLineCount.toString().length;
29
+
30
+ const content = node.textContent
31
+ .split('\n')
32
+ .map((_, i) => i + 1)
33
+ .join('\n');
34
+
35
+ return [
36
+ 'pre',
23
37
  {
24
- class: codeBlockClassNames.contentWrapper,
38
+ class: codeBlockClassNames.container,
39
+ style: `--lineNumberGutterWidth:${maxDigits}ch;`,
40
+ 'data-language': node.attrs.language || '',
25
41
  },
42
+ ['div', { class: codeBlockClassNames.start, contenteditable: 'false' }],
26
43
  [
27
44
  'div',
28
45
  {
29
- class: codeBlockClassNames.gutter,
30
- contenteditable: 'false',
31
- },
32
- ],
33
- [
34
- 'div',
35
- {
36
- class: codeBlockClassNames.content,
46
+ class: codeBlockClassNames.contentWrapper,
37
47
  },
38
48
  [
39
- 'code',
49
+ 'div',
40
50
  {
41
- 'data-language': node.attrs.language || '',
42
- spellcheck: 'false',
43
- contenteditable: contentEditable ? 'true' : 'false',
44
- 'data-testid': 'code-block--code',
45
- 'aria-label': formattedAriaLabel,
51
+ // Based on packages/editor/editor-common/src/styles/shared/code-block.ts
52
+ // But we can't reuse that class as it adds a ::before that intefers with this approach
53
+ style: convertToInlineCss({
54
+ backgroundColor: token('color.background.neutral'),
55
+ position: 'relative',
56
+ width: 'var(--lineNumberGutterWidth, 2rem)',
57
+ padding: token('space.100'),
58
+ flexShrink: 0,
59
+ fontSize: '0.875rem',
60
+ boxSizing: 'content-box',
61
+ }),
62
+ contenteditable: 'false',
46
63
  },
47
- 0,
64
+ [
65
+ 'div',
66
+ {
67
+ class: 'code-block-gutter-pseudo-element',
68
+ style: convertToInlineCss({
69
+ textAlign: 'right',
70
+ color: token('color.text.subtlest'),
71
+ fontFamily: token('font.family.code'),
72
+ whiteSpace: 'pre-wrap',
73
+ }),
74
+ 'data-label': content,
75
+ },
76
+ ],
77
+ ],
78
+ [
79
+ 'div',
80
+ {
81
+ class: codeBlockClassNames.content,
82
+ },
83
+ [
84
+ 'code',
85
+ {
86
+ 'data-language': node.attrs.language || '',
87
+ spellcheck: 'false',
88
+ 'data-testid': 'code-block--code',
89
+ 'aria-label': formattedAriaLabel,
90
+ },
91
+ 0,
92
+ ],
48
93
  ],
49
94
  ],
50
- ],
51
- ['div', { class: codeBlockClassNames.end, contenteditable: 'false' }],
52
- ];
95
+ ['div', { class: codeBlockClassNames.end, contenteditable: 'false' }],
96
+ ];
97
+ };
53
98
 
54
99
  export const codeBlockNodeWithFixedToDOM = (): NodeSpec => {
55
100
  return {
56
101
  ...codeBlock,
57
- toDOM: (node) => toDOM(node, false, ''),
102
+ toDOM: (node) => toDOM(node, ''),
58
103
  };
59
104
  };
@@ -0,0 +1,15 @@
1
+ import { RangeSetBuilder } from '@codemirror/state';
2
+ import { EditorView as CodeMirror, Decoration } from '@codemirror/view';
3
+
4
+ export const copyButtonDecorations = CodeMirror.decorations.compute([], (state) => {
5
+ const allTextDecoration = Decoration.mark({
6
+ attributes: { class: 'ProseMirror-fake-text-selection' },
7
+ });
8
+ // Create a set of decorations for the entire document
9
+ const builder = new RangeSetBuilder<Decoration>();
10
+ for (let i = 0; i < state.doc.lines; i++) {
11
+ builder.add(state.doc.line(i + 1).from, state.doc.line(i + 1).to, allTextDecoration);
12
+ }
13
+ const decorations = builder.finish();
14
+ return decorations;
15
+ });
@@ -5,7 +5,6 @@ import type { ExtractInjectionAPI } from '@atlaskit/editor-common/types';
5
5
  import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
6
6
  import type { EditorView } from '@atlaskit/editor-prosemirror/view';
7
7
 
8
-
9
8
  import type { CodeBlockAdvancedPlugin } from '../codeBlockAdvancedPluginType';
10
9
 
11
10
  interface Props {
@@ -38,7 +38,14 @@ export const highlightStyle = HighlightStyle.define([
38
38
  color: token('color.text.accent.blue'),
39
39
  },
40
40
  { tag: [tags.string, tags.deleted], color: token('color.text.accent.green') },
41
- { tag: [tags.regexp, tags.escape, tags.special(tags.string)], color: token('color.text') },
41
+ {
42
+ tag: [tags.special(tags.string)],
43
+ color: token('color.text.accent.green'),
44
+ },
45
+ {
46
+ tag: [tags.regexp, tags.escape],
47
+ color: token('color.text.accent.teal'),
48
+ },
42
49
  { tag: tags.definition(tags.variableName), color: token('color.text') },
43
50
  { tag: tags.local(tags.variableName), color: token('color.text') },
44
51
  { tag: [tags.typeName, tags.namespace], color: token('color.text.accent.blue') },
package/src/ui/theme.ts CHANGED
@@ -8,7 +8,6 @@ export const cmTheme = CodeMirror.theme({
8
8
  padding: '0',
9
9
  marginTop: token('space.100'),
10
10
  marginBottom: token('space.100'),
11
- borderRadius: token('border.radius'),
12
11
  fontSize: '0.875rem',
13
12
  // Custom syntax styling to match existing styling
14
13
  // eslint-disable-next-line @atlaskit/design-system/use-tokens-typography
@@ -35,6 +34,7 @@ export const cmTheme = CodeMirror.theme({
35
34
  // eslint-disable-next-line @atlaskit/design-system/use-tokens-typography
36
35
  lineHeight: 'unset',
37
36
  fontFamily: token('font.family.code'),
37
+ borderRadius: token('border.radius'),
38
38
  },
39
39
  '&.cm-focused .cm-cursor': {
40
40
  borderLeftColor: token('color.text'),
@@ -43,6 +43,7 @@ export const cmTheme = CodeMirror.theme({
43
43
  backgroundColor: token('color.background.neutral'),
44
44
  border: 'none',
45
45
  padding: token('space.100'),
46
+ color: token('color.text.subtlest'),
46
47
  },
47
48
  '.cm-lineNumbers .cm-gutterElement': {
48
49
  paddingLeft: token('space.0'),