@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,153 @@
1
+ import React, { useLayoutEffect, useState } from 'react';
2
+ import { TRIGGER_METHOD } from '@atlaskit/editor-common/analytics';
3
+ import { blur, toggleMatchCase } from './commands';
4
+ import { activateWithAnalytics, cancelSearchWithAnalytics, findNextWithAnalytics, findPrevWithAnalytics, findWithAnalytics, replaceAllWithAnalytics, replaceWithAnalytics } from './commands-with-analytics';
5
+ import FindReplaceToolbarButton from './ui/FindReplaceToolbarButton';
6
+
7
+ // light implementation of useSharedPluginState(). This is due to findreplace
8
+ // being the only plugin that previously used WithPluginState with
9
+ // debounce=false. That was implemented because of text sync issues
10
+ // between editor & findreplace dialog.
11
+ // To address under ENGHEALTH-5853
12
+ const useSharedPluginStateNoDebounce = api => {
13
+ const [state, setState] = useState(api === null || api === void 0 ? void 0 : api.findReplace.sharedState.currentState());
14
+ useLayoutEffect(() => {
15
+ const unsub = api === null || api === void 0 ? void 0 : api.findReplace.sharedState.onChange(({
16
+ nextSharedState
17
+ }) => {
18
+ setState(nextSharedState);
19
+ });
20
+ return () => {
21
+ unsub === null || unsub === void 0 ? void 0 : unsub();
22
+ };
23
+ }, [api]);
24
+ return {
25
+ findReplaceState: state
26
+ };
27
+ };
28
+ const FindReplaceToolbarButtonWithState = ({
29
+ popupsBoundariesElement,
30
+ popupsMountPoint,
31
+ popupsScrollableElement,
32
+ isToolbarReducedSpacing,
33
+ editorView,
34
+ containerElement,
35
+ dispatchAnalyticsEvent,
36
+ featureFlags,
37
+ takeFullWidth,
38
+ api
39
+ }) => {
40
+ var _api$analytics;
41
+ const editorAnalyticsAPI = api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions;
42
+ const {
43
+ findReplaceState
44
+ } = useSharedPluginStateNoDebounce(api);
45
+ if (!editorView) {
46
+ return null;
47
+ }
48
+
49
+ // we need the editor to be in focus for scrollIntoView() to work
50
+ // so we focus it while we run the command, then put focus back into
51
+ // whatever element was previously focused in find replace component
52
+ const runWithEditorFocused = fn => {
53
+ const activeElement = document.activeElement;
54
+ editorView.focus();
55
+ fn();
56
+ activeElement === null || activeElement === void 0 ? void 0 : activeElement.focus();
57
+ };
58
+ const dispatchCommand = cmd => {
59
+ const {
60
+ state,
61
+ dispatch
62
+ } = editorView;
63
+ cmd(state, dispatch);
64
+ };
65
+ const handleActivate = () => {
66
+ runWithEditorFocused(() => dispatchCommand(activateWithAnalytics(editorAnalyticsAPI)({
67
+ triggerMethod: TRIGGER_METHOD.TOOLBAR
68
+ })));
69
+ };
70
+ const handleFind = keyword => {
71
+ runWithEditorFocused(() => dispatchCommand(findWithAnalytics(editorAnalyticsAPI)({
72
+ editorView,
73
+ containerElement,
74
+ keyword
75
+ })));
76
+ };
77
+ const handleFindNext = ({
78
+ triggerMethod
79
+ }) => {
80
+ runWithEditorFocused(() => dispatchCommand(findNextWithAnalytics(editorAnalyticsAPI)({
81
+ triggerMethod
82
+ })));
83
+ };
84
+ const handleFindPrev = ({
85
+ triggerMethod
86
+ }) => {
87
+ runWithEditorFocused(() => dispatchCommand(findPrevWithAnalytics(editorAnalyticsAPI)({
88
+ triggerMethod
89
+ })));
90
+ };
91
+ const handleReplace = ({
92
+ triggerMethod,
93
+ replaceText
94
+ }) => {
95
+ runWithEditorFocused(() => dispatchCommand(replaceWithAnalytics(editorAnalyticsAPI)({
96
+ triggerMethod,
97
+ replaceText
98
+ })));
99
+ };
100
+ const handleReplaceAll = ({
101
+ replaceText
102
+ }) => {
103
+ runWithEditorFocused(() => dispatchCommand(replaceAllWithAnalytics(editorAnalyticsAPI)({
104
+ replaceText
105
+ })));
106
+ };
107
+ const handleFindBlur = () => {
108
+ dispatchCommand(blur());
109
+ };
110
+ const handleCancel = ({
111
+ triggerMethod
112
+ }) => {
113
+ dispatchCommand(cancelSearchWithAnalytics(editorAnalyticsAPI)({
114
+ triggerMethod
115
+ }));
116
+ editorView.focus();
117
+ };
118
+ const handleToggleMatchCase = () => {
119
+ dispatchCommand(toggleMatchCase());
120
+ };
121
+ const {
122
+ findReplaceMatchCase
123
+ } = featureFlags;
124
+ if (!findReplaceState) {
125
+ return null;
126
+ }
127
+ return /*#__PURE__*/React.createElement(FindReplaceToolbarButton, {
128
+ allowMatchCase: findReplaceMatchCase,
129
+ shouldMatchCase: findReplaceState.shouldMatchCase,
130
+ onToggleMatchCase: handleToggleMatchCase,
131
+ isActive: findReplaceState.isActive,
132
+ findText: findReplaceState.findText,
133
+ index: findReplaceState.index,
134
+ numMatches: findReplaceState.matches.length,
135
+ replaceText: findReplaceState.replaceText,
136
+ shouldFocus: findReplaceState.shouldFocus,
137
+ popupsBoundariesElement: popupsBoundariesElement,
138
+ popupsMountPoint: popupsMountPoint,
139
+ popupsScrollableElement: popupsScrollableElement,
140
+ isReducedSpacing: !!isToolbarReducedSpacing,
141
+ dispatchAnalyticsEvent: dispatchAnalyticsEvent,
142
+ onFindBlur: handleFindBlur,
143
+ onCancel: handleCancel,
144
+ onActivate: handleActivate,
145
+ onFind: handleFind,
146
+ onFindNext: handleFindNext,
147
+ onFindPrev: handleFindPrev,
148
+ onReplace: handleReplace,
149
+ onReplaceAll: handleReplaceAll,
150
+ takeFullWidth: !!takeFullWidth
151
+ });
152
+ };
153
+ export default /*#__PURE__*/React.memo(FindReplaceToolbarButtonWithState);
@@ -0,0 +1,13 @@
1
+ export let FindReplaceActionTypes = /*#__PURE__*/function (FindReplaceActionTypes) {
2
+ FindReplaceActionTypes["ACTIVATE"] = "ACTIVATE";
3
+ FindReplaceActionTypes["FIND"] = "FIND";
4
+ FindReplaceActionTypes["UPDATE_DECORATIONS"] = "UPDATE_DECORATIONS";
5
+ FindReplaceActionTypes["FIND_NEXT"] = "FIND_NEXT";
6
+ FindReplaceActionTypes["FIND_PREVIOUS"] = "FIND_PREVIOUS";
7
+ FindReplaceActionTypes["REPLACE"] = "REPLACE";
8
+ FindReplaceActionTypes["REPLACE_ALL"] = "REPLACE_ALL";
9
+ FindReplaceActionTypes["CANCEL"] = "CANCEL";
10
+ FindReplaceActionTypes["BLUR"] = "BLUR";
11
+ FindReplaceActionTypes["TOGGLE_MATCH_CASE"] = "TOGGLE_MATCH_CASE";
12
+ return FindReplaceActionTypes;
13
+ }({});
@@ -0,0 +1,72 @@
1
+ import { ACTION, ACTION_SUBJECT, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics';
2
+ import { withAnalytics } from '@atlaskit/editor-common/editor-analytics';
3
+ import { TextSelection } from '@atlaskit/editor-prosemirror/state';
4
+ import { activate, cancelSearch, find, findNext, findPrevious, replace, replaceAll } from './commands';
5
+ export const activateWithAnalytics = editorAnalyticsAPI => ({
6
+ triggerMethod
7
+ }) => withAnalytics(editorAnalyticsAPI, state => ({
8
+ eventType: EVENT_TYPE.UI,
9
+ action: ACTION.ACTIVATED,
10
+ actionSubject: ACTION_SUBJECT.FIND_REPLACE_DIALOG,
11
+ attributes: {
12
+ inputMethod: state.selection instanceof TextSelection && !state.selection.empty ? INPUT_METHOD.PREFILL : INPUT_METHOD.KEYBOARD,
13
+ triggerMethod
14
+ }
15
+ }))(activate());
16
+ export const findWithAnalytics = editorAnalyticsAPI => ({
17
+ editorView,
18
+ containerElement,
19
+ keyword
20
+ }) => withAnalytics(editorAnalyticsAPI, {
21
+ eventType: EVENT_TYPE.TRACK,
22
+ action: ACTION.FIND_PERFORMED,
23
+ actionSubject: ACTION_SUBJECT.TEXT
24
+ })(find(editorView, containerElement, keyword));
25
+ export const findNextWithAnalytics = editorAnalyticsAPI => ({
26
+ triggerMethod
27
+ }) => withAnalytics(editorAnalyticsAPI, {
28
+ eventType: EVENT_TYPE.TRACK,
29
+ action: ACTION.FIND_NEXT_PERFORMED,
30
+ actionSubject: ACTION_SUBJECT.TEXT,
31
+ attributes: {
32
+ triggerMethod
33
+ }
34
+ })(findNext());
35
+ export const findPrevWithAnalytics = editorAnalyticsAPI => ({
36
+ triggerMethod
37
+ }) => withAnalytics(editorAnalyticsAPI, {
38
+ eventType: EVENT_TYPE.TRACK,
39
+ action: ACTION.FIND_PREV_PERFORMED,
40
+ actionSubject: ACTION_SUBJECT.TEXT,
41
+ attributes: {
42
+ triggerMethod
43
+ }
44
+ })(findPrevious());
45
+ export const replaceWithAnalytics = editorAnalyticsAPI => ({
46
+ triggerMethod,
47
+ replaceText
48
+ }) => withAnalytics(editorAnalyticsAPI, {
49
+ eventType: EVENT_TYPE.TRACK,
50
+ action: ACTION.REPLACED_ONE,
51
+ actionSubject: ACTION_SUBJECT.TEXT,
52
+ attributes: {
53
+ triggerMethod
54
+ }
55
+ })(replace(replaceText));
56
+ export const replaceAllWithAnalytics = editorAnalyticsAPI => ({
57
+ replaceText
58
+ }) => withAnalytics(editorAnalyticsAPI, {
59
+ eventType: EVENT_TYPE.TRACK,
60
+ action: ACTION.REPLACED_ALL,
61
+ actionSubject: ACTION_SUBJECT.TEXT
62
+ })(replaceAll(replaceText));
63
+ export const cancelSearchWithAnalytics = editorAnalyticsAPI => ({
64
+ triggerMethod
65
+ }) => withAnalytics(editorAnalyticsAPI, {
66
+ eventType: EVENT_TYPE.UI,
67
+ action: ACTION.DEACTIVATED,
68
+ actionSubject: ACTION_SUBJECT.FIND_REPLACE_DIALOG,
69
+ attributes: {
70
+ triggerMethod
71
+ }
72
+ })(cancelSearch());
@@ -0,0 +1,240 @@
1
+ import { TextSelection } from '@atlaskit/editor-prosemirror/state';
2
+ import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
3
+ import { FindReplaceActionTypes } from './actions';
4
+ import { createCommand, getPluginState } from './pm-plugins/plugin-factory';
5
+ import { createDecoration, findDecorationFromMatch, findMatches, findSearchIndex, getSelectedText, getSelectionForMatch, nextIndex, prevIndex, removeDecorationsFromSet, removeMatchesFromSet } from './utils';
6
+ import batchDecorations from './utils/batch-decorations';
7
+ import { withScrollIntoView } from './utils/commands';
8
+ export const activate = () => createCommand(state => {
9
+ const {
10
+ selection
11
+ } = state;
12
+ let findText;
13
+ let matches;
14
+ let index;
15
+
16
+ // if user has selected text and hit cmd-f, set that as the keyword
17
+ if (selection instanceof TextSelection && !selection.empty) {
18
+ findText = getSelectedText(selection);
19
+ const {
20
+ shouldMatchCase
21
+ } = getPluginState(state);
22
+ matches = findMatches(state.doc, findText, shouldMatchCase);
23
+ index = findSearchIndex(selection.from, matches);
24
+ }
25
+ return {
26
+ type: FindReplaceActionTypes.ACTIVATE,
27
+ findText,
28
+ matches,
29
+ index
30
+ };
31
+ });
32
+ export const find = (editorView, containerElement, keyword) => withScrollIntoView(createCommand(state => {
33
+ const {
34
+ selection
35
+ } = state;
36
+ const {
37
+ shouldMatchCase
38
+ } = getPluginState(state);
39
+ const matches = keyword !== undefined ? findMatches(state.doc, keyword, shouldMatchCase) : [];
40
+ const index = findSearchIndex(selection.from, matches);
41
+
42
+ // we can't just apply all the decorations to highlight the search results at once
43
+ // as if there are a lot ProseMirror cries :'(
44
+ batchDecorations.applyAllSearchDecorations(editorView, containerElement, decorations => addDecorations(decorations)(editorView.state, editorView.dispatch), decorations => removeDecorations(decorations)(editorView.state, editorView.dispatch));
45
+ return {
46
+ type: FindReplaceActionTypes.FIND,
47
+ findText: keyword || '',
48
+ matches,
49
+ index
50
+ };
51
+ }, (tr, state) => {
52
+ const {
53
+ selection
54
+ } = state;
55
+ const {
56
+ shouldMatchCase
57
+ } = getPluginState(state);
58
+ const matches = keyword !== undefined ? findMatches(state.doc, keyword, shouldMatchCase) : [];
59
+ if (matches.length > 0) {
60
+ const index = findSearchIndex(selection.from, matches);
61
+ return tr.setSelection(getSelectionForMatch(tr.selection, tr.doc, index, matches));
62
+ }
63
+ return tr;
64
+ }));
65
+ export const findNext = () => withScrollIntoView(createCommand(state => findInDirection(state, 'next'), (tr, state) => {
66
+ const {
67
+ matches,
68
+ index
69
+ } = getPluginState(state);
70
+ // can't use index from plugin state because if the cursor has moved, it will still be the
71
+ // OLD index (the find next operation should look for the first match forward starting
72
+ // from the current cursor position)
73
+ let searchIndex = findSearchIndex(state.selection.from, matches);
74
+ if (searchIndex === index) {
75
+ // cursor has not moved, so we just want to find the next in matches array
76
+ searchIndex = nextIndex(searchIndex, matches.length);
77
+ }
78
+ return tr.setSelection(getSelectionForMatch(tr.selection, tr.doc, searchIndex, matches));
79
+ }));
80
+ export const findPrevious = () => withScrollIntoView(createCommand(state => findInDirection(state, 'previous'), (tr, state) => {
81
+ const {
82
+ matches
83
+ } = getPluginState(state);
84
+ // can't use index from plugin state because if the cursor has moved, it will still be the
85
+ // OLD index (the find prev operation should look for the first match backward starting
86
+ // from the current cursor position)
87
+ const searchIndex = findSearchIndex(state.selection.from, matches, true);
88
+ return tr.setSelection(getSelectionForMatch(tr.selection, tr.doc, searchIndex, matches));
89
+ }));
90
+ const findInDirection = (state, dir) => {
91
+ const pluginState = getPluginState(state);
92
+ const {
93
+ matches,
94
+ findText
95
+ } = pluginState;
96
+ let {
97
+ decorationSet,
98
+ index
99
+ } = pluginState;
100
+ if (findText) {
101
+ const searchIndex = findSearchIndex(state.selection.from, matches, dir === 'previous');
102
+ // compare index from plugin state and index of first match forward from cursor position
103
+ if (index === searchIndex) {
104
+ // normal case, cycling through matches
105
+ index = dir === 'next' ? nextIndex(index, matches.length) : prevIndex(index, matches.length);
106
+ } else {
107
+ // cursor has moved
108
+ index = searchIndex;
109
+ }
110
+ decorationSet = updateSelectedHighlight(state, index);
111
+ }
112
+ return {
113
+ type: dir === 'next' ? FindReplaceActionTypes.FIND_NEXT : FindReplaceActionTypes.FIND_PREVIOUS,
114
+ index,
115
+ decorationSet
116
+ };
117
+ };
118
+ export const replace = replaceText => withScrollIntoView(createCommand(state => {
119
+ const pluginState = getPluginState(state);
120
+ const {
121
+ findText
122
+ } = pluginState;
123
+ let {
124
+ decorationSet,
125
+ matches,
126
+ index
127
+ } = pluginState;
128
+ decorationSet = updateSelectedHighlight(state, nextIndex(index, matches.length));
129
+ if (replaceText.toLowerCase().indexOf(findText.toLowerCase()) === -1) {
130
+ decorationSet = removeMatchesFromSet(decorationSet, [matches[index]], state.doc);
131
+ matches.splice(index, 1);
132
+ if (index > matches.length - 1) {
133
+ index = 0;
134
+ }
135
+ } else {
136
+ index = nextIndex(index, matches.length);
137
+ }
138
+ return {
139
+ type: FindReplaceActionTypes.REPLACE,
140
+ replaceText,
141
+ decorationSet,
142
+ matches,
143
+ index
144
+ };
145
+ }, (tr, state) => {
146
+ const {
147
+ matches,
148
+ index,
149
+ findText
150
+ } = getPluginState(state);
151
+ if (matches[index]) {
152
+ const {
153
+ start,
154
+ end
155
+ } = matches[index];
156
+ const newIndex = nextIndex(index, matches.length);
157
+ tr.insertText(replaceText, start, end).setSelection(getSelectionForMatch(tr.selection, tr.doc, newIndex, matches, newIndex === 0 ? 0 : replaceText.length - findText.length));
158
+ }
159
+ return tr;
160
+ }));
161
+ export const replaceAll = replaceText => createCommand({
162
+ type: FindReplaceActionTypes.REPLACE_ALL,
163
+ replaceText: replaceText,
164
+ decorationSet: DecorationSet.empty,
165
+ matches: [],
166
+ index: 0
167
+ }, (tr, state) => {
168
+ const pluginState = getPluginState(state);
169
+ pluginState.matches.forEach(match => {
170
+ tr.insertText(replaceText, tr.mapping.map(match.start), tr.mapping.map(match.end));
171
+ });
172
+ tr.setMeta('scrollIntoView', false);
173
+ return tr;
174
+ });
175
+ export const addDecorations = decorations => createCommand(state => {
176
+ const {
177
+ decorationSet
178
+ } = getPluginState(state);
179
+ return {
180
+ type: FindReplaceActionTypes.UPDATE_DECORATIONS,
181
+ decorationSet: decorationSet.add(state.doc, decorations)
182
+ };
183
+ });
184
+ export const removeDecorations = decorations => createCommand(state => {
185
+ const {
186
+ decorationSet
187
+ } = getPluginState(state);
188
+ return {
189
+ type: FindReplaceActionTypes.UPDATE_DECORATIONS,
190
+ decorationSet: removeDecorationsFromSet(decorationSet, decorations, state.doc)
191
+ };
192
+ });
193
+ export const cancelSearch = () => createCommand(() => {
194
+ batchDecorations.stop();
195
+ return {
196
+ type: FindReplaceActionTypes.CANCEL
197
+ };
198
+ });
199
+ export const blur = () => createCommand({
200
+ type: FindReplaceActionTypes.BLUR
201
+ });
202
+ export const toggleMatchCase = () => createCommand({
203
+ type: FindReplaceActionTypes.TOGGLE_MATCH_CASE
204
+ });
205
+ const updateSelectedHighlight = (state, nextSelectedIndex) => {
206
+ let {
207
+ decorationSet,
208
+ index,
209
+ matches
210
+ } = getPluginState(state);
211
+ const currentSelectedMatch = matches[index];
212
+ const nextSelectedMatch = matches[nextSelectedIndex];
213
+ if (index === nextSelectedIndex) {
214
+ return decorationSet;
215
+ }
216
+ const currentSelectedDecoration = findDecorationFromMatch(decorationSet, currentSelectedMatch);
217
+ const nextSelectedDecoration = findDecorationFromMatch(decorationSet, nextSelectedMatch);
218
+
219
+ // Update decorations so the current selected match becomes a normal match
220
+ // and the next selected gets the selected styling
221
+ const decorationsToRemove = [];
222
+ if (currentSelectedDecoration) {
223
+ decorationsToRemove.push(currentSelectedDecoration);
224
+ }
225
+ if (nextSelectedDecoration) {
226
+ decorationsToRemove.push(nextSelectedDecoration);
227
+ }
228
+ if (decorationsToRemove.length > 0) {
229
+ // removeDecorationsFromSet depends on decorations being pre-sorted
230
+ decorationsToRemove.sort((a, b) => a.from < b.from ? -1 : 1);
231
+ decorationSet = removeDecorationsFromSet(decorationSet, decorationsToRemove, state.doc);
232
+ }
233
+ if (currentSelectedMatch) {
234
+ decorationSet = decorationSet.add(state.doc, [createDecoration(currentSelectedMatch.start, currentSelectedMatch.end)]);
235
+ }
236
+ if (nextSelectedMatch) {
237
+ decorationSet = decorationSet.add(state.doc, [createDecoration(nextSelectedMatch.start, nextSelectedMatch.end, true)]);
238
+ }
239
+ return decorationSet;
240
+ };
@@ -0,0 +1 @@
1
+ export { findReplacePlugin } from './plugin';
@@ -0,0 +1,88 @@
1
+ import React from 'react';
2
+ import FindReplaceToolbarButtonWithState from './FindReplaceToolbarButtonWithState';
3
+ import keymapPlugin from './pm-plugins/keymap';
4
+ import { createPlugin } from './pm-plugins/main';
5
+ import { findReplacePluginKey } from './pm-plugins/plugin-key';
6
+ export const findReplacePlugin = ({
7
+ config: props,
8
+ api
9
+ }) => {
10
+ var _api$featureFlags;
11
+ const featureFlags = (api === null || api === void 0 ? void 0 : (_api$featureFlags = api.featureFlags) === null || _api$featureFlags === void 0 ? void 0 : _api$featureFlags.sharedState.currentState()) || {};
12
+ return {
13
+ name: 'findReplace',
14
+ pmPlugins() {
15
+ return [{
16
+ name: 'findReplace',
17
+ plugin: ({
18
+ dispatch
19
+ }) => createPlugin(dispatch)
20
+ }, {
21
+ name: 'findReplaceKeymap',
22
+ plugin: () => {
23
+ var _api$analytics;
24
+ return keymapPlugin(api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions);
25
+ }
26
+ }];
27
+ },
28
+ getSharedState(editorState) {
29
+ if (!editorState) {
30
+ return undefined;
31
+ }
32
+ return findReplacePluginKey.getState(editorState) || undefined;
33
+ },
34
+ actions: {
35
+ getToolbarButton: ({
36
+ popupsBoundariesElement,
37
+ popupsMountPoint,
38
+ popupsScrollableElement,
39
+ editorView,
40
+ containerElement,
41
+ dispatchAnalyticsEvent,
42
+ isToolbarReducedSpacing
43
+ }) => {
44
+ return /*#__PURE__*/React.createElement(FindReplaceToolbarButtonWithState, {
45
+ popupsBoundariesElement: popupsBoundariesElement,
46
+ popupsMountPoint: popupsMountPoint,
47
+ popupsScrollableElement: popupsScrollableElement,
48
+ editorView: editorView,
49
+ containerElement: containerElement,
50
+ dispatchAnalyticsEvent: dispatchAnalyticsEvent
51
+ // `allowMatchCase` comes through the preset, but not the feature flags
52
+ // prop with the `ComposableEditor` - grab the FFs from the editor API
53
+ // instead until we clean this up.
54
+ ,
55
+ featureFlags: featureFlags,
56
+ isToolbarReducedSpacing: isToolbarReducedSpacing,
57
+ api: api
58
+ });
59
+ }
60
+ },
61
+ primaryToolbarComponent({
62
+ popupsBoundariesElement,
63
+ popupsMountPoint,
64
+ popupsScrollableElement,
65
+ isToolbarReducedSpacing,
66
+ editorView,
67
+ containerElement,
68
+ dispatchAnalyticsEvent
69
+ }) {
70
+ if (props !== null && props !== void 0 && props.twoLineEditorToolbar) {
71
+ return null;
72
+ } else {
73
+ return /*#__PURE__*/React.createElement(FindReplaceToolbarButtonWithState, {
74
+ popupsBoundariesElement: popupsBoundariesElement,
75
+ popupsMountPoint: popupsMountPoint,
76
+ popupsScrollableElement: popupsScrollableElement,
77
+ isToolbarReducedSpacing: isToolbarReducedSpacing,
78
+ editorView: editorView,
79
+ containerElement: containerElement,
80
+ dispatchAnalyticsEvent: dispatchAnalyticsEvent,
81
+ takeFullWidth: props === null || props === void 0 ? void 0 : props.takeFullWidth,
82
+ featureFlags: featureFlags,
83
+ api: api
84
+ });
85
+ }
86
+ }
87
+ };
88
+ };
@@ -0,0 +1,16 @@
1
+ import { TRIGGER_METHOD } from '@atlaskit/editor-common/analytics';
2
+ import { bindKeymapWithCommand, find as findKeymap } from '@atlaskit/editor-common/keymaps';
3
+ import { keymap } from '@atlaskit/editor-prosemirror/keymap';
4
+ import { activateWithAnalytics } from '../commands-with-analytics';
5
+ const activateFindReplace = editorAnalyticsAPI => (state, dispatch) => {
6
+ activateWithAnalytics(editorAnalyticsAPI)({
7
+ triggerMethod: TRIGGER_METHOD.SHORTCUT
8
+ })(state, dispatch);
9
+ return true;
10
+ };
11
+ const keymapPlugin = editorAnalyticsAPI => {
12
+ const list = {};
13
+ bindKeymapWithCommand(findKeymap.common, activateFindReplace(editorAnalyticsAPI), list);
14
+ return keymap(list);
15
+ };
16
+ export default keymapPlugin;
@@ -0,0 +1,30 @@
1
+ import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
2
+ import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
3
+ import { createPluginState, getPluginState } from './plugin-factory';
4
+ import { findReplacePluginKey } from './plugin-key';
5
+ export const initialState = {
6
+ isActive: false,
7
+ shouldFocus: false,
8
+ findText: '',
9
+ replaceText: '',
10
+ index: 0,
11
+ matches: [],
12
+ decorationSet: DecorationSet.empty,
13
+ shouldMatchCase: false
14
+ };
15
+ export const createPlugin = dispatch => new SafePlugin({
16
+ key: findReplacePluginKey,
17
+ state: createPluginState(dispatch, () => initialState),
18
+ props: {
19
+ decorations(state) {
20
+ const {
21
+ isActive,
22
+ findText,
23
+ decorationSet
24
+ } = getPluginState(state);
25
+ if (isActive && findText) {
26
+ return decorationSet;
27
+ }
28
+ }
29
+ }
30
+ });