mirror of
https://github.com/dergigi/boris.git
synced 2025-12-17 22:54:30 +01:00
fix(mobile): use selectionchange event for immediate text selection detection
Replace mouseup/touchend handlers with selectionchange event listener for more reliable mobile text selection detection. This fixes the issue where the highlight button required an extra tap to become active on mobile devices. - Extract selection checking logic into shared checkSelection function - Use selectionchange event with requestAnimationFrame for immediate detection - Remove onMouseUp and onTouchEnd props from VideoEmbedProcessor - Simplify code by eliminating separate mouse/touch event handlers
This commit is contained in:
@@ -133,7 +133,7 @@ const ContentPanel: React.FC<ContentPanelProps> = ({
|
|||||||
return selectedUrl || `${title || ''}:${(markdown || html || '').length}`
|
return selectedUrl || `${title || ''}:${(markdown || html || '').length}`
|
||||||
}, [selectedUrl, title, markdown, html])
|
}, [selectedUrl, title, markdown, html])
|
||||||
|
|
||||||
const { contentRef, handleSelectionEnd } = useHighlightInteractions({
|
const { contentRef } = useHighlightInteractions({
|
||||||
onHighlightClick,
|
onHighlightClick,
|
||||||
selectedHighlightId,
|
selectedHighlightId,
|
||||||
onTextSelection,
|
onTextSelection,
|
||||||
@@ -815,8 +815,6 @@ const ContentPanel: React.FC<ContentPanelProps> = ({
|
|||||||
html={finalHtml}
|
html={finalHtml}
|
||||||
renderVideoLinksAsEmbeds={settings?.renderVideoLinksAsEmbeds === true}
|
renderVideoLinksAsEmbeds={settings?.renderVideoLinksAsEmbeds === true}
|
||||||
className="reader-markdown"
|
className="reader-markdown"
|
||||||
onMouseUp={handleSelectionEnd}
|
|
||||||
onTouchEnd={handleSelectionEnd}
|
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="reader-markdown">
|
<div className="reader-markdown">
|
||||||
@@ -830,8 +828,6 @@ const ContentPanel: React.FC<ContentPanelProps> = ({
|
|||||||
html={finalHtml || html || ''}
|
html={finalHtml || html || ''}
|
||||||
renderVideoLinksAsEmbeds={settings?.renderVideoLinksAsEmbeds === true}
|
renderVideoLinksAsEmbeds={settings?.renderVideoLinksAsEmbeds === true}
|
||||||
className="reader-html"
|
className="reader-html"
|
||||||
onMouseUp={handleSelectionEnd}
|
|
||||||
onTouchEnd={handleSelectionEnd}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ interface VideoEmbedProcessorProps {
|
|||||||
html: string
|
html: string
|
||||||
renderVideoLinksAsEmbeds: boolean
|
renderVideoLinksAsEmbeds: boolean
|
||||||
className?: string
|
className?: string
|
||||||
onMouseUp?: (e: React.MouseEvent) => void
|
|
||||||
onTouchEnd?: (e: React.TouchEvent) => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -17,9 +15,7 @@ interface VideoEmbedProcessorProps {
|
|||||||
const VideoEmbedProcessor = forwardRef<HTMLDivElement, VideoEmbedProcessorProps>(({
|
const VideoEmbedProcessor = forwardRef<HTMLDivElement, VideoEmbedProcessorProps>(({
|
||||||
html,
|
html,
|
||||||
renderVideoLinksAsEmbeds,
|
renderVideoLinksAsEmbeds,
|
||||||
className,
|
className
|
||||||
onMouseUp,
|
|
||||||
onTouchEnd
|
|
||||||
}, ref) => {
|
}, ref) => {
|
||||||
// Process HTML and extract video URLs in a single pass to keep them in sync
|
// Process HTML and extract video URLs in a single pass to keep them in sync
|
||||||
const { processedHtml, videoUrls } = useMemo(() => {
|
const { processedHtml, videoUrls } = useMemo(() => {
|
||||||
@@ -109,8 +105,6 @@ const VideoEmbedProcessor = forwardRef<HTMLDivElement, VideoEmbedProcessorProps>
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
className={className}
|
className={className}
|
||||||
dangerouslySetInnerHTML={{ __html: processedHtml }}
|
dangerouslySetInnerHTML={{ __html: processedHtml }}
|
||||||
onMouseUp={onMouseUp}
|
|
||||||
onTouchEnd={onTouchEnd}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -119,7 +113,7 @@ const VideoEmbedProcessor = forwardRef<HTMLDivElement, VideoEmbedProcessorProps>
|
|||||||
const parts = processedHtml.split(/(__VIDEO_EMBED_\d+__)/)
|
const parts = processedHtml.split(/(__VIDEO_EMBED_\d+__)/)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={ref} className={className} onMouseUp={onMouseUp} onTouchEnd={onTouchEnd}>
|
<div ref={ref} className={className}>
|
||||||
{parts.map((part, index) => {
|
{parts.map((part, index) => {
|
||||||
const videoMatch = part.match(/^__VIDEO_EMBED_(\d+)__$/)
|
const videoMatch = part.match(/^__VIDEO_EMBED_(\d+)__$/)
|
||||||
if (videoMatch) {
|
if (videoMatch) {
|
||||||
|
|||||||
@@ -93,9 +93,8 @@ export const useHighlightInteractions = ({
|
|||||||
return () => clearTimeout(timeoutId)
|
return () => clearTimeout(timeoutId)
|
||||||
}, [selectedHighlightId, contentVersion])
|
}, [selectedHighlightId, contentVersion])
|
||||||
|
|
||||||
// Handle text selection (works for both mouse and touch)
|
// Shared function to check and handle text selection
|
||||||
const handleSelectionEnd = useCallback(() => {
|
const checkSelection = useCallback(() => {
|
||||||
setTimeout(() => {
|
|
||||||
const selection = window.getSelection()
|
const selection = window.getSelection()
|
||||||
if (!selection || selection.rangeCount === 0) {
|
if (!selection || selection.rangeCount === 0) {
|
||||||
onClearSelection?.()
|
onClearSelection?.()
|
||||||
@@ -110,9 +109,21 @@ export const useHighlightInteractions = ({
|
|||||||
} else {
|
} else {
|
||||||
onClearSelection?.()
|
onClearSelection?.()
|
||||||
}
|
}
|
||||||
}, 10)
|
|
||||||
}, [onTextSelection, onClearSelection])
|
}, [onTextSelection, onClearSelection])
|
||||||
|
|
||||||
return { contentRef, handleSelectionEnd }
|
// Listen to selectionchange events for immediate detection (works reliably on mobile)
|
||||||
|
useEffect(() => {
|
||||||
|
const handleSelectionChange = () => {
|
||||||
|
// Use requestAnimationFrame to ensure selection is checked after browser updates
|
||||||
|
requestAnimationFrame(checkSelection)
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('selectionchange', handleSelectionChange)
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('selectionchange', handleSelectionChange)
|
||||||
|
}
|
||||||
|
}, [checkSelection])
|
||||||
|
|
||||||
|
return { contentRef }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user