mirror of
https://github.com/suitenumerique/meet.git
synced 2026-08-15 13:13:56 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f75adef69f | |||
| 9392cd3e30 | |||
| 4c0d89ef81 | |||
| c4335d2809 | |||
| ac503b3ae5 |
@@ -11,12 +11,16 @@ and this project adheres to
|
|||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- 🔥(frontend) drop unused vendored ConnectionObserver
|
- 🔥(frontend) drop unused vendored ConnectionObserver
|
||||||
|
- 🐛(frontend) vendor formatChatMessageLinks and trim surrounding newlines
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- 📈(frontend) downgrade unreachable external home URL from error to event
|
- 📈(frontend) downgrade unreachable external home URL from error to event
|
||||||
- 🐛(frontend) handle 401 responses when syncing user preferences
|
- 🐛(frontend) handle 401 responses when syncing user preferences
|
||||||
- 🐛(frontend) harden speaker test against missing sinks and play errors
|
- 🐛(frontend) harden speaker test against missing sinks and play errors
|
||||||
|
- 🐛(frontend) implement hysteresis band for the control bar layout
|
||||||
|
- 🐛(frontend) fix toolbar ResizeObserver loop and alignment drift
|
||||||
|
- 🐛(analytics) filter benign ResizeObserver loop error in Sentry/PostHog
|
||||||
|
|
||||||
## [1.26.0] - 2026-08-12
|
## [1.26.0] - 2026-08-12
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import type { CaptureResult } from 'posthog-js'
|
||||||
|
|
||||||
|
const IGNORED_EXCEPTION_PATTERNS = [
|
||||||
|
/ResizeObserver loop (completed with undelivered notifications|limit exceeded)/,
|
||||||
|
]
|
||||||
|
|
||||||
|
const shouldIgnoreException = (value: unknown): boolean =>
|
||||||
|
typeof value === 'string' &&
|
||||||
|
IGNORED_EXCEPTION_PATTERNS.some((pattern) => pattern.test(value))
|
||||||
|
|
||||||
|
export const filterExceptions = (
|
||||||
|
event: CaptureResult | null
|
||||||
|
): CaptureResult | null => {
|
||||||
|
if (event?.event !== '$exception') return event
|
||||||
|
|
||||||
|
const exceptionList = event.properties?.['$exception_list']
|
||||||
|
const values: unknown[] = Array.isArray(exceptionList)
|
||||||
|
? exceptionList.map((exception) => exception?.value)
|
||||||
|
: []
|
||||||
|
|
||||||
|
values.push(event.properties?.['$exception_message'])
|
||||||
|
|
||||||
|
return values.some(shouldIgnoreException) ? null : event
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import { useEffect } from 'react'
|
|||||||
import { type ApiUser } from '@/features/auth/api/ApiUser'
|
import { type ApiUser } from '@/features/auth/api/ApiUser'
|
||||||
import { useUser } from '@/features/auth/api/useUser'
|
import { useUser } from '@/features/auth/api/useUser'
|
||||||
import { getPosthog } from '../utils'
|
import { getPosthog } from '../utils'
|
||||||
|
import { filterExceptions } from '../exceptionFilters'
|
||||||
|
|
||||||
export const startAnalyticsSession = (data: ApiUser) => {
|
export const startAnalyticsSession = (data: ApiUser) => {
|
||||||
getPosthog().then((ph) => {
|
getPosthog().then((ph) => {
|
||||||
@@ -47,6 +48,7 @@ export const useAnalytics = ({
|
|||||||
capture_unhandled_rejections: true,
|
capture_unhandled_rejections: true,
|
||||||
capture_console_errors: true,
|
capture_console_errors: true,
|
||||||
},
|
},
|
||||||
|
before_send: filterExceptions,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}, [id, host, flags_api_host, isDisabled])
|
}, [id, host, flags_api_host, isDisabled])
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { ChatRow } from '@/stores/chat'
|
import { ChatRow } from '@/stores/chat'
|
||||||
import React, { useMemo } from 'react'
|
import React, { useMemo } from 'react'
|
||||||
import { formatChatMessageLinks } from '@livekit/components-react'
|
import { formatChatMessageLinks } from '../utils'
|
||||||
import { css } from '@/styled-system/css'
|
import { css } from '@/styled-system/css'
|
||||||
import { Text } from '@/primitives'
|
import { Text } from '@/primitives'
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { tokenize, createDefaultGrammar } from '@livekit/components-core'
|
||||||
|
import { ReactNode } from 'react'
|
||||||
|
|
||||||
|
const defaultGrammar = Object.freeze(createDefaultGrammar())
|
||||||
|
|
||||||
|
export function formatChatMessageLinks(message: string): ReactNode {
|
||||||
|
const trimmedMessage = message.replace(/^[\r\n]+|[\r\n]+$/g, '')
|
||||||
|
return tokenize(trimmedMessage, defaultGrammar).map((tok, i) => {
|
||||||
|
if (typeof tok === `string`) {
|
||||||
|
return tok
|
||||||
|
} else {
|
||||||
|
const content = tok.content.toString()
|
||||||
|
const href =
|
||||||
|
tok.type === `url`
|
||||||
|
? /^http(s?):\/\//.test(content)
|
||||||
|
? content
|
||||||
|
: `https://${content}`
|
||||||
|
: `mailto:${content}`
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
className="lk-chat-link"
|
||||||
|
key={i}
|
||||||
|
href={href}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
{content}
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -26,8 +26,8 @@ const StyledContainer = styled('div', {
|
|||||||
backgroundColor: 'primaryDark.100',
|
backgroundColor: 'primaryDark.100',
|
||||||
maxWidth: '100%',
|
maxWidth: '100%',
|
||||||
opacity: 0,
|
opacity: 0,
|
||||||
transform: 'translateY(3.25rem)',
|
translate: '0 3.25rem',
|
||||||
transition: 'opacity, transform',
|
transition: 'opacity, translate',
|
||||||
transitionDuration: '0.5s',
|
transitionDuration: '0.5s',
|
||||||
transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
pointerEvents: 'none',
|
pointerEvents: 'none',
|
||||||
@@ -36,7 +36,7 @@ const StyledContainer = styled('div', {
|
|||||||
isVisible: {
|
isVisible: {
|
||||||
true: {
|
true: {
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
transform: 'translateY(0)',
|
translate: '0 0',
|
||||||
pointerEvents: 'auto',
|
pointerEvents: 'auto',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -84,7 +84,7 @@ export const ReactionButtonsContainer = ({
|
|||||||
shouldBeCenteredWithToggleButton,
|
shouldBeCenteredWithToggleButton,
|
||||||
setShouldBeCenteredWithToggleButton,
|
setShouldBeCenteredWithToggleButton,
|
||||||
] = useState(false)
|
] = useState(false)
|
||||||
const [rightOffset, setRightOffset] = useState(0)
|
const [offsetX, setOffsetX] = useState(0)
|
||||||
|
|
||||||
const updateArrows = useCallback(() => {
|
const updateArrows = useCallback(() => {
|
||||||
const el = scrollRef.current
|
const el = scrollRef.current
|
||||||
@@ -115,7 +115,7 @@ export const ReactionButtonsContainer = ({
|
|||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (!shouldBeCenteredWithToggleButton || isMobile) {
|
if (!shouldBeCenteredWithToggleButton || isMobile) {
|
||||||
setRightOffset(0)
|
setOffsetX(0)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +133,7 @@ export const ReactionButtonsContainer = ({
|
|||||||
const containerCenterX = containerRect.left + containerRect.width / 2
|
const containerCenterX = containerRect.left + containerRect.width / 2
|
||||||
const shift = toggleCenterX - containerCenterX
|
const shift = toggleCenterX - containerCenterX
|
||||||
if (Math.abs(shift) < 0.5) return
|
if (Math.abs(shift) < 0.5) return
|
||||||
setRightOffset((prev) => prev - shift * 2)
|
setOffsetX((prev) => prev + shift)
|
||||||
}
|
}
|
||||||
|
|
||||||
const schedule = () => {
|
const schedule = () => {
|
||||||
@@ -182,7 +182,7 @@ export const ReactionButtonsContainer = ({
|
|||||||
isVisible={isVisible}
|
isVisible={isVisible}
|
||||||
style={
|
style={
|
||||||
shouldBeCenteredWithToggleButton && !isMobile && adjustedCentering
|
shouldBeCenteredWithToggleButton && !isMobile && adjustedCentering
|
||||||
? { marginRight: `${rightOffset}px` }
|
? { transform: `translateX(${offsetX}px)` }
|
||||||
: { margin: '0 15px' }
|
: { margin: '0 15px' }
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ import type { ToggleButtonProps } from '@/primitives/ToggleButton'
|
|||||||
import { RiArrowDownSLine, RiArrowUpSLine } from '@remixicon/react'
|
import { RiArrowDownSLine, RiArrowUpSLine } from '@remixicon/react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
const CONTROL_BAR_BREAKPOINT = 1100
|
const CONTROL_BAR_BREAKPOINT_WIDE = 1100
|
||||||
|
const CONTROL_BAR_BREAKPOINT_NARROW = 1050
|
||||||
|
|
||||||
const NavigationControls = ({
|
const NavigationControls = ({
|
||||||
onPress,
|
onPress,
|
||||||
@@ -65,10 +66,9 @@ export const LateralMenu = () => {
|
|||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BreakpointObserverProps {
|
interface BreakpointObserverProps {
|
||||||
containerRef: RefObject<HTMLDivElement>
|
containerRef: RefObject<HTMLDivElement>
|
||||||
onWideChange: (isWide: boolean) => void
|
onWideChange: (isWide: boolean | null) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const BreakpointObserver = ({
|
const BreakpointObserver = ({
|
||||||
@@ -76,7 +76,20 @@ const BreakpointObserver = ({
|
|||||||
onWideChange,
|
onWideChange,
|
||||||
}: BreakpointObserverProps) => {
|
}: BreakpointObserverProps) => {
|
||||||
const { width } = useSize(containerRef)
|
const { width } = useSize(containerRef)
|
||||||
const isWide = width > CONTROL_BAR_BREAKPOINT
|
const [isWide, setIsWide] = useState<boolean | null>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!width) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (width > CONTROL_BAR_BREAKPOINT_WIDE) {
|
||||||
|
setIsWide(true)
|
||||||
|
} else if (width <= CONTROL_BAR_BREAKPOINT_NARROW) {
|
||||||
|
setIsWide(false)
|
||||||
|
} else {
|
||||||
|
setIsWide((prev) => (prev === null ? false : prev))
|
||||||
|
}
|
||||||
|
}, [width])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onWideChange(isWide)
|
onWideChange(isWide)
|
||||||
@@ -90,7 +103,7 @@ export const MoreOptions = ({
|
|||||||
}: {
|
}: {
|
||||||
parentElement: RefObject<HTMLDivElement>
|
parentElement: RefObject<HTMLDivElement>
|
||||||
}) => {
|
}) => {
|
||||||
const [isWide, setIsWide] = useState(false)
|
const [isWide, setIsWide] = useState<boolean | null>(null)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav
|
<nav
|
||||||
@@ -107,7 +120,7 @@ export const MoreOptions = ({
|
|||||||
containerRef={parentElement}
|
containerRef={parentElement}
|
||||||
onWideChange={setIsWide}
|
onWideChange={setIsWide}
|
||||||
/>
|
/>
|
||||||
{isWide ? <NavigationControls /> : <LateralMenu />}
|
{isWide !== null && (isWide ? <NavigationControls /> : <LateralMenu />)}
|
||||||
</nav>
|
</nav>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user