@atlaskit/editor-common 74.33.0 → 74.34.1

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 (49) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/cjs/keymaps/index.js +8 -1
  3. package/dist/cjs/keymaps/keymap.js +38 -0
  4. package/dist/cjs/monitoring/error.js +1 -1
  5. package/dist/cjs/ui/DropList/index.js +1 -1
  6. package/dist/cjs/utils/commands.js +180 -15
  7. package/dist/cjs/utils/editor-core-utils.js +53 -3
  8. package/dist/cjs/utils/index.js +48 -0
  9. package/dist/cjs/utils/input-rules.js +48 -2
  10. package/dist/cjs/version.json +1 -1
  11. package/dist/es2019/keymaps/index.js +3 -2
  12. package/dist/es2019/keymaps/keymap.js +33 -0
  13. package/dist/es2019/monitoring/error.js +1 -1
  14. package/dist/es2019/ui/DropList/index.js +1 -1
  15. package/dist/es2019/utils/commands.js +173 -2
  16. package/dist/es2019/utils/editor-core-utils.js +46 -1
  17. package/dist/es2019/utils/index.js +3 -3
  18. package/dist/es2019/utils/input-rules.js +45 -0
  19. package/dist/es2019/version.json +1 -1
  20. package/dist/esm/keymaps/index.js +3 -2
  21. package/dist/esm/keymaps/keymap.js +33 -0
  22. package/dist/esm/monitoring/error.js +1 -1
  23. package/dist/esm/ui/DropList/index.js +1 -1
  24. package/dist/esm/utils/commands.js +170 -14
  25. package/dist/esm/utils/editor-core-utils.js +47 -0
  26. package/dist/esm/utils/index.js +3 -3
  27. package/dist/esm/utils/input-rules.js +44 -0
  28. package/dist/esm/version.json +1 -1
  29. package/dist/types/keymaps/index.d.ts +1 -0
  30. package/dist/types/keymaps/keymap.d.ts +11 -0
  31. package/dist/types/types/block-type.d.ts +1 -0
  32. package/dist/types/types/feature-flags.d.ts +0 -18
  33. package/dist/types/types/index.d.ts +1 -1
  34. package/dist/types/types/input-rules.d.ts +1 -1
  35. package/dist/types/utils/commands.d.ts +11 -5
  36. package/dist/types/utils/editor-core-utils.d.ts +7 -2
  37. package/dist/types/utils/index.d.ts +3 -3
  38. package/dist/types/utils/input-rules.d.ts +12 -4
  39. package/dist/types-ts4.5/keymaps/index.d.ts +1 -0
  40. package/dist/types-ts4.5/keymaps/keymap.d.ts +11 -0
  41. package/dist/types-ts4.5/types/block-type.d.ts +1 -0
  42. package/dist/types-ts4.5/types/feature-flags.d.ts +0 -18
  43. package/dist/types-ts4.5/types/index.d.ts +1 -1
  44. package/dist/types-ts4.5/types/input-rules.d.ts +1 -1
  45. package/dist/types-ts4.5/utils/commands.d.ts +11 -5
  46. package/dist/types-ts4.5/utils/editor-core-utils.d.ts +7 -2
  47. package/dist/types-ts4.5/utils/index.d.ts +3 -3
  48. package/dist/types-ts4.5/utils/input-rules.d.ts +12 -4
  49. package/package.json +5 -4
@@ -1,7 +1,11 @@
1
- import { TextSelection } from '@atlaskit/editor-prosemirror/state';
1
+ import { Fragment } from '@atlaskit/editor-prosemirror/model';
2
+ import { NodeSelection, TextSelection } from '@atlaskit/editor-prosemirror/state';
3
+ import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE } from '../analytics';
4
+ import { withAnalytics } from '../editor-analytics';
2
5
  import { GapCursorSelection } from '../selection';
3
6
  import { isEmptyParagraph } from './editor-core-utils';
