@atlaskit/editor-plugin-code-block-advanced 2.1.3 → 2.2.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # @atlaskit/editor-plugin-code-block-advanced
2
2
 
3
+ ## 2.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#137683](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/pull-requests/137683)
8
+ [`c1020ef8cdf87`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/c1020ef8cdf87) -
9
+ Adds support for Handlebars syntax highlighting.
10
+
11
+ ### Patch Changes
12
+
13
+ - [#137043](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/pull-requests/137043)
14
+ [`616c9cd4a2c60`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/616c9cd4a2c60) -
15
+ Fix line wrapping and decorations being lost on breakout in advanced codeblocks
16
+ - Updated dependencies
17
+
18
+ ## 2.1.4
19
+
20
+ ### Patch Changes
21
+
22
+ - [#136263](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/pull-requests/136263)
23
+ [`602e9a7824b0c`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/602e9a7824b0c) -
24
+ Fix editor crashing with advanced code blocks due to infinite codemirror loop with decorations
25
+ when changing breakout.
26
+ - Updated dependencies
27
+
3
28
  ## 2.1.3
4
29
 
5
30
  ### Patch Changes
@@ -15,6 +15,7 @@ var _state = require("@codemirror/state");
15
15
  var _view = require("@codemirror/view");
16
16
  var _codeBlock = require("@atlaskit/editor-common/code-block");
17
17
  var _state2 = require("@atlaskit/editor-prosemirror/state");
18
+ var _view2 = require("@atlaskit/editor-prosemirror/view");
18
19
  var _syntaxHighlightingTheme = require("../ui/syntaxHighlightingTheme");
19
20
  var _theme = require("../ui/theme");
20
21
  var _syncCMWithPM = require("./codemirrorSync/syncCMWithPM");
@@ -25,7 +26,7 @@ var _prosemirrorDecorations = require("./extensions/prosemirrorDecorations");
25
26
  var _loader = require("./languages/loader");
26
27
  // Based on: https://prosemirror.net/examples/codemirror/
27
28
  var CodeBlockAdvancedNodeView = /*#__PURE__*/function () {
28
- function CodeBlockAdvancedNodeView(node, view, getPos, config) {
29
+ function CodeBlockAdvancedNodeView(node, view, getPos, innerDecorations, config) {
29
30
  var _config$api,
30
31
  _this = this,
31
32
  _config$api2,
@@ -86,11 +87,17 @@ var CodeBlockAdvancedNodeView = /*#__PURE__*/function () {
86
87
  // inner editor
87
88
  this.updating = false;
88
89
  this.updateLanguage();
90
+ this.updateWordWrap(node);
91
+ this.updateProseMirrorDecorations(innerDecorations);
89
92
  }
90
93
  return (0, _createClass2.default)(CodeBlockAdvancedNodeView, [{
91
94
  key: "destroy",
92
95
  value: function destroy() {
93
96
  var _this$cleanupDisabled;
97
+ // ED-27428: CodeMirror gets into an infinite loop as it detects mutations on removed
98
+ // decorations. When we change the breakout we destroy the node and cleanup these decorations from
99
+ // codemirror
100
+ this.clearProseMirrorDecorations();
94
101
  (_this$cleanupDisabled = this.cleanupDisabledState) === null || _this$cleanupDisabled === void 0 || _this$cleanupDisabled.call(this);
95
102
  }
96
103
  }, {
@@ -202,6 +209,18 @@ var CodeBlockAdvancedNodeView = /*#__PURE__*/function () {
202
209
  });
203
210
  this.updating = false;
204
211
  }
212
+ }, {
213
+ key: "clearProseMirrorDecorations",
214
+ value: function clearProseMirrorDecorations() {
215
+ this.updating = true;
216
+ var computedFacet = this.pmFacet.compute([], function () {
217
+ return _view2.DecorationSet.empty;
218
+ });
219
+ this.cm.dispatch({
220
+ effects: this.pmDecorationsCompartment.reconfigure(computedFacet)
221
+ });
222
+ this.updating = false;
223
+ }
205
224
  }, {
206
225
  key: "stopEvent",
207
226
  value: function stopEvent(e) {
@@ -234,7 +253,7 @@ var CodeBlockAdvancedNodeView = /*#__PURE__*/function () {
234
253
  }]);
235
254
  }();
236
255
  var getCodeBlockAdvancedNodeView = exports.getCodeBlockAdvancedNodeView = function getCodeBlockAdvancedNodeView(props) {
237
- return function (node, view, getPos) {
238
- return new CodeBlockAdvancedNodeView(node, view, getPos, props);
256
+ return function (node, view, getPos, innerDecorations) {
257
+ return new CodeBlockAdvancedNodeView(node, view, getPos, innerDecorations, props);
239
258
  };
240
259
  };
@@ -68,6 +68,18 @@ var mapLanguageToCodeMirror = exports.mapLanguageToCodeMirror = function mapLang
68
68
  return _languageData.languages.find(function (l) {
69
69
  return l.name === 'VB.NET';
70
70
  });
71
+ case 'handlebars':
72
+ return _language.LanguageDescription.of({
73
+ name: 'Handlebars',
74
+ load: function load() {
75
+ return Promise.resolve().then(function () {
76
+ return _interopRequireWildcard(require( /* webpackChunkName: "@atlaskit-internal_@atlaskit/editor-plugin-code-block-advanced-lang-handlebars" */
77
+ '@xiechao/codemirror-lang-handlebars'));
78
+ }).then(function (m) {
79
+ return new _language.LanguageSupport(m.handlebarsLanguage);
80
+ });
81
+ }
82
+ });
71
83
  case 'elixir':
72
84
  return _language.LanguageDescription.of({
73
85
  name: 'Elixir',
@@ -1,29 +1,47 @@
1
1
  "use strict";
2
2
 
3
3
  var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ var _typeof = require("@babel/runtime/helpers/typeof");
4
5
  Object.defineProperty(exports, "__esModule", {
5
6
  value: true
6
7
  });
7
8
  exports.lazyCodeBlockView = void 0;
8
- var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));
9
+ var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
10
+ var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
9
11
  var _lazyNodeView = require("@atlaskit/editor-common/lazy-node-view");
10
12
  function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); }
11
- function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != (0, _typeof2.default)(e) && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
13
+ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != _typeof(e) && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
12
14
  var lazyCodeBlockView = exports.lazyCodeBlockView = function lazyCodeBlockView(props) {
13
15
  return (0, _lazyNodeView.withLazyLoading)({
14
16
  nodeName: 'codeBlock',
15
17
  getNodeViewOptions: function getNodeViewOptions() {},
16
- loader: function loader() {
17
- var result = Promise.resolve().then(function () {
18
- return _interopRequireWildcard(require( /* webpackChunkName: "@atlaskit-internal_editor-plugin-code-block-advanced-nodeview" */
19
- './codeBlockAdvanced'));
20
- }).then(function (_ref) {
21
- var getCodeBlockAdvancedNodeView = _ref.getCodeBlockAdvancedNodeView;
22
- return function (node, view, getPos) {
23
- return getCodeBlockAdvancedNodeView(props)(node, view, getPos);
24
- };
25
- });
26
- return result;
27
- }
18
+ loader: function () {
19
+ var _loader = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee() {
20
+ var _yield$import, getCodeBlockAdvancedNodeView;
21
+ return _regenerator.default.wrap(function _callee$(_context) {
22
+ while (1) switch (_context.prev = _context.next) {
23
+ case 0:
24
+ _context.next = 2;
25
+ return Promise.resolve().then(function () {
26
+ return _interopRequireWildcard(require( /* webpackChunkName: "@atlaskit-internal_editor-plugin-code-block-advanced-nodeview" */
27
+ './codeBlockAdvanced'));
28
+ });
29
+ case 2:
30
+ _yield$import = _context.sent;
31
+ getCodeBlockAdvancedNodeView = _yield$import.getCodeBlockAdvancedNodeView;
32
+ return _context.abrupt("return", function (node, view, getPos, _decs, _nodeViewOptions, innerDecorations) {
33
+ return getCodeBlockAdvancedNodeView(props)(node, view, getPos, innerDecorations);
34
+ });
35
+ case 5:
36
+ case "end":
37
+ return _context.stop();
38
+ }
39
+ }, _callee);
40
+ }));
41
+ function loader() {
42
+ return _loader.apply(this, arguments);
43
+ }
44
+ return loader;
45
+ }()
28
46
  });
29
47
  };
@@ -6,11 +6,25 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.createPlugin = void 0;
7
7
  var _safePlugin = require("@atlaskit/editor-common/safe-plugin");
8
8
  var _lazyCodeBlockAdvanced = require("../nodeviews/lazyCodeBlockAdvanced");
9
+ var _shiftArrowKeyWorkaround = require("./shiftArrowKeyWorkaround");
9
10
  var createPlugin = exports.createPlugin = function createPlugin(props) {
10
11
  return new _safePlugin.SafePlugin({
11
12
  props: {
12
13
  nodeViews: {
13
14
  codeBlock: (0, _lazyCodeBlockAdvanced.lazyCodeBlockView)(props)
15
+ },
16
+ // Custom selection behaviour to fix issues with codeblocks with Shift + Arrow{Up || Down}
17
+ // These issues are also present in the prosemirror codemirror example and @marijnh suggests to
18
+ // use custom event handlers: https://github.com/ProseMirror/website/issues/83
19
+ handleKeyDown: function handleKeyDown(view, event) {
20
+ if (!(event instanceof KeyboardEvent)) {
21
+ return false;
22
+ }
23
+ if (event.key === 'ArrowUp' && event.shiftKey) {
24
+ return (0, _shiftArrowKeyWorkaround.shiftArrowUpWorkaround)(view, event);
25
+ } else if (event.key === 'ArrowDown' && event.shiftKey) {
26
+ return (0, _shiftArrowKeyWorkaround.shiftArrowDownWorkaround)(view, event);
27
+ }
14
28
  }
15
29
  }
16
30
  });
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.shiftArrowUpWorkaround = exports.shiftArrowDownWorkaround = void 0;
7
+ var _state = require("@atlaskit/editor-prosemirror/state");
8
+ var shiftArrowUpWorkaround = exports.shiftArrowUpWorkaround = function shiftArrowUpWorkaround(view, event) {
9
+ var _doc$resolve$nodeBefo;
10
+ var _view$state = view.state,
11
+ doc = _view$state.doc,
12
+ _view$state$selection = _view$state.selection,
13
+ $head = _view$state$selection.$head,
14
+ $anchor = _view$state$selection.$anchor,
15
+ tr = _view$state.tr,
16
+ codeBlock = _view$state.schema.nodes.codeBlock;
17
+
18
+ // Position we want to check (directly after our current position)
19
+ var pos = Math.max($head.pos - 1, 1);
20
+ var isNodeBefore = ((_doc$resolve$nodeBefo = doc.resolve(pos).nodeBefore) === null || _doc$resolve$nodeBefo === void 0 ? void 0 : _doc$resolve$nodeBefo.type) === codeBlock;
21
+ var maybeProblematicNode = isNodeBefore ? doc.resolve(pos).nodeBefore : doc.resolve(pos).node();
22
+ if ((maybeProblematicNode === null || maybeProblematicNode === void 0 ? void 0 : maybeProblematicNode.type) === codeBlock) {
23
+ var nodeSize = maybeProblematicNode.nodeSize;
24
+ var startPos = isNodeBefore ? pos : $head.pos;
25
+ tr.setSelection(_state.TextSelection.create(doc, $anchor.pos, Math.max(startPos - nodeSize, 0)));
26
+ view.dispatch(tr);
27
+ event.preventDefault();
28
+ return true;
29
+ }
30
+ return false;
31
+ };
32
+ var shiftArrowDownWorkaround = exports.shiftArrowDownWorkaround = function shiftArrowDownWorkaround(view, event) {
33
+ var _doc$resolve$nodeAfte;
34
+ var _view$state2 = view.state,
35
+ doc = _view$state2.doc,
36
+ _view$state2$selectio = _view$state2.selection,
37
+ $head = _view$state2$selectio.$head,
38
+ $anchor = _view$state2$selectio.$anchor,
39
+ tr = _view$state2.tr,
40
+ codeBlock = _view$state2.schema.nodes.codeBlock;
41
+
42
+ // Position we want to check (directly after our current position)
43
+ var pos = $head.pos + 1;
44
+ var isNodeAfter = ((_doc$resolve$nodeAfte = doc.resolve(pos).nodeAfter) === null || _doc$resolve$nodeAfte === void 0 ? void 0 : _doc$resolve$nodeAfte.type) === codeBlock;
45
+ var maybeProblematicNode = isNodeAfter ? doc.resolve(pos).nodeAfter : doc.resolve(pos).node();
46
+ if ((maybeProblematicNode === null || maybeProblematicNode === void 0 ? void 0 : maybeProblematicNode.type) === codeBlock) {
47
+ var nodeSize = maybeProblematicNode.nodeSize;
48
+ var startPos = isNodeAfter ? pos : $head.pos;
49
+ tr.setSelection(_state.TextSelection.create(doc, $anchor.pos, Math.min(startPos + nodeSize, doc.content.size)));
50
+ view.dispatch(tr);
51
+ event.preventDefault();
52
+ return true;
53
+ }
54
+ return false;
55
+ };
@@ -5,6 +5,7 @@ import { Compartment, EditorSelection, Facet } from '@codemirror/state';
5
5
  import { EditorView as CodeMirror, lineNumbers, gutters } from '@codemirror/view';
6
6
  import { isCodeBlockWordWrapEnabled } from '@atlaskit/editor-common/code-block';
7
7
  import { NodeSelection } from '@atlaskit/editor-prosemirror/state';
8
+ import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
8
9
  import { highlightStyle } from '../ui/syntaxHighlightingTheme';
9
10
  import { cmTheme } from '../ui/theme';
10
11
  import { syncCMWithPM } from './codemirrorSync/syncCMWithPM';
@@ -15,7 +16,7 @@ import { prosemirrorDecorationPlugin } from './extensions/prosemirrorDecorations
15
16
  import { LanguageLoader } from './languages/loader';
16
17
  // Based on: https://prosemirror.net/examples/codemirror/
17
18
  class CodeBlockAdvancedNodeView {
18
- constructor(node, view, getPos, config) {
19
+ constructor(node, view, getPos, innerDecorations, config) {
19
20
  var _config$api, _config$api$selection, _config$api2, _config$api2$editorDi, _config$api3;
20
21
  _defineProperty(this, "lineWrappingCompartment", new Compartment());
21
22
  _defineProperty(this, "languageCompartment", new Compartment());
@@ -66,9 +67,15 @@ class CodeBlockAdvancedNodeView {
66
67
  // inner editor
67
68
  this.updating = false;
68
69
  this.updateLanguage();
70
+ this.updateWordWrap(node);
71
+ this.updateProseMirrorDecorations(innerDecorations);
69
72
  }
70
73
  destroy() {
71
74
  var _this$cleanupDisabled;
75
+ // ED-27428: CodeMirror gets into an infinite loop as it detects mutations on removed
76
+ // decorations. When we change the breakout we destroy the node and cleanup these decorations from
77
+ // codemirror
78
+ this.clearProseMirrorDecorations();
72
79
  (_this$cleanupDisabled = this.cleanupDisabledState) === null || _this$cleanupDisabled === void 0 ? void 0 : _this$cleanupDisabled.call(this);
73
80
  }
74
81
  forwardUpdate(update) {
@@ -161,6 +168,14 @@ class CodeBlockAdvancedNodeView {
161
168
  });
162
169
  this.updating = false;
163
170
  }
171
+ clearProseMirrorDecorations() {
172
+ this.updating = true;
173
+ const computedFacet = this.pmFacet.compute([], () => DecorationSet.empty);
174
+ this.cm.dispatch({
175
+ effects: this.pmDecorationsCompartment.reconfigure(computedFacet)
176
+ });
177
+ this.updating = false;
178
+ }
164
179
  stopEvent(e) {
165
180
  var _this$getPos5;
166
181
  if (e instanceof MouseEvent && e.type === 'mousedown') {
@@ -188,6 +203,6 @@ class CodeBlockAdvancedNodeView {
188
203
  return true;
189
204
  }
190
205
  }
191
- export const getCodeBlockAdvancedNodeView = props => (node, view, getPos) => {
192
- return new CodeBlockAdvancedNodeView(node, view, getPos, props);
206
+ export const getCodeBlockAdvancedNodeView = props => (node, view, getPos, innerDecorations) => {
207
+ return new CodeBlockAdvancedNodeView(node, view, getPos, innerDecorations, props);
193
208
  };
@@ -1,4 +1,4 @@
1
- import { LanguageDescription } from '@codemirror/language';
1
+ import { LanguageDescription, LanguageSupport } from '@codemirror/language';
2
2
  import { languages } from '@codemirror/language-data';
3
3
  // getLanguageIdentifier defines `language.alias[0]`
4
4
  export const mapLanguageToCodeMirror = language => {
@@ -58,6 +58,14 @@ export const mapLanguageToCodeMirror = language => {
58
58
  return languages.find(l => {
59
59
  return l.name === 'VB.NET';
60
60
  });
61
+ case 'handlebars':
62
+ return LanguageDescription.of({
63
+ name: 'Handlebars',
64
+ load() {
65
+ return import( /* webpackChunkName: "@atlaskit-internal_@atlaskit/editor-plugin-code-block-advanced-lang-handlebars" */
66
+ '@xiechao/codemirror-lang-handlebars').then(m => new LanguageSupport(m.handlebarsLanguage));
67
+ }
68
+ });
61
69
  case 'elixir':
62
70
  return LanguageDescription.of({
63
71
  name: 'Elixir',
@@ -3,16 +3,12 @@ export const lazyCodeBlockView = props => {
3
3
  return withLazyLoading({
4
4
  nodeName: 'codeBlock',
5
5
  getNodeViewOptions: () => {},
6
- loader: () => {
7
- const result = import( /* webpackChunkName: "@atlaskit-internal_editor-plugin-code-block-advanced-nodeview" */
8
- './codeBlockAdvanced').then(({
6
+ loader: async () => {
7
+ const {
9
8
  getCodeBlockAdvancedNodeView
10
- }) => {
11
- return (node, view, getPos) => {
12
- return getCodeBlockAdvancedNodeView(props)(node, view, getPos);
13
- };
14
- });
15
- return result;
9
+ } = await import( /* webpackChunkName: "@atlaskit-internal_editor-plugin-code-block-advanced-nodeview" */
10
+ './codeBlockAdvanced');
11
+ return (node, view, getPos, _decs, _nodeViewOptions, innerDecorations) => getCodeBlockAdvancedNodeView(props)(node, view, getPos, innerDecorations);
16
12
  }
17
13
  });
18
14
  };
@@ -1,10 +1,24 @@
1
1
  import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
2
2
  import { lazyCodeBlockView } from '../nodeviews/lazyCodeBlockAdvanced';
3
+ import { shiftArrowDownWorkaround, shiftArrowUpWorkaround } from './shiftArrowKeyWorkaround';
3
4
  export const createPlugin = props => {
4
5
  return new SafePlugin({
5
6
  props: {
6
7
  nodeViews: {
7
8
  codeBlock: lazyCodeBlockView(props)
9
+ },
10
+ // Custom selection behaviour to fix issues with codeblocks with Shift + Arrow{Up || Down}
11
+ // These issues are also present in the prosemirror codemirror example and @marijnh suggests to
12
+ // use custom event handlers: https://github.com/ProseMirror/website/issues/83
13
+ handleKeyDown(view, event) {
14
+ if (!(event instanceof KeyboardEvent)) {
15
+ return false;
16
+ }
17
+ if (event.key === 'ArrowUp' && event.shiftKey) {
18
+ return shiftArrowUpWorkaround(view, event);
19
+ } else if (event.key === 'ArrowDown' && event.shiftKey) {
20
+ return shiftArrowDownWorkaround(view, event);
21
+ }
8
22
  }
9
23
  }
10
24
  });
@@ -0,0 +1,61 @@
1
+ import { TextSelection } from '@atlaskit/editor-prosemirror/state';
2
+ export const shiftArrowUpWorkaround = (view, event) => {
3
+ var _doc$resolve$nodeBefo;
4
+ const {
5
+ doc,
6
+ selection: {
7
+ $head,
8
+ $anchor
9
+ },
10
+ tr,
11
+ schema: {
12
+ nodes: {
13
+ codeBlock
14
+ }
15
+ }
16
+ } = view.state;
17
+
18
+ // Position we want to check (directly after our current position)
19
+ const pos = Math.max($head.pos - 1, 1);
20
+ const isNodeBefore = ((_doc$resolve$nodeBefo = doc.resolve(pos).nodeBefore) === null || _doc$resolve$nodeBefo === void 0 ? void 0 : _doc$resolve$nodeBefo.type) === codeBlock;
21
+ const maybeProblematicNode = isNodeBefore ? doc.resolve(pos).nodeBefore : doc.resolve(pos).node();
22
+ if ((maybeProblematicNode === null || maybeProblematicNode === void 0 ? void 0 : maybeProblematicNode.type) === codeBlock) {
23
+ const nodeSize = maybeProblematicNode.nodeSize;
24
+ const startPos = isNodeBefore ? pos : $head.pos;
25
+ tr.setSelection(TextSelection.create(doc, $anchor.pos, Math.max(startPos - nodeSize, 0)));
26
+ view.dispatch(tr);
27
+ event.preventDefault();
28
+ return true;
29
+ }
30
+ return false;
31
+ };
32
+ export const shiftArrowDownWorkaround = (view, event) => {
33
+ var _doc$resolve$nodeAfte;
34
+ const {
35
+ doc,
36
+ selection: {
37
+ $head,
38
+ $anchor
39
+ },
40
+ tr,
41
+ schema: {
42
+ nodes: {
43
+ codeBlock
44
+ }
45
+ }
46
+ } = view.state;
47
+
48
+ // Position we want to check (directly after our current position)
49
+ const pos = $head.pos + 1;
50
+ const isNodeAfter = ((_doc$resolve$nodeAfte = doc.resolve(pos).nodeAfter) === null || _doc$resolve$nodeAfte === void 0 ? void 0 : _doc$resolve$nodeAfte.type) === codeBlock;
51
+ const maybeProblematicNode = isNodeAfter ? doc.resolve(pos).nodeAfter : doc.resolve(pos).node();
52
+ if ((maybeProblematicNode === null || maybeProblematicNode === void 0 ? void 0 : maybeProblematicNode.type) === codeBlock) {
53
+ const nodeSize = maybeProblematicNode.nodeSize;
54
+ const startPos = isNodeAfter ? pos : $head.pos;
55
+ tr.setSelection(TextSelection.create(doc, $anchor.pos, Math.min(startPos + nodeSize, doc.content.size)));
56
+ view.dispatch(tr);
57
+ event.preventDefault();
58
+ return true;
59
+ }
60
+ return false;
61
+ };
@@ -8,6 +8,7 @@ import { Compartment, EditorSelection, Facet } from '@codemirror/state';
8
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
+ import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
11
12
  import { highlightStyle } from '../ui/syntaxHighlightingTheme';
12
13
  import { cmTheme } from '../ui/theme';
13
14
  import { syncCMWithPM } from './codemirrorSync/syncCMWithPM';
@@ -18,7 +19,7 @@ import { prosemirrorDecorationPlugin } from './extensions/prosemirrorDecorations
18
19
  import { LanguageLoader } from './languages/loader';
19
20
  // Based on: https://prosemirror.net/examples/codemirror/
20
21
  var CodeBlockAdvancedNodeView = /*#__PURE__*/function () {
21
- function CodeBlockAdvancedNodeView(node, view, getPos, config) {
22
+ function CodeBlockAdvancedNodeView(node, view, getPos, innerDecorations, config) {
22
23
  var _config$api,
23
24
  _this = this,
24
25
  _config$api2,
@@ -79,11 +80,17 @@ var CodeBlockAdvancedNodeView = /*#__PURE__*/function () {
79
80
  // inner editor
80
81
  this.updating = false;
81
82
  this.updateLanguage();
83
+ this.updateWordWrap(node);
84
+ this.updateProseMirrorDecorations(innerDecorations);
82
85
  }
83
86
  return _createClass(CodeBlockAdvancedNodeView, [{
84
87
  key: "destroy",
85
88
  value: function destroy() {
86
89
  var _this$cleanupDisabled;
90
+ // ED-27428: CodeMirror gets into an infinite loop as it detects mutations on removed
91
+ // decorations. When we change the breakout we destroy the node and cleanup these decorations from
92
+ // codemirror
93
+ this.clearProseMirrorDecorations();
87
94
  (_this$cleanupDisabled = this.cleanupDisabledState) === null || _this$cleanupDisabled === void 0 || _this$cleanupDisabled.call(this);
88
95
  }
89
96
  }, {
@@ -195,6 +202,18 @@ var CodeBlockAdvancedNodeView = /*#__PURE__*/function () {
195
202
  });
196
203
  this.updating = false;
197
204
  }
205
+ }, {
206
+ key: "clearProseMirrorDecorations",
207
+ value: function clearProseMirrorDecorations() {
208
+ this.updating = true;
209
+ var computedFacet = this.pmFacet.compute([], function () {
210
+ return DecorationSet.empty;
211
+ });
212
+ this.cm.dispatch({
213
+ effects: this.pmDecorationsCompartment.reconfigure(computedFacet)
214
+ });
215
+ this.updating = false;
216
+ }
198
217
  }, {
199
218
  key: "stopEvent",
200
219
  value: function stopEvent(e) {
@@ -227,7 +246,7 @@ var CodeBlockAdvancedNodeView = /*#__PURE__*/function () {
227
246
  }]);
228
247
  }();
229
248
  export var getCodeBlockAdvancedNodeView = function getCodeBlockAdvancedNodeView(props) {
230
- return function (node, view, getPos) {
231
- return new CodeBlockAdvancedNodeView(node, view, getPos, props);
249
+ return function (node, view, getPos, innerDecorations) {
250
+ return new CodeBlockAdvancedNodeView(node, view, getPos, innerDecorations, props);
232
251
  };
233
252
  };
@@ -1,4 +1,4 @@
1
- import { LanguageDescription } from '@codemirror/language';
1
+ import { LanguageDescription, LanguageSupport } from '@codemirror/language';
2
2
  import { languages } from '@codemirror/language-data';
3
3
  // getLanguageIdentifier defines `language.alias[0]`
4
4
  export var mapLanguageToCodeMirror = function mapLanguageToCodeMirror(language) {
@@ -58,6 +58,16 @@ export var mapLanguageToCodeMirror = function mapLanguageToCodeMirror(language)
58
58
  return languages.find(function (l) {
59
59
  return l.name === 'VB.NET';
60
60
  });
61
+ case 'handlebars':
62
+ return LanguageDescription.of({
63
+ name: 'Handlebars',
64
+ load: function load() {
65
+ return import( /* webpackChunkName: "@atlaskit-internal_@atlaskit/editor-plugin-code-block-advanced-lang-handlebars" */
66
+ '@xiechao/codemirror-lang-handlebars').then(function (m) {
67
+ return new LanguageSupport(m.handlebarsLanguage);
68
+ });
69
+ }
70
+ });
61
71
  case 'elixir':
62
72
  return LanguageDescription.of({
63
73
  name: 'Elixir',
@@ -1,17 +1,35 @@
1
+ import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
2
+ import _regeneratorRuntime from "@babel/runtime/regenerator";
1
3
  import { withLazyLoading } from '@atlaskit/editor-common/lazy-node-view';
2
4
  export var lazyCodeBlockView = function lazyCodeBlockView(props) {
3
5
  return withLazyLoading({
4
6
  nodeName: 'codeBlock',
5
7
  getNodeViewOptions: function getNodeViewOptions() {},
6
- loader: function loader() {
7
- var result = import( /* webpackChunkName: "@atlaskit-internal_editor-plugin-code-block-advanced-nodeview" */
8
- './codeBlockAdvanced').then(function (_ref) {
9
- var getCodeBlockAdvancedNodeView = _ref.getCodeBlockAdvancedNodeView;
10
- return function (node, view, getPos) {
11
- return getCodeBlockAdvancedNodeView(props)(node, view, getPos);
12
- };
13
- });
14
- return result;
15
- }
8
+ loader: function () {
9
+ var _loader = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
10
+ var _yield$import, getCodeBlockAdvancedNodeView;
11
+ return _regeneratorRuntime.wrap(function _callee$(_context) {
12
+ while (1) switch (_context.prev = _context.next) {
13
+ case 0:
14
+ _context.next = 2;
15
+ return import( /* webpackChunkName: "@atlaskit-internal_editor-plugin-code-block-advanced-nodeview" */
16
+ './codeBlockAdvanced');
17
+ case 2:
18
+ _yield$import = _context.sent;
19
+ getCodeBlockAdvancedNodeView = _yield$import.getCodeBlockAdvancedNodeView;
20
+ return _context.abrupt("return", function (node, view, getPos, _decs, _nodeViewOptions, innerDecorations) {
21
+ return getCodeBlockAdvancedNodeView(props)(node, view, getPos, innerDecorations);
22
+ });
23
+ case 5:
24
+ case "end":
25
+ return _context.stop();
26
+ }
27
+ }, _callee);
28
+ }));
29
+ function loader() {
30
+ return _loader.apply(this, arguments);
31
+ }
32
+ return loader;
33
+ }()
16
34
  });
17
35
  };
@@ -1,10 +1,24 @@
1
1
  import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
2
2
  import { lazyCodeBlockView } from '../nodeviews/lazyCodeBlockAdvanced';
3
+ import { shiftArrowDownWorkaround, shiftArrowUpWorkaround } from './shiftArrowKeyWorkaround';
3
4
  export var createPlugin = function createPlugin(props) {
4
5
  return new SafePlugin({
5
6
  props: {
6
7
  nodeViews: {
7
8
  codeBlock: lazyCodeBlockView(props)
9
+ },
10
+ // Custom selection behaviour to fix issues with codeblocks with Shift + Arrow{Up || Down}
11
+ // These issues are also present in the prosemirror codemirror example and @marijnh suggests to
12
+ // use custom event handlers: https://github.com/ProseMirror/website/issues/83
13
+ handleKeyDown: function handleKeyDown(view, event) {
14
+ if (!(event instanceof KeyboardEvent)) {
15
+ return false;
16
+ }
17
+ if (event.key === 'ArrowUp' && event.shiftKey) {
18
+ return shiftArrowUpWorkaround(view, event);
19
+ } else if (event.key === 'ArrowDown' && event.shiftKey) {
20
+ return shiftArrowDownWorkaround(view, event);
21
+ }
8
22
  }
9
23
  }
10
24
  });
@@ -0,0 +1,49 @@
1
+ import { TextSelection } from '@atlaskit/editor-prosemirror/state';
2
+ export var shiftArrowUpWorkaround = function shiftArrowUpWorkaround(view, event) {
3
+ var _doc$resolve$nodeBefo;
4
+ var _view$state = view.state,
5
+ doc = _view$state.doc,
6
+ _view$state$selection = _view$state.selection,
7
+ $head = _view$state$selection.$head,
8
+ $anchor = _view$state$selection.$anchor,
9
+ tr = _view$state.tr,
10
+ codeBlock = _view$state.schema.nodes.codeBlock;
11
+
12
+ // Position we want to check (directly after our current position)
13
+ var pos = Math.max($head.pos - 1, 1);
14
+ var isNodeBefore = ((_doc$resolve$nodeBefo = doc.resolve(pos).nodeBefore) === null || _doc$resolve$nodeBefo === void 0 ? void 0 : _doc$resolve$nodeBefo.type) === codeBlock;
15
+ var maybeProblematicNode = isNodeBefore ? doc.resolve(pos).nodeBefore : doc.resolve(pos).node();
16
+ if ((maybeProblematicNode === null || maybeProblematicNode === void 0 ? void 0 : maybeProblematicNode.type) === codeBlock) {
17
+ var nodeSize = maybeProblematicNode.nodeSize;
18
+ var startPos = isNodeBefore ? pos : $head.pos;
19
+ tr.setSelection(TextSelection.create(doc, $anchor.pos, Math.max(startPos - nodeSize, 0)));
20
+ view.dispatch(tr);
21
+ event.preventDefault();
22
+ return true;
23
+ }
24
+ return false;
25
+ };
26
+ export var shiftArrowDownWorkaround = function shiftArrowDownWorkaround(view, event) {
27
+ var _doc$resolve$nodeAfte;
28
+ var _view$state2 = view.state,
29
+ doc = _view$state2.doc,
30
+ _view$state2$selectio = _view$state2.selection,
31
+ $head = _view$state2$selectio.$head,
32
+ $anchor = _view$state2$selectio.$anchor,
33
+ tr = _view$state2.tr,
34
+ codeBlock = _view$state2.schema.nodes.codeBlock;
35
+
36
+ // Position we want to check (directly after our current position)
37
+ var pos = $head.pos + 1;
38
+ var isNodeAfter = ((_doc$resolve$nodeAfte = doc.resolve(pos).nodeAfter) === null || _doc$resolve$nodeAfte === void 0 ? void 0 : _doc$resolve$nodeAfte.type) === codeBlock;
39
+ var maybeProblematicNode = isNodeAfter ? doc.resolve(pos).nodeAfter : doc.resolve(pos).node();
40
+ if ((maybeProblematicNode === null || maybeProblematicNode === void 0 ? void 0 : maybeProblematicNode.type) === codeBlock) {
41
+ var nodeSize = maybeProblematicNode.nodeSize;
42
+ var startPos = isNodeAfter ? pos : $head.pos;
43
+ tr.setSelection(TextSelection.create(doc, $anchor.pos, Math.min(startPos + nodeSize, doc.content.size)));
44
+ view.dispatch(tr);
45
+ event.preventDefault();
46
+ return true;
47
+ }
48
+ return false;
49
+ };
@@ -24,7 +24,7 @@ declare class CodeBlockAdvancedNodeView implements NodeView {
24
24
  private cleanupDisabledState;
25
25
  private languageLoader;
26
26
  private pmFacet;
27
- constructor(node: PMNode, view: EditorView, getPos: getPosHandlerNode, config: ConfigProps);
27
+ constructor(node: PMNode, view: EditorView, getPos: getPosHandlerNode, innerDecorations: DecorationSource, config: ConfigProps);
28
28
  destroy(): void;
29
29
  forwardUpdate(update: ViewUpdate): void;
30
30
  setSelection(anchor: number, head: number): void;
@@ -40,7 +40,8 @@ declare class CodeBlockAdvancedNodeView implements NodeView {
40
40
  * This then gets translated to codemirror decorations in `prosemirrorDecorationPlugin`
41
41
  */
42
42
  private updateProseMirrorDecorations;
43
+ private clearProseMirrorDecorations;
43
44
  stopEvent(e: Event): boolean;
44
45
  }
45
- export declare const getCodeBlockAdvancedNodeView: (props: ConfigProps) => (node: PMNode, view: EditorView, getPos: getPosHandler) => CodeBlockAdvancedNodeView;
46
+ export declare const getCodeBlockAdvancedNodeView: (props: ConfigProps) => (node: PMNode, view: EditorView, getPos: getPosHandler, innerDecorations: DecorationSource) => CodeBlockAdvancedNodeView;
46
47
  export {};
@@ -0,0 +1,3 @@
1
+ import type { EditorView } from '@atlaskit/editor-prosemirror/view';
2
+ export declare const shiftArrowUpWorkaround: (view: EditorView, event: KeyboardEvent) => boolean;
3
+ export declare const shiftArrowDownWorkaround: (view: EditorView, event: KeyboardEvent) => boolean;
@@ -24,7 +24,7 @@ declare class CodeBlockAdvancedNodeView implements NodeView {
24
24
  private cleanupDisabledState;
25
25
  private languageLoader;
26
26
  private pmFacet;
27
- constructor(node: PMNode, view: EditorView, getPos: getPosHandlerNode, config: ConfigProps);
27
+ constructor(node: PMNode, view: EditorView, getPos: getPosHandlerNode, innerDecorations: DecorationSource, config: ConfigProps);
28
28
  destroy(): void;
29
29
  forwardUpdate(update: ViewUpdate): void;
30
30
  setSelection(anchor: number, head: number): void;
@@ -40,7 +40,8 @@ declare class CodeBlockAdvancedNodeView implements NodeView {
40
40
  * This then gets translated to codemirror decorations in `prosemirrorDecorationPlugin`
41
41
  */
42
42
  private updateProseMirrorDecorations;
43
+ private clearProseMirrorDecorations;
43
44
  stopEvent(e: Event): boolean;
44
45
  }
45
- export declare const getCodeBlockAdvancedNodeView: (props: ConfigProps) => (node: PMNode, view: EditorView, getPos: getPosHandler) => CodeBlockAdvancedNodeView;
46
+ export declare const getCodeBlockAdvancedNodeView: (props: ConfigProps) => (node: PMNode, view: EditorView, getPos: getPosHandler, innerDecorations: DecorationSource) => CodeBlockAdvancedNodeView;
46
47
  export {};
@@ -0,0 +1,3 @@
1
+ import type { EditorView } from '@atlaskit/editor-prosemirror/view';
2
+ export declare const shiftArrowUpWorkaround: (view: EditorView, event: KeyboardEvent) => boolean;
3
+ export declare const shiftArrowDownWorkaround: (view: EditorView, event: KeyboardEvent) => boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-code-block-advanced",
3
- "version": "2.1.3",
3
+ "version": "2.2.0",
4
4
  "description": "CodeBlockAdvanced plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -33,14 +33,14 @@
33
33
  },
34
34
  "dependencies": {
35
35
  "@atlaskit/adf-schema": "^47.6.0",
36
- "@atlaskit/editor-common": "^102.0.0",
37
- "@atlaskit/editor-plugin-code-block": "^4.1.0",
36
+ "@atlaskit/editor-common": "^102.19.0",
37
+ "@atlaskit/editor-plugin-code-block": "^4.2.0",
38
38
  "@atlaskit/editor-plugin-editor-disabled": "^2.0.0",
39
39
  "@atlaskit/editor-plugin-find-replace": "^2.0.0",
40
- "@atlaskit/editor-plugin-selection": "^2.0.0",
41
- "@atlaskit/editor-plugin-selection-marker": "^2.0.0",
40
+ "@atlaskit/editor-plugin-selection": "^2.1.0",
41
+ "@atlaskit/editor-plugin-selection-marker": "^2.1.0",
42
42
  "@atlaskit/editor-prosemirror": "7.0.0",
43
- "@atlaskit/tokens": "^4.3.0",
43
+ "@atlaskit/tokens": "^4.6.0",
44
44
  "@babel/runtime": "^7.0.0",
45
45
  "@codemirror/autocomplete": "6.18.4",
46
46
  "@codemirror/commands": "6.7.1",
@@ -49,6 +49,7 @@
49
49
  "@codemirror/state": "6.5.1",
50
50
  "@codemirror/view": "6.36.2",
51
51
  "@lezer/highlight": "1.2.1",
52
+ "@xiechao/codemirror-lang-handlebars": "^1.0.4",
52
53
  "cm6-graphql": "0.2.0",
53
54
  "codemirror-lang-elixir": "4.0.0"
54
55
  },
@@ -56,7 +57,7 @@
56
57
  "react": "^18.2.0"
57
58
  },
58
59
  "devDependencies": {
59
- "@atlaskit/code": "^16.0.0",
60
+ "@atlaskit/code": "^17.0.0",
60
61
  "typescript": "~5.4.2"
61
62
  },
62
63
  "techstack": {
@@ -19,6 +19,7 @@ import type {
19
19
  EditorView,
20
20
  NodeView,
21
21
  } from '@atlaskit/editor-prosemirror/view';
22
+ import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
22
23
 
23
24
  import type { CodeBlockAdvancedPlugin } from '../codeBlockAdvancedPluginType';
24
25
  import { highlightStyle } from '../ui/syntaxHighlightingTheme';
@@ -54,7 +55,13 @@ class CodeBlockAdvancedNodeView implements NodeView {
54
55
  private languageLoader: LanguageLoader;
55
56
  private pmFacet = Facet.define<DecorationSource>();
56
57
 
57
- constructor(node: PMNode, view: EditorView, getPos: getPosHandlerNode, config: ConfigProps) {
58
+ constructor(
59
+ node: PMNode,
60
+ view: EditorView,
61
+ getPos: getPosHandlerNode,
62
+ innerDecorations: DecorationSource,
63
+ config: ConfigProps,
64
+ ) {
58
65
  this.node = node;
59
66
  this.view = view;
60
67
  this.getPos = getPos;
@@ -110,9 +117,15 @@ class CodeBlockAdvancedNodeView implements NodeView {
110
117
  // inner editor
111
118
  this.updating = false;
112
119
  this.updateLanguage();
120
+ this.updateWordWrap(node);
121
+ this.updateProseMirrorDecorations(innerDecorations);
113
122
  }
114
123
 
115
124
  destroy() {
125
+ // ED-27428: CodeMirror gets into an infinite loop as it detects mutations on removed
126
+ // decorations. When we change the breakout we destroy the node and cleanup these decorations from
127
+ // codemirror
128
+ this.clearProseMirrorDecorations();
116
129
  this.cleanupDisabledState?.();
117
130
  }
118
131
 
@@ -214,6 +227,15 @@ class CodeBlockAdvancedNodeView implements NodeView {
214
227
  this.updating = false;
215
228
  }
216
229
 
230
+ private clearProseMirrorDecorations() {
231
+ this.updating = true;
232
+ const computedFacet = this.pmFacet.compute([], () => DecorationSet.empty);
233
+ this.cm.dispatch({
234
+ effects: this.pmDecorationsCompartment.reconfigure(computedFacet),
235
+ });
236
+ this.updating = false;
237
+ }
238
+
217
239
  stopEvent(e: Event) {
218
240
  if (e instanceof MouseEvent && e.type === 'mousedown') {
219
241
  // !Warning: Side effect!
@@ -247,6 +269,17 @@ class CodeBlockAdvancedNodeView implements NodeView {
247
269
 
248
270
  export const getCodeBlockAdvancedNodeView =
249
271
  (props: ConfigProps) =>
250
- (node: PMNode, view: EditorView, getPos: getPosHandler): CodeBlockAdvancedNodeView => {
251
- return new CodeBlockAdvancedNodeView(node, view, getPos as getPosHandlerNode, props);
272
+ (
273
+ node: PMNode,
274
+ view: EditorView,
275
+ getPos: getPosHandler,
276
+ innerDecorations: DecorationSource,
277
+ ): CodeBlockAdvancedNodeView => {
278
+ return new CodeBlockAdvancedNodeView(
279
+ node,
280
+ view,
281
+ getPos as getPosHandlerNode,
282
+ innerDecorations,
283
+ props,
284
+ );
252
285
  };
@@ -1,4 +1,4 @@
1
- import { LanguageDescription } from '@codemirror/language';
1
+ import { LanguageDescription, LanguageSupport } from '@codemirror/language';
2
2
  import { languages } from '@codemirror/language-data';
3
3
 
4
4
  import type { LanguageAlias } from '@atlaskit/code';
@@ -63,6 +63,16 @@ export const mapLanguageToCodeMirror = (language: LanguageAliasValue) => {
63
63
  return languages.find((l) => {
64
64
  return l.name === 'VB.NET';
65
65
  });
66
+ case 'handlebars':
67
+ return LanguageDescription.of({
68
+ name: 'Handlebars',
69
+ load() {
70
+ return import(
71
+ /* webpackChunkName: "@atlaskit-internal_@atlaskit/editor-plugin-code-block-advanced-lang-handlebars" */
72
+ '@xiechao/codemirror-lang-handlebars'
73
+ ).then((m) => new LanguageSupport(m.handlebarsLanguage));
74
+ },
75
+ });
66
76
  case 'elixir':
67
77
  return LanguageDescription.of({
68
78
  name: 'Elixir',
@@ -3,7 +3,7 @@ import { Extension } from '@codemirror/state';
3
3
  import { withLazyLoading } from '@atlaskit/editor-common/lazy-node-view';
4
4
  import type { ExtractInjectionAPI } from '@atlaskit/editor-common/types';
5
5
  import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
6
- import type { EditorView } from '@atlaskit/editor-prosemirror/view';
6
+ import type { EditorView, DecorationSource, Decoration } from '@atlaskit/editor-prosemirror/view';
7
7
 
8
8
  import type { CodeBlockAdvancedPlugin } from '../codeBlockAdvancedPluginType';
9
9
 
@@ -16,16 +16,19 @@ export const lazyCodeBlockView = (props: Props) => {
16
16
  return withLazyLoading({
17
17
  nodeName: 'codeBlock',
18
18
  getNodeViewOptions: () => {},
19
- loader: () => {
20
- const result = import(
19
+ loader: async () => {
20
+ const { getCodeBlockAdvancedNodeView } = await import(
21
21
  /* webpackChunkName: "@atlaskit-internal_editor-plugin-code-block-advanced-nodeview" */
22
22
  './codeBlockAdvanced'
23
- ).then(({ getCodeBlockAdvancedNodeView }) => {
24
- return (node: PMNode, view: EditorView, getPos: () => number | undefined) => {
25
- return getCodeBlockAdvancedNodeView(props)(node, view, getPos);
26
- };
27
- });
28
- return result;
23
+ );
24
+ return (
25
+ node: PMNode,
26
+ view: EditorView,
27
+ getPos: () => number | undefined,
28
+ _decs: readonly Decoration[],
29
+ _nodeViewOptions: () => void,
30
+ innerDecorations: DecorationSource,
31
+ ) => getCodeBlockAdvancedNodeView(props)(node, view, getPos, innerDecorations);
29
32
  },
30
33
  });
31
34
  };
@@ -2,10 +2,13 @@ import type { Extension } from '@codemirror/state';
2
2
 
3
3
  import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
4
4
  import type { ExtractInjectionAPI } from '@atlaskit/editor-common/types';
5
+ import type { EditorView } from '@atlaskit/editor-prosemirror/view';
5
6
 
6
7
  import type { CodeBlockAdvancedPlugin } from '../codeBlockAdvancedPluginType';
7
8
  import { lazyCodeBlockView } from '../nodeviews/lazyCodeBlockAdvanced';
8
9
 
10
+ import { shiftArrowDownWorkaround, shiftArrowUpWorkaround } from './shiftArrowKeyWorkaround';
11
+
9
12
  interface Props {
10
13
  api: ExtractInjectionAPI<CodeBlockAdvancedPlugin> | undefined;
11
14
  extensions: Extension[];
@@ -17,6 +20,20 @@ export const createPlugin = (props: Props) => {
17
20
  nodeViews: {
18
21
  codeBlock: lazyCodeBlockView(props),
19
22
  },
23
+ // Custom selection behaviour to fix issues with codeblocks with Shift + Arrow{Up || Down}
24
+ // These issues are also present in the prosemirror codemirror example and @marijnh suggests to
25
+ // use custom event handlers: https://github.com/ProseMirror/website/issues/83
26
+ handleKeyDown(view: EditorView, event: KeyboardEvent) {
27
+ if (!(event instanceof KeyboardEvent)) {
28
+ return false;
29
+ }
30
+
31
+ if (event.key === 'ArrowUp' && event.shiftKey) {
32
+ return shiftArrowUpWorkaround(view, event);
33
+ } else if (event.key === 'ArrowDown' && event.shiftKey) {
34
+ return shiftArrowDownWorkaround(view, event);
35
+ }
36
+ },
20
37
  },
21
38
  });
22
39
  };
@@ -0,0 +1,58 @@
1
+ import { TextSelection } from '@atlaskit/editor-prosemirror/state';
2
+ import type { EditorView } from '@atlaskit/editor-prosemirror/view';
3
+
4
+ export const shiftArrowUpWorkaround = (view: EditorView, event: KeyboardEvent) => {
5
+ const {
6
+ doc,
7
+ selection: { $head, $anchor },
8
+ tr,
9
+ schema: {
10
+ nodes: { codeBlock },
11
+ },
12
+ } = view.state;
13
+
14
+ // Position we want to check (directly after our current position)
15
+ const pos = Math.max($head.pos - 1, 1);
16
+ const isNodeBefore = doc.resolve(pos).nodeBefore?.type === codeBlock;
17
+ const maybeProblematicNode = isNodeBefore ? doc.resolve(pos).nodeBefore : doc.resolve(pos).node();
18
+
19
+ if (maybeProblematicNode?.type === codeBlock) {
20
+ const nodeSize = maybeProblematicNode.nodeSize;
21
+ const startPos = isNodeBefore ? pos : $head.pos;
22
+
23
+ tr.setSelection(TextSelection.create(doc, $anchor.pos, Math.max(startPos - nodeSize, 0)));
24
+ view.dispatch(tr);
25
+ event.preventDefault();
26
+ return true;
27
+ }
28
+ return false;
29
+ };
30
+
31
+ export const shiftArrowDownWorkaround = (view: EditorView, event: KeyboardEvent) => {
32
+ const {
33
+ doc,
34
+ selection: { $head, $anchor },
35
+ tr,
36
+ schema: {
37
+ nodes: { codeBlock },
38
+ },
39
+ } = view.state;
40
+
41
+ // Position we want to check (directly after our current position)
42
+ const pos = $head.pos + 1;
43
+ const isNodeAfter = doc.resolve(pos).nodeAfter?.type === codeBlock;
44
+ const maybeProblematicNode = isNodeAfter ? doc.resolve(pos).nodeAfter : doc.resolve(pos).node();
45
+
46
+ if (maybeProblematicNode?.type === codeBlock) {
47
+ const nodeSize = maybeProblematicNode.nodeSize;
48
+ const startPos = isNodeAfter ? pos : $head.pos;
49
+
50
+ tr.setSelection(
51
+ TextSelection.create(doc, $anchor.pos, Math.min(startPos + nodeSize, doc.content.size)),
52
+ );
53
+ view.dispatch(tr);
54
+ event.preventDefault();
55
+ return true;
56
+ }
57
+ return false;
58
+ };