From ec2da746dcbf4d731f2e712a6f7161b33466126e Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Thu, 5 Jan 2023 21:11:28 -0500 Subject: [PATCH] chore: Convert LinkToolbar to functional component MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ítalo Sousa --- app/editor/components/FloatingToolbar.tsx | 15 +- app/editor/components/LinkToolbar.tsx | 213 +++++++++++----------- app/editor/index.tsx | 3 - 3 files changed, 111 insertions(+), 120 deletions(-) diff --git a/app/editor/components/FloatingToolbar.tsx b/app/editor/components/FloatingToolbar.tsx index c72407e90..c288d5b9a 100644 --- a/app/editor/components/FloatingToolbar.tsx +++ b/app/editor/components/FloatingToolbar.tsx @@ -1,6 +1,5 @@ import { NodeSelection } from "prosemirror-state"; import { CellSelection } from "prosemirror-tables"; -import { EditorView } from "prosemirror-view"; import * as React from "react"; import { Portal } from "react-portal"; import styled from "styled-components"; @@ -9,10 +8,10 @@ import useComponentSize from "~/hooks/useComponentSize"; import useEventListener from "~/hooks/useEventListener"; import useMediaQuery from "~/hooks/useMediaQuery"; import useViewportHeight from "~/hooks/useViewportHeight"; +import { useEditor } from "./EditorContext"; type Props = { active?: boolean; - view: EditorView; children: React.ReactNode; forwardedRef?: React.RefObject | null; }; @@ -27,13 +26,13 @@ const defaultPosition = { function usePosition({ menuRef, isSelectingText, - props, + active, }: { menuRef: React.RefObject; isSelectingText: boolean; - props: Props; + active?: boolean; }) { - const { view, active } = props; + const { view } = useEditor(); const { selection } = view.state; const { width: menuWidth, height: menuHeight } = useComponentSize(menuRef); const viewportHeight = useViewportHeight(); @@ -155,14 +154,14 @@ function usePosition({ } const FloatingToolbar = React.forwardRef( - (props: Props, forwardedRef: React.RefObject) => { - const menuRef = forwardedRef || React.createRef(); + (props: Props, ref: React.RefObject) => { + const menuRef = ref || React.createRef(); const [isSelectingText, setSelectingText] = React.useState(false); const position = usePosition({ menuRef, isSelectingText, - props, + active: props.active, }); useEventListener("mouseup", () => { diff --git a/app/editor/components/LinkToolbar.tsx b/app/editor/components/LinkToolbar.tsx index 499a0a822..0f3d3568a 100644 --- a/app/editor/components/LinkToolbar.tsx +++ b/app/editor/components/LinkToolbar.tsx @@ -2,152 +2,147 @@ import { EditorView } from "prosemirror-view"; import * as React from "react"; import createAndInsertLink from "@shared/editor/commands/createAndInsertLink"; import { creatingUrlPrefix } from "@shared/utils/urls"; -import { Dictionary } from "~/hooks/useDictionary"; +import useDictionary from "~/hooks/useDictionary"; +import useEventListener from "~/hooks/useEventListener"; +import useToasts from "~/hooks/useToasts"; +import { useEditor } from "./EditorContext"; import FloatingToolbar from "./FloatingToolbar"; import LinkEditor, { SearchResult } from "./LinkEditor"; type Props = { isActive: boolean; - view: EditorView; - dictionary: Dictionary; onCreateLink?: (title: string) => Promise; onSearchLink?: (term: string) => Promise; onClickLink: ( href: string, event: React.MouseEvent ) => void; - onShowToast: (message: string) => void; onClose: () => void; }; -function isActive(props: Props) { - const { view } = props; - const { selection } = view.state; - +function isActive(view: EditorView, active: boolean): boolean { try { + const { selection } = view.state; const paragraph = view.domAtPos(selection.from); - return props.isActive && !!paragraph.node; + return active && !!paragraph.node; } catch (err) { return false; } } -export default class LinkToolbar extends React.Component { - menuRef = React.createRef(); +export default function LinkToolbar({ + onCreateLink, + onSearchLink, + onClickLink, + onClose, + ...rest +}: Props) { + const dictionary = useDictionary(); + const { view } = useEditor(); + const { showToast } = useToasts(); + const menuRef = React.useRef(null); - state = { - left: -1000, - top: undefined, - }; - - componentDidMount() { - window.addEventListener("mousedown", this.handleClickOutside); - } - - componentWillUnmount() { - window.removeEventListener("mousedown", this.handleClickOutside); - } - - handleClickOutside = (event: Event) => { + useEventListener("mousedown", (event: Event) => { if ( event.target instanceof HTMLElement && - this.menuRef.current && - this.menuRef.current.contains(event.target) + menuRef.current && + menuRef.current.contains(event.target) ) { return; } - - this.props.onClose(); - }; - - handleOnCreateLink = async (title: string) => { - const { dictionary, onCreateLink, view, onClose, onShowToast } = this.props; - onClose(); - this.props.view.focus(); + }); - if (!onCreateLink) { - return; - } + const handleOnCreateLink = React.useCallback( + async (title: string) => { + onClose(); + view.focus(); - const { dispatch, state } = view; - const { from, to } = state.selection; - if (from !== to) { - // selection must be collapsed - return; - } + if (!onCreateLink) { + return; + } - const href = `${creatingUrlPrefix}${title}…`; + const { dispatch, state } = view; + const { from, to } = state.selection; + if (from !== to) { + // selection must be collapsed + return; + } - // Insert a placeholder link - dispatch( - view.state.tr - .insertText(title, from, to) - .addMark( - from, - to + title.length, - state.schema.marks.link.create({ href }) - ) - ); + const href = `${creatingUrlPrefix}#${title}…`; - createAndInsertLink(view, title, href, { - onCreateLink, - onShowToast, - dictionary, - }); - }; + // Insert a placeholder link + dispatch( + view.state.tr + .insertText(title, from, to) + .addMark( + from, + to + title.length, + state.schema.marks.link.create({ href }) + ) + ); - handleOnSelectLink = ({ - href, - title, - }: { - href: string; - title: string; - from: number; - to: number; - }) => { - const { view, onClose } = this.props; + createAndInsertLink(view, title, href, { + onCreateLink, + onShowToast: showToast, + dictionary, + }); + }, + [onCreateLink, onClose, view, dictionary, showToast] + ); - onClose(); - this.props.view.focus(); + const handleOnSelectLink = React.useCallback( + ({ + href, + title, + }: { + href: string; + title: string; + from: number; + to: number; + }) => { + onClose(); + view.focus(); - const { dispatch, state } = view; - const { from, to } = state.selection; - if (from !== to) { - // selection must be collapsed - return; - } + const { dispatch, state } = view; + const { from, to } = state.selection; + if (from !== to) { + // selection must be collapsed + return; + } - dispatch( - view.state.tr - .insertText(title, from, to) - .addMark( - from, - to + title.length, - state.schema.marks.link.create({ href }) - ) - ); - }; + dispatch( + view.state.tr + .insertText(title, from, to) + .addMark( + from, + to + title.length, + state.schema.marks.link.create({ href }) + ) + ); + }, + [onClose, view] + ); - render() { - const { onCreateLink, onClose, ...rest } = this.props; - const { selection } = this.props.view.state; - const active = isActive(this.props); + const { selection } = view.state; + const active = isActive(view, rest.isActive); - return ( - - {active && ( - - )} - - ); - } + return ( + + {active && ( + + )} + + ); } diff --git a/app/editor/index.tsx b/app/editor/index.tsx index 8e97809e4..60064b8d5 100644 --- a/app/editor/index.tsx +++ b/app/editor/index.tsx @@ -685,13 +685,10 @@ export class Editor extends React.PureComponent< onShowToast={this.props.onShowToast} />