@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,91 @@
1
+ import { pluginFactory, stepHasSlice } from '@atlaskit/editor-common/utils';
2
+ import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
3
+ import reducer from '../reducer';
4
+ import { createDecorations, findDecorationFromMatch, findMatches, findSearchIndex, isMatchAffectedByStep, removeDecorationsFromSet, removeMatchesFromSet } from '../utils';
5
+ import { findUniqueItemsIn } from '../utils/array'; // TODO: move into index export
6
+
7
+ import { initialState } from './main';
8
+ import { findReplacePluginKey } from './plugin-key';
9
+ const handleDocChanged = (tr, pluginState) => {
10
+ const {
11
+ isActive,
12
+ findText
13
+ } = pluginState;
14
+ if (!isActive || !findText) {
15
+ return pluginState;
16
+ }
17
+ if (!tr.steps.find(stepHasSlice)) {
18
+ return pluginState;
19
+ }
20
+ let {
21
+ index,
22
+ decorationSet,
23
+ matches,
24
+ shouldMatchCase
25
+ } = pluginState;
26
+ const newMatches = findMatches(tr.doc, findText, shouldMatchCase);
27
+ decorationSet = decorationSet.map(tr.mapping, tr.doc);
28
+ const numDecorations = decorationSet.find().length;
29
+ const mappedMatches = matches.map(match => ({
30
+ start: tr.mapping.map(match.start),
31
+ end: tr.mapping.map(match.end)
32
+ }));
33
+ let matchesToAdd = [];
34
+ let matchesToDelete = [];
35
+ if (newMatches.length > 0 && numDecorations === 0) {
36
+ matchesToAdd = newMatches;
37
+ } else if (newMatches.length === 0 && numDecorations > 0) {
38
+ decorationSet = DecorationSet.empty;
39
+ } else if (newMatches.length > 0 || numDecorations > 0) {
40
+ // go through tr steps and find any new matches from user adding content or
41
+ // any dead matches from user deleting content
42
+ tr.steps.forEach(step => {
43
+ if (stepHasSlice(step)) {
44
+ // add all matches that are between the affected positions and don't already have
45
+ // corresponding decorations
46
+ matchesToAdd = [...matchesToAdd, ...newMatches.filter(match => isMatchAffectedByStep(match, step, tr) && !findDecorationFromMatch(decorationSet, match))];
47
+
48
+ // delete any matches that are missing from the newMatches array and have a
49
+ // corresponding decoration
50
+ matchesToDelete = [...matchesToDelete, ...findUniqueItemsIn(mappedMatches.filter(match => isMatchAffectedByStep(match, step, tr) && !!findDecorationFromMatch(decorationSet, match)), newMatches, (firstMatch, secondMatch) => firstMatch.start === secondMatch.start && firstMatch.end === secondMatch.end)];
51
+ }
52
+ });
53
+ }
54
+
55
+ // update decorations if matches changed following document update
56
+ if (matchesToDelete.length > 0) {
57
+ const decorationsToDelete = matchesToDelete.reduce((decorations, match) => [...decorations, ...decorationSet.find(match.start, match.end)], []);
58
+ decorationSet = removeDecorationsFromSet(decorationSet, decorationsToDelete, tr.doc);
59
+ }
60
+ if (matchesToAdd.length > 0) {
61
+ decorationSet = decorationSet.add(tr.doc, createDecorations(tr.selection.from, matchesToAdd));
62
+ }
63
+
64
+ // update selected match if it has changed
65
+ let newIndex = index;
66
+ const selectedMatch = mappedMatches[index];
67
+ if (selectedMatch) {
68
+ newIndex = newMatches.findIndex(match => match.start === selectedMatch.start);
69
+ }
70
+ if (newIndex === undefined || newIndex === -1) {
71
+ newIndex = findSearchIndex(tr.selection.from, newMatches);
72
+ }
73
+ const newSelectedMatch = newMatches[newIndex];
74
+ decorationSet = removeMatchesFromSet(decorationSet, [selectedMatch, newSelectedMatch], tr.doc);
75
+ if (newSelectedMatch) {
76
+ decorationSet = decorationSet.add(tr.doc, createDecorations(0, [newSelectedMatch]));
77
+ }
78
+ return {
79
+ ...pluginState,
80
+ matches: newMatches,
81
+ index: newIndex,
82
+ decorationSet
83
+ };
84
+ };
85
+ export const {
86
+ createCommand,
87
+ getPluginState,
88
+ createPluginState
89
+ } = pluginFactory(findReplacePluginKey, reducer(() => initialState), {
90
+ onDocChanged: handleDocChanged
91
+ });
@@ -0,0 +1,2 @@
1
+ import { PluginKey } from '@atlaskit/editor-prosemirror/state';
2
+ export const findReplacePluginKey = new PluginKey('findReplace');
@@ -0,0 +1,56 @@
1
+ import { FindReplaceActionTypes } from './actions';
2
+ const reducer = getInitialState => (state, action) => {
3
+ switch (action.type) {
4
+ case FindReplaceActionTypes.ACTIVATE:
5
+ case FindReplaceActionTypes.FIND:
6
+ return {
7
+ ...state,
8
+ isActive: true,
9
+ shouldFocus: action.type === FindReplaceActionTypes.ACTIVATE,
10
+ findText: action.findText !== undefined ? action.findText : state.findText,
11
+ matches: action.matches || state.matches,
12
+ index: action.index !== undefined ? action.index : state.index
13
+ };
14
+ case FindReplaceActionTypes.UPDATE_DECORATIONS:
15
+ return {
16
+ ...state,
17
+ decorationSet: action.decorationSet
18
+ };
19
+ case FindReplaceActionTypes.FIND_NEXT:
20
+ return {
21
+ ...state,
22
+ index: action.index,
23
+ decorationSet: action.decorationSet
24
+ };
25
+ case FindReplaceActionTypes.FIND_PREVIOUS:
26
+ return {
27
+ ...state,
28
+ index: action.index,
29
+ decorationSet: action.decorationSet
30
+ };
31
+ case FindReplaceActionTypes.REPLACE:
32
+ case FindReplaceActionTypes.REPLACE_ALL:
33
+ return {
34
+ ...state,
35
+ replaceText: action.replaceText,
36
+ decorationSet: action.decorationSet,
37
+ matches: action.matches,
38
+ index: action.index
39
+ };
40
+ case FindReplaceActionTypes.CANCEL:
41
+ return getInitialState();
42
+ case FindReplaceActionTypes.BLUR:
43
+ return {
44
+ ...state,
45
+ shouldFocus: false
46
+ };
47
+ case FindReplaceActionTypes.TOGGLE_MATCH_CASE:
48
+ return {
49
+ ...state,
50
+ shouldMatchCase: !state.shouldMatchCase
51
+ };
52
+ default:
53
+ return state;
54
+ }
55
+ };
56
+ export default reducer;
@@ -0,0 +1,18 @@
1
+ /* eslint-disable @atlaskit/design-system/ensure-design-token-usage/preview */
2
+ /* eslint-disable @atlaskit/design-system/ensure-design-token-usage */
3
+
4
+ // TODO: https://product-fabric.atlassian.net/browse/DSP-4290
5
+ import { css } from '@emotion/react';
6
+ import { B200, B75 } from '@atlaskit/theme/colors';
7
+ export const searchMatchClass = 'search-match';
8
+ export const selectedSearchMatchClass = 'selected-search-match';
9
+ export const findReplaceStyles = css`
10
+ .${searchMatchClass} {
11
+ background-color: ${B75};
12
+ }
13
+
14
+ .${selectedSearchMatchClass} {
15
+ background-color: ${B200};
16
+ color: white;
17
+ }
18
+ `;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,286 @@
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 debounce from 'lodash/debounce';
7
+ import rafSchd from 'raf-schd';
8
+ import { defineMessages, injectIntl } from 'react-intl-next';
9
+ import { TRIGGER_METHOD } from '@atlaskit/editor-common/analytics';
10
+ import EditorCloseIcon from '@atlaskit/icon/glyph/editor/close';
11
+ import MatchCaseIcon from '@atlaskit/icon/glyph/emoji/keyboard';
12
+ import ChevronDownIcon from '@atlaskit/icon/glyph/hipchat/chevron-down';
13
+ import ChevronUpIcon from '@atlaskit/icon/glyph/hipchat/chevron-up';
14
+ import { getBooleanFF } from '@atlaskit/platform-feature-flags';
15
+ import Textfield from '@atlaskit/textfield';
16
+ import { FindReplaceTooltipButton } from './FindReplaceTooltipButton';
17
+ import { countStyles, countWrapperStyles, sectionWrapperStyles } from './styles';
18
+ export const FIND_DEBOUNCE_MS = 100;
19
+ const messages = defineMessages({
20
+ find: {
21
+ id: 'fabric.editor.find',
22
+ defaultMessage: 'Find',
23
+ description: 'The word or phrase to search for on the document'
24
+ },
25
+ matchCase: {
26
+ id: 'fabric.editor.matchCase',
27
+ defaultMessage: 'Match case',
28
+ description: 'Toggle whether should also match case when searching for text'
29
+ },
30
+ findNext: {
31
+ id: 'fabric.editor.findNext',
32
+ defaultMessage: 'Find next',
33
+ description: 'Locate the next occurrence of the word or phrase that was searched for'
34
+ },
35
+ findPrevious: {
36
+ id: 'fabric.editor.findPrevious',
37
+ defaultMessage: 'Find previous',
38
+ description: 'Locate the previous occurrence of the word or phrase that was searched for'
39
+ },
40
+ closeFindReplaceDialog: {
41
+ id: 'fabric.editor.closeFindReplaceDialog',
42
+ defaultMessage: 'Close',
43
+ description: 'Cancel search and close the "Find and Replace" dialog'
44
+ },
45
+ noResultsFound: {
46
+ id: 'fabric.editor.noResultsFound',
47
+ defaultMessage: 'No results',
48
+ description: 'No matches were found for the word or phrase that was searched for'
49
+ },
50
+ resultsCount: {
51
+ id: 'fabric.editor.resultsCount',
52
+ description: 'Text for selected search match position and total results count',
53
+ defaultMessage: '{selectedMatchPosition} of {totalResultsCount}'
54
+ }
55
+ });
56
+ // eslint-disable-next-line @repo/internal/react/no-class-components
57
+ class Find extends React.Component {
58
+ constructor(props) {
59
+ super(props);
60
+ _defineProperty(this, "findTextfieldRef", /*#__PURE__*/React.createRef());
61
+ _defineProperty(this, "isComposing", false);
62
+ _defineProperty(this, "syncFindText", onSynced => {
63
+ var _this$state;
64
+ // If the external prop findText changes and we aren't in a composition we should update to
65
+ // use the external prop value.
66
+ //
67
+ // An example of where this may happen is when a find occurs through the user selecting some text
68
+ // and pressing Mod-f.
69
+ if (!this.isComposing && this.props.findText !== ((_this$state = this.state) === null || _this$state === void 0 ? void 0 : _this$state.localFindText)) {
70
+ this.updateFindValue(this.props.findText || '', onSynced);
71
+ }
72
+ });
73
+ _defineProperty(this, "focusFindTextfield", () => {
74
+ const input = this.findTextfieldRef.current;
75
+ if (this.props.shouldFocus && input) {
76
+ input.select();
77
+ }
78
+ });
79
+ _defineProperty(this, "handleFindChange", event => {
80
+ this.updateFindValue(event.target.value);
81
+ });
82
+ // debounce (vs throttle) to not block typing inside find input while onFind runs
83
+ _defineProperty(this, "debouncedFind", debounce(value => {
84
+ this.props.onFind(value);
85
+ }, FIND_DEBOUNCE_MS));
86
+ _defineProperty(this, "updateFindValue", (value, onSynced) => {
87
+ this.setState({
88
+ localFindText: value
89
+ }, () => {
90
+ if (this.isComposing) {
91
+ return;
92
+ }
93
+ onSynced && onSynced();
94
+ this.debouncedFind(value);
95
+ });
96
+ });
97
+ // throtlle between animation frames gives better experience on Enter compared to arbitrary value
98
+ // it adjusts based on performance (and document size)
99
+ _defineProperty(this, "handleFindKeyDownThrottled", rafSchd(event => {
100
+ if (event.key === 'Enter') {
101
+ if (event.shiftKey) {
102
+ this.props.onFindPrev({
103
+ triggerMethod: TRIGGER_METHOD.KEYBOARD
104
+ });
105
+ } else {
106
+ this.props.onFindNext({
107
+ triggerMethod: TRIGGER_METHOD.KEYBOARD
108
+ });
109
+ }
110
+ } else if (event.key === 'ArrowDown') {
111
+ // we want to move focus between find & replace texfields when user hits up/down arrows
112
+ this.props.onArrowDown();
113
+ }
114
+ }));
115
+ _defineProperty(this, "handleFindKeyDown", event => {
116
+ if (this.isComposing) {
117
+ return;
118
+ }
119
+ event.persist();
120
+ this.handleFindKeyDownThrottled(event);
121
+ });
122
+ _defineProperty(this, "handleFindKeyUp", () => {
123
+ this.handleFindKeyDownThrottled.cancel();
124
+ });
125
+ _defineProperty(this, "handleFindNextClick", ref => {
126
+ if (this.isComposing) {
127
+ return;
128
+ }
129
+ this.props.onFindNext({
130
+ triggerMethod: TRIGGER_METHOD.BUTTON
131
+ });
132
+ });
133
+ _defineProperty(this, "handleFindPrevClick", ref => {
134
+ if (this.isComposing) {
135
+ return;
136
+ }
137
+ this.props.onFindPrev({
138
+ triggerMethod: TRIGGER_METHOD.BUTTON
139
+ });
140
+ });
141
+ _defineProperty(this, "handleCompositionStart", () => {
142
+ this.isComposing = true;
143
+ });
144
+ _defineProperty(this, "handleCompositionEnd", event => {
145
+ this.isComposing = false;
146
+ // type for React.CompositionEvent doesn't set type for target correctly
147
+ this.updateFindValue(event.target.value);
148
+ });
149
+ _defineProperty(this, "clearSearch", () => {
150
+ this.props.onCancel({
151
+ triggerMethod: TRIGGER_METHOD.BUTTON
152
+ });
153
+ });
154
+ _defineProperty(this, "handleMatchCaseClick", buttonRef => {
155
+ if (this.props.allowMatchCase && this.props.onToggleMatchCase) {
156
+ this.props.onToggleMatchCase();
157
+ this.props.onFind(this.props.findText);
158
+ }
159
+ });
160
+ const {
161
+ intl: {
162
+ formatMessage
163
+ }
164
+ } = props;
165
+ this.find = formatMessage(messages.find);
166
+ this.closeFindReplaceDialog = formatMessage(messages.closeFindReplaceDialog);
167
+ this.noResultsFound = formatMessage(messages.noResultsFound);
168
+ this.findNext = formatMessage(messages.findNext);
169
+ this.findPrevious = formatMessage(messages.findPrevious);
170
+ this.matchCase = formatMessage(messages.matchCase);
171
+ this.matchCaseIcon = jsx(MatchCaseIcon, {
172
+ label: this.matchCase
173
+ });
174
+ this.findNextIcon = jsx(ChevronDownIcon, {
175
+ label: this.findNext
176
+ });
177
+ this.findPrevIcon = jsx(ChevronUpIcon, {
178
+ label: this.findPrevious
179
+ });
180
+ this.closeIcon = jsx(EditorCloseIcon, {
181
+ label: this.closeFindReplaceDialog
182
+ });
183
+
184
+ // We locally manage the value of the input inside this component in order to support compositions.
185
+ // This requires some additional work inside componentDidUpdate to ensure we support changes that
186
+ // occur to this value which do not originate from this component.
187
+ this.state = {
188
+ localFindText: ''
189
+ };
190
+ }
191
+ componentDidMount() {
192
+ this.props.onFindTextfieldRefSet(this.findTextfieldRef);
193
+
194
+ // focus initially on dialog mount if there is no find text provided
195
+ if (!this.props.findText) {
196
+ this.focusFindTextfield();
197
+ }
198
+ this.syncFindText(() => {
199
+ // focus after input is synced if find text provided
200
+ if (this.props.findText) {
201
+ this.focusFindTextfield();
202
+ }
203
+ });
204
+ }
205
+ componentDidUpdate(prevProps) {
206
+ // focus on update if find text did not change
207
+ if (!getBooleanFF('platform.editor.a11y-find-replace')) {
208
+ var _this$state2;
209
+ if (this.props.findText === ((_this$state2 = this.state) === null || _this$state2 === void 0 ? void 0 : _this$state2.localFindText)) {
210
+ this.focusFindTextfield();
211
+ }
212
+ if (this.props.findText !== prevProps.findText) {
213
+ this.syncFindText(() => {
214
+ // focus after input is synced if find text provided
215
+ if (this.props.findText) {
216
+ this.focusFindTextfield();
217
+ }
218
+ });
219
+ }
220
+ }
221
+ }
222
+ componentWillUnmount() {
223
+ this.debouncedFind.cancel();
224
+ this.handleFindKeyDownThrottled.cancel();
225
+ }
226
+ render() {
227
+ const {
228
+ findText,
229
+ count,
230
+ allowMatchCase,
231
+ shouldMatchCase,
232
+ intl: {
233
+ formatMessage
234
+ }
235
+ } = this.props;
236
+ const resultsCount = formatMessage(messages.resultsCount, {
237
+ selectedMatchPosition: count.index + 1,
238
+ totalResultsCount: count.total
239
+ });
240
+ return jsx("div", {
241
+ css: sectionWrapperStyles
242
+ }, jsx(Textfield, {
243
+ name: "find",
244
+ appearance: "none",
245
+ placeholder: this.find,
246
+ value: this.state.localFindText,
247
+ ref: this.findTextfieldRef,
248
+ autoComplete: "off",
249
+ onChange: this.handleFindChange,
250
+ onKeyDown: this.handleFindKeyDown,
251
+ onKeyUp: this.handleFindKeyUp,
252
+ onBlur: this.props.onFindBlur,
253
+ onCompositionStart: this.handleCompositionStart,
254
+ onCompositionEnd: this.handleCompositionEnd
255
+ }), jsx("div", {
256
+ css: countWrapperStyles,
257
+ "aria-live": "polite"
258
+ }, findText && jsx("span", {
259
+ "data-testid": "textfield-count",
260
+ css: countStyles
261
+ }, count.total === 0 ? this.noResultsFound : resultsCount)), allowMatchCase && jsx(FindReplaceTooltipButton, {
262
+ title: this.matchCase,
263
+ icon: this.matchCaseIcon,
264
+ onClick: this.handleMatchCaseClick,
265
+ isPressed: shouldMatchCase
266
+ }), jsx(FindReplaceTooltipButton, {
267
+ title: this.findNext,
268
+ icon: this.findNextIcon,
269
+ keymapDescription: 'Enter',
270
+ onClick: this.handleFindNextClick,
271
+ disabled: count.total <= 1
272
+ }), jsx(FindReplaceTooltipButton, {
273
+ title: this.findPrevious,
274
+ icon: this.findPrevIcon,
275
+ keymapDescription: 'Shift Enter',
276
+ onClick: this.handleFindPrevClick,
277
+ disabled: count.total <= 1
278
+ }), jsx(FindReplaceTooltipButton, {
279
+ title: this.closeFindReplaceDialog,
280
+ icon: this.closeIcon,
281
+ keymapDescription: 'Escape',
282
+ onClick: this.clearSearch
283
+ }));
284
+ }
285
+ }
286
+ export default injectIntl(Find);
@@ -0,0 +1,81 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ /* eslint-disable @atlaskit/design-system/consistent-css-prop-usage */
3
+ /* eslint-disable @atlaskit/design-system/prefer-primitives */
4
+ /** @jsx jsx */
5
+ import React from 'react';
6
+ import { jsx } from '@emotion/react';
7
+ import Find from './Find';
8
+ import Replace from './Replace';
9
+ import { ruleStyles, wrapperStyles } from './styles';
10
+ // eslint-disable-next-line @repo/internal/react/no-class-components
11
+ class FindReplace extends React.PureComponent {
12
+ constructor(...args) {
13
+ super(...args);
14
+ _defineProperty(this, "findTextfield", null);
15
+ _defineProperty(this, "replaceTextfield", null);
16
+ _defineProperty(this, "setFindTextfieldRef", findTextfieldRef => {
17
+ this.findTextfield = findTextfieldRef.current;
18
+ });
19
+ _defineProperty(this, "setReplaceTextfieldRef", replaceTextfieldRef => {
20
+ this.replaceTextfield = replaceTextfieldRef.current;
21
+ });
22
+ _defineProperty(this, "setFocusToFind", () => {
23
+ if (this.findTextfield) {
24
+ this.findTextfield.focus();
25
+ }
26
+ });
27
+ _defineProperty(this, "setFocusToReplace", () => {
28
+ if (this.replaceTextfield) {
29
+ this.replaceTextfield.focus();
30
+ }
31
+ });
32
+ }
33
+ render() {
34
+ const {
35
+ findText,
36
+ count,
37
+ shouldFocus,
38
+ onFind,
39
+ onFindBlur,
40
+ onFindNext,
41
+ onFindPrev,
42
+ onCancel,
43
+ replaceText,
44
+ onReplace,
45
+ onReplaceAll,
46
+ dispatchAnalyticsEvent,
47
+ allowMatchCase,
48
+ shouldMatchCase,
49
+ onToggleMatchCase
50
+ } = this.props;
51
+ return jsx("div", {
52
+ css: wrapperStyles
53
+ }, jsx(Find, {
54
+ allowMatchCase: allowMatchCase,
55
+ shouldMatchCase: shouldMatchCase,
56
+ onToggleMatchCase: onToggleMatchCase,
57
+ findText: findText,
58
+ count: count,
59
+ shouldFocus: shouldFocus,
60
+ onFind: onFind,
61
+ onFindBlur: onFindBlur,
62
+ onFindPrev: onFindPrev,
63
+ onFindNext: onFindNext,
64
+ onFindTextfieldRefSet: this.setFindTextfieldRef,
65
+ onCancel: onCancel,
66
+ onArrowDown: this.setFocusToReplace
67
+ }), jsx("hr", {
68
+ css: ruleStyles,
69
+ id: "replace-hr-element"
70
+ }), jsx(Replace, {
71
+ canReplace: count.total > 0,
72
+ replaceText: replaceText,
73
+ onReplace: onReplace,
74
+ onReplaceAll: onReplaceAll,
75
+ onReplaceTextfieldRefSet: this.setReplaceTextfieldRef,
76
+ onArrowUp: this.setFocusToFind,
77
+ dispatchAnalyticsEvent: dispatchAnalyticsEvent
78
+ }));
79
+ }
80
+ }
81
+ export default FindReplace;
@@ -0,0 +1,122 @@
1
+ import _extends from "@babel/runtime/helpers/extends";
2
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
3
+ /* eslint-disable @atlaskit/design-system/consistent-css-prop-usage */
4
+ /** @jsx jsx */
5
+ import React from 'react';
6
+ import { css, jsx } from '@emotion/react';
7
+ import { defineMessages, injectIntl } from 'react-intl-next';
8
+ import { TRIGGER_METHOD } from '@atlaskit/editor-common/analytics';
9
+ import { findKeymapByDescription, getAriaKeyshortcuts, tooltip, ToolTipContent } from '@atlaskit/editor-common/keymaps';
10
+ import { ArrowKeyNavigationType, Dropdown, TOOLBAR_BUTTON, ToolbarButton } from '@atlaskit/editor-common/ui-menu';
11
+ import { akEditorFloatingPanelZIndex, akEditorMobileMaxWidth } from '@atlaskit/editor-shared-styles';
12
+ import EditorSearchIcon from '@atlaskit/icon/glyph/editor/search';
13
+ import FindReplace from './FindReplace';
14
+ const toolbarButtonWrapper = css`
15
+ display: flex;
16
+ flex: 1 1 auto;
17
+ flex-grow: 0;
18
+ justify-content: flex-end;
19
+ align-items: center;
20
+ padding: 0 ${"var(--ds-space-100, 8px)"};
21
+ @media (max-width: ${akEditorMobileMaxWidth}px) {
22
+ justify-content: center;
23
+ padding: 0;
24
+ }
25
+ `;
26
+ const toolbarButtonWrapperFullWith = css`
27
+ flex-grow: 1;
28
+ `;
29
+ const wrapper = css`
30
+ display: flex;
31
+ flex-direction: column;
32
+ `;
33
+ const messages = defineMessages({
34
+ findReplaceToolbarButton: {
35
+ id: 'fabric.editor.findReplaceToolbarButton',
36
+ defaultMessage: 'Find and replace',
37
+ description: '"Find" highlights all instances of a word or phrase on the document, and "Replace" changes one or all of those instances to something else'
38
+ }
39
+ });
40
+ // eslint-disable-next-line @repo/internal/react/no-class-components
41
+ class FindReplaceToolbarButton extends React.PureComponent {
42
+ constructor(...args) {
43
+ super(...args);
44
+ _defineProperty(this, "toggleOpen", () => {
45
+ if (this.props.isActive) {
46
+ this.props.onCancel({
47
+ triggerMethod: TRIGGER_METHOD.TOOLBAR
48
+ });
49
+ } else {
50
+ this.props.onActivate();
51
+ }
52
+ });
53
+ }
54
+ render() {
55
+ const {
56
+ popupsMountPoint,
57
+ popupsBoundariesElement,
58
+ popupsScrollableElement,
59
+ isReducedSpacing,
60
+ findText,
61
+ replaceText,
62
+ isActive,
63
+ index,
64
+ numMatches,
65
+ intl: {
66
+ formatMessage
67
+ },
68
+ takeFullWidth
69
+ } = this.props;
70
+ const title = formatMessage(messages.findReplaceToolbarButton);
71
+ const stackBelowOtherEditorFloatingPanels = akEditorFloatingPanelZIndex - 1;
72
+ const keymap = findKeymapByDescription('Find');
73
+ return jsx("div", {
74
+ css: [toolbarButtonWrapper, takeFullWidth && toolbarButtonWrapperFullWith]
75
+ }, jsx(Dropdown, {
76
+ mountTo: popupsMountPoint,
77
+ boundariesElement: popupsBoundariesElement,
78
+ scrollableElement: popupsScrollableElement,
79
+ isOpen: isActive,
80
+ handleEscapeKeydown: () => {
81
+ if (isActive) {
82
+ this.props.onCancel({
83
+ triggerMethod: TRIGGER_METHOD.KEYBOARD
84
+ });
85
+ }
86
+ },
87
+ fitWidth: 352,
88
+ zIndex: stackBelowOtherEditorFloatingPanels,
89
+ arrowKeyNavigationProviderOptions: {
90
+ type: ArrowKeyNavigationType.MENU,
91
+ disableArrowKeyNavigation: true
92
+ },
93
+ trigger: jsx(ToolbarButton, {
94
+ buttonId: TOOLBAR_BUTTON.FIND_REPLACE,
95
+ spacing: isReducedSpacing ? 'none' : 'default',
96
+ selected: isActive,
97
+ title: jsx(ToolTipContent, {
98
+ description: title,
99
+ keymap: keymap
100
+ }),
101
+ iconBefore: jsx(EditorSearchIcon, {
102
+ label: title
103
+ }),
104
+ onClick: this.toggleOpen,
105
+ "aria-expanded": isActive,
106
+ "aria-haspopup": true,
107
+ "aria-label": keymap ? tooltip(keymap, title) : title,
108
+ "aria-keyshortcuts": getAriaKeyshortcuts(keymap)
109
+ })
110
+ }, jsx("div", {
111
+ css: wrapper
112
+ }, jsx(FindReplace, _extends({
113
+ findText: findText,
114
+ replaceText: replaceText,
115
+ count: {
116
+ index,
117
+ total: numMatches
118
+ }
119
+ }, this.props)))));
120
+ }
121
+ }
122
+ export default injectIntl(FindReplaceToolbarButton);