@atlaskit/editor-plugin-find-replace 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 (121) hide show
  1. package/.eslintrc.js +26 -0
  2. package/CHANGELOG.md +11 -0
  3. package/LICENSE.md +13 -0
  4. package/README.md +30 -0
  5. package/dist/cjs/FindReplaceToolbarButtonWithState.js +166 -0
  6. package/dist/cjs/actions.js +19 -0
  7. package/dist/cjs/commands-with-analytics.js +101 -0
  8. package/dist/cjs/commands.js +255 -0
  9. package/dist/cjs/index.js +12 -0
  10. package/dist/cjs/plugin.js +93 -0
  11. package/dist/cjs/pm-plugins/keymap.js +24 -0
  12. package/dist/cjs/pm-plugins/main.js +39 -0
  13. package/dist/cjs/pm-plugins/plugin-factory.js +109 -0
  14. package/dist/cjs/pm-plugins/plugin-key.js +8 -0
  15. package/dist/cjs/reducer.js +61 -0
  16. package/dist/cjs/styles.js +17 -0
  17. package/dist/cjs/types.js +5 -0
  18. package/dist/cjs/ui/Find.js +309 -0
  19. package/dist/cjs/ui/FindReplace.js +104 -0
  20. package/dist/cjs/ui/FindReplaceToolbarButton.js +133 -0
  21. package/dist/cjs/ui/FindReplaceTooltipButton.js +77 -0
  22. package/dist/cjs/ui/Replace.js +176 -0
  23. package/dist/cjs/ui/styles.js +46 -0
  24. package/dist/cjs/utils/array.js +13 -0
  25. package/dist/cjs/utils/batch-decorations.js +310 -0
  26. package/dist/cjs/utils/commands.js +16 -0
  27. package/dist/cjs/utils/index.js +290 -0
  28. package/dist/es2019/FindReplaceToolbarButtonWithState.js +153 -0
  29. package/dist/es2019/actions.js +13 -0
  30. package/dist/es2019/commands-with-analytics.js +72 -0
  31. package/dist/es2019/commands.js +240 -0
  32. package/dist/es2019/index.js +1 -0
  33. package/dist/es2019/plugin.js +88 -0
  34. package/dist/es2019/pm-plugins/keymap.js +16 -0
  35. package/dist/es2019/pm-plugins/main.js +30 -0
  36. package/dist/es2019/pm-plugins/plugin-factory.js +91 -0
  37. package/dist/es2019/pm-plugins/plugin-key.js +2 -0
  38. package/dist/es2019/reducer.js +56 -0
  39. package/dist/es2019/styles.js +18 -0
  40. package/dist/es2019/types.js +1 -0
  41. package/dist/es2019/ui/Find.js +286 -0
  42. package/dist/es2019/ui/FindReplace.js +81 -0
  43. package/dist/es2019/ui/FindReplaceToolbarButton.js +122 -0
  44. package/dist/es2019/ui/FindReplaceTooltipButton.js +51 -0
  45. package/dist/es2019/ui/Replace.js +155 -0
  46. package/dist/es2019/ui/styles.js +50 -0
  47. package/dist/es2019/utils/array.js +3 -0
  48. package/dist/es2019/utils/batch-decorations.js +189 -0
  49. package/dist/es2019/utils/commands.js +6 -0
  50. package/dist/es2019/utils/index.js +249 -0
  51. package/dist/esm/FindReplaceToolbarButtonWithState.js +157 -0
  52. package/dist/esm/actions.js +13 -0
  53. package/dist/esm/commands-with-analytics.js +95 -0
  54. package/dist/esm/commands.js +248 -0
  55. package/dist/esm/index.js +1 -0
  56. package/dist/esm/plugin.js +86 -0
  57. package/dist/esm/pm-plugins/keymap.js +18 -0
  58. package/dist/esm/pm-plugins/main.js +33 -0
  59. package/dist/esm/pm-plugins/plugin-factory.js +104 -0
  60. package/dist/esm/pm-plugins/plugin-key.js +2 -0
  61. package/dist/esm/reducer.js +54 -0
  62. package/dist/esm/styles.js +11 -0
  63. package/dist/esm/types.js +1 -0
  64. package/dist/esm/ui/Find.js +304 -0
  65. package/dist/esm/ui/FindReplace.js +100 -0
  66. package/dist/esm/ui/FindReplaceToolbarButton.js +126 -0
  67. package/dist/esm/ui/FindReplaceTooltipButton.js +70 -0
  68. package/dist/esm/ui/Replace.js +171 -0
  69. package/dist/esm/ui/styles.js +39 -0
  70. package/dist/esm/utils/array.js +7 -0
  71. package/dist/esm/utils/batch-decorations.js +304 -0
  72. package/dist/esm/utils/commands.js +10 -0
  73. package/dist/esm/utils/index.js +280 -0
  74. package/dist/types/FindReplaceToolbarButtonWithState.d.ts +4 -0
  75. package/dist/types/actions.d.ts +64 -0
  76. package/dist/types/commands-with-analytics.d.ts +27 -0
  77. package/dist/types/commands.d.ts +12 -0
  78. package/dist/types/index.d.ts +2 -0
  79. package/dist/types/plugin.d.ts +2 -0
  80. package/dist/types/pm-plugins/keymap.d.ts +4 -0
  81. package/dist/types/pm-plugins/main.d.ts +5 -0
  82. package/dist/types/pm-plugins/plugin-factory.d.ts +2 -0
  83. package/dist/types/pm-plugins/plugin-key.d.ts +3 -0
  84. package/dist/types/reducer.d.ts +4 -0
  85. package/dist/types/styles.d.ts +3 -0
  86. package/dist/types/types.d.ts +76 -0
  87. package/dist/types/ui/Find.d.ts +71 -0
  88. package/dist/types/ui/FindReplace.d.ts +43 -0
  89. package/dist/types/ui/FindReplaceToolbarButton.d.ts +21 -0
  90. package/dist/types/ui/FindReplaceTooltipButton.d.ts +18 -0
  91. package/dist/types/ui/Replace.d.ts +27 -0
  92. package/dist/types/ui/styles.d.ts +6 -0
  93. package/dist/types/utils/array.d.ts +1 -0
  94. package/dist/types/utils/batch-decorations.d.ts +36 -0
  95. package/dist/types/utils/commands.d.ts +2 -0
  96. package/dist/types/utils/index.d.ts +49 -0
  97. package/dist/types-ts4.5/FindReplaceToolbarButtonWithState.d.ts +4 -0
  98. package/dist/types-ts4.5/actions.d.ts +64 -0
  99. package/dist/types-ts4.5/commands-with-analytics.d.ts +27 -0
  100. package/dist/types-ts4.5/commands.d.ts +12 -0
  101. package/dist/types-ts4.5/index.d.ts +2 -0
  102. package/dist/types-ts4.5/plugin.d.ts +2 -0
  103. package/dist/types-ts4.5/pm-plugins/keymap.d.ts +4 -0
  104. package/dist/types-ts4.5/pm-plugins/main.d.ts +5 -0
  105. package/dist/types-ts4.5/pm-plugins/plugin-factory.d.ts +2 -0
  106. package/dist/types-ts4.5/pm-plugins/plugin-key.d.ts +3 -0
  107. package/dist/types-ts4.5/reducer.d.ts +4 -0
  108. package/dist/types-ts4.5/styles.d.ts +3 -0
  109. package/dist/types-ts4.5/types.d.ts +76 -0
  110. package/dist/types-ts4.5/ui/Find.d.ts +71 -0
  111. package/dist/types-ts4.5/ui/FindReplace.d.ts +43 -0
  112. package/dist/types-ts4.5/ui/FindReplaceToolbarButton.d.ts +21 -0
  113. package/dist/types-ts4.5/ui/FindReplaceTooltipButton.d.ts +18 -0
  114. package/dist/types-ts4.5/ui/Replace.d.ts +27 -0
  115. package/dist/types-ts4.5/ui/styles.d.ts +6 -0
  116. package/dist/types-ts4.5/utils/array.d.ts +1 -0
  117. package/dist/types-ts4.5/utils/batch-decorations.d.ts +36 -0
  118. package/dist/types-ts4.5/utils/commands.d.ts +2 -0
  119. package/dist/types-ts4.5/utils/index.d.ts +49 -0
  120. package/package.json +117 -0
  121. package/styles/package.json +17 -0
