Merge branch 'ditch-nextjs' into canary
This commit is contained in:
@@ -1,25 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { useSelectedLayoutSegments } from 'next/navigation';
|
||||
|
||||
export const ActiveLink = ({
|
||||
href,
|
||||
className,
|
||||
activeClassName,
|
||||
children,
|
||||
}: {
|
||||
href: string;
|
||||
className: string;
|
||||
activeClassName: string;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
const segments = useSelectedLayoutSegments();
|
||||
const isActive = href.includes(segments[1]);
|
||||
|
||||
return (
|
||||
<Link prefetch={false} href={href} className={`${className}` + ' ' + (isActive ? `${activeClassName}` : '')}>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
@@ -1,33 +1,38 @@
|
||||
'use client';
|
||||
|
||||
import { platform } from '@tauri-apps/api/os';
|
||||
import { ArrowLeft, ArrowRight, Refresh } from 'iconoir-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useLayoutEffect, useState } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
export default function AppActions() {
|
||||
const router = useRouter();
|
||||
const [os, setOS] = useState('');
|
||||
|
||||
const goBack = () => {
|
||||
router.back();
|
||||
window.history.back();
|
||||
};
|
||||
|
||||
const goForward = () => {
|
||||
router.forward();
|
||||
window.history.forward();
|
||||
};
|
||||
|
||||
const reload = () => {
|
||||
router.refresh();
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const getPlatform = async () => {
|
||||
const result = await platform();
|
||||
setOS(result);
|
||||
};
|
||||
const getPlatform = useCallback(async () => {
|
||||
const { platform } = await import('@tauri-apps/api/os');
|
||||
const result = await platform();
|
||||
|
||||
getPlatform().catch(console.error);
|
||||
setOS(result);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
let ignore = false;
|
||||
|
||||
if (!ignore) {
|
||||
getPlatform().catch(console.error);
|
||||
}
|
||||
|
||||
return () => {
|
||||
ignore = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const AppActions = dynamic(() => import('@components/appHeader/actions'), {
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
const EventCollector = dynamic(() => import('@components/eventCollector'), {
|
||||
ssr: false,
|
||||
});
|
||||
import AppActions from '@components/appHeader/actions';
|
||||
import EventCollector from '@components/eventCollector';
|
||||
|
||||
export default function AppHeader({ collector }: { collector: boolean }) {
|
||||
return (
|
||||
|
||||
@@ -1,31 +1,21 @@
|
||||
import { ImageWithFallback } from '@components/imageWithFallback';
|
||||
|
||||
import { DEFAULT_AVATAR } from '@stores/constants';
|
||||
|
||||
import { useChannelMetadata } from '@utils/hooks/useChannelMetadata';
|
||||
|
||||
import Link from 'next/link';
|
||||
|
||||
export const ChannelListItem = ({ data }: { data: any }) => {
|
||||
const channel = useChannelMetadata(data.event_id, data.metadata);
|
||||
|
||||
return (
|
||||
<Link
|
||||
prefetch={false}
|
||||
href={`/nostr/channel?channel-id=${data.event_id}`}
|
||||
<a
|
||||
href={`/channel?id=${data.event_id}`}
|
||||
className="inline-flex items-center gap-2 rounded-md px-2.5 py-1.5 hover:bg-zinc-900"
|
||||
>
|
||||
<div className="relative h-5 w-5 shrink-0 overflow-hidden rounded">
|
||||
<ImageWithFallback
|
||||
src={channel?.picture || DEFAULT_AVATAR}
|
||||
alt={data.event_id}
|
||||
fill={true}
|
||||
className="rounded object-cover"
|
||||
/>
|
||||
<div className="relative h-5 w-5 shrink-0 rounded">
|
||||
<img src={channel?.picture || DEFAULT_AVATAR} alt={data.event_id} className="h-5 w-5 rounded object-cover" />
|
||||
</div>
|
||||
<div>
|
||||
<h5 className="truncate text-sm font-medium text-zinc-400">{channel?.name.toLowerCase()}</h5>
|
||||
</div>
|
||||
</Link>
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
import { ChatListItem } from '@components/chats/chatListItem';
|
||||
import { ChatModal } from '@components/chats/chatModal';
|
||||
import { ImageWithFallback } from '@components/imageWithFallback';
|
||||
|
||||
import { DEFAULT_AVATAR } from '@stores/constants';
|
||||
|
||||
import { getChats } from '@utils/storage';
|
||||
|
||||
import useLocalStorage from '@rehooks/local-storage';
|
||||
import Link from 'next/link';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export default function ChatList() {
|
||||
const [list, setList] = useState([]);
|
||||
const [activeAccount]: any = useLocalStorage('account', {});
|
||||
const profile = JSON.parse(activeAccount.metadata);
|
||||
const profile = activeAccount.metadata ? JSON.parse(activeAccount.metadata) : null;
|
||||
|
||||
useEffect(() => {
|
||||
let ignore = false;
|
||||
@@ -33,17 +31,15 @@ export default function ChatList() {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-px">
|
||||
<Link
|
||||
prefetch={false}
|
||||
href={`/nostr/chat?pubkey=${activeAccount.pubkey}`}
|
||||
<a
|
||||
href={`/chat?pubkey=${activeAccount.pubkey}`}
|
||||
className="inline-flex items-center gap-2 rounded-md px-2.5 py-1.5 hover:bg-zinc-900"
|
||||
>
|
||||
<div className="relative h-5 w-5 shrink overflow-hidden rounded bg-white">
|
||||
<ImageWithFallback
|
||||
<div className="relative h-5 w-5 shrink rounded bg-white">
|
||||
<img
|
||||
src={profile?.picture || DEFAULT_AVATAR}
|
||||
alt={activeAccount.pubkey}
|
||||
fill={true}
|
||||
className="rounded object-cover"
|
||||
className="h-5 w-5 rounded object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
@@ -51,7 +47,7 @@ export default function ChatList() {
|
||||
{profile?.display_name || profile?.name} <span className="text-zinc-500">(you)</span>
|
||||
</h5>
|
||||
</div>
|
||||
</Link>
|
||||
</a>
|
||||
{list.map((item) => (
|
||||
<ChatListItem key={item.id} pubkey={item.pubkey} />
|
||||
))}
|
||||
|
||||
@@ -1,34 +1,24 @@
|
||||
import { ImageWithFallback } from '@components/imageWithFallback';
|
||||
|
||||
import { DEFAULT_AVATAR } from '@stores/constants';
|
||||
|
||||
import { useProfileMetadata } from '@utils/hooks/useProfileMetadata';
|
||||
import { shortenKey } from '@utils/shortenKey';
|
||||
|
||||
import Link from 'next/link';
|
||||
|
||||
export const ChatListItem = ({ pubkey }: { pubkey: string }) => {
|
||||
const profile = useProfileMetadata(pubkey);
|
||||
|
||||
return (
|
||||
<Link
|
||||
prefetch={false}
|
||||
href={`/nostr/chat?pubkey=${pubkey}`}
|
||||
<a
|
||||
href={`/chat?pubkey=${pubkey}`}
|
||||
className="inline-flex items-center gap-2 rounded-md px-2.5 py-1.5 hover:bg-zinc-900"
|
||||
>
|
||||
<div className="relative h-5 w-5 shrink overflow-hidden rounded">
|
||||
<ImageWithFallback
|
||||
src={profile?.picture || DEFAULT_AVATAR}
|
||||
alt={pubkey}
|
||||
fill={true}
|
||||
className="rounded object-cover"
|
||||
/>
|
||||
<div className="relative h-5 w-5 shrink rounded">
|
||||
<img src={profile?.picture || DEFAULT_AVATAR} alt={pubkey} className="h-5 w-5 rounded object-cover" />
|
||||
</div>
|
||||
<div>
|
||||
<h5 className="text-sm font-medium text-zinc-400">
|
||||
{profile?.display_name || profile?.name || shortenKey(pubkey)}
|
||||
</h5>
|
||||
</div>
|
||||
</Link>
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,28 +1,24 @@
|
||||
import { ImageWithFallback } from '@components/imageWithFallback';
|
||||
|
||||
import { DEFAULT_AVATAR } from '@stores/constants';
|
||||
|
||||
import { shortenKey } from '@utils/shortenKey';
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { navigate } from 'vite-plugin-ssr/client/router';
|
||||
|
||||
export const ChatModalUser = ({ data }: { data: any }) => {
|
||||
const router = useRouter();
|
||||
const profile = JSON.parse(data.metadata);
|
||||
|
||||
const openNewChat = () => {
|
||||
router.push(`/nostr/chat?pubkey=${data.pubkey}`, { forceOptimisticNavigation: true });
|
||||
navigate(`/chat?pubkey=${data.pubkey}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="group flex items-center justify-between px-3 py-2 hover:bg-zinc-800">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="relative h-10 w-10 shrink overflow-hidden rounded-md">
|
||||
<ImageWithFallback
|
||||
<div className="relative h-10 w-10 shrink-0 rounded-md">
|
||||
<img
|
||||
src={profile?.picture || DEFAULT_AVATAR}
|
||||
alt={data.pubkey}
|
||||
fill={true}
|
||||
className="rounded-md object-cover"
|
||||
className="h-10 w-10 rounded-md object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex w-full flex-1 flex-col items-start text-start">
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { ImageWithFallback } from '@components/imageWithFallback';
|
||||
|
||||
import { DEFAULT_AVATAR } from '@stores/constants';
|
||||
|
||||
import { useProfileMetadata } from '@utils/hooks/useProfileMetadata';
|
||||
@@ -15,13 +13,8 @@ export const MessageUser = ({ pubkey, time }: { pubkey: string; time: number })
|
||||
|
||||
return (
|
||||
<div className="group flex items-start gap-3">
|
||||
<div className="relative h-9 w-9 shrink overflow-hidden rounded-md">
|
||||
<ImageWithFallback
|
||||
src={profile?.picture || DEFAULT_AVATAR}
|
||||
alt={pubkey}
|
||||
fill={true}
|
||||
className="rounded-md object-cover"
|
||||
/>
|
||||
<div className="relative h-9 w-9 shrink rounded-md">
|
||||
<img src={profile?.picture || DEFAULT_AVATAR} alt={pubkey} className="h-9 w-9 rounded-md object-cover" />
|
||||
</div>
|
||||
<div className="flex w-full flex-1 items-start justify-between">
|
||||
<div className="flex items-baseline gap-2 text-sm">
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
'use client';
|
||||
|
||||
import { NetworkStatusIndicator } from '@components/networkStatusIndicator';
|
||||
import { RelayContext } from '@components/relaysProvider';
|
||||
|
||||
@@ -15,7 +13,7 @@ import { useCallback, useContext, useEffect, useRef } from 'react';
|
||||
|
||||
export default function EventCollector() {
|
||||
const [pool, relays]: any = useContext(RelayContext);
|
||||
const [activeAccount]: any = useLocalStorage('account', {});
|
||||
const [activeAccount]: any = useLocalStorage('account', null);
|
||||
|
||||
const setHasNewerNote = useSetAtom(hasNewerNoteAtom);
|
||||
const follows = JSON.parse(activeAccount.follows);
|
||||
@@ -108,7 +106,15 @@ export default function EventCollector() {
|
||||
}, [activeAccount.pubkey, activeAccount.id, follows, pool, relays, setHasNewerNote]);
|
||||
|
||||
useEffect(() => {
|
||||
subscribe();
|
||||
let ignore = false;
|
||||
|
||||
if (!ignore) {
|
||||
subscribe();
|
||||
}
|
||||
|
||||
return () => {
|
||||
ignore = true;
|
||||
};
|
||||
}, [setHasNewerNote, subscribe]);
|
||||
|
||||
return <NetworkStatusIndicator />;
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import { noteContentAtom } from '@stores/note';
|
||||
|
||||
import data from '@emoji-mart/data';
|
||||
import Picker from '@emoji-mart/react';
|
||||
import * as Popover from '@radix-ui/react-popover';
|
||||
import { Emoji } from 'iconoir-react';
|
||||
import { useAtom } from 'jotai';
|
||||
|
||||
export default function EmojiPicker() {
|
||||
const [value, setValue] = useAtom(noteContentAtom);
|
||||
|
||||
return (
|
||||
<Popover.Root>
|
||||
<Popover.Trigger asChild>
|
||||
<button className="inline-flex h-6 w-6 cursor-pointer items-center justify-center rounded-md hover:bg-zinc-700">
|
||||
<Emoji width={16} height={16} className="text-zinc-400" />
|
||||
</button>
|
||||
</Popover.Trigger>
|
||||
<Popover.Portal>
|
||||
<Popover.Content
|
||||
className="rounded-md will-change-[transform,opacity] data-[state=open]:data-[side=bottom]:animate-slideUpAndFade data-[state=open]:data-[side=left]:animate-slideRightAndFade data-[state=open]:data-[side=right]:animate-slideLeftAndFade data-[state=open]:data-[side=top]:animate-slideDownAndFade"
|
||||
sideOffset={5}
|
||||
>
|
||||
<Picker
|
||||
data={data}
|
||||
emojiSize={16}
|
||||
navPosition={'none'}
|
||||
skinTonePosition={'none'}
|
||||
onEmojiSelect={(res) => setValue(value + ' ' + res.native)}
|
||||
/>
|
||||
<Popover.Arrow className="fill-[#141516]" />
|
||||
</Popover.Content>
|
||||
</Popover.Portal>
|
||||
</Popover.Root>
|
||||
);
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import { DEFAULT_AVATAR } from '@stores/constants';
|
||||
|
||||
import Image from 'next/image';
|
||||
import { memo, useEffect, useState } from 'react';
|
||||
|
||||
export const ImageWithFallback = memo(function ImageWithFallback({
|
||||
src,
|
||||
alt,
|
||||
fill,
|
||||
className,
|
||||
}: {
|
||||
src: string;
|
||||
alt: string;
|
||||
fill: boolean;
|
||||
className: string;
|
||||
}) {
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
setError(null);
|
||||
}, [src]);
|
||||
|
||||
return (
|
||||
<Image src={error ? DEFAULT_AVATAR : src} alt={alt} fill={fill} className={className} onError={setError} priority />
|
||||
);
|
||||
});
|
||||
23
src/components/layouts/newsfeed.tsx
Normal file
23
src/components/layouts/newsfeed.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import AppHeader from '@components/appHeader';
|
||||
import MultiAccounts from '@components/multiAccounts';
|
||||
|
||||
export default function NewsfeedLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="h-screen w-screen bg-zinc-50 text-zinc-900 dark:bg-black dark:text-white">
|
||||
<div className="flex h-screen w-full flex-col">
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
className="relative h-11 shrink-0 border-b border-zinc-100 bg-white dark:border-zinc-900 dark:bg-black"
|
||||
>
|
||||
<AppHeader collector={true} />
|
||||
</div>
|
||||
<div className="relative flex min-h-0 w-full flex-1">
|
||||
<div className="relative w-[68px] shrink-0 border-r border-zinc-900">
|
||||
<MultiAccounts />
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
17
src/components/layouts/onboarding.tsx
Normal file
17
src/components/layouts/onboarding.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import AppHeader from '@components/appHeader';
|
||||
|
||||
export default function OnboardingLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="h-screen w-screen bg-zinc-50 text-zinc-900 dark:bg-black dark:text-white">
|
||||
<div className="flex h-screen w-full flex-col">
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
className="relative h-11 shrink-0 border-b border-zinc-100 bg-white dark:border-zinc-900 dark:bg-black"
|
||||
>
|
||||
<AppHeader collector={false} />
|
||||
</div>
|
||||
<div className="relative flex min-h-0 w-full flex-1">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
12
src/components/link.tsx
Normal file
12
src/components/link.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { usePageContext } from '@utils/hooks/usePageContext';
|
||||
|
||||
import { AnchorHTMLAttributes, ClassAttributes } from 'react';
|
||||
|
||||
export function Link(
|
||||
props: JSX.IntrinsicAttributes & ClassAttributes<HTMLAnchorElement> & AnchorHTMLAttributes<HTMLAnchorElement>,
|
||||
activeClass: string
|
||||
) {
|
||||
const pageContext = usePageContext();
|
||||
const className = [props.className, pageContext.urlPathname === props.href && activeClass].filter(Boolean).join(' ');
|
||||
return <a {...props} className={className} />;
|
||||
}
|
||||
@@ -1,20 +1,16 @@
|
||||
'use client';
|
||||
|
||||
import { DEFAULT_AVATAR } from '@stores/constants';
|
||||
|
||||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
|
||||
import { writeText } from '@tauri-apps/api/clipboard';
|
||||
import { LogOut, ProfileCircle, Settings } from 'iconoir-react';
|
||||
import Image from 'next/image';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { nip19 } from 'nostr-tools';
|
||||
import { navigate } from 'vite-plugin-ssr/client/router';
|
||||
|
||||
export const ActiveAccount = ({ user }: { user: any }) => {
|
||||
const router = useRouter();
|
||||
const userData = JSON.parse(user.metadata);
|
||||
|
||||
const openProfilePage = () => {
|
||||
router.push(`/nostr/user?pubkey=${user.pubkey}`, { forceOptimisticNavigation: true });
|
||||
navigate(`/user?pubkey=${user.pubkey}`);
|
||||
};
|
||||
|
||||
const copyPublicKey = async () => {
|
||||
@@ -25,12 +21,10 @@ export const ActiveAccount = ({ user }: { user: any }) => {
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild>
|
||||
<button className="relative h-11 w-11 rounded-lg">
|
||||
<Image
|
||||
<img
|
||||
src={userData.picture || DEFAULT_AVATAR}
|
||||
alt="user's avatar"
|
||||
fill={true}
|
||||
className="rounded-lg object-cover"
|
||||
priority
|
||||
className="h-11 w-11 rounded-lg object-cover"
|
||||
/>
|
||||
</button>
|
||||
</DropdownMenu.Trigger>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { DEFAULT_AVATAR } from '@stores/constants';
|
||||
|
||||
import Image from 'next/image';
|
||||
import { memo } from 'react';
|
||||
|
||||
export const InactiveAccount = memo(function InactiveAccount({ user }: { user: any }) {
|
||||
@@ -12,13 +11,7 @@ export const InactiveAccount = memo(function InactiveAccount({ user }: { user: a
|
||||
|
||||
return (
|
||||
<button onClick={() => setCurrentUser()} className="relative h-11 w-11 shrink rounded-lg">
|
||||
<Image
|
||||
src={userData.picture || DEFAULT_AVATAR}
|
||||
alt="user's avatar"
|
||||
fill={true}
|
||||
className="rounded-lg object-cover"
|
||||
priority
|
||||
/>
|
||||
<img src={userData.picture || DEFAULT_AVATAR} alt="user's avatar" className="h-11 w-11 rounded-lg object-cover" />
|
||||
</button>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
'use client';
|
||||
|
||||
import { ActiveAccount } from '@components/multiAccounts/activeAccount';
|
||||
import { InactiveAccount } from '@components/multiAccounts/inactiveAccount';
|
||||
|
||||
@@ -11,7 +9,6 @@ import LumeSymbol from '@assets/icons/Lume';
|
||||
|
||||
import useLocalStorage from '@rehooks/local-storage';
|
||||
import { Plus } from 'iconoir-react';
|
||||
import Link from 'next/link';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
export default function MultiAccounts() {
|
||||
@@ -38,21 +35,19 @@ export default function MultiAccounts() {
|
||||
return (
|
||||
<div className="flex h-full flex-col items-center justify-between px-2 pb-4 pt-3">
|
||||
<div className="flex flex-col gap-4">
|
||||
<Link
|
||||
prefetch={false}
|
||||
href="/nostr/newsfeed/following"
|
||||
<a
|
||||
href="/explore"
|
||||
className="group relative flex h-11 w-11 shrink cursor-pointer items-center justify-center rounded-lg bg-zinc-900 hover:bg-zinc-800"
|
||||
>
|
||||
<LumeSymbol className="h-6 w-auto text-zinc-400 group-hover:text-zinc-200" />
|
||||
</Link>
|
||||
</a>
|
||||
<div>{users.map((user) => renderAccount(user))}</div>
|
||||
<Link
|
||||
prefetch={false}
|
||||
<a
|
||||
href="/onboarding"
|
||||
className="group relative flex h-11 w-11 shrink cursor-pointer items-center justify-center rounded-lg border-2 border-dashed border-zinc-600 hover:border-zinc-400"
|
||||
>
|
||||
<Plus width={16} height={16} className="text-zinc-400 group-hover:text-zinc-200" />
|
||||
</Link>
|
||||
</a>
|
||||
</div>
|
||||
<div className="flex flex-col gap-0.5 text-center">
|
||||
<span className="animate-moveBg from-fuchsia-300 via-orange-100 to-amber-300 text-sm font-black uppercase leading-tight text-zinc-600 hover:bg-gradient-to-r hover:bg-clip-text hover:text-transparent">
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
'use client';
|
||||
|
||||
import ChannelList from '@components/channels/channelList';
|
||||
|
||||
import * as Collapsible from '@radix-ui/react-collapsible';
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
'use client';
|
||||
|
||||
import ChatList from '@components/chats/chatList';
|
||||
|
||||
import * as Collapsible from '@radix-ui/react-collapsible';
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
'use client';
|
||||
|
||||
import { ActiveLink } from '@components/activeLink';
|
||||
|
||||
import * as Collapsible from '@radix-ui/react-collapsible';
|
||||
import { Bonfire, NavArrowUp, PeopleTag } from 'iconoir-react';
|
||||
import { useState } from 'react';
|
||||
@@ -23,22 +19,22 @@ export default function Newsfeed() {
|
||||
<h3 className="text-[11px] font-bold uppercase tracking-widest text-zinc-600">Newsfeed</h3>
|
||||
</Collapsible.Trigger>
|
||||
<Collapsible.Content className="flex flex-col text-zinc-400">
|
||||
<ActiveLink
|
||||
href="/nostr/newsfeed/following"
|
||||
activeClassName="dark:bg-zinc-900 dark:text-zinc-100 hover:dark:bg-zinc-800"
|
||||
<a
|
||||
href="/newsfeed/following"
|
||||
//activeClassName="dark:bg-zinc-900 dark:text-zinc-100 hover:dark:bg-zinc-800"
|
||||
className="flex h-8 items-center gap-2.5 rounded-md px-2.5 text-sm font-medium hover:text-zinc-200"
|
||||
>
|
||||
<PeopleTag width={16} height={16} className="text-zinc-500" />
|
||||
<span>Following</span>
|
||||
</ActiveLink>
|
||||
<ActiveLink
|
||||
href="/nostr/newsfeed/circle"
|
||||
activeClassName="dark:bg-zinc-900 dark:text-zinc-100 hover:dark:bg-zinc-800"
|
||||
</a>
|
||||
<a
|
||||
href="/newsfeed/circle"
|
||||
//activeClassName="dark:bg-zinc-900 dark:text-zinc-100 hover:dark:bg-zinc-800"
|
||||
className="flex h-8 items-center gap-2.5 rounded-md px-2.5 text-sm font-medium hover:text-zinc-200"
|
||||
>
|
||||
<Bonfire width={16} height={16} className="text-zinc-500" />
|
||||
<span>Circle</span>
|
||||
</ActiveLink>
|
||||
</a>
|
||||
</Collapsible.Content>
|
||||
</div>
|
||||
</Collapsible.Root>
|
||||
|
||||
@@ -4,11 +4,10 @@ import { UserExtend } from '@components/user/extend';
|
||||
|
||||
import { contentParser } from '@utils/parser';
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { memo } from 'react';
|
||||
import { navigate } from 'vite-plugin-ssr/client/router';
|
||||
|
||||
export const NoteBase = memo(function NoteBase({ event }: { event: any }) {
|
||||
const router = useRouter();
|
||||
const content = contentParser(event.content, event.tags);
|
||||
|
||||
const parentNote = () => {
|
||||
@@ -22,13 +21,13 @@ export const NoteBase = memo(function NoteBase({ event }: { event: any }) {
|
||||
|
||||
const openUserPage = (e) => {
|
||||
e.stopPropagation();
|
||||
router.push(`/nostr/user?pubkey=${event.pubkey}`, { forceOptimisticNavigation: true });
|
||||
navigate(`/user?pubkey=${event.pubkey}`);
|
||||
};
|
||||
|
||||
const openThread = (e) => {
|
||||
const selection = window.getSelection();
|
||||
if (selection.toString().length === 0) {
|
||||
router.push(`/nostr/newsfeed/note?id=${event.parent_id}`, { forceOptimisticNavigation: true });
|
||||
navigate(`/newsfeed/note?id=${event.parent_id}`);
|
||||
} else {
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { ImageWithFallback } from '@components/imageWithFallback';
|
||||
import { RelayContext } from '@components/relaysProvider';
|
||||
import { UserExtend } from '@components/user/extend';
|
||||
|
||||
@@ -7,9 +6,9 @@ import { dateToUnix } from '@utils/getDate';
|
||||
import * as Dialog from '@radix-ui/react-dialog';
|
||||
import useLocalStorage from '@rehooks/local-storage';
|
||||
import { ChatLines, OpenNewWindow } from 'iconoir-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { getEventHash, signEvent } from 'nostr-tools';
|
||||
import { useContext, useState } from 'react';
|
||||
import { navigate } from 'vite-plugin-ssr/client/router';
|
||||
|
||||
export const NoteComment = ({
|
||||
count,
|
||||
@@ -24,7 +23,6 @@ export const NoteComment = ({
|
||||
eventTime: number;
|
||||
eventContent: any;
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
const [pool, relays]: any = useContext(RelayContext);
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
@@ -34,7 +32,7 @@ export const NoteComment = ({
|
||||
const profile = activeAccount.metadata ? JSON.parse(activeAccount.metadata) : null;
|
||||
|
||||
const openThread = () => {
|
||||
router.push(`/nostr/newsfeed/note?id=${eventID}`, { forceOptimisticNavigation: true });
|
||||
navigate(`/newsfeed/note?id=${eventID}`);
|
||||
};
|
||||
|
||||
const submitEvent = () => {
|
||||
@@ -84,12 +82,7 @@ export const NoteComment = ({
|
||||
<div className="flex gap-2">
|
||||
<div>
|
||||
<div className="relative h-11 w-11 shrink-0 overflow-hidden rounded-md border border-white/10">
|
||||
<ImageWithFallback
|
||||
src={profile?.picture}
|
||||
alt="user's avatar"
|
||||
fill={true}
|
||||
className="rounded-md object-cover"
|
||||
/>
|
||||
<img src={profile?.picture} alt="user's avatar" className="h-11 w-11 rounded-md object-cover" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative h-36 w-full flex-1 overflow-hidden before:pointer-events-none before:absolute before:-inset-1 before:rounded-[11px] before:border before:border-blue-500 before:opacity-0 before:ring-2 before:ring-blue-500/20 before:transition after:pointer-events-none after:absolute after:inset-px after:rounded-[7px] after:shadow-highlight after:shadow-white/5 after:transition focus-within:before:opacity-100 focus-within:after:shadow-blue-500/100 dark:focus-within:after:shadow-blue-500/20">
|
||||
|
||||
@@ -1,18 +1,9 @@
|
||||
import Image from 'next/image';
|
||||
import { memo } from 'react';
|
||||
|
||||
export const ImagePreview = memo(function ImagePreview({ url, size }: { url: string; size: string }) {
|
||||
return (
|
||||
<div className={`relative h-full ${size === 'large' ? 'w-4/5' : 'w-1/2'} mt-2 rounded-lg border border-zinc-800`}>
|
||||
<Image
|
||||
src={url}
|
||||
alt={url}
|
||||
width="0"
|
||||
height="0"
|
||||
sizes="100vw"
|
||||
className="h-auto w-full rounded-lg object-cover"
|
||||
priority
|
||||
/>
|
||||
<img src={url} alt={url} className="h-auto w-full rounded-lg object-cover" />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
|
||||
export default function LinkCard({ data }: { data: any }) {
|
||||
return (
|
||||
<Link
|
||||
href={data['url']}
|
||||
target={'_blank'}
|
||||
className="relative flex flex-col overflow-hidden rounded-lg border border-zinc-700"
|
||||
>
|
||||
<div className="relative aspect-video h-auto w-full">
|
||||
<Image src={data['image']} alt="image preview" fill={true} className="object-cover" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 p-4">
|
||||
<div>
|
||||
<h5 className="font-semibold leading-tight">{data['title']}</h5>
|
||||
<p className="text-sm text-zinc-300">{data['description']}</p>
|
||||
</div>
|
||||
<span className="text-sm text-zinc-500">{data['url']}</span>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { memo } from 'react';
|
||||
import ReactPlayer from 'react-player/lazy';
|
||||
import ReactPlayer from 'react-player';
|
||||
|
||||
export const VideoPreview = memo(function VideoPreview({ url }: { url: string }) {
|
||||
return (
|
||||
|
||||
@@ -4,11 +4,10 @@ import { UserExtend } from '@components/user/extend';
|
||||
|
||||
import { contentParser } from '@utils/parser';
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { memo, useCallback, useContext, useEffect, useState } from 'react';
|
||||
import { navigate } from 'vite-plugin-ssr/client/router';
|
||||
|
||||
export const RootNote = memo(function RootNote({ event }: { event: any }) {
|
||||
const router = useRouter();
|
||||
const [pool, relays]: any = useContext(RelayContext);
|
||||
|
||||
const [data, setData] = useState(null);
|
||||
@@ -16,13 +15,13 @@ export const RootNote = memo(function RootNote({ event }: { event: any }) {
|
||||
|
||||
const openUserPage = (e) => {
|
||||
e.stopPropagation();
|
||||
router.push(`/nostr/user?pubkey=${event.pubkey}`, { forceOptimisticNavigation: true });
|
||||
navigate(`/user?pubkey=${event.pubkey}`);
|
||||
};
|
||||
|
||||
const openThread = (e) => {
|
||||
const selection = window.getSelection();
|
||||
if (selection.toString().length === 0) {
|
||||
router.push(`/nostr/newsfeed/note?id=${event.parent_id}`, { forceOptimisticNavigation: true });
|
||||
navigate(`/newsfeed/note?id=${event.parent_id}`);
|
||||
} else {
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
'use client';
|
||||
|
||||
import { RelayContext } from '@components/relaysProvider';
|
||||
import { UserFollow } from '@components/user/follow';
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
'use client';
|
||||
|
||||
import { RelayContext } from '@components/relaysProvider';
|
||||
import { UserFollow } from '@components/user/follow';
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
'use client';
|
||||
|
||||
import { ImageWithFallback } from '@components/imageWithFallback';
|
||||
import { RelayContext } from '@components/relaysProvider';
|
||||
|
||||
import { DEFAULT_AVATAR } from '@stores/constants';
|
||||
@@ -8,7 +5,6 @@ import { DEFAULT_AVATAR } from '@stores/constants';
|
||||
import { shortenKey } from '@utils/shortenKey';
|
||||
|
||||
import destr from 'destr';
|
||||
import Image from 'next/image';
|
||||
import { Author } from 'nostr-relaypool';
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
|
||||
@@ -27,21 +23,11 @@ export default function ProfileMetadata({ id }: { id: string }) {
|
||||
<>
|
||||
<div className="relative">
|
||||
<div className="relative h-56 w-full rounded-t-lg bg-zinc-800">
|
||||
<Image
|
||||
src={profile?.banner || DEFAULT_BANNER}
|
||||
alt="user's banner"
|
||||
fill={true}
|
||||
className="h-full w-full object-cover"
|
||||
/>
|
||||
<img src={profile?.banner || DEFAULT_BANNER} alt="user's banner" className="h-58 w-full object-cover" />
|
||||
</div>
|
||||
<div className="relative -top-8 z-10 px-4">
|
||||
<div className="relative h-16 w-16 rounded-lg bg-zinc-900 ring-2 ring-zinc-900">
|
||||
<ImageWithFallback
|
||||
src={profile?.picture || DEFAULT_AVATAR}
|
||||
alt={id}
|
||||
fill={true}
|
||||
className="rounded-lg object-cover"
|
||||
/>
|
||||
<img src={profile?.picture || DEFAULT_AVATAR} alt={id} className="h-16 w-16 rounded-lg object-cover" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
'use client';
|
||||
|
||||
import { NoteBase } from '@components/note/base';
|
||||
import { RelayContext } from '@components/relaysProvider';
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
'use client';
|
||||
|
||||
import { DEFAULT_RELAYS } from '@stores/constants';
|
||||
|
||||
import { RelayPool } from 'nostr-relaypool';
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { ImageWithFallback } from '@components/imageWithFallback';
|
||||
|
||||
import { DEFAULT_AVATAR } from '@stores/constants';
|
||||
|
||||
import { useProfileMetadata } from '@utils/hooks/useProfileMetadata';
|
||||
@@ -13,12 +11,7 @@ export const UserBase = memo(function UserBase({ pubkey }: { pubkey: string }) {
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="relative h-11 w-11 shrink overflow-hidden rounded-full border border-white/10">
|
||||
<ImageWithFallback
|
||||
src={profile?.picture || DEFAULT_AVATAR}
|
||||
alt={pubkey}
|
||||
fill={true}
|
||||
className="rounded-full object-cover"
|
||||
/>
|
||||
<img src={profile?.picture || DEFAULT_AVATAR} alt={pubkey} className="h-11 w-11 rounded-full object-cover" />
|
||||
</div>
|
||||
<div className="flex w-full flex-1 flex-col items-start text-start">
|
||||
<span className="truncate font-medium leading-tight text-zinc-200">
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { ImageWithFallback } from '@components/imageWithFallback';
|
||||
|
||||
import { DEFAULT_AVATAR } from '@stores/constants';
|
||||
|
||||
import { useProfileMetadata } from '@utils/hooks/useProfileMetadata';
|
||||
@@ -16,12 +14,7 @@ export const UserExtend = ({ pubkey, time }: { pubkey: string; time: number }) =
|
||||
return (
|
||||
<div className="group flex h-11 items-center gap-2">
|
||||
<div className="relative h-11 w-11 shrink overflow-hidden rounded-md bg-white">
|
||||
<ImageWithFallback
|
||||
src={profile?.picture || DEFAULT_AVATAR}
|
||||
alt={pubkey}
|
||||
fill={true}
|
||||
className="rounded-md object-cover"
|
||||
/>
|
||||
<img src={profile?.picture || DEFAULT_AVATAR} alt={pubkey} className="h-11 w-11 rounded-md object-cover" />
|
||||
</div>
|
||||
<div className="flex w-full flex-1 items-start justify-between">
|
||||
<div className="flex flex-col gap-1">
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { ImageWithFallback } from '@components/imageWithFallback';
|
||||
|
||||
import { DEFAULT_AVATAR } from '@stores/constants';
|
||||
|
||||
import { useProfileMetadata } from '@utils/hooks/useProfileMetadata';
|
||||
@@ -11,12 +9,7 @@ export const UserFollow = ({ pubkey }: { pubkey: string }) => {
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="relative h-11 w-11 shrink overflow-hidden rounded-full border border-white/10">
|
||||
<ImageWithFallback
|
||||
src={profile?.picture || DEFAULT_AVATAR}
|
||||
alt={pubkey}
|
||||
fill={true}
|
||||
className="rounded-full object-cover"
|
||||
/>
|
||||
<img src={profile?.picture || DEFAULT_AVATAR} alt={pubkey} className="h-11 w-11 rounded-full object-cover" />
|
||||
</div>
|
||||
<div className="flex w-full flex-1 flex-col items-start text-start">
|
||||
<span className="truncate font-medium leading-tight text-zinc-200">
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { ImageWithFallback } from '@components/imageWithFallback';
|
||||
|
||||
import { DEFAULT_AVATAR } from '@stores/constants';
|
||||
|
||||
import { useProfileMetadata } from '@utils/hooks/useProfileMetadata';
|
||||
@@ -17,11 +15,10 @@ export const UserLarge = ({ pubkey, time }: { pubkey: string; time: number }) =>
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="relative h-11 w-11 shrink overflow-hidden rounded-md bg-white">
|
||||
<ImageWithFallback
|
||||
<img
|
||||
src={profile?.picture || DEFAULT_AVATAR}
|
||||
alt={pubkey}
|
||||
fill={true}
|
||||
className="rounded-md border border-white/10 object-cover"
|
||||
className="h-11 w-11 rounded-md border border-white/10 object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full flex-1">
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { ImageWithFallback } from '@components/imageWithFallback';
|
||||
|
||||
import { DEFAULT_AVATAR } from '@stores/constants';
|
||||
|
||||
import { useProfileMetadata } from '@utils/hooks/useProfileMetadata';
|
||||
@@ -11,12 +9,7 @@ export const UserMini = ({ pubkey }: { pubkey: string }) => {
|
||||
return (
|
||||
<div className="group flex items-start gap-1">
|
||||
<div className="relative h-7 w-7 shrink overflow-hidden rounded border border-white/10">
|
||||
<ImageWithFallback
|
||||
src={profile?.picture || DEFAULT_AVATAR}
|
||||
alt={pubkey}
|
||||
fill={true}
|
||||
className="rounded object-cover"
|
||||
/>
|
||||
<img src={profile?.picture || DEFAULT_AVATAR} alt={pubkey} className="h-7 w-7 rounded object-cover" />
|
||||
</div>
|
||||
<span className="text-xs font-medium leading-none text-zinc-500">
|
||||
Replying to {profile?.name || shortenKey(pubkey)}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { ImageWithFallback } from '@components/imageWithFallback';
|
||||
|
||||
import { DEFAULT_AVATAR } from '@stores/constants';
|
||||
|
||||
import { useProfileMetadata } from '@utils/hooks/useProfileMetadata';
|
||||
@@ -16,12 +14,7 @@ export const UserQuoteRepost = ({ pubkey, time }: { pubkey: string; time: number
|
||||
return (
|
||||
<div className="group flex items-center gap-2">
|
||||
<div className="relative h-11 w-11 shrink overflow-hidden rounded-md bg-white">
|
||||
<ImageWithFallback
|
||||
src={profile?.picture || DEFAULT_AVATAR}
|
||||
alt={pubkey}
|
||||
fill={true}
|
||||
className="rounded-md object-cover"
|
||||
/>
|
||||
<img src={profile?.picture || DEFAULT_AVATAR} alt={pubkey} className="h-11 w-11 rounded-md object-cover" />
|
||||
</div>
|
||||
<div className="flex items-baseline gap-2 text-sm">
|
||||
<h5 className="font-semibold leading-tight group-hover:underline">
|
||||
|
||||
Reference in New Issue
Block a user