4
- const filter = (predicates, cmd) => {
7
+ import { isMediaNode } from './nodes';
8
+ export const filter = (predicates, cmd) => {
5
9
  return function (state, dispatch, view) {
6
10
  if (!Array.isArray(predicates)) {
7
11
  predicates = [predicates];
@@ -51,6 +55,173 @@ export const walkPrevNode = $startPos => {
51
55
  foundNode: $pos.pos > 0
52
56
  };
53
57
  };
58
+ export function insertNewLine() {
59
+ return function (state, dispatch) {
60
+ const {
61
+ $from
62
+ } = state.selection;
63
+ const parent = $from.parent;
64
+ const {
65
+ hardBreak
66
+ } = state.schema.nodes;
67
+ if (hardBreak) {
68
+ const hardBreakNode = hardBreak.createChecked();
69
+ if (parent && parent.type.validContent(Fragment.from(hardBreakNode))) {
70
+ if (dispatch) {
71
+ dispatch(state.tr.replaceSelectionWith(hardBreakNode, false));
72
+ }
73
+ return true;
74
+ }
75
+ }
76
+ if (state.selection instanceof TextSelection) {
77
+ if (dispatch) {
78
+ dispatch(state.tr.insertText('\n'));
79
+ }
80
+ return true;
81
+ }
82
+ return false;
83
+ };
84
+ }
85
+ export const insertNewLineWithAnalytics = editorAnalyticsAPI => withAnalytics(editorAnalyticsAPI, {
86
+ action: ACTION.INSERTED,
87
+ actionSubject: ACTION_SUBJECT.TEXT,
88
+ actionSubjectId: ACTION_SUBJECT_ID.LINE_BREAK,
89
+ eventType: EVENT_TYPE.TRACK
90
+ })(insertNewLine());
91
+ export const createNewParagraphAbove = (state, dispatch) => {
92
+ const append = false;
93
+ if (!canMoveUp(state) && canCreateParagraphNear(state)) {
94
+ createParagraphNear(append)(state, dispatch);
95
+ return true;
96
+ }
97
+ return false;
98
+ };
99
+ export const createNewParagraphBelow = (state, dispatch) => {
100
+ const append = true;
101
+ if (!canMoveDown(state) && canCreateParagraphNear(state)) {
102
+ createParagraphNear(append)(state, dispatch);
103
+ return true;
104
+ }
105
+ return false;
106
+ };
107
+ function canCreateParagraphNear(state) {
108
+ const {
109
+ selection: {
110
+ $from
111
+ }
112
+ } = state;
113
+ const node = $from.node($from.depth);
114
+ const insideCodeBlock = !!node && node.type === state.schema.nodes.codeBlock;
115
+ const isNodeSelection = state.selection instanceof NodeSelection;
116
+ return $from.depth > 1 || isNodeSelection || insideCodeBlock;
117
+ }
118
+ export function createParagraphNear(append = true) {
119
+ return function (state, dispatch) {
120
+ const paragraph = state.schema.nodes.paragraph;
121
+ if (!paragraph) {
122
+ return false;
123
+ }
124
+ let insertPos;
125
+ if (state.selection instanceof TextSelection) {
126
+ if (topLevelNodeIsEmptyTextBlock(state)) {
127
+ return false;
128
+ }
129
+ insertPos = getInsertPosFromTextBlock(state, append);
130
+ } else {
131
+ insertPos = getInsertPosFromNonTextBlock(state, append);
132
+ }
133
+ const tr = state.tr.insert(insertPos, paragraph.createAndFill());
134
+ tr.setSelection(TextSelection.create(tr.doc, insertPos + 1));
135
+ if (dispatch) {
136
+ dispatch(tr);
137
+ }
138
+ return true;
139
+ };
140
+ }
141
+ function getInsertPosFromTextBlock(state, append) {
142
+ const {
143
+ $from,
144
+ $to
145
+ } = state.selection;
146
+ let pos;
147
+ if (!append) {
148
+ pos = $from.start(0);
149
+ } else {
150
+ pos = $to.end(0);
151
+ }
152
+ return pos;
153
+ }
154
+ function getInsertPosFromNonTextBlock(state, append) {
155
+ const {
156
+ $from,
157
+ $to
158
+ } = state.selection;
159
+ const nodeAtSelection = state.selection instanceof NodeSelection && state.doc.nodeAt(state.selection.$anchor.pos);
160
+ const isMediaSelection = nodeAtSelection && nodeAtSelection.type.name === 'mediaGroup';
161
+ let pos;
162
+ if (!append) {
163
+ // The start position is different with text block because it starts from 0
164
+ pos = $from.start($from.depth);
165
+ // The depth is different with text block because it starts from 0
166
+ pos = $from.depth > 0 && !isMediaSelection ? pos - 1 : pos;
167
+ } else {
168
+ pos = $to.end($to.depth);
169
+ pos = $to.depth > 0 && !isMediaSelection ? pos + 1 : pos;
170
+ }
171
+ return pos;
172
+ }
173
+ function topLevelNodeIsEmptyTextBlock(state) {
174
+ const topLevelNode = state.selection.$from.node(1);
175
+ return topLevelNode.isTextblock && topLevelNode.type !== state.schema.nodes.codeBlock && topLevelNode.nodeSize === 2;
176
+ }
177
+ function canMoveUp(state) {
178
+ const {
179
+ selection
180
+ } = state;
181
+ /**
182
+ * If there's a media element on the selection it will use a gap cursor to move
183
+ */
184
+ if (selection instanceof NodeSelection && isMediaNode(selection.node)) {
185
+ return true;
186
+ }
187
+ if (selection instanceof TextSelection) {
188
+ if (!selection.empty) {
189
+ return true;
190
+ }
191
+ }
192
+ return !atTheBeginningOfDoc(state);
193
+ }
194
+ function canMoveDown(state) {
195
+ const {
196
+ selection
197
+ } = state;
198
+
199
+ /**
200
+ * If there's a media element on the selection it will use a gap cursor to move
201
+ */
202
+ if (selection instanceof NodeSelection && isMediaNode(selection.node)) {
203
+ return true;
204
+ }
205
+ if (selection instanceof TextSelection) {
206
+ if (!selection.empty) {
207
+ return true;
208
+ }
209
+ }
210
+ return !atTheEndOfDoc(state);
211
+ }
212
+ export function atTheEndOfDoc(state) {
213
+ const {
214
+ selection,
215
+ doc
216
+ } = state;
217
+ return doc.nodeSize - selection.$to.pos - 2 === selection.$to.depth;
218
+ }
219
+ export function atTheBeginningOfDoc(state) {
220
+ const {
221
+ selection
222
+ } = state;
223
+ return selection.$from.pos === selection.$from.depth;
224
+ }
54
225
 
55
226
  /**
56
227
  * If the selection is empty, is inside a paragraph node and `canNextNodeMoveUp` is true then delete current paragraph
@@ -85,4 +85,49 @@ export const isValidPosition = (pos, state) => {
85
85
  };
86
86
  export const isInLayoutColumn = state => {
87
87
  return hasParentNodeOfType(state.schema.nodes.layoutSection)(state.selection);
88
- };
88
+ };
89
+ export function filterChildrenBetween(doc, from, to, predicate) {
90
+ const results = [];
91
+ doc.nodesBetween(from, to, (node, pos, parent) => {
92
+ if (predicate(node, pos, parent)) {
93
+ results.push({
94
+ node,
95
+ pos
96
+ });
97
+ }
98
+ });
99
+ return results;
100
+ }
101
+ export const removeBlockMarks = (state, marks) => {
102
+ const {
103
+ selection,
104
+ schema
105
+ } = state;
106
+ let {
107
+ tr
108
+ } = state;
109
+
110
+ // Marks might not exist in Schema
111
+ const marksToRemove = marks.filter(Boolean);
112
+ if (marksToRemove.length === 0) {
113
+ return undefined;
114
+ }
115
+
116
+ /** Saves an extra dispatch */
117
+ let blockMarksExists = false;
118
+ const hasMark = mark => marksToRemove.indexOf(mark.type) > -1;
119
+ /**
120
+ * When you need to toggle the selection
121
+ * when another type which does not allow alignment is applied
122
+ */
123
+ state.doc.nodesBetween(selection.from, selection.to, (node, pos) => {
124
+ if (node.type === schema.nodes.paragraph && node.marks.some(hasMark)) {
125
+ blockMarksExists = true;
126
+ const resolvedPos = state.doc.resolve(pos);
127
+ const withoutBlockMarks = node.marks.filter(not(hasMark));
128
+ tr = tr.setNodeMarkup(resolvedPos.pos, undefined, node.attrs, withoutBlockMarks);
129
+ }
130
+ });
131
+ return blockMarksExists ? tr : undefined;
132
+ };
133
+ const not = fn => arg => !fn(arg);
@@ -3,7 +3,7 @@ export { getExtensionLozengeData } from './macro';
3
3
  export { default as browser } from './browser';
4
4
  export { default as ErrorReporter } from './error-reporter';
5
5
  export { isPastDate, timestampToIsoFormat, timestampToString, timestampToTaskContext, timestampToUTCDate, todayTimestampInUTC } from './date';
6
- export { isElementInTableCell, isTextSelection, isLastItemMediaGroup, setNodeSelection, setTextSelection, nonNullable, stepAddsOneOf, stepHasSlice, extractSliceFromStep, isValidPosition, isEmptyParagraph, isInLayoutColumn } from './editor-core-utils';
6
+ export { isElementInTableCell, isTextSelection, isLastItemMediaGroup, setNodeSelection, setTextSelection, nonNullable, stepAddsOneOf, stepHasSlice, extractSliceFromStep, isValidPosition, isEmptyParagraph, isInLayoutColumn, removeBlockMarks, filterChildrenBetween } from './editor-core-utils';
7
7
  export { withImageLoader } from './imageLoader';
8
8
  export { absoluteBreakoutWidth, calcBreakoutWidth, calcWideWidth, breakoutConsts, calculateBreakoutStyles, calcBreakoutWidthPx, getNextBreakoutMode, getTitle } from './breakout';
9
9
  export { findChangedNodesFromTransaction, validNode, validateNodes, isType, isParagraph, isText, isLinkMark, SelectedState, isNodeSelectedOrInRange, isSupportedInParent, isMediaNode, isNodeBeforeMediaNode } from './nodes';
@@ -45,10 +45,10 @@ export { nodesBetweenChanged, getStepRange, isEmptyDocument, processRawValue, ha
45
45
  export { floatingLayouts, isRichMediaInsideOfBlockNode, calculateSnapPoints, alignAttributes, nonWrappedLayouts } from './rich-media-utils';
46
46
  export { sanitizeNodeForPrivacy } from './filter/privacy-filter';
47
47
  export { canRenderDatasource } from './datasource';
48
- export { filterCommand, walkPrevNode, walkNextNode, isEmptySelectionAtStart, isEmptySelectionAtEnd, insertContentDeleteRange, deleteEmptyParagraphAndMoveBlockUp } from './commands';
48
+ export { filterCommand, isEmptySelectionAtStart, isEmptySelectionAtEnd, insertContentDeleteRange, deleteEmptyParagraphAndMoveBlockUp, insertNewLineWithAnalytics, createNewParagraphAbove, createNewParagraphBelow, createParagraphNear, walkNextNode, walkPrevNode } from './commands';
49
49
  export function shallowEqual(obj1 = {}, obj2 = {}) {
50
50
  const keys1 = Object.keys(obj1);
51
51
  const keys2 = Object.keys(obj2);
52
52
  return keys1.length === keys2.length && keys1.reduce((acc, key) => acc && obj1[key] === obj2[key], true);
53
53
  }
54
- export { inputRuleWithAnalytics } from './input-rules';
54
+ export { inputRuleWithAnalytics, createWrappingJoinRule, createRule } from './input-rules';
@@ -1,3 +1,6 @@
1
+ import { closeHistory } from '@atlaskit/editor-prosemirror/history';
2
+ import { canJoin, findWrapping } from '@atlaskit/editor-prosemirror/transform';
3
+ import { JOIN_SCENARIOS_WHEN_TYPING_TO_INSERT_LIST } from '../analytics';
1
4
  // Roughly based on atlassian-frontend/packages/editor/editor-core/src/utils/input-rules.ts but with the Editor Analytics API that's injected in plugins
2
5
  export const inputRuleWithAnalytics = (getPayload, analyticsApi) => {
3
6
  return originalRule => {
@@ -13,4 +16,46 @@ export const inputRuleWithAnalytics = (getPayload, analyticsApi) => {
13
16
  onHandlerApply
14
17
  };
15
18
  };
19
+ };
20
+ export const createWrappingJoinRule = ({
21
+ match,
22
+ nodeType,
23
+ getAttrs,
24
+ joinPredicate
25
+ }) => {
26
+ const handler = (state, match, start, end) => {
27
+ const attrs = (getAttrs instanceof Function ? getAttrs(match) : getAttrs) || {};
28
+ const tr = state.tr;
29
+ const fixedStart = Math.max(start, 1);
30
+ tr.delete(fixedStart, end);
31
+ const $start = tr.doc.resolve(fixedStart);
32
+ const range = $start.blockRange();
33
+ const wrapping = range && findWrapping(range, nodeType, attrs);
34
+ if (!wrapping || !range) {
35
+ return null;
36
+ }
37
+ const parentNodePosMapped = tr.mapping.map(range.start);
38
+ const parentNode = tr.doc.nodeAt(parentNodePosMapped);
39
+ const lastWrap = wrapping[wrapping.length - 1];
40
+ if (parentNode && lastWrap) {
41
+ const allowedMarks = lastWrap.type.allowedMarks(parentNode.marks) || [];
42
+ tr.setNodeMarkup(parentNodePosMapped, parentNode.type, parentNode.attrs, allowedMarks);
43
+ }
44
+ tr.wrap(range, wrapping);
45
+ const before = tr.doc.resolve(fixedStart - 1).nodeBefore;
46
+ if (before && before.type === nodeType && canJoin(tr.doc, fixedStart - 1) && (!joinPredicate || joinPredicate(match, before, JOIN_SCENARIOS_WHEN_TYPING_TO_INSERT_LIST.JOINED_TO_LIST_ABOVE))) {
47
+ tr.join(fixedStart - 1);
48
+ }
49
+ return tr;
50
+ };
51
+ return createRule(match, handler);
52
+ };
53
+ export const createRule = (match, handler) => {
54
+ return {
55
+ match,
56
+ handler,
57
+ onHandlerApply: (_state, tr) => {
58
+ closeHistory(tr);
59
+ }
60
+ };
16
61
  };
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-common",
3
- "version": "74.33.0",
3
+ "version": "74.34.1",
4
4
  "sideEffects": false
5
5
  }
@@ -77,7 +77,7 @@ var arrowKeysMap = {
77
77
  ARROWUP: "\u2191",
78
78
  ARROWDOWN: "\u2193"
79
79
  };
80
- var tooltipShortcutStyle = css(_templateObject || (_templateObject = _taggedTemplateLiteral(["\n border-radius: 2px;\n background-color: ", ";\n padding: 0 2px;\n\n /* TODO: fix in develop: https://atlassian.slack.com/archives/CFG3PSQ9E/p1647395052443259?thread_ts=1647394572.556029&cid=CFG3PSQ9E */\n /* stylelint-disable-next-line */\n label: tooltip-shortcut;\n"])), "var(--ds-background-inverse-subtle, ".concat(N400, ")"));
80
+ var tooltipShortcutStyle = css(_templateObject || (_templateObject = _taggedTemplateLiteral(["\n border-radius: 2px;\n background-color: ", ";\n padding: 0 ", ";\n\n /* TODO: fix in develop: https://atlassian.slack.com/archives/CFG3PSQ9E/p1647395052443259?thread_ts=1647394572.556029&cid=CFG3PSQ9E */\n /* stylelint-disable-next-line */\n label: tooltip-shortcut;\n"])), "var(--ds-background-inverse-subtle, ".concat(N400, ")"), "var(--ds-space-025, 2px)");
81
81
  /* eslint-enable @atlaskit/design-system/ensure-design-token-usage */
82
82
 
83
83
  function formatShortcut(keymap) {
@@ -201,4 +201,5 @@ export function findKeyMapForBrowser(keyMap) {
201
201
  }
202
202
  return;
203
203
  }
204
- export { DOWN, HEADING_KEYS, KEY_0, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, LEFT, RIGHT, UP } from './consts';
204
+ export { DOWN, HEADING_KEYS, KEY_0, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, LEFT, RIGHT, UP } from './consts';
205
+ export { keymap } from './keymap';
@@ -0,0 +1,33 @@
1
+ import { base, keyName } from 'w3c-keyname';
2
+ import { keydownHandler } from '@atlaskit/editor-prosemirror/keymap';
3
+ import { SafePlugin } from '../safe-plugin';
4
+
5
+ /**
6
+ * A workaround for mostly Cyrillic but should have a positive affect
7
+ * on other languages / layouts. Attempts a similar approach to OS X.
8
+ * @see ED-7310
9
+ * @see https://github.com/ProseMirror/prosemirror/issues/957
10
+ * @param bindings
11
+ */
12
+ export function keymap(bindings) {
13
+ return new SafePlugin({
14
+ props: {
15
+ handleKeyDown: function handleKeyDown(view, event) {
16
+ var name = keyName(event);
17
+ var keyboardEvent = event;
18
+ if (event.ctrlKey && name.length === 1 &&
19
+ // Check the unicode of the character to
20
+ // assert that its not an ASCII character.
21
+ // These are characters outside Latin's range.
22
+ /[^\u0000-\u007f]/.test(name)) {
23
+ keyboardEvent = new KeyboardEvent('keydown', {
24
+ key: base[event.keyCode],
25
+ code: event.code,
26
+ ctrlKey: true
27
+ });
28
+ }
29
+ return keydownHandler(bindings)(view, keyboardEvent);
30
+ }
31
+ }
32
+ });
33
+ }
@@ -6,7 +6,7 @@ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (O
6
6
  function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
7
7
  var SENTRY_DSN = 'https://0b10c8e02fb44d8796c047b102c9bee8@o55978.ingest.sentry.io/4505129224110080';
8
8
  var packageName = 'editor-common'; // Sentry doesn't accept '/' in its releases https://docs.sentry.io/platforms/javascript/configuration/releases/
9
- var packageVersion = "74.33.0";
9
+ var packageVersion = "74.34.1";
10
10
  var sanitiseSentryEvents = function sanitiseSentryEvents(data, _hint) {
11
11
  // Remove URL as it has UGC
12
12
  // TODO: Sanitise the URL instead of just removing it
@@ -18,7 +18,7 @@ import { themed } from '@atlaskit/theme/components';
18
18
  import { borderRadius } from '@atlaskit/theme/constants';
19
19
  import Layer from '../Layer';
20
20
  var packageName = "@atlaskit/editor-common";
21
- var packageVersion = "74.33.0";
21
+ var packageVersion = "74.34.1";
22
22
  var halfFocusRing = 1;
23
23
  var dropOffset = '0, 8';
24
24
  var DropList = /*#__PURE__*/function (_Component) {
@@ -1,8 +1,12 @@
1
1
  import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
2
- import { TextSelection } from '@atlaskit/editor-prosemirror/state';
2
+ import { Fragment } from '@atlaskit/editor-prosemirror/model';
3
+ import { NodeSelection, TextSelection } from '@atlaskit/editor-prosemirror/state';
4
+ import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE } from '../analytics';
5
+ import { withAnalytics } from '../editor-analytics';
3
6
  import { GapCursorSelection } from '../selection';
4
7
  import { isEmptyParagraph } from './editor-core-utils';
5
- var filter = function filter(predicates, cmd) {
8
+ import { isMediaNode } from './nodes';
9
+ export var filter = function filter(predicates, cmd) {
6
10
  return function (state, dispatch, view) {
7
11
  if (!Array.isArray(predicates)) {
8
12
  predicates = [predicates];
@@ -54,6 +58,158 @@ export var walkPrevNode = function walkPrevNode($startPos) {
54
58
  foundNode: $pos.pos > 0
55
59
  };
56
60
  };
61
+ export function insertNewLine() {
62
+ return function (state, dispatch) {
63
+ var $from = state.selection.$from;
64
+ var parent = $from.parent;
65
+ var hardBreak = state.schema.nodes.hardBreak;
66
+ if (hardBreak) {
67
+ var hardBreakNode = hardBreak.createChecked();
68
+ if (parent && parent.type.validContent(Fragment.from(hardBreakNode))) {
69
+ if (dispatch) {
70
+ dispatch(state.tr.replaceSelectionWith(hardBreakNode, false));
71
+ }
72
+ return true;
73
+ }
74
+ }
75
+ if (state.selection instanceof TextSelection) {
76
+ if (dispatch) {
77
+ dispatch(state.tr.insertText('\n'));
78
+ }
79
+ return true;
80
+ }
81
+ return false;
82
+ };
83
+ }
84
+ export var insertNewLineWithAnalytics = function insertNewLineWithAnalytics(editorAnalyticsAPI) {
85
+ return withAnalytics(editorAnalyticsAPI, {
86
+ action: ACTION.INSERTED,
87
+ actionSubject: ACTION_SUBJECT.TEXT,
88
+ actionSubjectId: ACTION_SUBJECT_ID.LINE_BREAK,
89
+ eventType: EVENT_TYPE.TRACK
90
+ })(insertNewLine());
91
+ };
92
+ export var createNewParagraphAbove = function createNewParagraphAbove(state, dispatch) {
93
+ var append = false;
94
+ if (!canMoveUp(state) && canCreateParagraphNear(state)) {
95
+ createParagraphNear(append)(state, dispatch);
96
+ return true;
97
+ }
98
+ return false;
99
+ };
100
+ export var createNewParagraphBelow = function createNewParagraphBelow(state, dispatch) {
101
+ var append = true;
102
+ if (!canMoveDown(state) && canCreateParagraphNear(state)) {
103
+ createParagraphNear(append)(state, dispatch);
104
+ return true;
105
+ }
106
+ return false;
107
+ };
108
+ function canCreateParagraphNear(state) {
109
+ var $from = state.selection.$from;
110
+ var node = $from.node($from.depth);
111
+ var insideCodeBlock = !!node && node.type === state.schema.nodes.codeBlock;
112
+ var isNodeSelection = state.selection instanceof NodeSelection;
113
+ return $from.depth > 1 || isNodeSelection || insideCodeBlock;
114
+ }
115
+ export function createParagraphNear() {
116
+ var append = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
117
+ return function (state, dispatch) {
118
+ var paragraph = state.schema.nodes.paragraph;
119
+ if (!paragraph) {
120
+ return false;
121
+ }
122
+ var insertPos;
123
+ if (state.selection instanceof TextSelection) {
124
+ if (topLevelNodeIsEmptyTextBlock(state)) {
125
+ return false;
126
+ }
127
+ insertPos = getInsertPosFromTextBlock(state, append);
128
+ } else {
129
+ insertPos = getInsertPosFromNonTextBlock(state, append);
130
+ }
131
+ var tr = state.tr.insert(insertPos, paragraph.createAndFill());
132
+ tr.setSelection(TextSelection.create(tr.doc, insertPos + 1));
133
+ if (dispatch) {
134
+ dispatch(tr);
135
+ }
136
+ return true;
137
+ };
138
+ }
139
+ function getInsertPosFromTextBlock(state, append) {
140
+ var _state$selection = state.selection,
141
+ $from = _state$selection.$from,
142
+ $to = _state$selection.$to;
143
+ var pos;
144
+ if (!append) {
145
+ pos = $from.start(0);
146
+ } else {
147
+ pos = $to.end(0);
148
+ }
149
+ return pos;
150
+ }
151
+ function getInsertPosFromNonTextBlock(state, append) {
152
+ var _state$selection2 = state.selection,
153
+ $from = _state$selection2.$from,
154
+ $to = _state$selection2.$to;
155
+ var nodeAtSelection = state.selection instanceof NodeSelection && state.doc.nodeAt(state.selection.$anchor.pos);
156
+ var isMediaSelection = nodeAtSelection && nodeAtSelection.type.name === 'mediaGroup';
157
+ var pos;
158
+ if (!append) {
159
+ // The start position is different with text block because it starts from 0
160
+ pos = $from.start($from.depth);
161
+ // The depth is different with text block because it starts from 0
162
+ pos = $from.depth > 0 && !isMediaSelection ? pos - 1 : pos;
163
+ } else {
164
+ pos = $to.end($to.depth);
165
+ pos = $to.depth > 0 && !isMediaSelection ? pos + 1 : pos;
166
+ }
167
+ return pos;
168
+ }
169
+ function topLevelNodeIsEmptyTextBlock(state) {
170
+ var topLevelNode = state.selection.$from.node(1);
171
+ return topLevelNode.isTextblock && topLevelNode.type !== state.schema.nodes.codeBlock && topLevelNode.nodeSize === 2;
172
+ }
173
+ function canMoveUp(state) {
174
+ var selection = state.selection;
175
+ /**
176
+ * If there's a media element on the selection it will use a gap cursor to move
177
+ */
178
+ if (selection instanceof NodeSelection && isMediaNode(selection.node)) {
179
+ return true;
180
+ }
181
+ if (selection instanceof TextSelection) {
182
+ if (!selection.empty) {
183
+ return true;
184
+ }
185
+ }
186
+ return !atTheBeginningOfDoc(state);
187
+ }
188
+ function canMoveDown(state) {
189
+ var selection = state.selection;
190
+
191
+ /**
192
+ * If there's a media element on the selection it will use a gap cursor to move
193
+ */
194
+ if (selection instanceof NodeSelection && isMediaNode(selection.node)) {
195
+ return true;
196
+ }
197
+ if (selection instanceof TextSelection) {
198
+ if (!selection.empty) {
199
+ return true;
200
+ }
201
+ }
202
+ return !atTheEndOfDoc(state);
203
+ }
204
+ export function atTheEndOfDoc(state) {
205
+ var selection = state.selection,
206
+ doc = state.doc;
207
+ return doc.nodeSize - selection.$to.pos - 2 === selection.$to.depth;
208
+ }
209
+ export function atTheBeginningOfDoc(state) {
210
+ var selection = state.selection;
211
+ return selection.$from.pos === selection.$from.depth;
212
+ }
57
213
 
58
214
  /**
59
215
  * If the selection is empty, is inside a paragraph node and `canNextNodeMoveUp` is true then delete current paragraph
@@ -64,12 +220,12 @@ export var walkPrevNode = function walkPrevNode($startPos) {
64
220
  */
65
221
  export var deleteEmptyParagraphAndMoveBlockUp = function deleteEmptyParagraphAndMoveBlockUp(canNextNodeMoveUp) {
66
222
  return function (state, dispatch, view) {
67
- var _state$selection = state.selection,
68
- _state$selection$$fro = _state$selection.$from,
69
- pos = _state$selection$$fro.pos,
70
- parent = _state$selection$$fro.parent,
71
- $head = _state$selection.$head,
72
- empty = _state$selection.empty,
223
+ var _state$selection3 = state.selection,
224
+ _state$selection3$$fr = _state$selection3.$from,
225
+ pos = _state$selection3$$fr.pos,
226
+ parent = _state$selection3$$fr.parent,
227
+ $head = _state$selection3.$head,
228
+ empty = _state$selection3.empty,
73
229
  tr = state.tr,
74
230
  doc = state.doc;
75
231
  var _walkNextNode = walkNextNode($head),
@@ -101,15 +257,15 @@ export var insertContentDeleteRange = function insertContentDeleteRange(tr, getS
101
257
  tr.setSelection(new TextSelection(getSelectionResolvedPos(tr)));
102
258
  };
103
259
  export var isEmptySelectionAtStart = function isEmptySelectionAtStart(state) {
104
- var _state$selection2 = state.selection,
105
- empty = _state$selection2.empty,
106
- $from = _state$selection2.$from;
260
+ var _state$selection4 = state.selection,
261
+ empty = _state$selection4.empty,
262
+ $from = _state$selection4.$from;
107
263
  return empty && ($from.parentOffset === 0 || state.selection instanceof GapCursorSelection);
108
264
  };
109
265
  export var isEmptySelectionAtEnd = function isEmptySelectionAtEnd(state) {
110
- var _state$selection3 = state.selection,
111
- empty = _state$selection3.empty,
112
- $from = _state$selection3.$from;
266
+ var _state$selection5 = state.selection,
267
+ empty = _state$selection5.empty,
268
+ $from = _state$selection5.$from;
113
269
  return empty && ($from.end() === $from.pos || state.selection instanceof GapCursorSelection);
114
270
  };
115
271
  export { filter as filterCommand };
@@ -83,4 +83,51 @@ export var isValidPosition = function isValidPosition(pos, state) {
83
83
  };
84
84
  export var isInLayoutColumn = function isInLayoutColumn(state) {
85
85
  return hasParentNodeOfType(state.schema.nodes.layoutSection)(state.selection);
86
+ };
87
+ export function filterChildrenBetween(doc, from, to, predicate) {
88
+ var results = [];
89
+ doc.nodesBetween(from, to, function (node, pos, parent) {
90
+ if (predicate(node, pos, parent)) {
91
+ results.push({
92
+ node: node,
93
+ pos: pos
94
+ });
95
+ }
96
+ });
97
+ return results;
98
+ }
99
+ export var removeBlockMarks = function removeBlockMarks(state, marks) {
100
+ var selection = state.selection,
101
+ schema = state.schema;
102
+ var tr = state.tr;
103
+
104
+ // Marks might not exist in Schema
105
+ var marksToRemove = marks.filter(Boolean);
106
+ if (marksToRemove.length === 0) {
107
+ return undefined;
108
+ }
109
+
110
+ /** Saves an extra dispatch */
111
+ var blockMarksExists = false;
112
+ var hasMark = function hasMark(mark) {
113
+ return marksToRemove.indexOf(mark.type) > -1;
114
+ };
115
+ /**
116
+ * When you need to toggle the selection
117
+ * when another type which does not allow alignment is applied
118
+ */
119
+ state.doc.nodesBetween(selection.from, selection.to, function (node, pos) {
120
+ if (node.type === schema.nodes.paragraph && node.marks.some(hasMark)) {
121
+ blockMarksExists = true;
122
+ var resolvedPos = state.doc.resolve(pos);
123
+ var withoutBlockMarks = node.marks.filter(not(hasMark));
124
+ tr = tr.setNodeMarkup(resolvedPos.pos, undefined, node.attrs, withoutBlockMarks);
125
+ }
126
+ });
127
+ return blockMarksExists ? tr : undefined;
128
+ };
129
+ var not = function not(fn) {
130
+ return function (arg) {
131
+ return !fn(arg);
132
+ };
86
133
  };