···1import {StyleSheet} from 'react-native'
2import type React from 'react'
34-import {isHighDPI} from '#/lib/browser'
5import {atoms as a, platform, useTheme, type ViewStyleProp} from '#/alf'
6import {Fill} from '#/components/Fill'
078/**
9 * Applies and thin border within a bounding box. Used to contrast media from
···33 // while we generally use hairlineWidth (aka 1px),
34 // we make an exception here for high DPI screens
35 // as the 1px border is very noticeable -sfn
36- web: isHighDPI ? 0.5 : StyleSheet.hairlineWidth,
37 }),
38 },
39 opaque
···1import {StyleSheet} from 'react-native'
2import type React from 'react'
304import {atoms as a, platform, useTheme, type ViewStyleProp} from '#/alf'
5import {Fill} from '#/components/Fill'
6+import {IS_HIGH_DPI} from '#/env'
78/**
9 * Applies and thin border within a bounding box. Used to contrast media from
···33 // while we generally use hairlineWidth (aka 1px),
34 // we make an exception here for high DPI screens
35 // as the 1px border is very noticeable -sfn
36+ web: IS_HIGH_DPI ? 0.5 : StyleSheet.hairlineWidth,
37 }),
38 },
39 opaque
···3import {msg} from '@lingui/macro'
4import {useLingui} from '@lingui/react'
56-import {isFirefox, isTouchDevice} from '#/lib/browser'
7import {clamp} from '#/lib/numbers'
8import {atoms as a, useTheme, web} from '#/alf'
9import {useInteractionState} from '#/components/hooks/useInteractionState'
010import {formatTime} from './utils'
1112export function Scrubber({
···100 // a pointerUp event is fired outside the element that captured the
101 // pointer. Firefox clicks on the element the mouse is over, so we have
102 // to make everything unclickable while seeking -sfn
103- if (isFirefox && scrubberActive) {
104 document.body.classList.add('force-no-clicks')
105106 return () => {
···151 <View
152 testID="scrubber"
153 style={[
154- {height: isTouchDevice ? 32 : 18, width: '100%'},
155 a.flex_shrink_0,
156 a.px_xs,
157 ]}
···3import {msg} from '@lingui/macro'
4import {useLingui} from '@lingui/react'
506import {clamp} from '#/lib/numbers'
7import {atoms as a, useTheme, web} from '#/alf'
8import {useInteractionState} from '#/components/hooks/useInteractionState'
9+import {IS_WEB_FIREFOX, IS_WEB_TOUCH_DEVICE} from '#/env'
10import {formatTime} from './utils'
1112export function Scrubber({
···100 // a pointerUp event is fired outside the element that captured the
101 // pointer. Firefox clicks on the element the mouse is over, so we have
102 // to make everything unclickable while seeking -sfn
103+ if (IS_WEB_FIREFOX && scrubberActive) {
104 document.body.classList.add('force-no-clicks')
105106 return () => {
···151 <View
152 testID="scrubber"
153 style={[
154+ {height: IS_WEB_TOUCH_DEVICE ? 32 : 18, width: '100%'},
155 a.flex_shrink_0,
156 a.px_xs,
157 ]}
···4import {msg} from '@lingui/macro'
5import {useLingui} from '@lingui/react'
67-import {isSafari, isTouchDevice} from '#/lib/browser'
8import {atoms as a} from '#/alf'
9import {Mute_Stroke2_Corner0_Rounded as MuteIcon} from '#/components/icons/Mute'
10import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as UnmuteIcon} from '#/components/icons/Speaker'
11import {useVideoVolumeState} from '#/components/Post/Embed/VideoEmbed/VideoVolumeContext'
012import {ControlButton} from './ControlButton'
1314export function VolumeControl({
···57 onPointerEnter={onHover}
58 onPointerLeave={onEndHover}
59 style={[a.relative]}>
60- {hovered && !isTouchDevice && (
61 <Animated.View
62 entering={FadeIn.duration(100)}
63 exiting={FadeOut.duration(100)}
···80 aria-label={_(msg`Volume`)}
81 style={
82 // Ridiculous safari hack for old version of safari. Fixed in sonoma beta -h
83- isSafari ? {height: 92, minHeight: '100%'} : {height: '100%'}
0084 }
85 onChange={onVolumeChange}
86 // @ts-expect-error for old versions of firefox, and then re-using it for targeting the CSS -sfn
···4import {msg} from '@lingui/macro'
5import {useLingui} from '@lingui/react'
607import {atoms as a} from '#/alf'
8import {Mute_Stroke2_Corner0_Rounded as MuteIcon} from '#/components/icons/Mute'
9import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as UnmuteIcon} from '#/components/icons/Speaker'
10import {useVideoVolumeState} from '#/components/Post/Embed/VideoEmbed/VideoVolumeContext'
11+import {IS_WEB_SAFARI, IS_WEB_TOUCH_DEVICE} from '#/env'
12import {ControlButton} from './ControlButton'
1314export function VolumeControl({
···57 onPointerEnter={onHover}
58 onPointerLeave={onEndHover}
59 style={[a.relative]}>
60+ {hovered && !IS_WEB_TOUCH_DEVICE && (
61 <Animated.View
62 entering={FadeIn.duration(100)}
63 exiting={FadeOut.duration(100)}
···80 aria-label={_(msg`Volume`)}
81 style={
82 // Ridiculous safari hack for old version of safari. Fixed in sonoma beta -h
83+ IS_WEB_SAFARI
84+ ? {height: 92, minHeight: '100%'}
85+ : {height: '100%'}
86 }
87 onChange={onVolumeChange}
88 // @ts-expect-error for old versions of firefox, and then re-using it for targeting the CSS -sfn
···1import {type RefObject, useCallback, useEffect, useRef, useState} from 'react'
23-import {isSafari} from '#/lib/browser'
4import {logger} from '#/logger'
5import {useVideoVolumeState} from '#/components/Post/Embed/VideoEmbed/VideoVolumeContext'
067export function useVideoElement(ref: RefObject<HTMLVideoElement | null>) {
8 const [playing, setPlaying] = useState(false)
···41 setCurrentTime(round(ref.current.currentTime) || 0)
42 // HACK: Safari randomly fires `stalled` events when changing between segments
43 // let's just clear the buffering state if the video is still progressing -sfn
44- if (isSafari) {
45 if (bufferingTimeout) clearTimeout(bufferingTimeout)
46 setBuffering(false)
47 }
···1import {type RefObject, useCallback, useEffect, useRef, useState} from 'react'
203import {logger} from '#/logger'
4import {useVideoVolumeState} from '#/components/Post/Embed/VideoEmbed/VideoVolumeContext'
5+import {IS_WEB_SAFARI} from '#/env'
67export function useVideoElement(ref: RefObject<HTMLVideoElement | null>) {
8 const [playing, setPlaying] = useState(false)
···41 setCurrentTime(round(ref.current.currentTime) || 0)
42 // HACK: Safari randomly fires `stalled` events when changing between segments
43 // let's just clear the buffering state if the video is still progressing -sfn
44+ if (IS_WEB_SAFARI) {
45 if (bufferingTimeout) clearTimeout(bufferingTimeout)
46 setBuffering(false)
47 }
···11import {msg} from '@lingui/macro'
12import {useLingui} from '@lingui/react'
1314-import {isFirefox} from '#/lib/browser'
15import {ErrorBoundary} from '#/view/com/util/ErrorBoundary'
16import {atoms as a, useTheme} from '#/alf'
17import {useIsWithinMessage} from '#/components/dms/MessageContext'
···23 VideoEmbedInnerWeb,
24 VideoNotFoundError,
25} from '#/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerWeb'
026import {useActiveVideoWeb} from './ActiveVideoWebContext'
27import * as VideoFallback from './VideoEmbedInner/VideoFallback'
28···3738 useEffect(() => {
39 if (!ref.current) return
40- if (isFullscreen && !isFirefox) return
41 const observer = new IntersectionObserver(
42 entries => {
43 const entry = entries[0]
···150 // observing a div of 100vh height
151 useEffect(() => {
152 if (!ref.current) return
153- if (isFullscreen && !isFirefox) return
154 const observer = new IntersectionObserver(
155 entries => {
156 const entry = entries[0]
···11import {msg} from '@lingui/macro'
12import {useLingui} from '@lingui/react'
13014import {ErrorBoundary} from '#/view/com/util/ErrorBoundary'
15import {atoms as a, useTheme} from '#/alf'
16import {useIsWithinMessage} from '#/components/dms/MessageContext'
···22 VideoEmbedInnerWeb,
23 VideoNotFoundError,
24} from '#/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerWeb'
25+import {IS_WEB_FIREFOX} from '#/env'
26import {useActiveVideoWeb} from './ActiveVideoWebContext'
27import * as VideoFallback from './VideoEmbedInner/VideoFallback'
28···3738 useEffect(() => {
39 if (!ref.current) return
40+ if (isFullscreen && !IS_WEB_FIREFOX) return
41 const observer = new IntersectionObserver(
42 entries => {
43 const entry = entries[0]
···150 // observing a div of 100vh height
151 useEffect(() => {
152 if (!ref.current) return
153+ if (isFullscreen && !IS_WEB_FIREFOX) return
154 const observer = new IntersectionObserver(
155 entries => {
156 const entry = entries[0]
+2-2
src/components/ProfileHoverCard/index.web.tsx
···11import {useNavigation} from '@react-navigation/native'
1213import {useActorStatus} from '#/lib/actor-status'
14-import {isTouchDevice} from '#/lib/browser'
15import {getModerationCauseKey} from '#/lib/moderation'
16import {makeProfileLink} from '#/lib/routes/links'
17import {type NavigationProp} from '#/lib/routes/types'
···43import {Text} from '#/components/Typography'
44import {useSimpleVerificationState} from '#/components/verification'
45import {VerificationCheck} from '#/components/verification/VerificationCheck'
046import {type ProfileHoverCardProps} from './types'
4748const floatingMiddlewares = [
···70 }
71 }
7273- if (props.disable || isTouchDevice) {
74 return props.children
75 } else {
76 return (
···11import {useNavigation} from '@react-navigation/native'
1213import {useActorStatus} from '#/lib/actor-status'
014import {getModerationCauseKey} from '#/lib/moderation'
15import {makeProfileLink} from '#/lib/routes/links'
16import {type NavigationProp} from '#/lib/routes/types'
···42import {Text} from '#/components/Typography'
43import {useSimpleVerificationState} from '#/components/verification'
44import {VerificationCheck} from '#/components/verification/VerificationCheck'
45+import {IS_WEB_TOUCH_DEVICE} from '#/env'
46import {type ProfileHoverCardProps} from './types'
4748const floatingMiddlewares = [
···70 }
71 }
7273+ if (props.disable || IS_WEB_TOUCH_DEVICE) {
74 return props.children
75 } else {
76 return (
+2-3
src/components/SubtleHover.tsx
···1import {View} from 'react-native'
23-import {isTouchDevice} from '#/lib/browser'
4import {atoms as a, useTheme, type ViewStyleProp} from '#/alf'
5-import {IS_NATIVE, IS_WEB} from '#/env'
67export function SubtleHover({
8 style,
···40 )
4142 if (IS_WEB && web) {
43- return isTouchDevice ? null : el
44 } else if (IS_NATIVE && native) {
45 return el
46 }
···1import {View} from 'react-native'
203import {atoms as a, useTheme, type ViewStyleProp} from '#/alf'
4+import {IS_NATIVE, IS_WEB, IS_WEB_TOUCH_DEVICE} from '#/env'
56export function SubtleHover({
7 style,
···39 )
4041 if (IS_WEB && web) {
42+ return IS_WEB_TOUCH_DEVICE ? null : el
43 } else if (IS_NATIVE && native) {
44 return el
45 }
+2-3
src/components/hooks/useFullscreen.ts
···6 useSyncExternalStore,
7} from 'react'
89-import {isFirefox, isSafari} from '#/lib/browser'
10-import {IS_WEB} from '#/env'
1112function fullscreenSubscribe(onChange: () => void) {
13 document.addEventListener('fullscreenchange', onChange)
···3940 // Chrome has an issue where it doesn't scroll back to the top after exiting fullscreen
41 // Let's play it safe and do it if not FF or Safari, since anything else will probably be chromium
42- if (prevIsFullscreen && !isFirefox && !isSafari) {
43 setTimeout(() => {
44 if (scrollYRef.current !== null) {
45 window.scrollTo(0, scrollYRef.current)
···6 useSyncExternalStore,
7} from 'react'
89+import {IS_WEB, IS_WEB_FIREFOX, IS_WEB_SAFARI} from '#/env'
01011function fullscreenSubscribe(onChange: () => void) {
12 document.addEventListener('fullscreenchange', onChange)
···3839 // Chrome has an issue where it doesn't scroll back to the top after exiting fullscreen
40 // Let's play it safe and do it if not FF or Safari, since anything else will probably be chromium
41+ if (prevIsFullscreen && !IS_WEB_FIREFOX && !IS_WEB_SAFARI) {
42 setTimeout(() => {
43 if (scrollYRef.current !== null) {
44 window.scrollTo(0, scrollYRef.current)
···6import TextareaAutosize from 'react-textarea-autosize'
7import {countGraphemes} from 'unicode-segmenter/grapheme'
89-import {isSafari, isTouchDevice} from '#/lib/browser'
10import {MAX_DM_GRAPHEME_LENGTH} from '#/lib/constants'
11import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
12import {
···24import {useSharedInputStyles} from '#/components/forms/TextField'
25import {EmojiArc_Stroke2_Corner0_Rounded as EmojiSmile} from '#/components/icons/Emoji'
26import {PaperPlane_Stroke2_Corner0_Rounded as PaperPlane} from '#/components/icons/PaperPlane'
027import {useExtractEmbedFromFacets} from './MessageInputEmbed'
2829export function MessageInput({
···85 // far too long of a delay, and a subsequent enter press would often just end up doing nothing. A shorter time
86 // frame was also not great, since it was too short to be reliable (i.e. an older system might have a larger
87 // time gap between the two events firing.
88- if (isSafari && e.key === 'Enter' && e.keyCode === 229) {
89 return
90 }
91···228 onChange={onChange}
229 // On mobile web phones, we want to keep the same behavior as the native app. Do not submit the message
230 // in these cases.
231- onKeyDown={isTouchDevice && isMobile ? undefined : onKeyDown}
232 />
233 <Pressable
234 accessibilityRole="button"
···6import TextareaAutosize from 'react-textarea-autosize'
7import {countGraphemes} from 'unicode-segmenter/grapheme'
809import {MAX_DM_GRAPHEME_LENGTH} from '#/lib/constants'
10import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
11import {
···23import {useSharedInputStyles} from '#/components/forms/TextField'
24import {EmojiArc_Stroke2_Corner0_Rounded as EmojiSmile} from '#/components/icons/Emoji'
25import {PaperPlane_Stroke2_Corner0_Rounded as PaperPlane} from '#/components/icons/PaperPlane'
26+import {IS_WEB_SAFARI, IS_WEB_TOUCH_DEVICE} from '#/env'
27import {useExtractEmbedFromFacets} from './MessageInputEmbed'
2829export function MessageInput({
···85 // far too long of a delay, and a subsequent enter press would often just end up doing nothing. A shorter time
86 // frame was also not great, since it was too short to be reliable (i.e. an older system might have a larger
87 // time gap between the two events firing.
88+ if (IS_WEB_SAFARI && e.key === 'Enter' && e.keyCode === 229) {
89 return
90 }
91···228 onChange={onChange}
229 // On mobile web phones, we want to keep the same behavior as the native app. Do not submit the message
230 // in these cases.
231+ onKeyDown={IS_WEB_TOUCH_DEVICE && isMobile ? undefined : onKeyDown}
232 />
233 <Pressable
234 accessibilityRole="button"