diff --git a/app/components/Editor.tsx b/app/components/Editor.tsx index cd85e1933..4d43d1f6d 100644 --- a/app/components/Editor.tsx +++ b/app/components/Editor.tsx @@ -65,19 +65,18 @@ function Editor(props: Props, ref: React.RefObject | null) { const localRef = React.useRef(); const preferences = auth.user?.preferences; const previousHeadings = React.useRef(null); - const [ - activeLinkEvent, - setActiveLinkEvent, - ] = React.useState(null); + activeLinkElement, + setActiveLink, + ] = React.useState(null); - const handleLinkActive = React.useCallback((event: MouseEvent) => { - setActiveLinkEvent(event); + const handleLinkActive = React.useCallback((element: HTMLAnchorElement) => { + setActiveLink(element); return false; }, []); const handleLinkInactive = React.useCallback(() => { - setActiveLinkEvent(null); + setActiveLink(null); }, []); const handleSearchLink = React.useCallback( @@ -309,10 +308,9 @@ function Editor(props: Props, ref: React.RefObject | null) { minHeight={props.bottomPadding} /> )} - {activeLinkEvent && !shareId && ( + {activeLinkElement && !shareId && ( )} diff --git a/app/components/HoverPreview.tsx b/app/components/HoverPreview.tsx index 67e12ab4a..20eb25d80 100644 --- a/app/components/HoverPreview.tsx +++ b/app/components/HoverPreview.tsx @@ -14,14 +14,13 @@ const DELAY_OPEN = 300; const DELAY_CLOSE = 300; type Props = { - node: HTMLAnchorElement; - event: MouseEvent; + element: HTMLAnchorElement; onClose: () => void; }; -function HoverPreviewInternal({ node, onClose }: Props) { +function HoverPreviewInternal({ element, onClose }: Props) { const { documents } = useStores(); - const slug = parseDocumentSlug(node.href); + const slug = parseDocumentSlug(element.href); const [isVisible, setVisible] = React.useState(false); const timerClose = React.useRef>(); const timerOpen = React.useRef>(); @@ -68,13 +67,13 @@ function HoverPreviewInternal({ node, onClose }: Props) { cardRef.current.addEventListener("mouseleave", startCloseTimer); } - node.addEventListener("mouseout", startCloseTimer); - node.addEventListener("mouseover", stopCloseTimer); - node.addEventListener("mouseover", startOpenTimer); + element.addEventListener("mouseout", startCloseTimer); + element.addEventListener("mouseover", stopCloseTimer); + element.addEventListener("mouseover", startOpenTimer); return () => { - node.removeEventListener("mouseout", startCloseTimer); - node.removeEventListener("mouseover", stopCloseTimer); - node.removeEventListener("mouseover", startOpenTimer); + element.removeEventListener("mouseout", startCloseTimer); + element.removeEventListener("mouseover", stopCloseTimer); + element.removeEventListener("mouseover", startOpenTimer); if (cardRef.current) { cardRef.current.removeEventListener("mouseenter", stopCloseTimer); @@ -88,9 +87,9 @@ function HoverPreviewInternal({ node, onClose }: Props) { clearTimeout(timerClose.current); } }; - }, [node, slug]); + }, [element, slug]); - const anchorBounds = node.getBoundingClientRect(); + const anchorBounds = element.getBoundingClientRect(); const cardBounds = cardRef.current?.getBoundingClientRect(); const left = cardBounds ? Math.min(anchorBounds.left, window.innerWidth - 16 - 350) @@ -105,7 +104,7 @@ function HoverPreviewInternal({ node, onClose }: Props) { aria-hidden >
- + {(content: React.ReactNode) => isVisible ? ( @@ -124,18 +123,18 @@ function HoverPreviewInternal({ node, onClose }: Props) { ); } -function HoverPreview({ node, ...rest }: Props) { +function HoverPreview({ element, ...rest }: Props) { const isMobile = useMobile(); if (isMobile) { return null; } // previews only work for internal doc links for now - if (isExternalUrl(node.href)) { + if (isExternalUrl(element.href)) { return null; } - return ; + return ; } const Animate = styled.div` diff --git a/app/editor/index.tsx b/app/editor/index.tsx index 60064b8d5..b30e9cb1e 100644 --- a/app/editor/index.tsx +++ b/app/editor/index.tsx @@ -100,7 +100,7 @@ export type Props = { event: MouseEvent | React.MouseEvent ) => void; /** Callback when user hovers on any link in the document */ - onHoverLink?: (event: MouseEvent) => boolean; + onHoverLink?: (element: HTMLAnchorElement) => boolean; /** Callback when user presses any key with document focused */ onKeyDown?: (event: React.KeyboardEvent) => void; /** Collection of embed types to render in the document */ diff --git a/shared/editor/marks/Link.tsx b/shared/editor/marks/Link.tsx index 04ce4714b..b73d64d25 100644 --- a/shared/editor/marks/Link.tsx +++ b/shared/editor/marks/Link.tsx @@ -10,7 +10,7 @@ import { Mark as ProsemirrorMark, } from "prosemirror-model"; import { EditorState, Plugin } from "prosemirror-state"; -import { Decoration, DecorationSet } from "prosemirror-view"; +import { Decoration, DecorationSet, EditorView } from "prosemirror-view"; import * as React from "react"; import ReactDOM from "react-dom"; import { isExternalUrl, sanitizeUrl } from "../../utils/urls"; @@ -210,29 +210,28 @@ export default class Link extends Mark { }, }, props: { - decorations: (state) => plugin.getState(state), + decorations: (state: EditorState) => plugin.getState(state), handleDOMEvents: { - mouseover: (view, event: MouseEvent) => { + mouseover: (view: EditorView, event: MouseEvent) => { + const target = (event.target as HTMLElement)?.closest("a"); if ( - event.target instanceof HTMLAnchorElement && - !event.target.className.includes("ProseMirror-widget") && + target instanceof HTMLAnchorElement && + !target.className.includes("ProseMirror-widget") && (!view.editable || (view.editable && !view.hasFocus())) ) { if (this.options.onHoverLink) { - return this.options.onHoverLink(event); + return this.options.onHoverLink(target); } } return false; }, - mousedown: (view, event: MouseEvent) => { - if ( - !(event.target instanceof HTMLAnchorElement) || - event.button !== 0 - ) { + mousedown: (view: EditorView, event: MouseEvent) => { + const target = (event.target as HTMLElement)?.closest("a"); + if (!(target instanceof HTMLAnchorElement) || event.button !== 0) { return false; } - if (event.target.matches(".component-attachment *")) { + if (target.matches(".component-attachment *")) { return false; } @@ -240,9 +239,9 @@ export default class Link extends Mark { // clicking in read-only will navigate if (!view.editable || (view.editable && !view.hasFocus())) { const href = - event.target.href || - (event.target.parentNode instanceof HTMLAnchorElement - ? event.target.parentNode.href + target.href || + (target.parentNode instanceof HTMLAnchorElement + ? target.parentNode.href : ""); try { @@ -262,7 +261,7 @@ export default class Link extends Mark { return false; }, - click: (view, event) => { + click: (view: EditorView, event: MouseEvent) => { if ( !(event.target instanceof HTMLAnchorElement) || event.button !== 0