diff --git a/src/PageManager.tsx b/src/PageManager.tsx index 567f6dbe..589c491f 100644 --- a/src/PageManager.tsx +++ b/src/PageManager.tsx @@ -1,6 +1,6 @@ import Sidebar from '@/components/Sidebar' import { Separator } from '@/components/ui/separator' -import { cn } from '@/lib/utils' +import { cn, isAndroid } from '@/lib/utils' import NoteListPage from '@/pages/primary/NoteListPage' import HomePage from '@/pages/secondary/HomePage' import { TPageRef } from '@/types' @@ -20,6 +20,7 @@ import NotificationListPage from './pages/primary/NotificationListPage' import { NotificationProvider } from './providers/NotificationProvider' import { useScreenSize } from './providers/ScreenSizeProvider' import { routes } from './routes' +import modalManager from './services/modal-manager.service' export type TPrimaryPageName = keyof typeof PRIMARY_PAGE_MAP @@ -115,6 +116,9 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) { } const onPopState = (e: PopStateEvent) => { + const closeModal = modalManager.pop() + if (closeModal) return + let state = e.state as { index: number; url: string } | null setSecondaryStack((pre) => { const currentItem = pre[pre.length - 1] as TStackItem | undefined @@ -136,10 +140,7 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) { } if (state.index === currentIndex) { - if (currentIndex !== 0) return pre - - window.history.replaceState(null, '', '/') - return [] + return pre } // Go back @@ -171,10 +172,25 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) { }) } + const onLeave = (event: BeforeUnloadEvent) => { + // Cancel the event as stated by the standard. + event.preventDefault() + // Chrome requires returnValue to be set. + event.returnValue = '' + } + window.addEventListener('popstate', onPopState) + if (isAndroid()) { + window.addEventListener('beforeunload', onLeave) + } + return () => { window.removeEventListener('popstate', onPopState) + + if (isAndroid()) { + window.removeEventListener('beforeunload', onLeave) + } } }, []) diff --git a/src/components/Content/index.tsx b/src/components/Content/index.tsx index 1e9921c6..262ac3f6 100644 --- a/src/components/Content/index.tsx +++ b/src/components/Content/index.tsx @@ -51,6 +51,24 @@ const Content = memo( const imageInfos = event.tags .map((tag) => extractImageInfoFromTag(tag)) .filter(Boolean) as TImageInfo[] + const allImages = nodes + .map((node) => { + if (node.type === 'image') { + const imageInfo = imageInfos.find((image) => image.url === node.data) + return imageInfo ?? { url: node.data } + } + if (node.type === 'images') { + const urls = Array.isArray(node.data) ? node.data : [node.data] + return urls.map((url) => { + const imageInfo = imageInfos.find((image) => image.url === url) + return imageInfo ?? { url } + }) + } + return null + }) + .filter(Boolean) + .flat() as TImageInfo[] + let imageIndex = 0 const emojiInfos = extractEmojiInfosFromTags(event.tags) @@ -65,17 +83,18 @@ const Content = memo( return node.data } if (node.type === 'image' || node.type === 'images') { - const imageUrls = Array.isArray(node.data) ? node.data : [node.data] - const images = imageUrls.map( - (url) => imageInfos.find((image) => image.url === url) ?? { url } - ) + const start = imageIndex + const end = imageIndex + (Array.isArray(node.data) ? node.data.length : 1) + imageIndex = end return ( ) } diff --git a/src/components/ImageGallery/index.tsx b/src/components/ImageGallery/index.tsx index 6fc0dd2a..84c40113 100644 --- a/src/components/ImageGallery/index.tsx +++ b/src/components/ImageGallery/index.tsx @@ -1,7 +1,9 @@ +import { randomString } from '@/lib/random' import { cn } from '@/lib/utils' import { useScreenSize } from '@/providers/ScreenSizeProvider' +import modalManager from '@/services/modal-manager.service' import { TImageInfo } from '@/types' -import { ReactNode, useState } from 'react' +import { ReactNode, useEffect, useMemo, useState } from 'react' import { createPortal } from 'react-dom' import Lightbox from 'yet-another-react-lightbox' import Zoom from 'yet-another-react-lightbox/plugins/zoom' @@ -12,24 +14,39 @@ export default function ImageGallery({ className, images, isNsfw = false, - size = 'normal' + size = 'normal', + start = 0, + end = images.length }: { className?: string images: TImageInfo[] isNsfw?: boolean size?: 'normal' | 'small' + start?: number + end?: number }) { + const id = useMemo(() => `image-gallery-${randomString()}`, []) const { isSmallScreen } = useScreenSize() const [index, setIndex] = useState(-1) + useEffect(() => { + if (index >= 0) { + modalManager.register(id, () => { + setIndex(-1) + }) + } else { + modalManager.unregister(id) + } + }, [index]) const handlePhotoClick = (event: React.MouseEvent, current: number) => { event.stopPropagation() event.preventDefault() - setIndex(current) + setIndex(start + current) } + const displayImages = images.slice(start, end) let imageContent: ReactNode | null = null - if (images.length === 1) { + if (displayImages.length === 1) { imageContent = ( handlePhotoClick(e, 0)} /> ) } else if (size === 'small') { imageContent = (
- {images.map((image, i) => ( + {displayImages.map((image, i) => ( ) - } else if (isSmallScreen && (images.length === 2 || images.length === 4)) { + } else if (isSmallScreen && (displayImages.length === 2 || displayImages.length === 4)) { imageContent = (
- {images.map((image, i) => ( + {displayImages.map((image, i) => ( - {images.map((image, i) => ( + {displayImages.map((image, i) => ( +
{imageContent} {index >= 0 && createPortal(
e.stopPropagation()}> ({ src: url }))} plugins={[Zoom]} open={index >= 0} diff --git a/src/components/NoteOptions/index.tsx b/src/components/NoteOptions/index.tsx index 4dd13cb5..3c6e1a87 100644 --- a/src/components/NoteOptions/index.tsx +++ b/src/components/NoteOptions/index.tsx @@ -76,7 +76,6 @@ export default function NoteOptions({ event, className }: { event: Event; classN