@atlaskit/editor-plugin-code-block 0.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 (126) hide show
  1. package/CHANGELOG.md +1 -0
  2. package/LICENSE.md +13 -0
  3. package/README.md +30 -0
  4. package/dist/cjs/actions.js +201 -0
  5. package/dist/cjs/ide-ux/bracket-handling.js +35 -0
  6. package/dist/cjs/ide-ux/commands.js +115 -0
  7. package/dist/cjs/ide-ux/line-handling.js +81 -0
  8. package/dist/cjs/ide-ux/paired-character-handling.js +23 -0
  9. package/dist/cjs/ide-ux/quote-handling.js +40 -0
  10. package/dist/cjs/index.js +13 -0
  11. package/dist/cjs/language-list.js +62 -0
  12. package/dist/cjs/nodeviews/code-block.js +153 -0
  13. package/dist/cjs/plugin-key.js +8 -0
  14. package/dist/cjs/plugin.js +120 -0
  15. package/dist/cjs/pm-plugins/actions.js +10 -0
  16. package/dist/cjs/pm-plugins/codeBlockCopySelectionPlugin.js +113 -0
  17. package/dist/cjs/pm-plugins/ide-ux.js +132 -0
  18. package/dist/cjs/pm-plugins/input-rule.js +68 -0
  19. package/dist/cjs/pm-plugins/keymaps.js +66 -0
  20. package/dist/cjs/pm-plugins/main-state.js +10 -0
  21. package/dist/cjs/pm-plugins/main.js +114 -0
  22. package/dist/cjs/refresh-browser-selection.js +29 -0
  23. package/dist/cjs/toolbar.js +131 -0
  24. package/dist/cjs/transform-to-code-block.js +84 -0
  25. package/dist/cjs/types.js +5 -0
  26. package/dist/cjs/ui/class-names.js +15 -0
  27. package/dist/cjs/utils.js +28 -0
  28. package/dist/es2019/actions.js +211 -0
  29. package/dist/es2019/ide-ux/bracket-handling.js +27 -0
  30. package/dist/es2019/ide-ux/commands.js +115 -0
  31. package/dist/es2019/ide-ux/line-handling.js +75 -0
  32. package/dist/es2019/ide-ux/paired-character-handling.js +12 -0
  33. package/dist/es2019/ide-ux/quote-handling.js +32 -0
  34. package/dist/es2019/index.js +1 -0
  35. package/dist/es2019/language-list.js +51 -0
  36. package/dist/es2019/nodeviews/code-block.js +126 -0
  37. package/dist/es2019/plugin-key.js +2 -0
  38. package/dist/es2019/plugin.js +104 -0
  39. package/dist/es2019/pm-plugins/actions.js +4 -0
  40. package/dist/es2019/pm-plugins/codeBlockCopySelectionPlugin.js +101 -0
  41. package/dist/es2019/pm-plugins/ide-ux.js +135 -0
  42. package/dist/es2019/pm-plugins/input-rule.js +60 -0
  43. package/dist/es2019/pm-plugins/keymaps.js +58 -0
  44. package/dist/es2019/pm-plugins/main-state.js +2 -0
  45. package/dist/es2019/pm-plugins/main.js +102 -0
  46. package/dist/es2019/refresh-browser-selection.js +25 -0
  47. package/dist/es2019/toolbar.js +108 -0
  48. package/dist/es2019/transform-to-code-block.js +79 -0
  49. package/dist/es2019/types.js +1 -0
  50. package/dist/es2019/ui/class-names.js +9 -0
  51. package/dist/es2019/utils.js +4 -0
  52. package/dist/esm/actions.js +191 -0
  53. package/dist/esm/ide-ux/bracket-handling.js +29 -0
  54. package/dist/esm/ide-ux/commands.js +107 -0
  55. package/dist/esm/ide-ux/line-handling.js +73 -0
  56. package/dist/esm/ide-ux/paired-character-handling.js +16 -0
  57. package/dist/esm/ide-ux/quote-handling.js +34 -0
  58. package/dist/esm/index.js +1 -0
  59. package/dist/esm/language-list.js +52 -0
  60. package/dist/esm/nodeviews/code-block.js +146 -0
  61. package/dist/esm/plugin-key.js +2 -0
  62. package/dist/esm/plugin.js +113 -0
  63. package/dist/esm/pm-plugins/actions.js +4 -0
  64. package/dist/esm/pm-plugins/codeBlockCopySelectionPlugin.js +103 -0
  65. package/dist/esm/pm-plugins/ide-ux.js +126 -0
  66. package/dist/esm/pm-plugins/input-rule.js +62 -0
  67. package/dist/esm/pm-plugins/keymaps.js +59 -0
  68. package/dist/esm/pm-plugins/main-state.js +4 -0
  69. package/dist/esm/pm-plugins/main.js +107 -0
  70. package/dist/esm/refresh-browser-selection.js +25 -0
  71. package/dist/esm/toolbar.js +121 -0
  72. package/dist/esm/transform-to-code-block.js +77 -0
  73. package/dist/esm/types.js +1 -0
  74. package/dist/esm/ui/class-names.js +9 -0
  75. package/dist/esm/utils.js +4 -0
  76. package/dist/types/actions.d.ts +18 -0
  77. package/dist/types/ide-ux/bracket-handling.d.ts +12 -0
  78. package/dist/types/ide-ux/commands.d.ts +7 -0
  79. package/dist/types/ide-ux/line-handling.d.ts +25 -0
  80. package/dist/types/ide-ux/paired-character-handling.d.ts +2 -0
  81. package/dist/types/ide-ux/quote-handling.d.ts +12 -0
  82. package/dist/types/index.d.ts +3 -0
  83. package/dist/types/language-list.d.ts +942 -0
  84. package/dist/types/nodeviews/code-block.d.ts +21 -0
  85. package/dist/types/plugin-key.d.ts +2 -0
  86. package/dist/types/plugin.d.ts +19 -0
  87. package/dist/types/pm-plugins/actions.d.ts +4 -0
  88. package/dist/types/pm-plugins/codeBlockCopySelectionPlugin.d.ts +11 -0
  89. package/dist/types/pm-plugins/ide-ux.d.ts +5 -0
  90. package/dist/types/pm-plugins/input-rule.d.ts +3 -0
  91. package/dist/types/pm-plugins/keymaps.d.ts +4 -0
  92. package/dist/types/pm-plugins/main-state.d.ts +8 -0
  93. package/dist/types/pm-plugins/main.d.ts +10 -0
  94. package/dist/types/refresh-browser-selection.d.ts +5 -0
  95. package/dist/types/toolbar.d.ts +14 -0
  96. package/dist/types/transform-to-code-block.d.ts +3 -0
  97. package/dist/types/types.d.ts +6 -0
  98. package/dist/types/ui/class-names.d.ts +8 -0
  99. package/dist/types/utils.d.ts +4 -0
  100. package/dist/types-ts4.5/actions.d.ts +18 -0
  101. package/dist/types-ts4.5/ide-ux/bracket-handling.d.ts +12 -0
  102. package/dist/types-ts4.5/ide-ux/commands.d.ts +7 -0
  103. package/dist/types-ts4.5/ide-ux/line-handling.d.ts +25 -0
  104. package/dist/types-ts4.5/ide-ux/paired-character-handling.d.ts +2 -0
  105. package/dist/types-ts4.5/ide-ux/quote-handling.d.ts +12 -0
  106. package/dist/types-ts4.5/index.d.ts +3 -0
  107. package/dist/types-ts4.5/language-list.d.ts +1641 -0
  108. package/dist/types-ts4.5/nodeviews/code-block.d.ts +21 -0
  109. package/dist/types-ts4.5/plugin-key.d.ts +2 -0
  110. package/dist/types-ts4.5/plugin.d.ts +19 -0
  111. package/dist/types-ts4.5/pm-plugins/actions.d.ts +4 -0
  112. package/dist/types-ts4.5/pm-plugins/codeBlockCopySelectionPlugin.d.ts +14 -0
  113. package/dist/types-ts4.5/pm-plugins/ide-ux.d.ts +5 -0
  114. package/dist/types-ts4.5/pm-plugins/input-rule.d.ts +3 -0
  115. package/dist/types-ts4.5/pm-plugins/keymaps.d.ts +4 -0
  116. package/dist/types-ts4.5/pm-plugins/main-state.d.ts +8 -0
  117. package/dist/types-ts4.5/pm-plugins/main.d.ts +10 -0
  118. package/dist/types-ts4.5/refresh-browser-selection.d.ts +5 -0
  119. package/dist/types-ts4.5/toolbar.d.ts +14 -0
  120. package/dist/types-ts4.5/transform-to-code-block.d.ts +3 -0
  121. package/dist/types-ts4.5/types.d.ts +6 -0
  122. package/dist/types-ts4.5/ui/class-names.d.ts +8 -0
  123. package/dist/types-ts4.5/utils.d.ts +4 -0
  124. package/package.json +99 -0
  125. package/report.api.md +73 -0
  126. package/tmp/api-report-tmp.d.ts +45 -0
