@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,135 @@
1
+ import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
2
+ import { filterCommand as filter } from '@atlaskit/editor-common/utils';
3
+ import { keydownHandler } from '@atlaskit/editor-prosemirror/keymap';
4
+ import { TextSelection } from '@atlaskit/editor-prosemirror/state';
5
+ import { setTextSelection } from '@atlaskit/editor-prosemirror/utils';
6
+ import { getAutoClosingBracketInfo, shouldAutoCloseBracket } from '../ide-ux/bracket-handling';
7
+ import { indent, insertIndent, insertNewlineWithIndent, outdent } from '../ide-ux/commands';
8
+ import { getEndOfCurrentLine, getLineInfo, getStartOfCurrentLine, isCursorInsideCodeBlock, isSelectionEntirelyInsideCodeBlock } from '../ide-ux/line-handling';
9
+ import { isClosingCharacter, isCursorBeforeClosingCharacter } from '../ide-ux/paired-character-handling';
10
+ import { getAutoClosingQuoteInfo, shouldAutoCloseQuote } from '../ide-ux/quote-handling';
11
+ import { getCursor } from '../utils';
12
+ const ideUX = pluginInjectionApi => {
13
+ var _pluginInjectionApi$a;
14
+ const editorAnalyticsAPI = pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$a = pluginInjectionApi.analytics) === null || _pluginInjectionApi$a === void 0 ? void 0 : _pluginInjectionApi$a.actions;
15
+ return new SafePlugin({
16
+ props: {
17
+ handleTextInput(view, from, to, text) {
18
+ var _pluginInjectionApi$c;
19
+ const {
20
+ state,
21
+ dispatch
22
+ } = view;
23
+ const compositionPluginState = pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$c = pluginInjectionApi.composition) === null || _pluginInjectionApi$c === void 0 ? void 0 : _pluginInjectionApi$c.sharedState.currentState();
24
+ if (isCursorInsideCodeBlock(state) && !(compositionPluginState !== null && compositionPluginState !== void 0 && compositionPluginState.isComposing)) {
25
+ const beforeText = getStartOfCurrentLine(state).text;
26
+ const afterText = getEndOfCurrentLine(state).text;
27
+
28
+ // If text is a closing bracket/quote and we've already inserted it, move the selection after
29
+ if (isCursorBeforeClosingCharacter(afterText) && isClosingCharacter(text) && afterText.startsWith(text)) {
30
+ dispatch(setTextSelection(to + text.length)(state.tr));
31
+ return true;
32
+ }
33
+
34
+ // Automatically add right-hand side bracket when user types the left bracket
35
+ if (shouldAutoCloseBracket(beforeText, afterText)) {
36
+ const {
37
+ left,
38
+ right
39
+ } = getAutoClosingBracketInfo(beforeText + text, afterText);
40
+ if (left && right) {
41
+ const bracketPair = state.schema.text(text + right);
42
+ let tr = state.tr.replaceWith(from, to, bracketPair);
43
+ dispatch(setTextSelection(from + text.length)(tr));
44
+ return true;
45
+ }
46
+ }
47
+
48
+ // Automatically add closing quote when user types a starting quote
49
+ if (shouldAutoCloseQuote(beforeText, afterText)) {
50
+ const {
51
+ left: leftQuote,
52
+ right: rightQuote
53
+ } = getAutoClosingQuoteInfo(beforeText + text, afterText);
54
+ if (leftQuote && rightQuote) {
55
+ const quotePair = state.schema.text(text + rightQuote);
56
+ let tr = state.tr.replaceWith(from, to, quotePair);
57
+ dispatch(setTextSelection(from + text.length)(tr));
58
+ return true;
59
+ }
60
+ }
61
+ }
62
+ return false;
63
+ },
64
+ handleKeyDown: keydownHandler({
65
+ Backspace: (state, dispatch) => {
66
+ if (isCursorInsideCodeBlock(state)) {
67
+ const $cursor = getCursor(state.selection);
68
+ const beforeText = getStartOfCurrentLine(state).text;
69
+ const afterText = getEndOfCurrentLine(state).text;
70
+ const {
71
+ left: leftBracket,
72
+ right: rightBracket,
73
+ hasTrailingMatchingBracket
74
+ } = getAutoClosingBracketInfo(beforeText, afterText);
75
+ if (leftBracket && rightBracket && hasTrailingMatchingBracket && dispatch) {
76
+ dispatch(state.tr.delete($cursor.pos - leftBracket.length, $cursor.pos + rightBracket.length));
77
+ return true;
78
+ }
79
+ const {
80
+ left: leftQuote,
81
+ right: rightQuote,
82
+ hasTrailingMatchingQuote
83
+ } = getAutoClosingQuoteInfo(beforeText, afterText);
84
+ if (leftQuote && rightQuote && hasTrailingMatchingQuote && dispatch) {
85
+ dispatch(state.tr.delete($cursor.pos - leftQuote.length, $cursor.pos + rightQuote.length));
86
+ return true;
87
+ }
88
+ const {
89
+ indentToken: {
90
+ size,
91
+ token
92
+ },
93
+ indentText
94
+ } = getLineInfo(beforeText);
95
+ if (beforeText === indentText) {
96
+ if (indentText.endsWith(token.repeat(size)) && dispatch) {
97
+ dispatch(state.tr.delete($cursor.pos - (size - indentText.length % size || size), $cursor.pos));
98
+ return true;
99
+ }
100
+ }
101
+ }
102
+ return false;
103
+ },
104
+ Enter: filter(isSelectionEntirelyInsideCodeBlock, insertNewlineWithIndent),
105
+ 'Mod-]': filter(isSelectionEntirelyInsideCodeBlock, indent(editorAnalyticsAPI)),
106
+ 'Mod-[': filter(isSelectionEntirelyInsideCodeBlock, outdent(editorAnalyticsAPI)),
107
+ Tab: filter(isSelectionEntirelyInsideCodeBlock, (state, dispatch) => {
108
+ if (!dispatch) {
109
+ return false;
110
+ }
111
+ if (isCursorInsideCodeBlock(state)) {
112
+ return insertIndent(state, dispatch);
113
+ }
114
+ return indent(editorAnalyticsAPI)(state, dispatch);
115
+ }),
116
+ 'Shift-Tab': filter(isSelectionEntirelyInsideCodeBlock, outdent(editorAnalyticsAPI)),
117
+ 'Mod-a': (state, dispatch) => {
118
+ if (isSelectionEntirelyInsideCodeBlock(state)) {
119
+ const {
120
+ $from,
121
+ $to
122
+ } = state.selection;
123
+ const isFullCodeBlockSelection = $from.parentOffset === 0 && $to.parentOffset === $to.parent.nodeSize - 2;
124
+ if (!isFullCodeBlockSelection && dispatch) {
125
+ dispatch(state.tr.setSelection(TextSelection.create(state.doc, $from.start(), $to.end())));
126
+ return true;
127
+ }
128
+ }
129
+ return false;
130
+ }
131
+ })
132
+ }
133
+ });
134
+ };
135
+ export default ideUX;
@@ -0,0 +1,60 @@
1
+ import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics';
2
+ import { insertBlock } from '@atlaskit/editor-common/commands';
3
+ import { inputRuleWithAnalytics } from '@atlaskit/editor-common/utils';
4
+ import { safeInsert } from '@atlaskit/editor-prosemirror/utils';
5
+ import { createPlugin, createRule, leafNodeReplacementCharacter } from '@atlaskit/prosemirror-input-rules';
6
+ import { isConvertableToCodeBlock, transformToCodeBlockAction } from '../transform-to-code-block';
7
+ export function createCodeBlockInputRule(schema, editorAnalyticsAPI) {
8
+ const rules = getCodeBlockRules(editorAnalyticsAPI, schema);
9
+ return createPlugin('code-block-input-rule', rules, {
10
+ isBlockNodeRule: true
11
+ });
12
+ }
13
+
14
+ /**
15
+ * Get all code block input rules
16
+ *
17
+ * @param {Schema} schema
18
+ * @returns {InputRuleWithHandler[]}
19
+ */
20
+ function getCodeBlockRules(editorAnalyticsAPI, schema) {
21
+ const ruleAnalytics = inputRuleWithAnalytics({
22
+ action: ACTION.INSERTED,
23
+ actionSubject: ACTION_SUBJECT.DOCUMENT,
24
+ actionSubjectId: ACTION_SUBJECT_ID.CODE_BLOCK,
25
+ attributes: {
26
+ inputMethod: INPUT_METHOD.FORMATTING
27
+ },
28
+ eventType: EVENT_TYPE.TRACK
29
+ }, editorAnalyticsAPI);
30
+ const validMatchLength = match => match.length > 0 && match[0].length === 3;
31
+ const threeTildeRule = createRule(/(?!\s)(`{3,})$/, (state, match, start, end) => {
32
+ if (!validMatchLength(match)) {
33
+ return null;
34
+ }
35
+ const attributes = {};
36
+ if (match[4]) {
37
+ attributes.language = match[4];
38
+ }
39
+ if (isConvertableToCodeBlock(state)) {
40
+ return transformToCodeBlockAction(state, start, attributes);
41
+ }
42
+ const tr = state.tr;
43
+ tr.delete(start, end);
44
+ const codeBlock = tr.doc.type.schema.nodes.codeBlock.createChecked();
45
+ safeInsert(codeBlock)(tr);
46
+ return tr;
47
+ });
48
+ const leftNodeReplacementThreeTildeRule = createRule(new RegExp(`((${leafNodeReplacementCharacter}\`{3,})|^\\s(\`{3,}))(\\S*)$`), (state, match, start, end) => {
49
+ if (!validMatchLength(match)) {
50
+ return null;
51
+ }
52
+ const attributes = {};
53
+ if (match[4]) {
54
+ attributes.language = match[4];
55
+ }
56
+ const inlineStart = Math.max(match.index + state.selection.$from.start(), 1);
57
+ return insertBlock(state, schema.nodes.codeBlock, inlineStart, end, attributes);
58
+ });
59
+ return [ruleAnalytics(threeTildeRule), ruleAnalytics(leftNodeReplacementThreeTildeRule)];
60
+ }
@@ -0,0 +1,58 @@
1
+ import { isEmptyNode } from '@atlaskit/editor-common/utils';
2
+ import { keymap } from '@atlaskit/editor-prosemirror/keymap';
3
+ import { Selection } from '@atlaskit/editor-prosemirror/state';
4
+ import { findParentNodeOfTypeClosestToPos, hasParentNodeOfType } from '@atlaskit/editor-prosemirror/utils';
5
+ import { getCursor } from '../utils';
6
+ const deleteCurrentItem = $from => tr => {
7
+ return tr.delete($from.before($from.depth), $from.after($from.depth));
8
+ };
9
+ const setTextSelection = pos => tr => {
10
+ const newSelection = Selection.findFrom(tr.doc.resolve(pos), -1, true);
11
+ if (newSelection) {
12
+ tr.setSelection(newSelection);
13
+ }
14
+ return tr;
15
+ };
16
+ export function keymapPlugin(schema) {
17
+ return keymap({
18
+ Backspace: (state, dispatch) => {
19
+ const $cursor = getCursor(state.selection);
20
+ const {
21
+ paragraph,
22
+ codeBlock,
23
+ listItem,
24
+ table,
25
+ layoutColumn
26
+ } = state.schema.nodes;
27
+ if (!$cursor || $cursor.parent.type !== codeBlock || !dispatch) {
28
+ return false;
29
+ }
30
+ if ($cursor.pos === 1 || hasParentNodeOfType(listItem)(state.selection) && $cursor.parentOffset === 0) {
31
+ const node = findParentNodeOfTypeClosestToPos($cursor, codeBlock);
32
+ if (!node) {
33
+ return false;
34
+ }
35
+ dispatch(state.tr.setNodeMarkup(node.pos, node.node.type, node.node.attrs, []).setBlockType($cursor.pos, $cursor.pos, paragraph));
36
+ return true;
37
+ }
38
+ if ($cursor.node && isEmptyNode(schema)($cursor.node()) && (hasParentNodeOfType(layoutColumn)(state.selection) || hasParentNodeOfType(table)(state.selection))) {
39
+ const {
40
+ tr
41
+ } = state;
42
+ const insertPos = $cursor.pos;
43
+ deleteCurrentItem($cursor)(tr);
44
+ setTextSelection(insertPos)(tr);
45
+ dispatch(tr.scrollIntoView());
46
+ return true;
47
+ }
48
+
49
+ // Handle not nested empty code block
50
+ if (isEmptyNode(schema)($cursor.node())) {
51
+ dispatch(deleteCurrentItem($cursor)(state === null || state === void 0 ? void 0 : state.tr));
52
+ return true;
53
+ }
54
+ return false;
55
+ }
56
+ });
57
+ }
58
+ export default keymapPlugin;
@@ -0,0 +1,2 @@
1
+ import { pluginKey } from '../plugin-key';
2
+ export const getPluginState = state => pluginKey.getState(state);
@@ -0,0 +1,102 @@
1
+ import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
2
+ import { createSelectionClickHandler } from '@atlaskit/editor-common/selection';
3
+ import { browser } from '@atlaskit/editor-common/utils';
4
+ import { NodeSelection } from '@atlaskit/editor-prosemirror/state';
5
+ import { ignoreFollowingMutations, resetShouldIgnoreFollowingMutations } from '../actions';
6
+ import { codeBlockNodeView } from '../nodeviews/code-block';
7
+ import { pluginKey } from '../plugin-key';
8
+ import { codeBlockClassNames } from '../ui/class-names';
9
+ import { findCodeBlock } from '../utils';
10
+ import { ACTIONS } from './actions';
11
+ export const createPlugin = ({
12
+ useLongPressSelection = false,
13
+ getIntl,
14
+ appearance,
15
+ allowCompositionInputOverride = false
16
+ }) => {
17
+ const handleDOMEvents = {};
18
+
19
+ // ME-1599: Composition on mobile was causing the DOM observer to mutate the code block
20
+ // incorrecly and lose content when pressing enter in the middle of a code block line.
21
+ if (allowCompositionInputOverride) {
22
+ handleDOMEvents.beforeinput = (view, event) => {
23
+ const keyEvent = event;
24
+ const eventInputType = keyEvent.inputType;
25
+ const eventText = keyEvent.data;
26
+ if (browser.ios && event.composed &&
27
+ // insertParagraph will be the input type when the enter key is pressed.
28
+ eventInputType === 'insertParagraph' && findCodeBlock(view.state, view.state.selection)) {
29
+ event.preventDefault();
30
+ return true;
31
+ } else if (browser.android && event.composed && eventInputType === 'insertCompositionText' && eventText[(eventText === null || eventText === void 0 ? void 0 : eventText.length) - 1] === '\n' && findCodeBlock(view.state, view.state.selection)) {
32
+ const resultingText = event.target.outerText + '\n';
33
+ if (resultingText.endsWith(eventText)) {
34
+ // End of paragraph
35
+ setTimeout(() => {
36
+ view.someProp('handleKeyDown', f => f(view, new KeyboardEvent('keydown', {
37
+ bubbles: true,
38
+ cancelable: true,
39
+ key: 'Enter',
40
+ code: 'Enter'
41
+ })));
42
+ }, 0);
43
+ } else {
44
+ // Middle of paragraph, end of line
45
+ ignoreFollowingMutations(view.state, view.dispatch);
46
+ }
47
+ return true;
48
+ }
49
+ if (browser.android) {
50
+ resetShouldIgnoreFollowingMutations(view.state, view.dispatch);
51
+ }
52
+ return false;
53
+ };
54
+ }
55
+ return new SafePlugin({
56
+ state: {
57
+ init(_, state) {
58
+ const node = findCodeBlock(state, state.selection);
59
+ return {
60
+ pos: node ? node.pos : null,
61
+ contentCopied: false,
62
+ isNodeSelected: false,
63
+ shouldIgnoreFollowingMutations: false
64
+ };
65
+ },
66
+ apply(tr, pluginState, _oldState, newState) {
67
+ if (tr.docChanged || tr.selectionSet) {
68
+ const node = findCodeBlock(newState, tr.selection);
69
+ const newPluginState = {
70
+ ...pluginState,
71
+ pos: node ? node.pos : null,
72
+ isNodeSelected: tr.selection instanceof NodeSelection
73
+ };
74
+ return newPluginState;
75
+ }
76
+ const meta = tr.getMeta(pluginKey);
77
+ if ((meta === null || meta === void 0 ? void 0 : meta.type) === ACTIONS.SET_COPIED_TO_CLIPBOARD) {
78
+ return {
79
+ ...pluginState,
80
+ contentCopied: meta.data
81
+ };
82
+ } else if ((meta === null || meta === void 0 ? void 0 : meta.type) === ACTIONS.SET_SHOULD_IGNORE_FOLLOWING_MUTATIONS) {
83
+ return {
84
+ ...pluginState,
85
+ shouldIgnoreFollowingMutations: meta.data
86
+ };
87
+ }
88
+ return pluginState;
89
+ }
90
+ },
91
+ key: pluginKey,
92
+ props: {
93
+ nodeViews: {
94
+ codeBlock: codeBlockNodeView
95
+ },
96
+ handleClickOn: createSelectionClickHandler(['codeBlock'], target => !!(target.closest(`.${codeBlockClassNames.gutter}`) || target.classList.contains(codeBlockClassNames.content)), {
97
+ useLongPressSelection
98
+ }),
99
+ handleDOMEvents
100
+ }
101
+ });
102
+ };
@@ -0,0 +1,25 @@
1
+ import { browser } from '@atlaskit/editor-common/utils';
2
+ import { pluginKey } from './plugin-key';
3
+
4
+ // Workaround for a firefox issue where dom selection is off sync
5
+ // https://product-fabric.atlassian.net/browse/ED-12442
6
+ const refreshBrowserSelection = () => {
7
+ const domSelection = window.getSelection();
8
+ if (domSelection) {
9
+ const domRange = domSelection && domSelection.rangeCount === 1 && domSelection.getRangeAt(0).cloneRange();
10
+ if (domRange) {
11
+ domSelection.removeAllRanges();
12
+ domSelection.addRange(domRange);
13
+ }
14
+ }
15
+ };
16
+ const refreshBrowserSelectionOnChange = (transaction, editorState) => {
17
+ var _pluginKey$getState;
18
+ if (browser.gecko && transaction.docChanged &&
19
+ // codeblockState.pos should be set if current selection is in a codeblock.
20
+ typeof ((_pluginKey$getState = pluginKey.getState(editorState)) === null || _pluginKey$getState === void 0 ? void 0 : _pluginKey$getState.pos) === 'number') {
21
+ refreshBrowserSelection();
22
+ }
23
+ };
24
+ export default refreshBrowserSelectionOnChange;
25
+ export { refreshBrowserSelection };
@@ -0,0 +1,108 @@
1
+ import { defineMessages } from 'react-intl-next';
2
+ import commonMessages, { codeBlockButtonMessages } from '@atlaskit/editor-common/messages';
3
+ import { findDomRefAtPos } from '@atlaskit/editor-prosemirror/utils';
4
+ import CopyIcon from '@atlaskit/icon/glyph/copy';
5
+ import RemoveIcon from '@atlaskit/icon/glyph/editor/remove';
6
+ import { changeLanguage, copyContentToClipboard, removeCodeBlock, resetCopiedState } from './actions';
7
+ import { createLanguageList, DEFAULT_LANGUAGES, getLanguageIdentifier } from './language-list';
8
+ import { pluginKey } from './plugin-key';
9
+ import { provideVisualFeedbackForCopyButton, removeVisualFeedbackForCopyButton } from './pm-plugins/codeBlockCopySelectionPlugin';
10
+ export const messages = defineMessages({
11
+ selectLanguage: {
12
+ id: 'fabric.editor.selectLanguage',
13
+ defaultMessage: 'Select language',
14
+ description: 'Code blocks display software code. A prompt to select the software language the code is written in.'
15
+ }
16
+ });
17
+ const languageList = createLanguageList(DEFAULT_LANGUAGES);
18
+ export const getToolbarConfig = (allowCopyToClipboard = false, api) => (state, {
19
+ formatMessage
20
+ }) => {
21
+ var _api$decorations$acti, _api$analytics, _codeBlockState$pos, _node$attrs;
22
+ const {
23
+ hoverDecoration
24
+ } = (_api$decorations$acti = api === null || api === void 0 ? void 0 : api.decorations.actions) !== null && _api$decorations$acti !== void 0 ? _api$decorations$acti : {};
25
+ const editorAnalyticsAPI = api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions;
26
+ const codeBlockState = pluginKey.getState(state);
27
+ const pos = (_codeBlockState$pos = codeBlockState === null || codeBlockState === void 0 ? void 0 : codeBlockState.pos) !== null && _codeBlockState$pos !== void 0 ? _codeBlockState$pos : null;
28
+ if (!codeBlockState || pos === null) {
29
+ return;
30
+ }
31
+ const node = state.doc.nodeAt(pos);
32
+ const nodeType = state.schema.nodes.codeBlock;
33
+ if ((node === null || node === void 0 ? void 0 : node.type) !== nodeType) {
34
+ return;
35
+ }
36
+ const language = node === null || node === void 0 ? void 0 : (_node$attrs = node.attrs) === null || _node$attrs === void 0 ? void 0 : _node$attrs.language;
37
+ const options = languageList.map(lang => ({
38
+ label: lang.name,
39
+ value: getLanguageIdentifier(lang),
40
+ alias: lang.alias
41
+ }));
42
+
43
+ // If language is not undefined search for it in the value and then search in the aliases
44
+ const defaultValue = language ? options.find(option => option.value === language) || options.find(option => option.alias.includes(language)) : null;
45
+ const languageSelect = {
46
+ id: 'editor.codeBlock.languageOptions',
47
+ type: 'select',
48
+ selectType: 'list',
49
+ onChange: option => changeLanguage(editorAnalyticsAPI)(option.value),
50
+ defaultValue,
51
+ placeholder: formatMessage(messages.selectLanguage),
52
+ options,
53
+ filterOption: languageListFilter
54
+ };
55
+ const separator = {
56
+ type: 'separator'
57
+ };
58
+ const copyToClipboardItems = !allowCopyToClipboard ? [] : [{
59
+ id: 'editor.codeBlock.copy',
60
+ type: 'button',
61
+ appearance: 'subtle',
62
+ icon: CopyIcon,
63
+ // note: copyContentToClipboard contains logic that also removes the
64
+ // visual feedback for the copy button
65
+ onClick: copyContentToClipboard,
66
+ title: formatMessage(codeBlockState.contentCopied ? codeBlockButtonMessages.copiedCodeToClipboard : codeBlockButtonMessages.copyCodeToClipboard),
67
+ onMouseEnter: provideVisualFeedbackForCopyButton,
68
+ // note: resetCopiedState contains logic that also removes the
69
+ // visual feedback for the copy button
70
+ onMouseLeave: resetCopiedState,
71
+ onFocus: provideVisualFeedbackForCopyButton,
72
+ onBlur: removeVisualFeedbackForCopyButton,
73
+ hideTooltipOnClick: false,
74
+ disabled: codeBlockState.isNodeSelected,
75
+ tabIndex: null
76
+ }, separator];
77
+ const deleteButton = {
78
+ id: 'editor.codeBlock.delete',
79
+ type: 'button',
80
+ appearance: 'danger',
81
+ icon: RemoveIcon,
82
+ onMouseEnter: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(nodeType, true),
83
+ onMouseLeave: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(nodeType, false),
84
+ onFocus: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(nodeType, true),
85
+ onBlur: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(nodeType, false),
86
+ onClick: removeCodeBlock,
87
+ title: formatMessage(commonMessages.remove),
88
+ tabIndex: null
89
+ };
90
+ return {
91
+ title: 'CodeBlock floating controls',
92
+ getDomRef: view => findDomRefAtPos(pos, view.domAtPos.bind(view)),
93
+ nodeType,
94
+ items: [languageSelect, separator, ...copyToClipboardItems, deleteButton],
95
+ scrollable: true
96
+ };
97
+ };
98
+
99
+ /**
100
+ * Filters language list based on both name and alias properties.
101
+ */
102
+ export const languageListFilter = (option, rawInput) => {
103
+ const {
104
+ data
105
+ } = option;
106
+ const searchString = rawInput.toLowerCase();
107
+ return data.label.toLowerCase().includes(searchString) || data.alias.some(alias => alias.toLowerCase() === searchString);
108
+ };
@@ -0,0 +1,79 @@
1
+ import { GapCursorSelection } from '@atlaskit/editor-common/selection';
2
+ import { mapSlice, timestampToString } from '@atlaskit/editor-common/utils';
3
+ import { Fragment } from '@atlaskit/editor-prosemirror/model';
4
+ import { TextSelection } from '@atlaskit/editor-prosemirror/state';
5
+ export function transformToCodeBlockAction(state, start, attrs) {
6
+ const startOfCodeBlockText = state.selection.$from;
7
+ const endPosition = state.selection.empty && !(state.selection instanceof GapCursorSelection) ? startOfCodeBlockText.end() : state.selection.$to.pos;
8
+ const startLinePosition = startOfCodeBlockText.start();
9
+ //when cmd+A is used to select the content. start position should be 0.
10
+ const parentStartPosition = startOfCodeBlockText.depth === 0 ? 0 : startOfCodeBlockText.before();
11
+ const contentSlice = state.doc.slice(startOfCodeBlockText.pos, endPosition);
12
+ const codeBlockSlice = mapSlice(contentSlice, (node, parent, index) => {
13
+ if (node.type === state.schema.nodes.hardBreak) {
14
+ return state.schema.text('\n');
15
+ }
16
+ if (node.isText) {
17
+ return node.mark([]);
18
+ }
19
+ if (node.isInline) {
20
+ // Convert dates
21
+ if (node.attrs.timestamp) {
22
+ return state.schema.text(timestampToString(node.attrs.timestamp, null));
23
+ }
24
+ // Convert links
25
+ if (node.attrs.url) {
26
+ return state.schema.text(node.attrs.url);
27
+ }
28
+ return node.attrs.text ? state.schema.text(node.attrs.text) : null;
29
+ }
30
+
31
+ // if the current node is the last child of the Slice exit early to prevent
32
+ // adding additional line breaks
33
+ if (contentSlice.content.childCount - 1 === index) {
34
+ return node.content;
35
+ }
36
+
37
+ //useful to decide whether to append line breaks when the content has list items.
38
+ const isParentLastChild = parent && contentSlice.content.childCount - 1 === index;
39
+
40
+ // add line breaks at the end of each paragraph to mimic layout of selected content
41
+ // do not add line breaks when the 'paragraph' parent is last child.
42
+ if (node.content.childCount && node.type === state.schema.nodes.paragraph && !isParentLastChild) {
43
+ return node.content.append(Fragment.from(state.schema.text('\n\n')));
44
+ }
45
+ return node.content.childCount ? node.content : null;
46
+ });
47
+ const tr = state.tr;
48
+
49
+ // Replace current block node
50
+ const startMapped = startLinePosition === start ? parentStartPosition : start;
51
+ const codeBlock = state.schema.nodes.codeBlock;
52
+ const codeBlockNode = codeBlock.createChecked(attrs, codeBlockSlice.content);
53
+ tr.replaceWith(startMapped, Math.min(endPosition, tr.doc.content.size), codeBlockNode);
54
+
55
+ // Reposition cursor when inserting into layouts or table headers
56
+ const mapped = tr.doc.resolve(tr.mapping.map(startMapped) + 1);
57
+ const selection = TextSelection.findFrom(mapped, state.selection instanceof GapCursorSelection ? -1 : 1, true);
58
+ if (selection) {
59
+ return tr.setSelection(selection);
60
+ }
61
+ return tr.setSelection(TextSelection.create(tr.doc, Math.min(start + startOfCodeBlockText.node().nodeSize - 1, tr.doc.content.size)));
62
+ }
63
+ export function isConvertableToCodeBlock(state) {
64
+ // Before a document is loaded, there is no selection.
65
+ if (!state.selection) {
66
+ return false;
67
+ }
68
+ const {
69
+ $from
70
+ } = state.selection;
71
+ const node = $from.parent;
72
+ if (!node.isTextblock || node.type === state.schema.nodes.codeBlock) {
73
+ return false;
74
+ }
75
+ const parentDepth = $from.depth - 1;
76
+ const parentNode = $from.node(parentDepth);
77
+ const index = $from.index(parentDepth);
78
+ return parentNode.canReplaceWith(index, index + 1, state.schema.nodes.codeBlock);
79
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ import { CodeBlockSharedCssClassName } from '@atlaskit/editor-common/styles';
2
+ export const codeBlockClassNames = {
3
+ container: CodeBlockSharedCssClassName.CODEBLOCK_CONTAINER,
4
+ start: CodeBlockSharedCssClassName.CODEBLOCK_START,
5
+ end: CodeBlockSharedCssClassName.CODEBLOCK_END,
6
+ contentWrapper: CodeBlockSharedCssClassName.CODEBLOCK_CONTENT_WRAPPER,
7
+ gutter: CodeBlockSharedCssClassName.CODEBLOCK_LINE_NUMBER_GUTTER,
8
+ content: CodeBlockSharedCssClassName.CODEBLOCK_CONTENT
9
+ };
@@ -0,0 +1,4 @@
1
+ export { findCodeBlock, transformSliceToJoinAdjacentCodeBlocks, transformSingleLineCodeBlockToCodeMark } from '@atlaskit/editor-common/transforms';
2
+ export function getCursor(selection) {
3
+ return selection.$cursor || undefined;
4
+ }