@atlaskit/editor-plugin-find-replace
Version:
find replace plugin for @atlaskit/editor-core
250 lines (249 loc) • 9.39 kB
JavaScript
import React, { useState, useRef, useEffect } from 'react';
import { injectIntl } from 'react-intl';
import Button from '@atlaskit/button/new';
import { ACTION, ACTION_SUBJECT, EVENT_TYPE, TRIGGER_METHOD } from '@atlaskit/editor-common/analytics';
import { findReplaceMessages as messages } from '@atlaskit/editor-common/messages';
import { ValidMessage } from '@atlaskit/form';
import ChevronDownIcon from '@atlaskit/icon/core/chevron-down';
import ChevronUpIcon from '@atlaskit/icon/core/chevron-up';
// eslint-disable-next-line @atlaskit/design-system/no-emotion-primitives -- to be migrated to @atlaskit/primitives/compiled – go/akcss
import { Box, Inline, Text, xcss } from '@atlaskit/primitives';
import Textfield from '@atlaskit/textfield';
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
import { FindReplaceTooltipButton } from './FindReplaceTooltipButton';
const replaceContainerStyles = xcss({
padding: 'space.100'
});
const replaceWithLabelStyle = xcss({
paddingBottom: 'space.050'
});
const actionButtonContainerStyles = xcss({
paddingTop: 'space.200'
});
const actionButtonParentInlineStyles = xcss({
justifyContent: 'space-between',
flexDirection: 'row-reverse'
});
const actionButtonParentInlineStylesNew = xcss({
justifyContent: 'space-between',
flexDirection: 'row-reverse',
flexWrap: 'wrap'
});
const actionButtonInlineStyles = xcss({
gap: 'space.075'
});
const closeButtonInlineStyles = xcss({
marginRight: 'auto'
});
const Replace = ({
canReplace,
replaceText: initialReplaceText,
onReplace,
onReplaceAll,
onReplaceTextfieldRefSet,
onArrowUp,
onCancel,
count,
onFindNext,
onFindPrev,
dispatchAnalyticsEvent,
setFindTyped,
findTyped,
focusToolbarButton,
intl: {
formatMessage
}
}) => {
const [replaceText, setReplaceText] = useState(initialReplaceText || '');
const [isComposing, setIsComposing] = useState(false);
const [isHelperMessageVisible, setIsHelperMessageVisible] = useState(false);
const [fakeSuccessReplacementMessageUpdate, setFakeSuccessReplacementMessageUpdate] = useState(false);
const [replaceCount, setReplaceCount] = useState(0);
const replaceTextfieldRef = useRef(null);
const successReplacementMessageRef = useRef(null);
const replaceWith = formatMessage(messages.replaceWith);
const replaceAll = formatMessage(messages.replaceAll);
const findPrevious = formatMessage(messages.findPrevious);
const closeFindReplaceDialog = formatMessage(messages.closeFindReplaceDialog);
useEffect(() => {
onReplaceTextfieldRefSet(replaceTextfieldRef);
}, [onReplaceTextfieldRefSet]);
useEffect(() => {
setReplaceText(initialReplaceText || '');
}, [initialReplaceText]);
const skipWhileComposing = fn => {
if (!isComposing) {
fn();
}
};
const triggerSuccessReplacementMessageUpdate = currentReplaceCount => {
if (replaceCount === currentReplaceCount) {
setFakeSuccessReplacementMessageUpdate(!fakeSuccessReplacementMessageUpdate);
}
if (successReplacementMessageRef.current) {
const ariaLiveRegion = successReplacementMessageRef.current.querySelector('[aria-live="polite"]');
ariaLiveRegion === null || ariaLiveRegion === void 0 ? void 0 : ariaLiveRegion.removeAttribute('aria-live');
ariaLiveRegion === null || ariaLiveRegion === void 0 ? void 0 : ariaLiveRegion.setAttribute('aria-live', 'polite');
}
};
const handleReplaceClick = () => skipWhileComposing(() => {
onReplace({
triggerMethod: TRIGGER_METHOD.BUTTON,
replaceText
});
triggerSuccessReplacementMessageUpdate(1);
setIsHelperMessageVisible(true);
setReplaceCount(1);
setFindTyped(false);
});
const handleReplaceChange = event => skipWhileComposing(() => {
updateReplaceValue(event.target.value);
});
const updateReplaceValue = text => {
if (dispatchAnalyticsEvent) {
dispatchAnalyticsEvent({
eventType: EVENT_TYPE.TRACK,
action: ACTION.CHANGED_REPLACEMENT_TEXT,
actionSubject: ACTION_SUBJECT.FIND_REPLACE_DIALOG
});
}
setReplaceText(text);
};
const handleReplaceKeyDown = event => skipWhileComposing(() => {
if (event.key === 'Enter') {
onReplace({
triggerMethod: TRIGGER_METHOD.KEYBOARD,
replaceText
});
} else if (event.key === 'ArrowUp') {
onArrowUp();
}
});
const handleReplaceAllClick = () => skipWhileComposing(() => {
onReplaceAll({
replaceText
});
setIsHelperMessageVisible(true);
if (count.totalReplaceable && expValEquals('platform_editor_find_and_replace_improvements', 'isEnabled', true)) {
triggerSuccessReplacementMessageUpdate(count.totalReplaceable);
setReplaceCount(count.totalReplaceable);
} else {
triggerSuccessReplacementMessageUpdate(count.total);
setReplaceCount(count.total);
}
setFindTyped(false);
});
const handleCompositionStart = () => {
setIsComposing(true);
};
const handleCompositionEnd = event => {
setIsComposing(false);
// type for React.CompositionEvent doesn't set type for target correctly
updateReplaceValue(event.target.value);
};
const clearSearch = () => {
onCancel({
triggerMethod: TRIGGER_METHOD.BUTTON
});
focusToolbarButton();
};
const handleFindNextClick = () => {
if (!isComposing) {
onFindNext({
triggerMethod: TRIGGER_METHOD.BUTTON
});
}
};
const handleFindPrevClick = () => {
if (!isComposing) {
onFindPrev({
triggerMethod: TRIGGER_METHOD.BUTTON
});
}
};
const resultsReplace = formatMessage(messages.replaceSuccess, {
numberOfMatches: replaceCount
});
return /*#__PURE__*/React.createElement(Box, {
xcss: replaceContainerStyles
}, /*#__PURE__*/React.createElement(Box, {
xcss: replaceWithLabelStyle
}, /*#__PURE__*/React.createElement(Text, {
id: "replace-text-field-label",
size: "medium",
weight: "bold",
color: "color.text.subtle"
}, replaceWith)), /*#__PURE__*/React.createElement(Textfield, {
name: "replace",
"aria-labelledby": "replace-text-field-label",
testId: "replace-field",
appearance: "standard",
defaultValue: replaceText,
ref: replaceTextfieldRef,
autoComplete: "off",
onChange: handleReplaceChange,
onKeyDown: handleReplaceKeyDown,
onCompositionStart: handleCompositionStart,
onCompositionEnd: handleCompositionEnd
}), isHelperMessageVisible && !findTyped && /*#__PURE__*/React.createElement("div", {
ref: successReplacementMessageRef
}, /*#__PURE__*/React.createElement(ValidMessage, {
testId: "message-success-replacement"
}, fakeSuccessReplacementMessageUpdate ?
// @ts-ignore - TS1501 TypeScript 5.9.2 upgrade
resultsReplace.replace(/ /u, '\u00a0') : resultsReplace)), /*#__PURE__*/React.createElement(Box, {
xcss: actionButtonContainerStyles
}, /*#__PURE__*/React.createElement(Inline, {
xcss: expValEquals('platform_editor_find_and_replace_improvements', 'isEnabled', true) ? [actionButtonInlineStyles, actionButtonParentInlineStylesNew] : [actionButtonInlineStyles, actionButtonParentInlineStyles]
}, /*#__PURE__*/React.createElement(Inline, {
xcss: actionButtonInlineStyles
}, /*#__PURE__*/React.createElement(FindReplaceTooltipButton, {
title: formatMessage(messages.findNext)
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
,
icon: iconProps => /*#__PURE__*/React.createElement(ChevronDownIcon, {
label: iconProps.label,
size: "small"
}),
iconLabel: formatMessage(messages.findNext),
keymapDescription: 'Enter',
onClick: handleFindNextClick,
disabled: count.total <= 1
}), /*#__PURE__*/React.createElement(FindReplaceTooltipButton, {
title: findPrevious
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
,
icon: iconProps => /*#__PURE__*/React.createElement(ChevronUpIcon, {
label: iconProps.label,
size: "small"
}),
iconLabel: findPrevious,
keymapDescription: 'Shift Enter',
onClick: handleFindPrevClick,
disabled: count.total <= 1
}), /*#__PURE__*/React.createElement(Button, {
testId: 'Replace',
id: "replace-button",
onClick: handleReplaceClick,
isDisabled: !canReplace
}, formatMessage(messages.replace)), /*#__PURE__*/React.createElement(Button, {
appearance: "primary",
testId: replaceAll,
id: "replaceAll-button",
onClick: handleReplaceAllClick,
isDisabled: expValEquals('platform_editor_find_and_replace_improvements', 'isEnabled', true) ? count.totalReplaceable === 0 : !canReplace
}, replaceAll)), expValEquals('platform_editor_find_and_replace_improvements', 'isEnabled', true) ? /*#__PURE__*/React.createElement(Inline, {
xcss: closeButtonInlineStyles
}, /*#__PURE__*/React.createElement(Button, {
appearance: "subtle",
testId: closeFindReplaceDialog,
onClick: clearSearch
}, closeFindReplaceDialog)) : /*#__PURE__*/React.createElement(Button, {
appearance: "subtle",
testId: closeFindReplaceDialog,
onClick: clearSearch
}, closeFindReplaceDialog))));
};
// eslint-disable-next-line @typescript-eslint/ban-types
const _default_1 = injectIntl(Replace);
export default _default_1;