Add 'Find and replace' option to menu on mobile (#6368)
This commit is contained in:
@@ -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<HTMLElement>(
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -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) => (
|
||||
<FindAndReplace readOnly={readOnly} />
|
||||
<FindAndReplace
|
||||
readOnly={readOnly}
|
||||
open={this.open}
|
||||
onOpen={() => {
|
||||
this.open = true;
|
||||
}}
|
||||
onClose={() => {
|
||||
this.open = false;
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
@observable
|
||||
private open = false;
|
||||
|
||||
private results: { from: number; to: number }[] = [];
|
||||
private currentResultIndex = 0;
|
||||
private searchTerm = "";
|
||||
|
||||
@@ -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: <SearchIcon />,
|
||||
},
|
||||
{
|
||||
type: "separator",
|
||||
},
|
||||
|
||||
@@ -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
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user