@@ -0,0 +1,115 @@
1
+ import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, INDENT_DIRECTION, INDENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics';
2
+ import { TextSelection } from '@atlaskit/editor-prosemirror/state';
3
+ import { forEachLine, getLineInfo, getLinesFromSelection, getStartOfCurrentLine } from './line-handling';
4
+
5
+ /**
6
+ * Return the current indentation level
7
+ * @param indentText - Text in the code block that represent an indentation
8
+ * @param indentSize - Size of the indentation token in a string
9
+ */
10
+ function getIndentLevel(indentText, indentSize) {
11
+ if (indentSize === 0 || indentText.length === 0) {
12
+ return 0;
13
+ }
14
+ return indentText.length / indentSize;
15
+ }
16
+ export const indent = editorAnalyticsAPI => (state, dispatch) => {
17
+ const {
18
+ text,
19
+ start
20
+ } = getLinesFromSelection(state);
21
+ const {
22
+ tr,
23
+ selection
24
+ } = state;
25
+ forEachLine(text, (line, offset) => {
26
+ const {
27
+ indentText,
28
+ indentToken
29
+ } = getLineInfo(line);
30
+ const indentLevel = getIndentLevel(indentText, indentToken.size);
31
+ const indentToAdd = indentToken.token.repeat(indentToken.size - indentText.length % indentToken.size || indentToken.size);
32
+ tr.insertText(indentToAdd, tr.mapping.map(start + offset, -1));
33
+ editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 ? void 0 : editorAnalyticsAPI.attachAnalyticsEvent({
34
+ action: ACTION.FORMATTED,
35
+ actionSubject: ACTION_SUBJECT.TEXT,
36
+ actionSubjectId: ACTION_SUBJECT_ID.FORMAT_INDENT,
37
+ eventType: EVENT_TYPE.TRACK,
38
+ attributes: {
39
+ inputMethod: INPUT_METHOD.KEYBOARD,
40
+ previousIndentationLevel: indentLevel,
41
+ newIndentLevel: indentLevel + 1,
42
+ direction: INDENT_DIRECTION.INDENT,
43
+ indentType: INDENT_TYPE.CODE_BLOCK
44
+ }
45
+ })(tr);
46
+ if (!selection.empty) {
47
+ tr.setSelection(TextSelection.create(tr.doc, tr.mapping.map(selection.from, -1), tr.selection.to));
48
+ }
49
+ });
50
+ if (dispatch) {
51
+ dispatch(tr);
52
+ }
53
+ return true;
54
+ };
55
+ export const outdent = editorAnalyticsAPI => (state, dispatch) => {
56
+ const {
57
+ text,
58
+ start
59
+ } = getLinesFromSelection(state);
60
+ const {
61
+ tr
62
+ } = state;
63
+ forEachLine(text, (line, offset) => {
64
+ const {
65
+ indentText,
66
+ indentToken
67
+ } = getLineInfo(line);
68
+ if (indentText) {
69
+ const indentLevel = getIndentLevel(indentText, indentToken.size);
70
+ const unindentLength = indentText.length % indentToken.size || indentToken.size;
71
+ tr.delete(tr.mapping.map(start + offset), tr.mapping.map(start + offset + unindentLength));
72
+ editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 ? void 0 : editorAnalyticsAPI.attachAnalyticsEvent({
73
+ action: ACTION.FORMATTED,
74
+ actionSubject: ACTION_SUBJECT.TEXT,
75
+ actionSubjectId: ACTION_SUBJECT_ID.FORMAT_INDENT,
76
+ eventType: EVENT_TYPE.TRACK,
77
+ attributes: {
78
+ inputMethod: INPUT_METHOD.KEYBOARD,
79
+ previousIndentationLevel: indentLevel,
80
+ newIndentLevel: indentLevel - 1,
81
+ direction: INDENT_DIRECTION.OUTDENT,
82
+ indentType: INDENT_TYPE.CODE_BLOCK
83
+ }
84
+ })(tr);
85
+ }
86
+ });
87
+ if (dispatch) {
88
+ dispatch(tr);
89
+ }
90
+ return true;
91
+ };
92
+ export function insertIndent(state, dispatch) {
93
+ const {
94
+ text: textAtStartOfLine
95
+ } = getStartOfCurrentLine(state);
96
+ const {
97
+ indentToken
98
+ } = getLineInfo(textAtStartOfLine);
99
+ const indentToAdd = indentToken.token.repeat(indentToken.size - textAtStartOfLine.length % indentToken.size || indentToken.size);
100
+ dispatch(state.tr.insertText(indentToAdd));
101
+ return true;
102
+ }
103
+ export function insertNewlineWithIndent(state, dispatch) {
104
+ const {
105
+ text: textAtStartOfLine
106
+ } = getStartOfCurrentLine(state);
107
+ const {
108
+ indentText
109
+ } = getLineInfo(textAtStartOfLine);
110
+ if (indentText && dispatch) {
111
+ dispatch(state.tr.insertText('\n' + indentText));
112
+ return true;
113
+ }
114
+ return false;
115
+ }
@@ -0,0 +1,75 @@
1
+ import { getCursor } from '../utils';
2
+ export const isSelectionEntirelyInsideCodeBlock = state => state.selection.$from.sameParent(state.selection.$to) && state.selection.$from.parent.type === state.schema.nodes.codeBlock;
3
+ export const isCursorInsideCodeBlock = state => !!getCursor(state.selection) && isSelectionEntirelyInsideCodeBlock(state);
4
+ export const getStartOfCurrentLine = state => {
5
+ const {
6
+ $from
7
+ } = state.selection;
8
+ if ($from.nodeBefore && $from.nodeBefore.isText) {
9
+ const prevNewLineIndex = $from.nodeBefore.text.lastIndexOf('\n');
10
+ return {
11
+ text: $from.nodeBefore.text.substring(prevNewLineIndex + 1),
12
+ pos: $from.start() + prevNewLineIndex + 1
13
+ };
14
+ }
15
+ return {
16
+ text: '',
17
+ pos: $from.pos
18
+ };
19
+ };
20
+ export const getEndOfCurrentLine = state => {
21
+ const {
22
+ $to
23
+ } = state.selection;
24
+ if ($to.nodeAfter && $to.nodeAfter.isText) {
25
+ const nextNewLineIndex = $to.nodeAfter.text.indexOf('\n');
26
+ return {
27
+ text: $to.nodeAfter.text.substring(0, nextNewLineIndex >= 0 ? nextNewLineIndex : undefined),
28
+ pos: nextNewLineIndex >= 0 ? $to.pos + nextNewLineIndex : $to.end()
29
+ };
30
+ }
31
+ return {
32
+ text: '',
33
+ pos: $to.pos
34
+ };
35
+ };
36
+ export function getLinesFromSelection(state) {
37
+ const {
38
+ pos: start
39
+ } = getStartOfCurrentLine(state);
40
+ const {
41
+ pos: end
42
+ } = getEndOfCurrentLine(state);
43
+ const text = state.doc.textBetween(start, end);
44
+ return {
45
+ text,
46
+ start,
47
+ end
48
+ };
49
+ }
50
+ export const forEachLine = (text, callback) => {
51
+ let offset = 0;
52
+ text.split('\n').forEach(line => {
53
+ callback(line, offset);
54
+ offset += line.length + 1;
55
+ });
56
+ };
57
+ const SPACE = {
58
+ token: ' ',
59
+ size: 2,
60
+ regex: /[^ ]/
61
+ };
62
+ const TAB = {
63
+ token: '\t',
64
+ size: 1,
65
+ regex: /[^\t]/
66
+ };
67
+ export const getLineInfo = line => {
68
+ const indentToken = line.startsWith('\t') ? TAB : SPACE;
69
+ const indentLength = line.search(indentToken.regex);
70
+ const indentText = line.substring(0, indentLength >= 0 ? indentLength : line.length);
71
+ return {
72
+ indentToken,
73
+ indentText
74
+ };
75
+ };
@@ -0,0 +1,12 @@
1
+ import { BRACKET_MAP } from './bracket-handling';
2
+ import { QUOTE_MAP } from './quote-handling';
3
+ const PAIRED_CHARACTER_MAP = {
4
+ ...BRACKET_MAP,
5
+ ...QUOTE_MAP
6
+ };
7
+ export const isCursorBeforeClosingCharacter = after => {
8
+ return Object.keys(PAIRED_CHARACTER_MAP).some(leftCharacter => after.startsWith(PAIRED_CHARACTER_MAP[leftCharacter]));
9
+ };
10
+ export const isClosingCharacter = text => {
11
+ return Object.keys(PAIRED_CHARACTER_MAP).some(leftCharacter => text === PAIRED_CHARACTER_MAP[leftCharacter]);
12
+ };
@@ -0,0 +1,32 @@
1
+ export const QUOTE_MAP = {
2
+ "'": "'",
3
+ '"': '"',
4
+ '`': '`'
5
+ };
6
+ export const shouldAutoCloseQuote = (before, after) => {
7
+ // when directly before a closing bracket
8
+ if (/^[}\])]/.test(after)) {
9
+ return true;
10
+ }
11
+
12
+ // exclusion: when directly before a non-whitespace character
13
+ if (/^[^\s]/.test(after)) {
14
+ return false;
15
+ }
16
+
17
+ // exclusion: when directly after a letter or quote
18
+ if (/[A-Za-z0-9]$/.test(before) || /[\'\"\`]$/.test(before)) {
19
+ return false;
20
+ }
21
+ return true;
22
+ };
23
+ export const getAutoClosingQuoteInfo = (before, after) => {
24
+ const left = Object.keys(QUOTE_MAP).find(item => before.endsWith(item));
25
+ const right = left ? QUOTE_MAP[left] : undefined;
26
+ const hasTrailingMatchingQuote = right ? after.startsWith(right) : false;
27
+ return {
28
+ left,
29
+ right,
30
+ hasTrailingMatchingQuote
31
+ };
32
+ };
@@ -0,0 +1 @@
1
+ export { default as codeBlockPlugin } from './plugin';
@@ -0,0 +1,51 @@
1
+ import { SUPPORTED_LANGUAGES } from '@atlaskit/code/constants';
2
+
3
+ // We expect alias[0] to be used for the ADF attribute, see ED-2813
4
+ export const DEFAULT_LANGUAGES = [{
5
+ name: '(None)',
6
+ alias: ['none'],
7
+ value: 'none'
8
+ }, ...SUPPORTED_LANGUAGES];
9
+ export function findMatchedLanguage(supportedLanguages, language) {
10
+ if (!language) {
11
+ return undefined;
12
+ }
13
+ const matches = supportedLanguages.filter(supportedLanguage => {
14
+ return supportedLanguage.alias.indexOf(language.toLowerCase()) !== -1;
15
+ });
16
+ if (matches.length > 0) {
17
+ return matches[0];
18
+ }
19
+ return undefined;
20
+ }
21
+ export function filterSupportedLanguages(supportedLanguages) {
22
+ if (!supportedLanguages || !supportedLanguages.length) {
23
+ return DEFAULT_LANGUAGES;
24
+ }
25
+ return DEFAULT_LANGUAGES.filter(language => {
26
+ let i = language.alias.length;
27
+ while (i--) {
28
+ if (supportedLanguages.indexOf(language.alias[i]) > -1) {
29
+ return true;
30
+ }
31
+ }
32
+ return false;
33
+ });
34
+ }
35
+ export function getLanguageIdentifier(language) {
36
+ return language.alias[0];
37
+ }
38
+ export function createLanguageList(supportedLanguages) {
39
+ return supportedLanguages.sort((left, right) => {
40
+ if (left.alias[0] === 'none') {
41
+ return -1;
42
+ }
43
+ if (left.name.toLowerCase() > right.name.toLowerCase()) {
44
+ return 1;
45
+ }
46
+ if (left.name.toLowerCase() < right.name.toLowerCase()) {
47
+ return -1;
48
+ }
49
+ return 0;
50
+ });
51
+ }
@@ -0,0 +1,126 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ import rafSchedule from 'raf-schd';
3
+ import { browser } from '@atlaskit/editor-common/utils';
4
+ import { DOMSerializer } from '@atlaskit/editor-prosemirror/model';
5
+ import { resetShouldIgnoreFollowingMutations } from '../actions';
6
+ import { getPluginState } from '../pm-plugins/main-state';
7
+ import { codeBlockClassNames } from '../ui/class-names';
8
+ const MATCH_NEWLINES = new RegExp('\n', 'g');
9
+ const toDOM = node => ['div', {
10
+ class: 'code-block'
11
+ }, ['div', {
12
+ class: codeBlockClassNames.start,
13
+ contenteditable: 'false'
14
+ }], ['div', {
15
+ class: codeBlockClassNames.contentWrapper
16
+ }, ['div', {
17
+ class: codeBlockClassNames.gutter,
18
+ contenteditable: 'false'
19
+ }], ['div', {
20
+ class: codeBlockClassNames.content
21
+ }, ['code', {
22
+ 'data-language': node.attrs.language || '',
23
+ spellcheck: 'false',
24
+ contenteditable: 'true',
25
+ 'data-testid': 'code-block--code'
26
+ }, 0]]], ['div', {
27
+ class: codeBlockClassNames.end,
28
+ contenteditable: 'false'
29
+ }]];
30
+ export class CodeBlockView {
31
+ constructor(_node, view, getPos) {
32
+ _defineProperty(this, "ensureLineNumbers", rafSchedule(() => {
33
+ let lines = 1;
34
+ this.node.forEach(node => {
35
+ const text = node.text;
36
+ if (text) {
37
+ lines += (node.text.match(MATCH_NEWLINES) || []).length;
38
+ }
39
+ });
40
+ while (this.lineNumberGutter.childElementCount < lines) {
41
+ this.lineNumberGutter.appendChild(document.createElement('span'));
42
+ }
43
+ while (this.lineNumberGutter.childElementCount > lines) {
44
+ this.lineNumberGutter.removeChild(this.lineNumberGutter.lastChild);
45
+ }
46
+ }));
47
+ const {
48
+ dom,
49
+ contentDOM
50
+ } = DOMSerializer.renderSpec(document, toDOM(_node));
51
+ this.getPos = getPos;
52
+ this.view = view;
53
+ this.node = _node;
54
+ this.dom = dom;
55
+ this.contentDOM = contentDOM;
56
+ this.lineNumberGutter = this.dom.querySelector(`.${codeBlockClassNames.gutter}`);
57
+ this.ensureLineNumbers();
58
+ }
59
+ updateDOMAndSelection(savedInnerHTML, newCursorPosition) {
60
+ var _this$dom;
61
+ if ((_this$dom = this.dom) !== null && _this$dom !== void 0 && _this$dom.childNodes && this.dom.childNodes.length > 1) {
62
+ var _contentView$childNod;
63
+ const contentWrapper = this.dom.childNodes[1];
64
+ const contentView = contentWrapper === null || contentWrapper === void 0 ? void 0 : contentWrapper.childNodes[1];
65
+ if ((contentView === null || contentView === void 0 ? void 0 : (_contentView$childNod = contentView.childNodes) === null || _contentView$childNod === void 0 ? void 0 : _contentView$childNod.length) > 0) {
66
+ const codeElement = contentView.firstChild;
67
+ codeElement.innerHTML = savedInnerHTML;
68
+
69
+ // We need to set cursor for the DOM update
70
+ const textElement = [...codeElement.childNodes].find(child => child.nodeName === '#text');
71
+ const sel = window.getSelection();
72
+ const range = document.createRange();
73
+ range.setStart(textElement, newCursorPosition);
74
+ range.collapse(true);
75
+ sel === null || sel === void 0 ? void 0 : sel.removeAllRanges();
76
+ sel === null || sel === void 0 ? void 0 : sel.addRange(range);
77
+ }
78
+ }
79
+ }
80
+ coalesceDOMElements() {
81
+ var _this$dom2;
82
+ if ((_this$dom2 = this.dom) !== null && _this$dom2 !== void 0 && _this$dom2.childNodes && this.dom.childNodes.length > 1) {
83
+ const contentWrapper = this.dom.childNodes[1];
84
+ const contentView = contentWrapper === null || contentWrapper === void 0 ? void 0 : contentWrapper.childNodes[1];
85
+ if (contentView !== null && contentView !== void 0 && contentView.childNodes && contentView.childNodes.length > 1) {
86
+ let savedInnerHTML = '';
87
+ while (contentView.childNodes.length > 1) {
88
+ const lastChild = contentView.lastChild;
89
+ savedInnerHTML = lastChild.innerHTML + savedInnerHTML;
90
+ contentView.removeChild(lastChild);
91
+ }
92
+ const firstChild = contentView.firstChild;
93
+ savedInnerHTML = firstChild.innerHTML + '\n' + savedInnerHTML;
94
+ const newCursorPosition = firstChild.innerHTML.length + 1;
95
+ setTimeout(this.updateDOMAndSelection.bind(this, savedInnerHTML, newCursorPosition), 20);
96
+ }
97
+ }
98
+ }
99
+ update(node) {
100
+ if (node.type !== this.node.type) {
101
+ return false;
102
+ }
103
+ if (node !== this.node) {
104
+ if (node.attrs.language !== this.node.attrs.language) {
105
+ this.contentDOM.setAttribute('data-language', node.attrs.language || '');
106
+ }
107
+ this.node = node;
108
+ this.ensureLineNumbers();
109
+ if (browser.android) {
110
+ this.coalesceDOMElements();
111
+ resetShouldIgnoreFollowingMutations(this.view.state, this.view.dispatch);
112
+ }
113
+ }
114
+ return true;
115
+ }
116
+ ignoreMutation(record) {
117
+ const pluginState = getPluginState(this.view.state);
118
+ if (pluginState !== null && pluginState !== void 0 && pluginState.shouldIgnoreFollowingMutations) {
119
+ return true;
120
+ }
121
+
122
+ // Ensure updating the line-number gutter doesn't trigger reparsing the codeblock
123
+ return record.target === this.lineNumberGutter || record.target.parentNode === this.lineNumberGutter;
124
+ }
125
+ }
126
+ export const codeBlockNodeView = (node, view, getPos) => new CodeBlockView(node, view, getPos);
@@ -0,0 +1,2 @@
1
+ import { PluginKey } from '@atlaskit/editor-prosemirror/state';
2
+ export const pluginKey = new PluginKey('codeBlockPlugin');
@@ -0,0 +1,104 @@
1
+ import React from 'react';
2
+ import { codeBlock } from '@atlaskit/adf-schema';
3
+ import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics';
4
+ import { blockTypeMessages } from '@atlaskit/editor-common/messages';
5
+ import { IconCode } from '@atlaskit/editor-common/quick-insert';
6
+ import { createInsertCodeBlockTransaction, insertCodeBlockWithAnalytics } from './actions';
7
+ import { codeBlockCopySelectionPlugin } from './pm-plugins/codeBlockCopySelectionPlugin';
8
+ import ideUX from './pm-plugins/ide-ux';
9
+ import { createCodeBlockInputRule } from './pm-plugins/input-rule';
10
+ import keymap from './pm-plugins/keymaps';
11
+ import { createPlugin } from './pm-plugins/main';
12
+ import refreshBrowserSelectionOnChange from './refresh-browser-selection';
13
+ import { getToolbarConfig } from './toolbar';
14
+ const codeBlockPlugin = ({
15
+ config: options,
16
+ api
17
+ }) => ({
18
+ name: 'codeBlock',
19
+ nodes() {
20
+ return [{
21
+ name: 'codeBlock',
22
+ node: codeBlock
23
+ }];
24
+ },
25
+ pmPlugins() {
26
+ return [{
27
+ name: 'codeBlock',
28
+ plugin: ({
29
+ getIntl
30
+ }) => {
31
+ var _options$appearance;
32
+ return createPlugin({
33
+ ...options,
34
+ getIntl,
35
+ appearance: (_options$appearance = options === null || options === void 0 ? void 0 : options.appearance) !== null && _options$appearance !== void 0 ? _options$appearance : 'comment'
36
+ });
37
+ }
38
+ }, {
39
+ name: 'codeBlockInputRule',
40
+ plugin: ({
41
+ schema
42
+ }) => {
43
+ var _api$analytics;
44
+ return createCodeBlockInputRule(schema, api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions);
45
+ }
46
+ }, {
47
+ name: 'codeBlockIDEKeyBindings',
48
+ plugin: () => ideUX(api)
49
+ }, {
50
+ name: 'codeBlockKeyMap',
51
+ plugin: ({
52
+ schema
53
+ }) => keymap(schema)
54
+ }, {
55
+ name: 'codeBlockCopySelection',
56
+ plugin: () => codeBlockCopySelectionPlugin()
57
+ }];
58
+ },
59
+ // Workaround for a firefox issue where dom selection is off sync
60
+ // https://product-fabric.atlassian.net/browse/ED-12442
61
+ onEditorViewStateUpdated(props) {
62
+ refreshBrowserSelectionOnChange(props.originalTransaction, props.newEditorState);
63
+ },
64
+ actions: {
65
+ /*
66
+ * Function will insert code block at current selection if block is empty or below current selection and set focus on it.
67
+ */
68
+ insertCodeBlock: inputMethod => {
69
+ var _api$analytics2;
70
+ return insertCodeBlockWithAnalytics(inputMethod, api === null || api === void 0 ? void 0 : (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 ? void 0 : _api$analytics2.actions);
71
+ }
72
+ },
73
+ pluginsOptions: {
74
+ quickInsert: ({
75
+ formatMessage
76
+ }) => [{
77
+ id: 'codeblock',
78
+ title: formatMessage(blockTypeMessages.codeblock),
79
+ description: formatMessage(blockTypeMessages.codeblockDescription),
80
+ keywords: ['code block'],
81
+ priority: 700,
82
+ keyshortcut: '```',
83
+ icon: () => /*#__PURE__*/React.createElement(IconCode, null),
84
+ action(_insert, state) {
85
+ var _api$analytics3;
86
+ const tr = createInsertCodeBlockTransaction({
87
+ state
88
+ });
89
+ api === null || api === void 0 ? void 0 : (_api$analytics3 = api.analytics) === null || _api$analytics3 === void 0 ? void 0 : _api$analytics3.actions.attachAnalyticsEvent({
90
+ action: ACTION.INSERTED,
91
+ actionSubject: ACTION_SUBJECT.DOCUMENT,
92
+ actionSubjectId: ACTION_SUBJECT_ID.CODE_BLOCK,
93
+ attributes: {
94
+ inputMethod: INPUT_METHOD.QUICK_INSERT
95
+ },
96
+ eventType: EVENT_TYPE.TRACK
97
+ })(tr);
98
+ return tr;
99
+ }
100
+ }],
101
+ floatingToolbar: getToolbarConfig(options === null || options === void 0 ? void 0 : options.allowCopyToClipboard, api)
102
+ }
103
+ });
104
+ export default codeBlockPlugin;
@@ -0,0 +1,4 @@
1
+ export const ACTIONS = {
2
+ SET_COPIED_TO_CLIPBOARD: 'SET_COPIED_TO_CLIPBOARD',
3
+ SET_SHOULD_IGNORE_FOLLOWING_MUTATIONS: 'SET_SHOULD_IGNORE_FOLLOWING_MUTATIONS'
4
+ };
@@ -0,0 +1,101 @@
1
+ import { getSelectedNodeOrNodeParentByNodeType } from '@atlaskit/editor-common/copy-button';
2
+ import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
3
+ import { PluginKey } from '@atlaskit/editor-prosemirror/state';
4
+ import { Decoration, DecorationSet } from '@atlaskit/editor-prosemirror/view';
5
+ export const copySelectionPluginKey = new PluginKey('codeBlockCopySelectionPlugin');
6
+ function getSelectionDecorationStartAndEnd({
7
+ state,
8
+ transaction
9
+ }) {
10
+ const codeBlockNode = getSelectedNodeOrNodeParentByNodeType({
11
+ nodeType: state.schema.nodes.codeBlock,
12
+ selection: transaction.selection
13
+ });
14
+ if (!codeBlockNode) {
15
+ return {
16
+ decorationStartAndEnd: undefined
17
+ };
18
+ }
19
+ const decorationStartAndEnd = [codeBlockNode.start, codeBlockNode.start + codeBlockNode.node.nodeSize];
20
+ return {
21
+ decorationStartAndEnd
22
+ };
23
+ }
24
+ export function codeBlockCopySelectionPlugin() {
25
+ return new SafePlugin({
26
+ key: copySelectionPluginKey,
27
+ state: {
28
+ init() {
29
+ return {
30
+ decorationStartAndEnd: undefined
31
+ };
32
+ },
33
+ apply(transaction, currentCodeBlockCopySelectionPluginState, _oldState, newState) {
34
+ switch (transaction.getMeta(copySelectionPluginKey)) {
35
+ case 'show-selection':
36
+ {
37
+ return getSelectionDecorationStartAndEnd({
38
+ state: newState,
39
+ transaction
40
+ });
41
+ }
42
+ case 'remove-selection':
43
+ return {
44
+ decorationStartAndEnd: undefined
45
+ };
46
+ default:
47
+ // The contents of the code block can change while the selection is being shown
48
+ // (either from collab edits -- or from the user continuing to type while hovering
49
+ // the mouse over the copy button).
50
+ // This ensures the selection is updated in these cases.
51
+ if (currentCodeBlockCopySelectionPluginState.decorationStartAndEnd !== undefined) {
52
+ return getSelectionDecorationStartAndEnd({
53
+ state: newState,
54
+ transaction
55
+ });
56
+ }
57
+ return currentCodeBlockCopySelectionPluginState;
58
+ }
59
+ }
60
+ },
61
+ props: {
62
+ decorations(state) {
63
+ if (copySelectionPluginKey.getState(state).decorationStartAndEnd) {
64
+ const [start, end] = copySelectionPluginKey.getState(state).decorationStartAndEnd;
65
+ return DecorationSet.create(state.doc, [Decoration.inline(start, end, {
66
+ class: 'ProseMirror-fake-text-selection'
67
+ })]);
68
+ }
69
+ return DecorationSet.empty;
70
+ }
71
+ }
72
+ });
73
+ }
74
+ export function provideVisualFeedbackForCopyButton(state, dispatch) {
75
+ const tr = state.tr;
76
+ tr.setMeta(copySelectionPluginKey, 'show-selection');
77
+
78
+ // note: dispatch should always be defined when called from the
79
+ // floating toolbar. Howver the Command type which the floating toolbar
80
+ // uses suggests it's optional.
81
+ // Using the type here to protect against future refactors of the
82
+ // floating toolbar
83
+ if (dispatch) {
84
+ dispatch(tr);
85
+ }
86
+ return true;
87
+ }
88
+ export function removeVisualFeedbackForCopyButton(state, dispatch) {
89
+ const tr = state.tr;
90
+ tr.setMeta(copySelectionPluginKey, 'remove-selection');
91
+
92
+ // note: dispatch should always be defined when called from the
93
+ // floating toolbar. Howver the Command type which the floating toolbar
94
+ // uses suggests it's optional.
95
+ // Using the type here to protect against future refactors of the
96
+ // floating toolbar
97
+ if (dispatch) {
98
+ dispatch(tr);
99
+ }
100
+ return true;
101
+ }