From 629ad3f7cd8d57eb2195fbed4c1b879e96f093da Mon Sep 17 00:00:00 2001 From: codytseng Date: Wed, 30 Jul 2025 23:17:55 +0800 Subject: [PATCH] feat: remove picture tab --- package-lock.json | 26 -- package.json | 1 - src/components/BookmarkList/index.tsx | 4 +- src/components/ImageCarousel/index.tsx | 129 ---------- src/components/NoteCard/index.tsx | 9 +- src/components/NoteList/index.tsx | 57 ++--- src/components/PictureContent/index.tsx | 74 ------ src/components/PictureNote/index.tsx | 57 ----- src/components/PictureNoteCard/index.tsx | 64 ----- .../PictureNoteCardMasonry/index.tsx | 40 --- src/components/QuoteList/index.tsx | 2 +- src/components/ui/carousel.tsx | 233 ------------------ src/pages/secondary/NotePage/index.tsx | 13 +- src/types/index.d.ts | 2 +- 14 files changed, 26 insertions(+), 685 deletions(-) delete mode 100644 src/components/ImageCarousel/index.tsx delete mode 100644 src/components/PictureContent/index.tsx delete mode 100644 src/components/PictureNote/index.tsx delete mode 100644 src/components/PictureNoteCard/index.tsx delete mode 100644 src/components/PictureNoteCardMasonry/index.tsx delete mode 100644 src/components/ui/carousel.tsx diff --git a/package-lock.json b/package-lock.json index c27b8de..9794d41 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,7 +41,6 @@ "cmdk": "^1.0.0", "dataloader": "^2.2.3", "dayjs": "^1.11.13", - "embla-carousel-react": "^8.5.1", "emoji-picker-react": "^4.12.2", "flexsearch": "^0.7.43", "franc-min": "^6.2.0", @@ -6132,31 +6131,6 @@ "integrity": "sha512-Lf3++DumRE/QmweGjU+ZcKqQ+3bKkU/qjaKYhIJKEOhgIO9Xs6IiAQFkfFoj+RhgDk4LUeNsLo6plExHqSyu6Q==", "dev": true }, - "node_modules/embla-carousel-react": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/embla-carousel-react/-/embla-carousel-react-8.5.1.tgz", - "integrity": "sha512-z9Y0K84BJvhChXgqn2CFYbfEi6AwEr+FFVVKm/MqbTQ2zIzO1VQri6w67LcfpVF0AjbhwVMywDZqY4alYkjW5w==", - "dependencies": { - "embla-carousel": "8.5.1", - "embla-carousel-reactive-utils": "8.5.1" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - } - }, - "node_modules/embla-carousel-react/node_modules/embla-carousel": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.5.1.tgz", - "integrity": "sha512-JUb5+FOHobSiWQ2EJNaueCNT/cQU9L6XWBbWmorWPQT9bkbk+fhsuLr8wWrzXKagO3oWszBO7MSx+GfaRk4E6A==" - }, - "node_modules/embla-carousel-react/node_modules/embla-carousel-reactive-utils": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/embla-carousel-reactive-utils/-/embla-carousel-reactive-utils-8.5.1.tgz", - "integrity": "sha512-n7VSoGIiiDIc4MfXF3ZRTO59KDp820QDuyBDGlt5/65+lumPHxX2JLz0EZ23hZ4eg4vZGUXwMkYv02fw2JVo/A==", - "peerDependencies": { - "embla-carousel": "8.5.1" - } - }, "node_modules/emoji-picker-react": { "version": "4.12.2", "resolved": "https://registry.npmjs.org/emoji-picker-react/-/emoji-picker-react-4.12.2.tgz", diff --git a/package.json b/package.json index 11bbfeb..bdf68b2 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,6 @@ "cmdk": "^1.0.0", "dataloader": "^2.2.3", "dayjs": "^1.11.13", - "embla-carousel-react": "^8.5.1", "emoji-picker-react": "^4.12.2", "flexsearch": "^0.7.43", "franc-min": "^6.2.0", diff --git a/src/components/BookmarkList/index.tsx b/src/components/BookmarkList/index.tsx index 1b5ed16..c6b44c3 100644 --- a/src/components/BookmarkList/index.tsx +++ b/src/components/BookmarkList/index.tsx @@ -70,7 +70,7 @@ export default function BookmarkList() { {showCount < eventIds.length ? (
- +
) : (
@@ -85,7 +85,7 @@ function BookmarkedNote({ eventId }: { eventId: string }) { const { event, isFetching } = useFetchEvent(eventId) if (isFetching) { - return + return } if (!event) { diff --git a/src/components/ImageCarousel/index.tsx b/src/components/ImageCarousel/index.tsx deleted file mode 100644 index bb85e06..0000000 --- a/src/components/ImageCarousel/index.tsx +++ /dev/null @@ -1,129 +0,0 @@ -import { Carousel, CarouselApi, CarouselContent, CarouselItem } from '@/components/ui/carousel' -import { isTouchDevice } from '@/lib/utils' -import { TImageInfo } from '@/types' -import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react' -import { useEffect, useState } from 'react' -import Lightbox from 'yet-another-react-lightbox' -import Zoom from 'yet-another-react-lightbox/plugins/zoom' -import Image from '../Image' - -export function ImageCarousel({ images }: { images: TImageInfo[] }) { - const [api, setApi] = useState() - const [currentIndex, setCurrentIndex] = useState(0) - const [lightboxIndex, setLightboxIndex] = useState(-1) - - useEffect(() => { - if (!api) { - return - } - - setCurrentIndex(api.selectedScrollSnap()) - - api.on('select', () => { - setCurrentIndex(api.selectedScrollSnap()) - }) - }, [api]) - - const handlePhotoClick = (event: React.MouseEvent, current: number) => { - event.preventDefault() - setLightboxIndex(current) - } - - const onDotClick = (index: number) => { - api?.scrollTo(index) - setCurrentIndex(index) - } - - return ( -
- - - {images.map((image, index) => ( - - handlePhotoClick(e, index)} - /> - - ))} - - - {!isTouchDevice() && ( - - )} - {images.length > 1 && ( - - )} - ({ src: url }))} - plugins={[Zoom]} - open={lightboxIndex >= 0} - close={() => setLightboxIndex(-1)} - controller={{ - closeOnBackdropClick: true, - closeOnPullUp: true, - closeOnPullDown: true - }} - styles={{ toolbar: { paddingTop: '2.25rem' } }} - /> -
- ) -} - -function CarouselDot({ - total, - currentIndex, - onClick -}: { - total: number - currentIndex: number - onClick: (index: number) => void -}) { - return ( -
- {Array.from({ length: total }).map((_, index) => ( -
onClick(index)} - /> - ))} -
- ) -} - -function ArrowButton({ - total, - currentIndex, - onClick -}: { - total: number - currentIndex: number - onClick: (index: number) => void -}) { - return ( -
-
- - -
-
- ) -} diff --git a/src/components/NoteCard/index.tsx b/src/components/NoteCard/index.tsx index 1edabf3..b8bde9d 100644 --- a/src/components/NoteCard/index.tsx +++ b/src/components/NoteCard/index.tsx @@ -1,7 +1,6 @@ import { Skeleton } from '@/components/ui/skeleton' import { useMuteList } from '@/providers/MuteListProvider' import { Event, kinds } from 'nostr-tools' -import { useTranslation } from 'react-i18next' import MainNoteCard from './MainNoteCard' import RepostNoteCard from './RepostNoteCard' @@ -27,13 +26,7 @@ export default function NoteCard({ return } -export function NoteCardLoadingSkeleton({ isPictures }: { isPictures: boolean }) { - const { t } = useTranslation() - - if (isPictures) { - return
{t('loading...')}
- } - +export function NoteCardLoadingSkeleton() { return (
diff --git a/src/components/NoteList/index.tsx b/src/components/NoteList/index.tsx index a5bd99d..3aa3134 100644 --- a/src/components/NoteList/index.tsx +++ b/src/components/NoteList/index.tsx @@ -6,7 +6,6 @@ import { checkAlgoRelay } from '@/lib/relay' import { isSafari } from '@/lib/utils' import { useMuteList } from '@/providers/MuteListProvider' import { useNostr } from '@/providers/NostrProvider' -import { useScreenSize } from '@/providers/ScreenSizeProvider' import { useUserTrust } from '@/providers/UserTrustProvider' import client from '@/services/client.service' import storage from '@/services/local-storage.service' @@ -18,7 +17,6 @@ import { useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import PullToRefresh from 'react-simple-pull-to-refresh' import NoteCard, { NoteCardLoadingSkeleton } from '../NoteCard' -import { PictureNoteCardMasonry } from '../PictureNoteCardMasonry' import Tabs from '../Tabs' const LIMIT = 100 @@ -32,7 +30,8 @@ const KINDS = [ ExtendedKind.COMMENT, ExtendedKind.POLL, ExtendedKind.VOICE, - ExtendedKind.VOICE_COMMENT + ExtendedKind.VOICE_COMMENT, + ExtendedKind.PICTURE ] export default function NoteList({ @@ -57,7 +56,6 @@ export default function NoteList({ skipTrustCheck?: boolean }) { const { t } = useTranslation() - const { isLargeScreen } = useScreenSize() const { pubkey, startLogin } = useNostr() const { mutePubkeys } = useMuteList() const [refreshCount, setRefreshCount] = useState(0) @@ -90,9 +88,6 @@ export default function NoteList({ case 'postsAndReplies': setFilterType('posts') break - case 'pictures': - setFilterType('pictures') - break case 'you': if (!pubkey || pubkey === author) { setFilterType('posts') @@ -147,7 +142,7 @@ export default function NoteList({ } const _filter = { ...filter, - kinds: filterType === 'pictures' ? [ExtendedKind.PICTURE] : KINDS, + kinds: KINDS, limit: areAlgoRelays ? ALGO_LIMIT : LIMIT } if (relayUrls.length === 0 && (_filter.authors?.length || author)) { @@ -310,13 +305,11 @@ export default function NoteList({ ? [ { value: 'posts', label: 'Notes' }, { value: 'postsAndReplies', label: 'Replies' }, - { value: 'pictures', label: 'Pictures' }, { value: 'you', label: 'YouTabName' } ] : [ { value: 'posts', label: 'Notes' }, - { value: 'postsAndReplies', label: 'Replies' }, - { value: 'pictures', label: 'Pictures' } + { value: 'postsAndReplies', label: 'Replies' } ] } onTabChange={(listMode) => { @@ -343,34 +336,24 @@ export default function NoteList({ pullingContent="" >
- {listMode === 'pictures' ? ( - - ) : ( -
- {events - .slice(0, showCount) - .filter( - (event: Event) => - (listMode !== 'posts' || !isReplyNoteEvent(event)) && - (skipTrustCheck || !hideUntrustedNotes || isUserTrusted(event.pubkey)) - ) - .map((event) => ( - - ))} -
- )} + {events + .slice(0, showCount) + .filter( + (event: Event) => + (listMode !== 'posts' || !isReplyNoteEvent(event)) && + (skipTrustCheck || !hideUntrustedNotes || isUserTrusted(event.pubkey)) + ) + .map((event) => ( + + ))} {hasMore || loading ? (
- +
) : events.length ? (
diff --git a/src/components/PictureContent/index.tsx b/src/components/PictureContent/index.tsx deleted file mode 100644 index 40d58cc..0000000 --- a/src/components/PictureContent/index.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { - EmbeddedEmojiParser, - EmbeddedHashtagParser, - EmbeddedLNInvoiceParser, - EmbeddedMentionParser, - EmbeddedNormalUrlParser, - EmbeddedWebsocketUrlParser, - parseContent -} from '@/lib/content-parser' -import { getImageInfosFromEvent } from '@/lib/event' -import { getEmojiInfosFromEmojiTags } from '@/lib/tag' -import { cn } from '@/lib/utils' -import { Event } from 'nostr-tools' -import { memo, useMemo } from 'react' -import { - EmbeddedHashtag, - EmbeddedLNInvoice, - EmbeddedMention, - EmbeddedNormalUrl, - EmbeddedWebsocketUrl -} from '../Embedded' -import Emoji from '../Emoji' -import { ImageCarousel } from '../ImageCarousel' - -const PictureContent = memo(({ event, className }: { event: Event; className?: string }) => { - const images = useMemo(() => getImageInfosFromEvent(event), [event]) - - const nodes = parseContent(event.content, [ - EmbeddedNormalUrlParser, - EmbeddedLNInvoiceParser, - EmbeddedWebsocketUrlParser, - EmbeddedHashtagParser, - EmbeddedMentionParser, - EmbeddedEmojiParser - ]) - - const emojiInfos = getEmojiInfosFromEmojiTags(event.tags) - - return ( -
- -
- {nodes.map((node, index) => { - if (node.type === 'text') { - return node.data - } - if (node.type === 'url') { - return - } - if (node.type === 'invoice') { - return - } - if (node.type === 'websocket-url') { - return - } - if (node.type === 'hashtag') { - return - } - if (node.type === 'mention') { - return - } - if (node.type === 'emoji') { - const shortcode = node.data.split(':')[1] - const emoji = emojiInfos.find((e) => e.shortcode === shortcode) - if (!emoji) return node.data - return - } - })} -
-
- ) -}) -PictureContent.displayName = 'PictureContent' -export default PictureContent diff --git a/src/components/PictureNote/index.tsx b/src/components/PictureNote/index.tsx deleted file mode 100644 index f3881f1..0000000 --- a/src/components/PictureNote/index.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { getUsingClient } from '@/lib/event' -import { Event } from 'nostr-tools' -import { useMemo } from 'react' -import { FormattedTimestamp } from '../FormattedTimestamp' -import NoteStats from '../NoteStats' -import UserAvatar from '../UserAvatar' -import Username from '../Username' -import PictureContent from '../PictureContent' -import NoteOptions from '../NoteOptions' - -export default function PictureNote({ - event, - className, - hideStats = false, - fetchNoteStats = false -}: { - event: Event - className?: string - hideStats?: boolean - fetchNoteStats?: boolean -}) { - const usingClient = useMemo(() => getUsingClient(event), [event]) - - return ( -
-
-
- -
-
- - {usingClient && ( -
using {usingClient}
- )} -
-
- -
-
-
- -
- - {!hideStats && ( - - )} -
- ) -} diff --git a/src/components/PictureNoteCard/index.tsx b/src/components/PictureNoteCard/index.tsx deleted file mode 100644 index b231579..0000000 --- a/src/components/PictureNoteCard/index.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { EmbeddedHashtagParser, EmbeddedMentionParser, parseContent } from '@/lib/content-parser' -import { getImageInfosFromEvent } from '@/lib/event' -import { toNote } from '@/lib/link' -import { tagNameEquals } from '@/lib/tag' -import { cn } from '@/lib/utils' -import { useSecondaryPage } from '@/PageManager' -import { Images } from 'lucide-react' -import { Event } from 'nostr-tools' -import { useMemo } from 'react' -import { EmbeddedHashtag, EmbeddedMention } from '../Embedded' -import Image from '../Image' -import LikeButton from '../NoteStats/LikeButton' -import UserAvatar from '../UserAvatar' -import Username from '../Username' - -export default function PictureNoteCard({ - event, - className -}: { - event: Event - className?: string -}) { - const { push } = useSecondaryPage() - const images = useMemo(() => getImageInfosFromEvent(event), [event]) - const title = useMemo(() => { - const nodes = parseContent(event.tags.find(tagNameEquals('title'))?.[1] ?? event.content, [ - EmbeddedMentionParser, - EmbeddedHashtagParser - ]) - return nodes.map((node, index) => { - if (node.type === 'text') { - return node.data - } - if (node.type === 'mention') { - return - } - if (node.type === 'hashtag') { - return - } - }) - }, [event]) - if (!images.length) return null - - return ( -
push(toNote(event))}> - - {images.length > 1 && ( -
- -
- )} -
-
{title}
-
-
- - -
- -
-
-
- ) -} diff --git a/src/components/PictureNoteCardMasonry/index.tsx b/src/components/PictureNoteCardMasonry/index.tsx deleted file mode 100644 index 8026ef1..0000000 --- a/src/components/PictureNoteCardMasonry/index.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { cn } from '@/lib/utils' -import { Event } from 'nostr-tools' -import { useMemo } from 'react' -import PictureNoteCard from '../PictureNoteCard' - -export function PictureNoteCardMasonry({ - events, - columnCount, - className -}: { - events: Event[] - columnCount: 2 | 3 - className?: string -}) { - const columns = useMemo(() => { - const newColumns: React.ReactNode[][] = Array.from({ length: columnCount }, () => []) - events.forEach((event, i) => { - newColumns[i % columnCount].push( - - ) - }) - return newColumns - }, [events, columnCount]) - - return ( -
- {columns.map((column, i) => ( -
- {column} -
- ))} -
- ) -} diff --git a/src/components/QuoteList/index.tsx b/src/components/QuoteList/index.tsx index 9f42797..ce240be 100644 --- a/src/components/QuoteList/index.tsx +++ b/src/components/QuoteList/index.tsx @@ -135,7 +135,7 @@ export default function QuoteList({ event, className }: { event: Event; classNam
{hasMore || loading ? (
- +
) : (
{t('no more notes')}
diff --git a/src/components/ui/carousel.tsx b/src/components/ui/carousel.tsx deleted file mode 100644 index 103226e..0000000 --- a/src/components/ui/carousel.tsx +++ /dev/null @@ -1,233 +0,0 @@ -import * as React from 'react' -import useEmblaCarousel, { type UseEmblaCarouselType } from 'embla-carousel-react' -import { ArrowLeft, ArrowRight } from 'lucide-react' - -import { cn } from '@/lib/utils' -import { Button } from '@/components/ui/button' - -type CarouselApi = UseEmblaCarouselType[1] -type UseCarouselParameters = Parameters -type CarouselOptions = UseCarouselParameters[0] -type CarouselPlugin = UseCarouselParameters[1] - -type CarouselProps = { - opts?: CarouselOptions - plugins?: CarouselPlugin - orientation?: 'horizontal' | 'vertical' - setApi?: (api: CarouselApi) => void -} - -type CarouselContextProps = { - carouselRef: ReturnType[0] - api: ReturnType[1] - scrollPrev: () => void - scrollNext: () => void - canScrollPrev: boolean - canScrollNext: boolean -} & CarouselProps - -const CarouselContext = React.createContext(null) - -function useCarousel() { - const context = React.useContext(CarouselContext) - - if (!context) { - throw new Error('useCarousel must be used within a ') - } - - return context -} - -const Carousel = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes & CarouselProps ->(({ orientation = 'horizontal', opts, setApi, plugins, className, children, ...props }, ref) => { - const [carouselRef, api] = useEmblaCarousel( - { - ...opts, - axis: orientation === 'horizontal' ? 'x' : 'y' - }, - plugins - ) - const [canScrollPrev, setCanScrollPrev] = React.useState(false) - const [canScrollNext, setCanScrollNext] = React.useState(false) - - const onSelect = React.useCallback((api: CarouselApi) => { - if (!api) { - return - } - - setCanScrollPrev(api.canScrollPrev()) - setCanScrollNext(api.canScrollNext()) - }, []) - - const scrollPrev = React.useCallback(() => { - api?.scrollPrev() - }, [api]) - - const scrollNext = React.useCallback(() => { - api?.scrollNext() - }, [api]) - - const handleKeyDown = React.useCallback( - (event: React.KeyboardEvent) => { - if (event.key === 'ArrowLeft') { - event.preventDefault() - scrollPrev() - } else if (event.key === 'ArrowRight') { - event.preventDefault() - scrollNext() - } - }, - [scrollPrev, scrollNext] - ) - - React.useEffect(() => { - if (!api || !setApi) { - return - } - - setApi(api) - }, [api, setApi]) - - React.useEffect(() => { - if (!api) { - return - } - - onSelect(api) - api.on('reInit', onSelect) - api.on('select', onSelect) - - return () => { - api?.off('select', onSelect) - } - }, [api, onSelect]) - - return ( - -
- {children} -
-
- ) -}) -Carousel.displayName = 'Carousel' - -const CarouselContent = React.forwardRef>( - ({ className, ...props }, ref) => { - const { carouselRef, orientation } = useCarousel() - - return ( -
-
-
- ) - } -) -CarouselContent.displayName = 'CarouselContent' - -const CarouselItem = React.forwardRef>( - ({ className, ...props }, ref) => { - const { orientation } = useCarousel() - - return ( -
- ) - } -) -CarouselItem.displayName = 'CarouselItem' - -const CarouselPrevious = React.forwardRef>( - ({ className, variant = 'outline', size = 'icon', ...props }, ref) => { - const { orientation, scrollPrev, canScrollPrev } = useCarousel() - - return ( - - ) - } -) -CarouselPrevious.displayName = 'CarouselPrevious' - -const CarouselNext = React.forwardRef>( - ({ className, variant = 'outline', size = 'icon', ...props }, ref) => { - const { orientation, scrollNext, canScrollNext } = useCarousel() - - return ( - - ) - } -) -CarouselNext.displayName = 'CarouselNext' - -export { type CarouselApi, Carousel, CarouselContent, CarouselItem, CarouselPrevious, CarouselNext } diff --git a/src/pages/secondary/NotePage/index.tsx b/src/pages/secondary/NotePage/index.tsx index 4b5dec2..e48d048 100644 --- a/src/pages/secondary/NotePage/index.tsx +++ b/src/pages/secondary/NotePage/index.tsx @@ -3,7 +3,6 @@ import ContentPreview from '@/components/ContentPreview' import Note from '@/components/Note' import NoteInteractions from '@/components/NoteInteractions' import NoteStats from '@/components/NoteStats' -import PictureNote from '@/components/PictureNote' import UserAvatar from '@/components/UserAvatar' import { Card } from '@/components/ui/card' import { Separator } from '@/components/ui/separator' @@ -11,7 +10,7 @@ import { Skeleton } from '@/components/ui/skeleton' import { ExtendedKind } from '@/constants' import { useFetchEvent } from '@/hooks' import SecondaryPageLayout from '@/layouts/SecondaryPageLayout' -import { getParentBech32Id, getParentETag, getRootBech32Id, isPictureEvent } from '@/lib/event' +import { getParentBech32Id, getParentETag, getRootBech32Id } from '@/lib/event' import { toNote, toNoteList } from '@/lib/link' import { tagNameEquals } from '@/lib/tag' import { cn } from '@/lib/utils' @@ -62,16 +61,6 @@ const NotePage = forwardRef(({ id, index }: { id?: string; index?: number }, ref } if (!event) return - if (isPictureEvent(event)) { - return ( - - - - - - ) - } - return (
diff --git a/src/types/index.d.ts b/src/types/index.d.ts index 5f0911b..ab8fb60 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -108,7 +108,7 @@ export type TImageInfo = { pubkey?: string } -export type TNoteListMode = 'posts' | 'postsAndReplies' | 'pictures' | 'you' +export type TNoteListMode = 'posts' | 'postsAndReplies' | 'you' export type TNotificationType = 'all' | 'mentions' | 'reactions' | 'zaps'