Skip to content

Commit

Permalink
Merge pull request #19 from ostyjs/note-enhancements
Browse files Browse the repository at this point in the history
Note Widget enhancements
  • Loading branch information
sepehr-safari authored Jan 3, 2025
2 parents 490303a + d02fb40 commit e895463
Show file tree
Hide file tree
Showing 26 changed files with 345 additions and 72 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "create-osty",
"version": "0.5.4",
"version": "0.5.5",
"type": "module",
"license": "MIT",
"author": "Sepehr Safari",
Expand Down
3 changes: 2 additions & 1 deletion templates/react-shadcn/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,13 @@
"input-otp": "^1.4.1",
"lucide-react": "^0.462.0",
"next-themes": "^0.4.3",
"nostr-hooks": "^4.2.6",
"nostr-hooks": "^4.2.9",
"nostr-tools": "^2.10.4",
"react": "^18.3.1",
"react-day-picker": "^8.10.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.53.2",
"react-intersection-observer": "^9.14.1",
"react-resizable-panels": "^2.1.7",
"react-router-dom": "^6.28.0",
"sonner": "^1.7.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ export const NewNoteWidget = ({ replyingToEvent }: { replyingToEvent?: NDKEvent

return (
<>
<div className={cn('p-2 bg-muted flex flex-col gap-2', replyingToEvent && '-mx-2 pl-6')}>
<div
className={cn('p-2 bg-foreground/10 flex flex-col gap-2', replyingToEvent && '-mx-2 pl-4')}
>
<div className="flex gap-2">
{replyingToEvent && (
<div className="pt-2 opacity-50">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { useSubscription } from 'nostr-hooks';
import { useEffect, useMemo } from 'react';

export const useNoteCommentsWidget = (event: NDKEvent) => {
const rootEventId = useMemo(() => {
const rootTags = event
.getMatchingTags('e')
.filter((tag) => tag.length > 2 && tag[3] === 'root');

if (rootTags.length === 0) {
return event.id;
}

if (rootTags[0].length < 2) {
return event.id;
}

return rootTags[0][1];
}, [event]);

const subId = `note-comments-${rootEventId}`;

const { createSubscription, events, loadMore, hasMore, isLoading } = useSubscription(subId);

const processedEvents = useMemo(
() =>
events
?.filter((e) => {
const eTags = e.getMatchingTags('e');
if (eTags.length === 0) {
return false;
}

if (event.id === rootEventId) {
return (
eTags.some((eTag) => eTag.length > 3 && eTag[1] === event.id && eTag[3] === 'root') &&
!eTags.some((eTag) => eTag.length > 3 && eTag[3] === 'reply')
);
} else {
return (
eTags.some(
(eTag) => eTag.length > 3 && eTag[1] === event.id && eTag[3] === 'reply',
) &&
eTags.some((eTag) => eTag.length > 3 && eTag[1] === rootEventId && eTag[3] === 'root')
);
}
})
.reverse(),
[events, event, rootEventId],
);

useEffect(() => {
createSubscription({
filters: [{ kinds: [1], '#e': [rootEventId], limit: 10 }],
opts: { groupableDelay: 500 },
});
}, [createSubscription]);

return {
processedEvents,
loadMore,
hasMore,
isLoading,
};
};
46 changes: 46 additions & 0 deletions templates/react-shadcn/src/features/note-comments-widget/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { NDKEvent } from '@nostr-dev-kit/ndk';

import { Button } from '@/shared/components/ui/button';

import { Spinner } from '@/shared/components/spinner';

import { cn } from '@/shared/utils';

import { NewNoteWidget } from '@/features/new-note-widget';
import { NoteByEvent } from '@/features/note-widget';

import { useNoteCommentsWidget } from './hooks';

export const NoteCommentsWidget = ({ event }: { event: NDKEvent }) => {
const { processedEvents, hasMore, loadMore } = useNoteCommentsWidget(event);

return (
<>
<NewNoteWidget replyingToEvent={event} />

<div className="bg-foreground/5 pl-4 -mx-2">
{processedEvents === undefined ? (
<Spinner />
) : (
processedEvents.length > 0 && (
<div className="pt-2 flex flex-col gap-2">
{processedEvents.map((event, i) => (
<div className={cn({ 'border-b': i !== processedEvents.length - 1 })}>
<NoteByEvent key={event.id} event={event} />
</div>
))}
</div>
)
)}

{hasMore && (
<div className="py-4 flex justify-center">
<Button variant="secondary" onClick={() => loadMore(50)} className="w-full">
Load more
</Button>
</div>
)}
</div>
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@ import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
import { useActiveUser, useNdk, useSubscription } from 'nostr-hooks';
import { useCallback, useEffect, useMemo } from 'react';

export const useNoteBookmarkBtn = (event: NDKEvent) => {
export const useNoteBookmarkBtn = (event: NDKEvent | undefined) => {
const { ndk } = useNdk();

const { myBookmarkList } = useMyBookmarkList();
const { myBookmarkList } = useMyBookmarkList(event !== undefined);

const isBookmarkedByMe = useMemo(
() => myBookmarkList?.getMatchingTags('e')?.some((e) => e.length > 1 && e[1] === event.id),
() => myBookmarkList?.getMatchingTags('e')?.some((e) => e.length > 1 && e[1] === event?.id),
[myBookmarkList, event],
);

const bookmark = useCallback(() => {
if (!ndk) {
if (!event || !ndk) {
return;
}

Expand All @@ -29,7 +29,7 @@ export const useNoteBookmarkBtn = (event: NDKEvent) => {
}, [ndk, event, isBookmarkedByMe, myBookmarkList]);

const unbookmark = useCallback(() => {
if (!ndk) {
if (!event || !ndk) {
return;
}

Expand All @@ -46,10 +46,10 @@ export const useNoteBookmarkBtn = (event: NDKEvent) => {
return { isBookmarkedByMe, bookmark, unbookmark };
};

const useMyBookmarkList = () => {
const useMyBookmarkList = (enable: boolean) => {
const { activeUser } = useActiveUser();

const subId = activeUser ? `my-bookmark-list` : undefined;
const subId = activeUser && enable ? `my-bookmark-list` : undefined;

const { createSubscription, events } = useSubscription(subId);

Expand All @@ -60,10 +60,12 @@ const useMyBookmarkList = () => {

useEffect(() => {
activeUser &&
enable &&
createSubscription({
filters: [{ kinds: [NDKKind.BookmarkList], authors: [activeUser.pubkey], limit: 1 }],
opts: { groupableDelay: 500 },
});
}, [createSubscription]);
}, [createSubscription, enable, activeUser]);

return { myBookmarkList };
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { cn } from '@/shared/utils';

import { useNoteBookmarkBtn } from './hooks';

export const NoteBookmarkBtn = ({ event }: { event: NDKEvent }) => {
const { isBookmarkedByMe, bookmark, unbookmark } = useNoteBookmarkBtn(event);
export const NoteBookmarkBtn = ({ event, inView }: { event: NDKEvent; inView: boolean }) => {
const { isBookmarkedByMe, bookmark, unbookmark } = useNoteBookmarkBtn(inView ? event : undefined);

return (
<>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { useSubscription } from 'nostr-hooks';
import { useEffect, useMemo } from 'react';

export const useNoteCommentBtn = (event: NDKEvent | undefined) => {
const rootEventId = useMemo(() => {
if (!event) {
return undefined;
}

const rootTags = event
.getMatchingTags('e')
.filter((tag) => tag.length > 2 && tag[3] === 'root');

if (rootTags.length === 0) {
return event.id;
}

if (rootTags[0].length < 2) {
return event.id;
}

return rootTags[0][1];
}, [event]);

const subId = rootEventId ? `note-comments-${rootEventId}` : undefined;

const { createSubscription, events } = useSubscription(subId);

const processedEvents = useMemo(
() =>
events
?.filter((e) => {
const eTags = e.getMatchingTags('e');
if (eTags.length === 0) {
return false;
}

if (event?.id === rootEventId) {
return (
eTags.some(
(eTag) => eTag.length > 3 && eTag[1] === event?.id && eTag[3] === 'root',
) && !eTags.some((eTag) => eTag.length > 3 && eTag[3] === 'reply')
);
} else {
return (
eTags.some(
(eTag) => eTag.length > 3 && eTag[1] === event?.id && eTag[3] === 'reply',
) &&
eTags.some((eTag) => eTag.length > 3 && eTag[1] === rootEventId && eTag[3] === 'root')
);
}
})
.reverse(),
[events, event, rootEventId],
);

const count = useMemo(() => processedEvents?.length || 0, [processedEvents]);

useEffect(() => {
rootEventId &&
createSubscription({
filters: [{ kinds: [1], '#e': [rootEventId], limit: 10 }],
opts: { groupableDelay: 500 },
});
}, [createSubscription, rootEventId]);

return { count };
};
Original file line number Diff line number Diff line change
@@ -1,14 +1,29 @@
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { MessageSquareIcon } from 'lucide-react';

import { Button } from '@/shared/components/ui/button';

export const NoteCommentBtn = ({ onClick }: { onClick: () => void }) => {
import { useNoteCommentBtn } from './hooks';

export const NoteCommentBtn = ({
event,
onClick,
inView,
}: {
event: NDKEvent;
onClick: () => void;
inView: boolean;
}) => {
const { count } = useNoteCommentBtn(inView ? event : undefined);

return (
<>
<Button variant="link" size="icon" className="opacity-50 hover:opacity-100" onClick={onClick}>
<div>
<MessageSquareIcon size={18} />
</div>

<span className="ml-1 text-xs">{count < 10 ? count : '10+'}</span>
</Button>
</>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useState } from 'react';
import { useInView } from 'react-intersection-observer';

export const useNoteFooter = () => {
const [showingComments, setShowingComments] = useState(false);

const { ref, inView } = useInView({
threshold: 0,
triggerOnce: true,
});

return {
ref,
showingComments,
setShowingComments,
inView,
};
};
Original file line number Diff line number Diff line change
@@ -1,28 +1,39 @@
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { useState } from 'react';

import { NewNoteWidget } from '@/features/new-note-widget';
import { NoteCommentsWidget } from '@/features/note-comments-widget';

import { NoteBookmarkBtn, NoteCommentBtn, NoteLikeBtn, NoteRepostBtn, NoteZapBtn } from '..';
import {
NoteBookmarkBtn,
NoteCommentBtn,
NoteLikeBtn,
NoteRepostBtn,
NoteZapBtn,
} from '@/features/note-widget/components';

import { useNoteFooter } from './hooks';

export const NoteFooter = ({ event }: { event: NDKEvent }) => {
const [isCommenting, setIsCommenting] = useState(false);
const { inView, ref, setShowingComments, showingComments } = useNoteFooter();

return (
<>
<div className="flex items-center justify-between gap-2">
<NoteCommentBtn onClick={() => setIsCommenting((prev) => !prev)} />
<div className="flex items-center justify-between gap-2" ref={ref}>
<NoteCommentBtn
onClick={() => setShowingComments((prev) => !prev)}
event={event}
inView={inView}
/>

<NoteZapBtn event={event} />
<NoteZapBtn event={event} inView={inView} />

<NoteLikeBtn event={event} />
<NoteLikeBtn event={event} inView={inView} />

<NoteRepostBtn event={event} />
<NoteRepostBtn event={event} inView={inView} />

<NoteBookmarkBtn event={event} />
<NoteBookmarkBtn event={event} inView={inView} />
</div>

{isCommenting && <NewNoteWidget replyingToEvent={event} />}
{showingComments && <NoteCommentsWidget event={event} />}
</>
);
};
Loading

0 comments on commit e895463

Please sign in to comment.