From 6e1347c2a78803d19d80091aa3756663595801ba Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Wed, 10 Jan 2024 05:07:05 -0800 Subject: [PATCH] Add 'Find and replace' option to menu on mobile (#6368) --- app/editor/components/FindAndReplace.tsx | 18 +++++++++++++- app/editor/extensions/FindAndReplace.tsx | 30 ++++++++++++++++++++++- app/menus/DocumentMenu.tsx | 11 ++++++++- app/scenes/Document/components/Header.tsx | 7 +++++- 4 files changed, 62 insertions(+), 4 deletions(-) diff --git a/app/editor/components/FindAndReplace.tsx b/app/editor/components/FindAndReplace.tsx index ff21ada9e..dccf4972c 100644 --- a/app/editor/components/FindAndReplace.tsx +++ b/app/editor/components/FindAndReplace.tsx @@ -25,10 +25,18 @@ import { altDisplay, isModKey, metaDisplay } from "~/utils/keyboard"; import { useEditor } from "./EditorContext"; type Props = { + open: boolean; + onOpen: () => void; + onClose: () => void; readOnly?: boolean; }; -export default function FindAndReplace({ readOnly }: Props) { +export default function FindAndReplace({ + readOnly, + open, + onOpen, + onClose, +}: Props) { const editor = useEditor(); const finalFocusRef = React.useRef( editor.view.dom.parentElement @@ -46,6 +54,12 @@ export default function FindAndReplace({ readOnly }: Props) { const popover = usePopoverState(); const { show } = popover; + React.useEffect(() => { + if (open) { + show(); + } + }, [open]); + // Hooks for desktop app menu items React.useEffect(() => { if (!Desktop.bridge) { @@ -209,6 +223,7 @@ export default function FindAndReplace({ readOnly }: Props) { React.useEffect(() => { if (popover.visible) { + onOpen(); const startSearchText = selectionRef.current || searchTerm; editor.commands.find({ @@ -225,6 +240,7 @@ export default function FindAndReplace({ readOnly }: Props) { setSearchTerm(selectionRef.current); } } else { + onClose(); setShowReplace(false); editor.commands.clearSearch(); } diff --git a/app/editor/extensions/FindAndReplace.tsx b/app/editor/extensions/FindAndReplace.tsx index f3c53782c..d488f91b3 100644 --- a/app/editor/extensions/FindAndReplace.tsx +++ b/app/editor/extensions/FindAndReplace.tsx @@ -1,4 +1,5 @@ import escapeRegExp from "lodash/escapeRegExp"; +import { observable } from "mobx"; import { Node } from "prosemirror-model"; import { Command, Plugin, PluginKey } from "prosemirror-state"; import { Decoration, DecorationSet } from "prosemirror-view"; @@ -68,6 +69,11 @@ export default class FindAndReplaceExtension extends Extension { * Clear the current search */ clearSearch: () => this.clear(), + + /** + * Open the find and replace UI + */ + openFindAndReplace: () => this.openFindAndReplace(), }; } @@ -142,6 +148,13 @@ export default class FindAndReplaceExtension extends Extension { }; } + public openFindAndReplace(): Command { + return (state, dispatch) => { + dispatch?.(state.tr.setMeta(pluginKey, { open: true })); + return true; + }; + } + private get findRegExp() { try { return RegExp( @@ -275,6 +288,9 @@ export default class FindAndReplaceExtension extends Extension { const action = tr.getMeta(pluginKey); if (action) { + if (action.open) { + this.open = true; + } return this.createDeco(tr.doc); } @@ -295,9 +311,21 @@ export default class FindAndReplaceExtension extends Extension { } public widget = ({ readOnly }: WidgetProps) => ( - + { + this.open = true; + }} + onClose={() => { + this.open = false; + }} + /> ); + @observable + private open = false; + private results: { from: number; to: number }[] = []; private currentResultIndex = 0; private searchTerm = ""; diff --git a/app/menus/DocumentMenu.tsx b/app/menus/DocumentMenu.tsx index 42a3f0059..29320e3c0 100644 --- a/app/menus/DocumentMenu.tsx +++ b/app/menus/DocumentMenu.tsx @@ -1,5 +1,5 @@ import { observer } from "mobx-react"; -import { EditIcon, RestoreIcon } from "outline-icons"; +import { EditIcon, RestoreIcon, SearchIcon } from "outline-icons"; import * as React from "react"; import { useTranslation } from "react-i18next"; import { useHistory } from "react-router-dom"; @@ -64,6 +64,7 @@ type Props = { showToggleEmbeds?: boolean; showPin?: boolean; label?: (props: MenuButtonHTMLProps) => React.ReactNode; + onFindAndReplace?: () => void; onRename?: () => void; onOpen?: () => void; onClose?: () => void; @@ -76,6 +77,7 @@ function DocumentMenu({ showToggleEmbeds, showDisplayOptions, label, + onFindAndReplace, onRename, onOpen, onClose, @@ -259,6 +261,13 @@ function DocumentMenu({ actionToMenuItem(subscribeDocument, context), actionToMenuItem(unsubscribeDocument, context), ...(isMobile ? [actionToMenuItem(shareDocument, context)] : []), + { + type: "button", + title: `${t("Find and replace")}…`, + visible: !!onFindAndReplace && isMobile, + onClick: () => onFindAndReplace?.(), + icon: , + }, { type: "separator", }, diff --git a/app/scenes/Document/components/Header.tsx b/app/scenes/Document/components/Header.tsx index 52bcea1bb..441fbf7f0 100644 --- a/app/scenes/Document/components/Header.tsx +++ b/app/scenes/Document/components/Header.tsx @@ -20,7 +20,10 @@ import Badge from "~/components/Badge"; import Button from "~/components/Button"; import Collaborators from "~/components/Collaborators"; import DocumentBreadcrumb from "~/components/DocumentBreadcrumb"; -import { useEditingFocus } from "~/components/DocumentContext"; +import { + useDocumentContext, + useEditingFocus, +} from "~/components/DocumentContext"; import Header from "~/components/Header"; import EmojiIcon from "~/components/Icons/EmojiIcon"; import Star from "~/components/Star"; @@ -94,6 +97,7 @@ function DocumentHeader({ const isMobile = useMobile(); const isRevision = !!revision; const isEditingFocus = useEditingFocus(); + const { editor } = useDocumentContext(); // We cache this value for as long as the component is mounted so that if you // apply a template there is still the option to replace it until the user @@ -344,6 +348,7 @@ function DocumentHeader({ neutral /> )} + onFindAndReplace={editor?.commands.openFindAndReplace} showToggleEmbeds={canToggleEmbeds} showDisplayOptions />