@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.
- package/.eslintrc.js +26 -0
- package/CHANGELOG.md +11 -0
- package/LICENSE.md +13 -0
- package/README.md +30 -0
- package/dist/cjs/FindReplaceToolbarButtonWithState.js +166 -0
- package/dist/cjs/actions.js +19 -0
- package/dist/cjs/commands-with-analytics.js +101 -0
- package/dist/cjs/commands.js +255 -0
- package/dist/cjs/index.js +12 -0
- package/dist/cjs/plugin.js +93 -0
- package/dist/cjs/pm-plugins/keymap.js +24 -0
- package/dist/cjs/pm-plugins/main.js +39 -0
- package/dist/cjs/pm-plugins/plugin-factory.js +109 -0
- package/dist/cjs/pm-plugins/plugin-key.js +8 -0
- package/dist/cjs/reducer.js +61 -0
- package/dist/cjs/styles.js +17 -0
- package/dist/cjs/types.js +5 -0
- package/dist/cjs/ui/Find.js +309 -0
- package/dist/cjs/ui/FindReplace.js +104 -0
- package/dist/cjs/ui/FindReplaceToolbarButton.js +133 -0
- package/dist/cjs/ui/FindReplaceTooltipButton.js +77 -0
- package/dist/cjs/ui/Replace.js +176 -0
- package/dist/cjs/ui/styles.js +46 -0
- package/dist/cjs/utils/array.js +13 -0
- package/dist/cjs/utils/batch-decorations.js +310 -0
- package/dist/cjs/utils/commands.js +16 -0
- package/dist/cjs/utils/index.js +290 -0
- package/dist/es2019/FindReplaceToolbarButtonWithState.js +153 -0
- package/dist/es2019/actions.js +13 -0
- package/dist/es2019/commands-with-analytics.js +72 -0
- package/dist/es2019/commands.js +240 -0
- package/dist/es2019/index.js +1 -0
- package/dist/es2019/plugin.js +88 -0
- package/dist/es2019/pm-plugins/keymap.js +16 -0
- package/dist/es2019/pm-plugins/main.js +30 -0
- package/dist/es2019/pm-plugins/plugin-factory.js +91 -0
- package/dist/es2019/pm-plugins/plugin-key.js +2 -0
- package/dist/es2019/reducer.js +56 -0
- package/dist/es2019/styles.js +18 -0
- package/dist/es2019/types.js +1 -0
- package/dist/es2019/ui/Find.js +286 -0
- package/dist/es2019/ui/FindReplace.js +81 -0
- package/dist/es2019/ui/FindReplaceToolbarButton.js +122 -0
- package/dist/es2019/ui/FindReplaceTooltipButton.js +51 -0
- package/dist/es2019/ui/Replace.js +155 -0
- package/dist/es2019/ui/styles.js +50 -0
- package/dist/es2019/utils/array.js +3 -0
- package/dist/es2019/utils/batch-decorations.js +189 -0
- package/dist/es2019/utils/commands.js +6 -0
- package/dist/es2019/utils/index.js +249 -0
- package/dist/esm/FindReplaceToolbarButtonWithState.js +157 -0
- package/dist/esm/actions.js +13 -0
- package/dist/esm/commands-with-analytics.js +95 -0
- package/dist/esm/commands.js +248 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/plugin.js +86 -0
- package/dist/esm/pm-plugins/keymap.js +18 -0
- package/dist/esm/pm-plugins/main.js +33 -0
- package/dist/esm/pm-plugins/plugin-factory.js +104 -0
- package/dist/esm/pm-plugins/plugin-key.js +2 -0
- package/dist/esm/reducer.js +54 -0
- package/dist/esm/styles.js +11 -0
- package/dist/esm/types.js +1 -0
- package/dist/esm/ui/Find.js +304 -0
- package/dist/esm/ui/FindReplace.js +100 -0
- package/dist/esm/ui/FindReplaceToolbarButton.js +126 -0
- package/dist/esm/ui/FindReplaceTooltipButton.js +70 -0
- package/dist/esm/ui/Replace.js +171 -0
- package/dist/esm/ui/styles.js +39 -0
- package/dist/esm/utils/array.js +7 -0
- package/dist/esm/utils/batch-decorations.js +304 -0
- package/dist/esm/utils/commands.js +10 -0
- package/dist/esm/utils/index.js +280 -0
- package/dist/types/FindReplaceToolbarButtonWithState.d.ts +4 -0
- package/dist/types/actions.d.ts +64 -0
- package/dist/types/commands-with-analytics.d.ts +27 -0
- package/dist/types/commands.d.ts +12 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/plugin.d.ts +2 -0
- package/dist/types/pm-plugins/keymap.d.ts +4 -0
- package/dist/types/pm-plugins/main.d.ts +5 -0
- package/dist/types/pm-plugins/plugin-factory.d.ts +2 -0
- package/dist/types/pm-plugins/plugin-key.d.ts +3 -0
- package/dist/types/reducer.d.ts +4 -0
- package/dist/types/styles.d.ts +3 -0
- package/dist/types/types.d.ts +76 -0
- package/dist/types/ui/Find.d.ts +71 -0
- package/dist/types/ui/FindReplace.d.ts +43 -0
- package/dist/types/ui/FindReplaceToolbarButton.d.ts +21 -0
- package/dist/types/ui/FindReplaceTooltipButton.d.ts +18 -0
- package/dist/types/ui/Replace.d.ts +27 -0
- package/dist/types/ui/styles.d.ts +6 -0
- package/dist/types/utils/array.d.ts +1 -0
- package/dist/types/utils/batch-decorations.d.ts +36 -0
- package/dist/types/utils/commands.d.ts +2 -0
- package/dist/types/utils/index.d.ts +49 -0
- package/dist/types-ts4.5/FindReplaceToolbarButtonWithState.d.ts +4 -0
- package/dist/types-ts4.5/actions.d.ts +64 -0
- package/dist/types-ts4.5/commands-with-analytics.d.ts +27 -0
- package/dist/types-ts4.5/commands.d.ts +12 -0
- package/dist/types-ts4.5/index.d.ts +2 -0
- package/dist/types-ts4.5/plugin.d.ts +2 -0
- package/dist/types-ts4.5/pm-plugins/keymap.d.ts +4 -0
- package/dist/types-ts4.5/pm-plugins/main.d.ts +5 -0
- package/dist/types-ts4.5/pm-plugins/plugin-factory.d.ts +2 -0
- package/dist/types-ts4.5/pm-plugins/plugin-key.d.ts +3 -0
- package/dist/types-ts4.5/reducer.d.ts +4 -0
- package/dist/types-ts4.5/styles.d.ts +3 -0
- package/dist/types-ts4.5/types.d.ts +76 -0
- package/dist/types-ts4.5/ui/Find.d.ts +71 -0
- package/dist/types-ts4.5/ui/FindReplace.d.ts +43 -0
- package/dist/types-ts4.5/ui/FindReplaceToolbarButton.d.ts +21 -0
- package/dist/types-ts4.5/ui/FindReplaceTooltipButton.d.ts +18 -0
- package/dist/types-ts4.5/ui/Replace.d.ts +27 -0
- package/dist/types-ts4.5/ui/styles.d.ts +6 -0
- package/dist/types-ts4.5/utils/array.d.ts +1 -0
- package/dist/types-ts4.5/utils/batch-decorations.d.ts +36 -0
- package/dist/types-ts4.5/utils/commands.d.ts +2 -0
- package/dist/types-ts4.5/utils/index.d.ts +49 -0
- package/package.json +117 -0
- 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
|
+
});
|