UNPKG

@atlaskit/editor-plugin-find-replace

Version:

find replace plugin for @atlaskit/editor-core

250 lines (249 loc) 9.39 kB
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;