@@ -0,0 +1,51 @@
1
+ import _extends from "@babel/runtime/helpers/extends";
2
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
3
+ import React from 'react';
4
+ import Button from '@atlaskit/button/standard-button';
5
+ import { findKeymapByDescription, ToolTipContent } from '@atlaskit/editor-common/keymaps';
6
+ import Tooltip from '@atlaskit/tooltip';
7
+ // eslint-disable-next-line @repo/internal/react/no-class-components
8
+ export class FindReplaceTooltipButton extends React.PureComponent {
9
+ constructor(...args) {
10
+ super(...args);
11
+ _defineProperty(this, "buttonRef", /*#__PURE__*/React.createRef());
12
+ _defineProperty(this, "handleClick", () => {
13
+ this.props.onClick(this.buttonRef);
14
+ });
15
+ }
16
+ render() {
17
+ const {
18
+ title,
19
+ icon,
20
+ keymapDescription,
21
+ disabled,
22
+ isPressed
23
+ } = this.props;
24
+ const pressedProps = {
25
+ ...(typeof isPressed === 'boolean' && {
26
+ 'aria-pressed': isPressed
27
+ })
28
+ };
29
+ return /*#__PURE__*/React.createElement(Tooltip, {
30
+ content: /*#__PURE__*/React.createElement(ToolTipContent, {
31
+ description: title,
32
+ keymap: findKeymapByDescription(keymapDescription)
33
+ }),
34
+ hideTooltipOnClick: true,
35
+ position: 'top'
36
+ }, /*#__PURE__*/React.createElement(Button, _extends({
37
+ label: title,
38
+ appearance: "subtle",
39
+ testId: title,
40
+ ref: this.buttonRef,
41
+ iconBefore: icon,
42
+ isDisabled: disabled,
43
+ onClick: this.handleClick,
44
+ isSelected: isPressed,
45
+ shouldFitContainer: true
46
+ }, pressedProps)));
47
+ }
48
+ }
49
+ _defineProperty(FindReplaceTooltipButton, "defaultProps", {
50
+ keymapDescription: 'no-keymap'
51
+ });
@@ -0,0 +1,155 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ /* eslint-disable @atlaskit/design-system/consistent-css-prop-usage */
3
+ /** @jsx jsx */
4
+ import React from 'react';
5
+ import { jsx } from '@emotion/react';
6
+ import { defineMessages, injectIntl } from 'react-intl-next';
7
+ import Button from '@atlaskit/button/standard-button';
8
+ import { ACTION, ACTION_SUBJECT, EVENT_TYPE, TRIGGER_METHOD } from '@atlaskit/editor-common/analytics';
9
+ import Textfield from '@atlaskit/textfield';
10
+ import { replaceSectionButtonStyles, sectionWrapperStyles } from './styles';
11
+ const messages = defineMessages({
12
+ replaceWith: {
13
+ id: 'fabric.editor.replaceWith',
14
+ defaultMessage: 'Replace with',
15
+ description: 'The value that will replace the word or phrase that was searched for'
16
+ },
17
+ replace: {
18
+ id: 'fabric.editor.replace',
19
+ defaultMessage: 'Replace',
20
+ description: 'Replace only the currently selected instance of the word or phrase'
21
+ },
22
+ replaceAll: {
23
+ id: 'fabric.editor.replaceAll',
24
+ defaultMessage: 'Replace all',
25
+ description: 'Replace all instances of the word or phrase throughout the entire document'
26
+ }
27
+ });
28
+
29
+ // eslint-disable-next-line @repo/internal/react/no-class-components
30
+ class Replace extends React.PureComponent {
31
+ constructor(props) {
32
+ super(props);
33
+ _defineProperty(this, "replaceTextfieldRef", /*#__PURE__*/React.createRef());
34
+ _defineProperty(this, "skipWhileComposing", fn => {
35
+ if (this.state.isComposing) {
36
+ return;
37
+ }
38
+ fn();
39
+ });
40
+ _defineProperty(this, "handleReplaceClick", () => this.skipWhileComposing(() => {
41
+ this.props.onReplace({
42
+ triggerMethod: TRIGGER_METHOD.BUTTON,
43
+ replaceText: this.state.replaceText
44
+ });
45
+ }));
46
+ _defineProperty(this, "handleReplaceChange", event => this.skipWhileComposing(() => {
47
+ this.updateReplaceValue(event.target.value);
48
+ }));
49
+ _defineProperty(this, "updateReplaceValue", replaceText => {
50
+ const {
51
+ dispatchAnalyticsEvent
52
+ } = this.props;
53
+ if (dispatchAnalyticsEvent) {
54
+ dispatchAnalyticsEvent({
55
+ eventType: EVENT_TYPE.TRACK,
56
+ action: ACTION.CHANGED_REPLACEMENT_TEXT,
57
+ actionSubject: ACTION_SUBJECT.FIND_REPLACE_DIALOG
58
+ });
59
+ }
60
+ this.setState({
61
+ replaceText
62
+ });
63
+ });
64
+ _defineProperty(this, "handleReplaceKeyDown", event => this.skipWhileComposing(() => {
65
+ if (event.key === 'Enter') {
66
+ this.props.onReplace({
67
+ triggerMethod: TRIGGER_METHOD.KEYBOARD,
68
+ replaceText: this.state.replaceText
69
+ });
70
+ } else if (event.key === 'ArrowUp') {
71
+ // we want to move focus between find & replace texfields when user hits up/down arrows
72
+ this.props.onArrowUp();
73
+ }
74
+ }));
75
+ _defineProperty(this, "handleReplaceAllClick", () => this.skipWhileComposing(() => {
76
+ this.props.onReplaceAll({
77
+ replaceText: this.state.replaceText
78
+ });
79
+ }));
80
+ _defineProperty(this, "handleCompositionStart", () => {
81
+ this.setState({
82
+ isComposing: true
83
+ });
84
+ });
85
+ _defineProperty(this, "handleCompositionEnd", event => {
86
+ this.setState({
87
+ isComposing: false
88
+ });
89
+ // type for React.CompositionEvent doesn't set type for target correctly
90
+ this.updateReplaceValue(event.target.value);
91
+ });
92
+ const {
93
+ replaceText: _replaceText,
94
+ intl: {
95
+ formatMessage
96
+ }
97
+ } = props;
98
+ this.state = {
99
+ replaceText: _replaceText || '',
100
+ isComposing: false
101
+ };
102
+ this.replaceWith = formatMessage(messages.replaceWith);
103
+ this.replace = formatMessage(messages.replace);
104
+ this.replaceAll = formatMessage(messages.replaceAll);
105
+ }
106
+ componentDidMount() {
107
+ this.props.onReplaceTextfieldRefSet(this.replaceTextfieldRef);
108
+ }
109
+ componentDidUpdate({
110
+ replaceText: prevReplaceText
111
+ }) {
112
+ const {
113
+ replaceText
114
+ } = this.props;
115
+ if (replaceText && replaceText !== prevReplaceText) {
116
+ this.setState({
117
+ replaceText,
118
+ isComposing: false
119
+ });
120
+ }
121
+ }
122
+ render() {
123
+ const {
124
+ replaceText
125
+ } = this.state;
126
+ const {
127
+ canReplace
128
+ } = this.props;
129
+ return jsx("div", {
130
+ css: sectionWrapperStyles
131
+ }, jsx(Textfield, {
132
+ name: "replace",
133
+ appearance: "none",
134
+ placeholder: this.replaceWith,
135
+ defaultValue: replaceText,
136
+ ref: this.replaceTextfieldRef,
137
+ autoComplete: "off",
138
+ onChange: this.handleReplaceChange,
139
+ onKeyDown: this.handleReplaceKeyDown,
140
+ onCompositionStart: this.handleCompositionStart,
141
+ onCompositionEnd: this.handleCompositionEnd
142
+ }), jsx(Button, {
143
+ css: replaceSectionButtonStyles,
144
+ testId: this.replace,
145
+ onClick: this.handleReplaceClick,
146
+ isDisabled: !canReplace
147
+ }, this.replace), jsx(Button, {
148
+ css: replaceSectionButtonStyles,
149
+ testId: this.replaceAll,
150
+ onClick: this.handleReplaceAllClick,
151
+ isDisabled: !canReplace
152
+ }, this.replaceAll));
153
+ }
154
+ }
155
+ export default injectIntl(Replace);
@@ -0,0 +1,50 @@
1
+ /* eslint-disable @atlaskit/design-system/no-nested-styles */
2
+ /* eslint-disable @repo/internal/styles/no-exported-styles */
3
+ /** @jsx jsx */
4
+ import { css } from '@emotion/react';
5
+ import { relativeFontSizeToBase16 } from '@atlaskit/editor-shared-styles';
6
+ import { N30A, N60 } from '@atlaskit/theme/colors';
7
+ export const replaceSectionButtonStyles = css({
8
+ marginLeft: "var(--ds-space-050, 4px)"
9
+ });
10
+ export const ruleStyles = css({
11
+ width: '100%',
12
+ border: 'none',
13
+ backgroundColor: `${`var(--ds-border, ${N30A})`}`,
14
+ margin: `${"var(--ds-space-050, 4px)"} 0px`,
15
+ height: '1px',
16
+ borderRadius: '1px'
17
+ });
18
+ export const wrapperStyles = css({
19
+ display: 'flex',
20
+ flexDirection: 'column',
21
+ '> *:not(#replace-hr-element)': {
22
+ margin: `0px ${"var(--ds-space-050, 4px)"}`
23
+ }
24
+ });
25
+ export const sectionWrapperStyles = css`
26
+ display: flex;
27
+
28
+ & > * {
29
+ display: inline-flex;
30
+ height: 32px;
31
+ flex: 0 0 auto;
32
+ }
33
+
34
+ & > [data-ds--text-field--container] {
35
+ display: flex;
36
+ flex: 1 1 auto;
37
+ }
38
+ `;
39
+ export const countStyles = css({
40
+ color: `${`var(--ds-text-subtlest, ${N60})`}`,
41
+ fontSize: `${relativeFontSizeToBase16(12)}`,
42
+ flex: '0 0 auto',
43
+ justifyContent: 'center',
44
+ alignItems: 'center',
45
+ marginLeft: "var(--ds-space-050, 4px)",
46
+ marginRight: "var(--ds-space-100, 8px)"
47
+ });
48
+ export const countWrapperStyles = css({
49
+ alignItems: 'center'
50
+ });
@@ -0,0 +1,3 @@
1
+ export function findUniqueItemsIn(findIn, checkWith, comparator) {
2
+ return findIn.filter(firstItem => checkWith.findIndex(secondItem => comparator ? comparator(firstItem, secondItem) : firstItem === secondItem) === -1);
3
+ }
@@ -0,0 +1,189 @@
1
+ import { getPluginState } from '../pm-plugins/plugin-factory';
2
+ import { createDecorations, findDecorationFromMatch } from './index';
3
+
4
+ // max number of decorations to apply at once
5
+ const batchIncrement = 100;
6
+ // position range to apply decorations between before alternating above or below viewport
7
+ const posIncrement = 2000;
8
+ /**
9
+ * Provides support for applying search match highlight decorations in batches
10
+ */
11
+ class BatchDecorations {
12
+ stop() {
13
+ if (this.rafId) {
14
+ cancelAnimationFrame(this.rafId);
15
+ }
16
+ }
17
+
18
+ /**
19
+ * Applies the decorations needed for the current search results
20
+ * It does so async, splitting them up into batches to help with performance
21
+ */
22
+ async applyAllSearchDecorations(editorView, containerElement, addDecorations, removeDecorations) {
23
+ this.stop();
24
+ this.addDecorations = addDecorations;
25
+ this.removeDecorations = removeDecorations;
26
+ if (!containerElement) {
27
+ return;
28
+ }
29
+ const pmElement = containerElement.querySelector('.ProseMirror');
30
+ if (!pmElement) {
31
+ return;
32
+ }
33
+ const positions = this.calcDecorationPositions(editorView, containerElement, pmElement);
34
+ const {
35
+ startPos,
36
+ endPos,
37
+ viewportStartPos,
38
+ viewportEndPos
39
+ } = positions;
40
+ let dir = 0;
41
+ let before = viewportStartPos;
42
+ let after = viewportEndPos - posIncrement;
43
+ await this.updateDecorationsBetween(editorView, viewportStartPos, viewportEndPos);
44
+ while (before > startPos || after < endPos) {
45
+ if (dir++ % 2 === 0 && before > startPos || after >= endPos) {
46
+ const diff = before - startPos;
47
+ before = Math.max(before - posIncrement, startPos);
48
+ await this.updateDecorationsBetween(editorView, before, before + Math.min(diff, posIncrement));
49
+ } else {
50
+ after = Math.min(after + posIncrement, endPos);
51
+ await this.updateDecorationsBetween(editorView, after, Math.min(after + posIncrement, endPos));
52
+ }
53
+ }
54
+ }
55
+ async updateDecorationsBetween(editorView, startPos, endPos) {
56
+ await this.removeDecorationsBetween(editorView, startPos, endPos);
57
+ await this.addDecorationsBetween(editorView, startPos, endPos);
58
+ }
59
+ async addDecorationsBetween(editorView, startPos, endPos) {
60
+ const {
61
+ selection
62
+ } = editorView.state;
63
+ const {
64
+ matches,
65
+ decorationSet
66
+ } = getPluginState(editorView.state);
67
+ if (matches.length === 0) {
68
+ return;
69
+ }
70
+ const matchesBetween = matches.filter(m => m.start >= startPos && (endPos === undefined || m.start < endPos));
71
+ const selectionMatch = matches.find(match => match.start >= selection.from);
72
+ const selectionIndex = matchesBetween.findIndex(match => match === selectionMatch);
73
+ return await this.batchRequests(counter => {
74
+ let matchesToDecorate = matchesBetween.slice(counter, counter + batchIncrement);
75
+ if (matchesToDecorate.length === 0) {
76
+ return false;
77
+ }
78
+ let useSelectionIndex = selectionIndex >= counter && selectionIndex < counter + batchIncrement;
79
+ if (selectionMatch && useSelectionIndex) {
80
+ const selectionMatchDecoration = findDecorationFromMatch(decorationSet, selectionMatch);
81
+ if (selectionMatchDecoration) {
82
+ matchesToDecorate.splice(selectionIndex % batchIncrement, 1);
83
+ useSelectionIndex = false;
84
+ }
85
+ }
86
+ if (this.addDecorations) {
87
+ this.addDecorations(createDecorations(useSelectionIndex ? selectionIndex % batchIncrement : -1, matchesToDecorate));
88
+ }
89
+ }, {
90
+ increment: batchIncrement,
91
+ until: matchesBetween.length
92
+ });
93
+ }
94
+ async removeDecorationsBetween(editorView, startPos, endPos) {
95
+ const {
96
+ decorationSet
97
+ } = getPluginState(editorView.state);
98
+ const decorations = decorationSet.find(startPos, endPos);
99
+ if (decorations.length === 0) {
100
+ return;
101
+ }
102
+ return await this.batchRequests(counter => {
103
+ let decorationsToRemove = decorations.slice(counter, counter + batchIncrement);
104
+ if (decorationsToRemove.length === 0) {
105
+ return false;
106
+ }
107
+ // only get those decorations whose from >= startPos
108
+ for (let i = 0; i < decorationsToRemove.length; i++) {
109
+ if (decorationsToRemove[i].from >= startPos) {
110
+ break;
111
+ }
112
+ decorationsToRemove = decorationsToRemove.slice(1);
113
+ }
114
+ if (this.removeDecorations) {
115
+ this.removeDecorations(decorationsToRemove);
116
+ }
117
+ }, {
118
+ increment: batchIncrement,
119
+ until: decorations.length
120
+ });
121
+ }
122
+
123
+ /**
124
+ * Calculates Prosemirror start and end positions we want to apply the decorations
125
+ * between
126
+ * Also calculates the positions the are the start and end of the user's viewport
127
+ * so we can apply decorations there first and work outwards
128
+ */
129
+ calcDecorationPositions(editorView, containerElement, pmElement) {
130
+ const containerRect = containerElement.getBoundingClientRect();
131
+ const pmRect = pmElement.getBoundingClientRect();
132
+ const viewportStartPos = this.getStartPos(editorView, 0, pmRect.left);
133
+ const viewportEndPos = this.getEndPos(editorView, containerRect.top + containerRect.height, pmRect.left);
134
+ return {
135
+ viewportStartPos,
136
+ viewportEndPos,
137
+ startPos: 1,
138
+ endPos: editorView.state.doc.nodeSize
139
+ };
140
+ }
141
+ getStartPos(editorView, y, x) {
142
+ const startPos = editorView.posAtCoords({
143
+ top: y,
144
+ left: x
145
+ });
146
+ return startPos ? startPos.pos : 1;
147
+ }
148
+ getEndPos(editorView, y, x) {
149
+ const maxPos = editorView.state.doc.nodeSize;
150
+ const endPos = editorView.posAtCoords({
151
+ top: y,
152
+ left: x
153
+ });
154
+ return endPos ? endPos.pos : maxPos;
155
+ }
156
+
157
+ /**
158
+ * Util to batch function calls by animation frames
159
+ * A counter will start at 0 and increment by provided value until reaches limit
160
+ * Passed in fn receives the counter as a param, return false to skip waiting
161
+ * for the animation frame for the next call
162
+ */
163
+ batchRequests(fn, opts) {
164
+ let counter = 0;
165
+ const {
166
+ increment,
167
+ until
168
+ } = opts;
169
+ return new Promise(resolve => {
170
+ const batchedFn = () => {
171
+ let result = fn(counter);
172
+ while (result === false && counter < until) {
173
+ counter += increment;
174
+ result = fn(counter);
175
+ }
176
+ if (counter < until) {
177
+ counter += increment;
178
+ this.rafId = requestAnimationFrame(batchedFn);
179
+ } else {
180
+ this.rafId = undefined;
181
+ resolve();
182
+ }
183
+ };
184
+ this.rafId = requestAnimationFrame(batchedFn);
185
+ });
186
+ }
187
+ }
188
+ const batchDecorations = new BatchDecorations();
189
+ export default batchDecorations;
@@ -0,0 +1,6 @@
1
+ export const withScrollIntoView = command => (state, dispatch, view) => command(state, tr => {
2
+ tr.scrollIntoView();
3
+ if (dispatch) {
4
+ dispatch(tr);
5
+ }
6
+ }, view);