fix: context menu on longpress on ios safari (#1405)

This commit is contained in:
Aman Harwara
2022-08-17 20:20:18 +05:30
committed by GitHub
parent 13343c9a56
commit 818c3066cc
5 changed files with 119 additions and 14 deletions

View File

@@ -1,6 +1,6 @@
import { FileItem } from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite'
import { FunctionComponent, useCallback } from 'react'
import { FunctionComponent, useCallback, useRef } from 'react'
import { getFileIconComponent } from '../AttachedFilesPopover/getFileIconComponent'
import ListItemConflictIndicator from './ListItemConflictIndicator'
import ListItemFlagIcons from './ListItemFlagIcons'
@@ -9,6 +9,7 @@ import ListItemMetadata from './ListItemMetadata'
import { DisplayableListItemProps } from './Types/DisplayableListItemProps'
import { useResponsiveAppPane } from '../ResponsivePane/ResponsivePaneProvider'
import { AppPaneId } from '../ResponsivePane/AppPaneMetadata'
import { useContextMenuEvent } from '@/Hooks/useContextMenuEvent'
const FileListItem: FunctionComponent<DisplayableListItemProps> = ({
application,
@@ -24,8 +25,11 @@ const FileListItem: FunctionComponent<DisplayableListItemProps> = ({
}) => {
const { toggleAppPane } = useResponsiveAppPane()
const listItemRef = useRef<HTMLDivElement>(null)
const openFileContextMenu = useCallback(
(posX: number, posY: number) => {
filesController.setShowFileContextMenu(false)
filesController.setFileContextMenuLocation({
x: posX,
y: posY,
@@ -66,17 +70,16 @@ const FileListItem: FunctionComponent<DisplayableListItemProps> = ({
'w-5 h-5 flex-shrink-0',
)
useContextMenuEvent(listItemRef, openContextMenu)
return (
<div
ref={listItemRef}
className={`content-list-item flex w-full cursor-pointer items-stretch text-text ${
selected && 'selected border-l-2px border-solid border-info'
}`}
id={item.uuid}
onClick={onClick}
onContextMenu={(event) => {
event.preventDefault()
void openContextMenu(event.clientX, event.clientY)
}}
>
{!hideIcon ? (
<div className="mr-0 flex flex-col items-center justify-between p-4.5 pr-3">

View File

@@ -1,7 +1,7 @@
import { PLAIN_EDITOR_NAME } from '@/Constants/Constants'
import { sanitizeHtmlString, SNNote } from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite'
import { FunctionComponent, useCallback } from 'react'
import { FunctionComponent, useCallback, useRef } from 'react'
import Icon from '@/Components/Icon/Icon'
import ListItemConflictIndicator from './ListItemConflictIndicator'
import ListItemFlagIcons from './ListItemFlagIcons'
@@ -10,6 +10,7 @@ import ListItemMetadata from './ListItemMetadata'
import { DisplayableListItemProps } from './Types/DisplayableListItemProps'
import { useResponsiveAppPane } from '../ResponsivePane/ResponsivePaneProvider'
import { AppPaneId } from '../ResponsivePane/AppPaneMetadata'
import { useContextMenuEvent } from '@/Hooks/useContextMenuEvent'
const NoteListItem: FunctionComponent<DisplayableListItemProps> = ({
application,
@@ -26,12 +27,15 @@ const NoteListItem: FunctionComponent<DisplayableListItemProps> = ({
}) => {
const { toggleAppPane } = useResponsiveAppPane()
const listItemRef = useRef<HTMLDivElement>(null)
const editorForNote = application.componentManager.editorForNote(item as SNNote)
const editorName = editorForNote?.name ?? PLAIN_EDITOR_NAME
const [icon, tint] = application.iconsController.getIconAndTintForNoteType(editorForNote?.package_info.note_type)
const hasFiles = application.items.getFilesForNote(item as SNNote).length > 0
const openNoteContextMenu = (posX: number, posY: number) => {
notesController.setContextMenuOpen(false)
notesController.setContextMenuClickLocation({
x: posX,
y: posY,
@@ -62,17 +66,16 @@ const NoteListItem: FunctionComponent<DisplayableListItemProps> = ({
}
}, [item.uuid, selectionController, toggleAppPane])
useContextMenuEvent(listItemRef, openContextMenu)
return (
<div
ref={listItemRef}
className={`content-list-item flex w-full cursor-pointer items-stretch text-text ${
selected && 'selected border-l-2 border-solid border-info'
}`}
id={item.uuid}
onClick={onClick}
onContextMenu={(event) => {
event.preventDefault()
void openContextMenu(event.clientX, event.clientY)
}}
>
{!hideIcon ? (
<div className="mr-0 flex flex-col items-center justify-between p-4 pr-4">

View File

@@ -0,0 +1,38 @@
import { isIOS } from '@/Utils'
import { RefObject, useCallback, useEffect } from 'react'
import { useLongPressEvent } from './useLongPress'
export const useContextMenuEvent = (elementRef: RefObject<HTMLElement>, listener: (x: number, y: number) => void) => {
const { attachEvents, cleanupEvents } = useLongPressEvent(elementRef, listener)
const handleContextMenuEvent = useCallback(
(event: MouseEvent) => {
event.preventDefault()
listener(event.clientX, event.clientY)
},
[listener],
)
useEffect(() => {
const element = elementRef.current
if (!element) {
return
}
const shouldUseLongPress = isIOS()
element.addEventListener('contextmenu', handleContextMenuEvent)
if (shouldUseLongPress) {
attachEvents()
}
return () => {
element.removeEventListener('contextmenu', handleContextMenuEvent)
if (shouldUseLongPress) {
cleanupEvents()
}
}
}, [attachEvents, cleanupEvents, elementRef, handleContextMenuEvent, listener])
}

View File

@@ -0,0 +1,59 @@
import { RefObject, useCallback, useMemo, useRef } from 'react'
// According to https://reactnative.dev/docs/touchablewithoutfeedback#onlongpress
const ReactNativeLongpressDelay = 370
export const useLongPressEvent = (
elementRef: RefObject<HTMLElement>,
listener: (x: number, y: number) => void,
delay = ReactNativeLongpressDelay,
) => {
const longPressTimeout = useRef<number>()
const clearLongPressTimeout = useCallback(() => {
if (longPressTimeout.current) {
clearTimeout(longPressTimeout.current)
}
}, [])
const createLongPressTimeout = useCallback(
(event: PointerEvent) => {
clearLongPressTimeout()
longPressTimeout.current = window.setTimeout(() => {
const x = event.clientX
const y = event.clientY
listener(x, y)
}, delay)
},
[clearLongPressTimeout, delay, listener],
)
const attachEvents = useCallback(() => {
if (!elementRef.current) {
return
}
elementRef.current.addEventListener('pointerdown', createLongPressTimeout)
elementRef.current.addEventListener('pointercancel', clearLongPressTimeout)
}, [clearLongPressTimeout, createLongPressTimeout, elementRef])
const cleanupEvents = useCallback(() => {
if (!elementRef.current) {
return
}
elementRef.current.removeEventListener('pointerdown', createLongPressTimeout)
elementRef.current.removeEventListener('pointercancel', clearLongPressTimeout)
}, [clearLongPressTimeout, createLongPressTimeout, elementRef])
const memoizedReturn = useMemo(
() => ({
attachEvents,
cleanupEvents,
}),
[attachEvents, cleanupEvents],
)
return memoizedReturn
}

View File

@@ -172,6 +172,11 @@ export const convertStringifiedBooleanToBoolean = (value: string) => {
return value !== 'false'
}
// https://stackoverflow.com/questions/9038625/detect-if-device-is-ios/9039885#9039885
export const isIOS = () =>
(/iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream) ||
(navigator.userAgent.includes('Mac') && 'ontouchend' in document && navigator.maxTouchPoints > 1)
// https://stackoverflow.com/a/57527009/2504429
export const disableIosTextFieldZoom = () => {
const addMaximumScaleToMetaViewport = () => {
@@ -194,10 +199,7 @@ export const disableIosTextFieldZoom = () => {
}
}
// https://stackoverflow.com/questions/9038625/detect-if-device-is-ios/9039885#9039885
const checkIsIOS = () => /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream
if (checkIsIOS()) {
if (isIOS()) {
addMaximumScaleToMetaViewport()
}
}