diff --git a/apps/readest-app/src/app/reader/components/FoliateViewer.tsx b/apps/readest-app/src/app/reader/components/FoliateViewer.tsx index 056d8ae6..bf105115 100644 --- a/apps/readest-app/src/app/reader/components/FoliateViewer.tsx +++ b/apps/readest-app/src/app/reader/components/FoliateViewer.tsx @@ -309,13 +309,29 @@ const FoliateViewer: React.FC<{ const allImages: { src: string; cfi: string | null }[] = []; docs?.forEach(({ doc, index }) => { - const images = doc.querySelectorAll('img'); - images.forEach((img) => { - if (img.src && index !== undefined && img.parentNode) { - const range = doc.createRange(); - range.selectNodeContents(img); - const cfi = viewRef.current?.getCFI(index, range) || null; - allImages.push({ src: img.src, cfi }); + const elements = doc.querySelectorAll('img, svg'); + elements.forEach((el) => { + if (index === undefined) return; + if (el.localName === 'img') { + const img = el as HTMLImageElement; + if (img.src && img.parentNode) { + const range = doc.createRange(); + range.selectNodeContents(img); + const cfi = viewRef.current?.getCFI(index, range) || null; + allImages.push({ src: img.src, cfi }); + } + } else if (el.localName === 'svg') { + const svg = el as unknown as SVGSVGElement; + const svgImage = svg.querySelector('image'); + const href = + svgImage?.getAttribute('href') || + svgImage?.getAttributeNS('http://www.w3.org/1999/xlink', 'href'); + if (href) { + const range = doc.createRange(); + range.selectNodeContents(svg); + const cfi = viewRef.current?.getCFI(index, range) || null; + allImages.push({ src: href, cfi }); + } } }); }); diff --git a/apps/readest-app/src/app/reader/utils/iframeEventHandlers.ts b/apps/readest-app/src/app/reader/utils/iframeEventHandlers.ts index 12d33bbe..0be62a3d 100644 --- a/apps/readest-app/src/app/reader/utils/iframeEventHandlers.ts +++ b/apps/readest-app/src/app/reader/utils/iframeEventHandlers.ts @@ -294,6 +294,23 @@ export const addLongPressListeners = (bookKey: string, doc: Document) => { }, '*', ); + } else if (target.closest('svg')) { + const svg = target.closest('svg')!; + const svgImage = svg.querySelector('image'); + const href = + svgImage?.getAttribute('href') || + svgImage?.getAttributeNS('http://www.w3.org/1999/xlink', 'href'); + if (href) { + window.postMessage( + { + type: 'iframe-long-press', + bookKey, + elementType: 'image', + src: href, + }, + '*', + ); + } } else if (target.localName === 'table' || target.closest('table')) { const tableTarget = ( target.localName === 'table' ? target : target.closest('table') @@ -313,13 +330,16 @@ export const addLongPressListeners = (bookKey: string, doc: Document) => { const startPress = (event: Event) => { const target = event.target as HTMLElement; const isImage = target.localName === 'img'; + const isSvgImage = !isImage && !!target.closest('svg')?.querySelector('image'); const isTableOrInTable = target.localName === 'table' || target.closest('table'); - if (!isImage && !isTableOrInTable) return; + if (!isImage && !isSvgImage && !isTableOrInTable) return; const elementToTrack = isImage ? target - : ((target.localName === 'table' ? target : target.closest('table')) as HTMLElement); + : isSvgImage + ? (target.closest('svg') as unknown as HTMLElement) + : ((target.localName === 'table' ? target : target.closest('table')) as HTMLElement); // Store initial position for movement detection if ('clientX' in event && 'clientY' in event) { @@ -341,13 +361,16 @@ export const addLongPressListeners = (bookKey: string, doc: Document) => { const handleMove = (event: Event) => { const target = event.target as HTMLElement; const isImage = target.localName === 'img'; + const isSvgImage = !isImage && !!target.closest('svg')?.querySelector('image'); const isTableOrInTable = target.localName === 'table' || target.closest('table'); - if (!isImage && !isTableOrInTable) return; + if (!isImage && !isSvgImage && !isTableOrInTable) return; const elementToTrack = isImage ? target - : ((target.localName === 'table' ? target : target.closest('table')) as HTMLElement); + : isSvgImage + ? (target.closest('svg') as unknown as HTMLElement) + : ((target.localName === 'table' ? target : target.closest('table')) as HTMLElement); // Check if mouse/touch moved beyond threshold - if so, user is probably selecting text or dragging const startPos = pressStartPositions.get(elementToTrack); @@ -384,13 +407,16 @@ export const addLongPressListeners = (bookKey: string, doc: Document) => { const cancelPress = (event: Event) => { const target = event.target as HTMLElement; const isImage = target.localName === 'img'; + const isSvgImage = !isImage && !!target.closest('svg')?.querySelector('image'); const isTableOrInTable = target.localName === 'table' || target.closest('table'); - if (!isImage && !isTableOrInTable) return; + if (!isImage && !isSvgImage && !isTableOrInTable) return; const elementToTrack = isImage ? target - : ((target.localName === 'table' ? target : target.closest('table')) as HTMLElement); + : isSvgImage + ? (target.closest('svg') as unknown as HTMLElement) + : ((target.localName === 'table' ? target : target.closest('table')) as HTMLElement); clearTimeout(pressTimers.get(elementToTrack)); pressTimers.delete(elementToTrack); @@ -398,33 +424,21 @@ export const addLongPressListeners = (bookKey: string, doc: Document) => { }; const processElements = () => { - const images = doc.querySelectorAll('img'); - const tables = doc.querySelectorAll('table'); + const addLongPressListeners = (el: Element) => { + if (el.hasAttribute('data-long-press-added')) return; + el.setAttribute('data-long-press-added', 'true'); + el.addEventListener('mousedown', startPress); + el.addEventListener('mousemove', handleMove); + el.addEventListener('mouseup', cancelPress); + el.addEventListener('mouseleave', cancelPress); + el.addEventListener('touchstart', startPress, { passive: true }); + el.addEventListener('touchmove', handleMove, { passive: true }); + el.addEventListener('touchend', cancelPress); + }; - images.forEach((img) => { - if (!img.hasAttribute('data-long-press-added')) { - img.setAttribute('data-long-press-added', 'true'); - img.addEventListener('mousedown', startPress); - img.addEventListener('mousemove', handleMove); - img.addEventListener('mouseup', cancelPress); - img.addEventListener('mouseleave', cancelPress); - img.addEventListener('touchstart', startPress, { passive: true }); - img.addEventListener('touchmove', handleMove, { passive: true }); - img.addEventListener('touchend', cancelPress); - } - }); - - tables.forEach((table) => { - if (!table.hasAttribute('data-long-press-added')) { - table.setAttribute('data-long-press-added', 'true'); - table.addEventListener('mousedown', startPress); - table.addEventListener('mousemove', handleMove); - table.addEventListener('mouseup', cancelPress); - table.addEventListener('mouseleave', cancelPress); - table.addEventListener('touchstart', startPress, { passive: true }); - table.addEventListener('touchmove', handleMove, { passive: true }); - table.addEventListener('touchend', cancelPress); - } + doc.querySelectorAll('img, table').forEach(addLongPressListeners); + doc.querySelectorAll('svg').forEach((svg) => { + if (svg.querySelector('image')) addLongPressListeners(svg); }); };