@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.
- package/CHANGELOG.md +1 -0
- package/LICENSE.md +13 -0
- package/README.md +30 -0
- package/dist/cjs/actions.js +201 -0
- package/dist/cjs/ide-ux/bracket-handling.js +35 -0
- package/dist/cjs/ide-ux/commands.js +115 -0
- package/dist/cjs/ide-ux/line-handling.js +81 -0
- package/dist/cjs/ide-ux/paired-character-handling.js +23 -0
- package/dist/cjs/ide-ux/quote-handling.js +40 -0
- package/dist/cjs/index.js +13 -0
- package/dist/cjs/language-list.js +62 -0
- package/dist/cjs/nodeviews/code-block.js +153 -0
- package/dist/cjs/plugin-key.js +8 -0
- package/dist/cjs/plugin.js +120 -0
- package/dist/cjs/pm-plugins/actions.js +10 -0
- package/dist/cjs/pm-plugins/codeBlockCopySelectionPlugin.js +113 -0
- package/dist/cjs/pm-plugins/ide-ux.js +132 -0
- package/dist/cjs/pm-plugins/input-rule.js +68 -0
- package/dist/cjs/pm-plugins/keymaps.js +66 -0
- package/dist/cjs/pm-plugins/main-state.js +10 -0
- package/dist/cjs/pm-plugins/main.js +114 -0
- package/dist/cjs/refresh-browser-selection.js +29 -0
- package/dist/cjs/toolbar.js +131 -0
- package/dist/cjs/transform-to-code-block.js +84 -0
- package/dist/cjs/types.js +5 -0
- package/dist/cjs/ui/class-names.js +15 -0
- package/dist/cjs/utils.js +28 -0
- package/dist/es2019/actions.js +211 -0
- package/dist/es2019/ide-ux/bracket-handling.js +27 -0
- package/dist/es2019/ide-ux/commands.js +115 -0
- package/dist/es2019/ide-ux/line-handling.js +75 -0
- package/dist/es2019/ide-ux/paired-character-handling.js +12 -0
- package/dist/es2019/ide-ux/quote-handling.js +32 -0
- package/dist/es2019/index.js +1 -0
- package/dist/es2019/language-list.js +51 -0
- package/dist/es2019/nodeviews/code-block.js +126 -0
- package/dist/es2019/plugin-key.js +2 -0
- package/dist/es2019/plugin.js +104 -0
- package/dist/es2019/pm-plugins/actions.js +4 -0
- package/dist/es2019/pm-plugins/codeBlockCopySelectionPlugin.js +101 -0
- package/dist/es2019/pm-plugins/ide-ux.js +135 -0
- package/dist/es2019/pm-plugins/input-rule.js +60 -0
- package/dist/es2019/pm-plugins/keymaps.js +58 -0
- package/dist/es2019/pm-plugins/main-state.js +2 -0
- package/dist/es2019/pm-plugins/main.js +102 -0
- package/dist/es2019/refresh-browser-selection.js +25 -0
- package/dist/es2019/toolbar.js +108 -0
- package/dist/es2019/transform-to-code-block.js +79 -0
- package/dist/es2019/types.js +1 -0
- package/dist/es2019/ui/class-names.js +9 -0
- package/dist/es2019/utils.js +4 -0
- package/dist/esm/actions.js +191 -0
- package/dist/esm/ide-ux/bracket-handling.js +29 -0
- package/dist/esm/ide-ux/commands.js +107 -0
- package/dist/esm/ide-ux/line-handling.js +73 -0
- package/dist/esm/ide-ux/paired-character-handling.js +16 -0
- package/dist/esm/ide-ux/quote-handling.js +34 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/language-list.js +52 -0
- package/dist/esm/nodeviews/code-block.js +146 -0
- package/dist/esm/plugin-key.js +2 -0
- package/dist/esm/plugin.js +113 -0
- package/dist/esm/pm-plugins/actions.js +4 -0
- package/dist/esm/pm-plugins/codeBlockCopySelectionPlugin.js +103 -0
- package/dist/esm/pm-plugins/ide-ux.js +126 -0
- package/dist/esm/pm-plugins/input-rule.js +62 -0
- package/dist/esm/pm-plugins/keymaps.js +59 -0
- package/dist/esm/pm-plugins/main-state.js +4 -0
- package/dist/esm/pm-plugins/main.js +107 -0
- package/dist/esm/refresh-browser-selection.js +25 -0
- package/dist/esm/toolbar.js +121 -0
- package/dist/esm/transform-to-code-block.js +77 -0
- package/dist/esm/types.js +1 -0
- package/dist/esm/ui/class-names.js +9 -0
- package/dist/esm/utils.js +4 -0
- package/dist/types/actions.d.ts +18 -0
- package/dist/types/ide-ux/bracket-handling.d.ts +12 -0
- package/dist/types/ide-ux/commands.d.ts +7 -0
- package/dist/types/ide-ux/line-handling.d.ts +25 -0
- package/dist/types/ide-ux/paired-character-handling.d.ts +2 -0
- package/dist/types/ide-ux/quote-handling.d.ts +12 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/language-list.d.ts +942 -0
- package/dist/types/nodeviews/code-block.d.ts +21 -0
- package/dist/types/plugin-key.d.ts +2 -0
- package/dist/types/plugin.d.ts +19 -0
- package/dist/types/pm-plugins/actions.d.ts +4 -0
- package/dist/types/pm-plugins/codeBlockCopySelectionPlugin.d.ts +11 -0
- package/dist/types/pm-plugins/ide-ux.d.ts +5 -0
- package/dist/types/pm-plugins/input-rule.d.ts +3 -0
- package/dist/types/pm-plugins/keymaps.d.ts +4 -0
- package/dist/types/pm-plugins/main-state.d.ts +8 -0
- package/dist/types/pm-plugins/main.d.ts +10 -0
- package/dist/types/refresh-browser-selection.d.ts +5 -0
- package/dist/types/toolbar.d.ts +14 -0
- package/dist/types/transform-to-code-block.d.ts +3 -0
- package/dist/types/types.d.ts +6 -0
- package/dist/types/ui/class-names.d.ts +8 -0
- package/dist/types/utils.d.ts +4 -0
- package/dist/types-ts4.5/actions.d.ts +18 -0
- package/dist/types-ts4.5/ide-ux/bracket-handling.d.ts +12 -0
- package/dist/types-ts4.5/ide-ux/commands.d.ts +7 -0
- package/dist/types-ts4.5/ide-ux/line-handling.d.ts +25 -0
- package/dist/types-ts4.5/ide-ux/paired-character-handling.d.ts +2 -0
- package/dist/types-ts4.5/ide-ux/quote-handling.d.ts +12 -0
- package/dist/types-ts4.5/index.d.ts +3 -0
- package/dist/types-ts4.5/language-list.d.ts +1641 -0
- package/dist/types-ts4.5/nodeviews/code-block.d.ts +21 -0
- package/dist/types-ts4.5/plugin-key.d.ts +2 -0
- package/dist/types-ts4.5/plugin.d.ts +19 -0
- package/dist/types-ts4.5/pm-plugins/actions.d.ts +4 -0
- package/dist/types-ts4.5/pm-plugins/codeBlockCopySelectionPlugin.d.ts +14 -0
- package/dist/types-ts4.5/pm-plugins/ide-ux.d.ts +5 -0
- package/dist/types-ts4.5/pm-plugins/input-rule.d.ts +3 -0
- package/dist/types-ts4.5/pm-plugins/keymaps.d.ts +4 -0
- package/dist/types-ts4.5/pm-plugins/main-state.d.ts +8 -0
- package/dist/types-ts4.5/pm-plugins/main.d.ts +10 -0
- package/dist/types-ts4.5/refresh-browser-selection.d.ts +5 -0
- package/dist/types-ts4.5/toolbar.d.ts +14 -0
- package/dist/types-ts4.5/transform-to-code-block.d.ts +3 -0
- package/dist/types-ts4.5/types.d.ts +6 -0
- package/dist/types-ts4.5/ui/class-names.d.ts +8 -0
- package/dist/types-ts4.5/utils.d.ts +4 -0
- package/package.json +99 -0
- package/report.api.md +73 -0
- 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,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,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
|
+
}
|