mirror of
https://github.com/luminous-devs/lume.git
synced 2024-09-18 11:13:30 +00:00
wip: rework widget
This commit is contained in:
parent
cb9006abb2
commit
0cdf199cb5
@ -54,8 +54,8 @@ export default function App() {
|
|||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
async lazy() {
|
async lazy() {
|
||||||
const { SpaceScreen } = await import('@app/space');
|
const { HomeScreen } = await import('@app/home');
|
||||||
return { Component: SpaceScreen };
|
return { Component: HomeScreen };
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -2,16 +2,12 @@ import { message } from '@tauri-apps/plugin-dialog';
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import { useStorage } from '@libs/storage/provider';
|
|
||||||
|
|
||||||
import { ArrowLeftIcon, CheckCircleIcon, LoaderIcon } from '@shared/icons';
|
import { ArrowLeftIcon, CheckCircleIcon, LoaderIcon } from '@shared/icons';
|
||||||
|
|
||||||
import { HASHTAGS, WIDGET_KIND } from '@stores/constants';
|
import { HASHTAGS } from '@stores/constants';
|
||||||
import { useOnboarding } from '@stores/onboarding';
|
import { useOnboarding } from '@stores/onboarding';
|
||||||
|
|
||||||
export function OnboardHashtagScreen() {
|
export function OnboardHashtagScreen() {
|
||||||
const { db } = useStorage();
|
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [tags, setTags] = useState(new Set<string>());
|
const [tags, setTags] = useState(new Set<string>());
|
||||||
|
|
||||||
@ -34,10 +30,6 @@ export function OnboardHashtagScreen() {
|
|||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
for (const tag of tags) {
|
|
||||||
await db.createWidget(WIDGET_KIND.global.hashtag, tag, tag.replace('#', ''));
|
|
||||||
}
|
|
||||||
|
|
||||||
setHashtag();
|
setHashtag();
|
||||||
navigate(-1);
|
navigate(-1);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -2,34 +2,21 @@ import { useQuery } from '@tanstack/react-query';
|
|||||||
import { useCallback, useRef, useState } from 'react';
|
import { useCallback, useRef, useState } from 'react';
|
||||||
import { VList, VListHandle } from 'virtua';
|
import { VList, VListHandle } from 'virtua';
|
||||||
|
|
||||||
import { ToggleWidgetList } from '@app/space/components/toggle';
|
|
||||||
import { WidgetList } from '@app/space/components/widgetList';
|
|
||||||
|
|
||||||
import { useStorage } from '@libs/storage/provider';
|
import { useStorage } from '@libs/storage/provider';
|
||||||
|
|
||||||
import { LoaderIcon } from '@shared/icons';
|
import { LoaderIcon } from '@shared/icons';
|
||||||
import {
|
import {
|
||||||
GlobalArticlesWidget,
|
|
||||||
GlobalFilesWidget,
|
|
||||||
GlobalHashtagWidget,
|
|
||||||
LocalArticlesWidget,
|
|
||||||
LocalFeedsWidget,
|
|
||||||
LocalFilesWidget,
|
|
||||||
LocalThreadWidget,
|
|
||||||
LocalUserWidget,
|
|
||||||
NewsfeedWidget,
|
NewsfeedWidget,
|
||||||
NotificationWidget,
|
NotificationWidget,
|
||||||
TrendingAccountsWidget,
|
ToggleWidgetList,
|
||||||
TrendingNotesWidget,
|
WidgetList,
|
||||||
XfeedsWidget,
|
|
||||||
XhashtagWidget,
|
|
||||||
} from '@shared/widgets';
|
} from '@shared/widgets';
|
||||||
|
|
||||||
import { WIDGET_KIND } from '@stores/constants';
|
import { WIDGET_KIND } from '@stores/constants';
|
||||||
|
|
||||||
import { Widget } from '@utils/types';
|
import { Widget } from '@utils/types';
|
||||||
|
|
||||||
export function SpaceScreen() {
|
export function HomeScreen() {
|
||||||
const ref = useRef<VListHandle>(null);
|
const ref = useRef<VListHandle>(null);
|
||||||
const [selectedIndex, setSelectedIndex] = useState(-1);
|
const [selectedIndex, setSelectedIndex] = useState(-1);
|
||||||
|
|
||||||
@ -40,16 +27,16 @@ export function SpaceScreen() {
|
|||||||
const dbWidgets = await db.getWidgets();
|
const dbWidgets = await db.getWidgets();
|
||||||
const defaultWidgets = [
|
const defaultWidgets = [
|
||||||
{
|
{
|
||||||
id: '9998',
|
id: '99999',
|
||||||
title: 'Notification',
|
|
||||||
content: '',
|
|
||||||
kind: WIDGET_KIND.local.notification,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '9999',
|
|
||||||
title: 'Newsfeed',
|
title: 'Newsfeed',
|
||||||
content: '',
|
content: '',
|
||||||
kind: WIDGET_KIND.local.network,
|
kind: WIDGET_KIND.newsfeed,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '99998',
|
||||||
|
title: 'Notification',
|
||||||
|
content: '',
|
||||||
|
kind: WIDGET_KIND.notification,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -63,36 +50,12 @@ export function SpaceScreen() {
|
|||||||
|
|
||||||
const renderItem = useCallback((widget: Widget) => {
|
const renderItem = useCallback((widget: Widget) => {
|
||||||
switch (widget.kind) {
|
switch (widget.kind) {
|
||||||
case WIDGET_KIND.local.feeds:
|
case WIDGET_KIND.notification:
|
||||||
return <LocalFeedsWidget key={widget.id} params={widget} />;
|
|
||||||
case WIDGET_KIND.local.files:
|
|
||||||
return <LocalFilesWidget key={widget.id} params={widget} />;
|
|
||||||
case WIDGET_KIND.local.articles:
|
|
||||||
return <LocalArticlesWidget key={widget.id} params={widget} />;
|
|
||||||
case WIDGET_KIND.local.user:
|
|
||||||
return <LocalUserWidget key={widget.id} params={widget} />;
|
|
||||||
case WIDGET_KIND.local.thread:
|
|
||||||
return <LocalThreadWidget key={widget.id} params={widget} />;
|
|
||||||
case WIDGET_KIND.global.hashtag:
|
|
||||||
return <GlobalHashtagWidget key={widget.id} params={widget} />;
|
|
||||||
case WIDGET_KIND.global.articles:
|
|
||||||
return <GlobalArticlesWidget key={widget.id} params={widget} />;
|
|
||||||
case WIDGET_KIND.global.files:
|
|
||||||
return <GlobalFilesWidget key={widget.id} params={widget} />;
|
|
||||||
case WIDGET_KIND.nostrBand.trendingAccounts:
|
|
||||||
return <TrendingAccountsWidget key={widget.id} params={widget} />;
|
|
||||||
case WIDGET_KIND.nostrBand.trendingNotes:
|
|
||||||
return <TrendingNotesWidget key={widget.id} params={widget} />;
|
|
||||||
case WIDGET_KIND.tmp.xfeed:
|
|
||||||
return <XfeedsWidget key={widget.id} params={widget} />;
|
|
||||||
case WIDGET_KIND.tmp.xhashtag:
|
|
||||||
return <XhashtagWidget key={widget.id} params={widget} />;
|
|
||||||
case WIDGET_KIND.tmp.list:
|
|
||||||
return <WidgetList key={widget.id} params={widget} />;
|
|
||||||
case WIDGET_KIND.local.notification:
|
|
||||||
return <NotificationWidget key={widget.id} />;
|
return <NotificationWidget key={widget.id} />;
|
||||||
case WIDGET_KIND.local.network:
|
case WIDGET_KIND.newsfeed:
|
||||||
return <NewsfeedWidget key={widget.id} />;
|
return <NewsfeedWidget key={widget.id} />;
|
||||||
|
case WIDGET_KIND.list:
|
||||||
|
return <WidgetList key={widget.id} widget={widget} />;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -108,14 +71,15 @@ export function SpaceScreen() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<VList
|
<VList
|
||||||
className="h-full w-full flex-nowrap overflow-x-auto !overflow-y-hidden scrollbar-none focus:outline-none"
|
|
||||||
horizontal
|
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
className="h-full w-full flex-nowrap overflow-x-auto !overflow-y-hidden scrollbar-none focus:outline-none"
|
||||||
initialItemSize={420}
|
initialItemSize={420}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
|
horizontal
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (!ref.current) return;
|
if (!ref.current) return;
|
||||||
switch (e.code) {
|
switch (e.code) {
|
||||||
|
case 'ArrowUp':
|
||||||
case 'ArrowLeft': {
|
case 'ArrowLeft': {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const prevIndex = Math.max(selectedIndex - 1, 0);
|
const prevIndex = Math.max(selectedIndex - 1, 0);
|
||||||
@ -126,6 +90,7 @@ export function SpaceScreen() {
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'ArrowDown':
|
||||||
case 'ArrowRight': {
|
case 'ArrowRight': {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const nextIndex = Math.min(selectedIndex + 1, data.length - 1);
|
const nextIndex = Math.min(selectedIndex + 1, data.length - 1);
|
||||||
@ -136,6 +101,8 @@ export function SpaceScreen() {
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
@ -1,128 +0,0 @@
|
|||||||
import { useCallback } from 'react';
|
|
||||||
|
|
||||||
import {
|
|
||||||
ArticleIcon,
|
|
||||||
BellIcon,
|
|
||||||
FileIcon,
|
|
||||||
FollowsIcon,
|
|
||||||
GroupFeedsIcon,
|
|
||||||
HashtagIcon,
|
|
||||||
ThreadsIcon,
|
|
||||||
TrendingIcon,
|
|
||||||
} from '@shared/icons';
|
|
||||||
import { TitleBar } from '@shared/titleBar';
|
|
||||||
import { WidgetWrapper } from '@shared/widgets';
|
|
||||||
|
|
||||||
import { DEFAULT_WIDGETS, WIDGET_KIND } from '@stores/constants';
|
|
||||||
|
|
||||||
import { useWidget } from '@utils/hooks/useWidget';
|
|
||||||
import { Widget, WidgetGroup, WidgetGroupItem } from '@utils/types';
|
|
||||||
|
|
||||||
export function WidgetList({ params }: { params: Widget }) {
|
|
||||||
const { addWidget, removeWidget } = useWidget();
|
|
||||||
|
|
||||||
const open = (item: WidgetGroupItem) => {
|
|
||||||
addWidget.mutate({
|
|
||||||
kind: item.kind,
|
|
||||||
title: item.title,
|
|
||||||
content: JSON.stringify(item.content),
|
|
||||||
});
|
|
||||||
removeWidget.mutate(params.id);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderIcon = useCallback((kind: number) => {
|
|
||||||
switch (kind) {
|
|
||||||
case WIDGET_KIND.tmp.xfeed:
|
|
||||||
return (
|
|
||||||
<GroupFeedsIcon className="h-5 w-5 text-neutral-900 dark:text-neutral-100" />
|
|
||||||
);
|
|
||||||
case WIDGET_KIND.local.follows:
|
|
||||||
return <FollowsIcon className="h-5 w-5 text-neutral-900 dark:text-neutral-100" />;
|
|
||||||
case WIDGET_KIND.local.files:
|
|
||||||
case WIDGET_KIND.global.files:
|
|
||||||
return <FileIcon className="h-5 w-5 text-neutral-900 dark:text-neutral-100" />;
|
|
||||||
case WIDGET_KIND.local.articles:
|
|
||||||
case WIDGET_KIND.global.articles:
|
|
||||||
return <ArticleIcon className="h-5 w-5 text-neutral-900 dark:text-neutral-100" />;
|
|
||||||
case WIDGET_KIND.tmp.xhashtag:
|
|
||||||
return <HashtagIcon className="h-5 w-5 text-neutral-900 dark:text-neutral-100" />;
|
|
||||||
case WIDGET_KIND.nostrBand.trendingAccounts:
|
|
||||||
case WIDGET_KIND.nostrBand.trendingNotes:
|
|
||||||
return (
|
|
||||||
<TrendingIcon className="h-5 w-5 text-neutral-900 dark:text-neutral-100" />
|
|
||||||
);
|
|
||||||
case WIDGET_KIND.local.notification:
|
|
||||||
return <BellIcon className="h-5 w-5 text-neutral-900 dark:text-neutral-100" />;
|
|
||||||
case WIDGET_KIND.other.learnNostr:
|
|
||||||
return <ThreadsIcon className="h-5 w-5 text-neutral-900 dark:text-neutral-100" />;
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const renderItem = useCallback((row: WidgetGroup, index: number) => {
|
|
||||||
return (
|
|
||||||
<div key={index} className="flex flex-col gap-2">
|
|
||||||
<h3 className="font-semibold">{row.title}</h3>
|
|
||||||
<div className="flex flex-col divide-y divide-neutral-200 overflow-hidden rounded-xl bg-neutral-100 dark:divide-neutral-800 dark:bg-neutral-900">
|
|
||||||
{row.data.map((item, index) => (
|
|
||||||
<button
|
|
||||||
onClick={() => open(item)}
|
|
||||||
key={index}
|
|
||||||
className="group flex items-center gap-2.5 px-4 hover:bg-neutral-200 dark:hover:bg-neutral-800"
|
|
||||||
>
|
|
||||||
{item.icon ? (
|
|
||||||
<div className="h-10 w-10 shrink-0 rounded-lg">
|
|
||||||
<img
|
|
||||||
src={item.icon}
|
|
||||||
alt={item.title}
|
|
||||||
className="h-10 w-10 object-cover"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-neutral-200 group-hover:bg-neutral-300 dark:bg-neutral-800 dark:group-hover:bg-neutral-700">
|
|
||||||
{renderIcon(item.kind)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="inline-flex h-16 w-full flex-col items-start justify-center">
|
|
||||||
<h5 className="line-clamp-1 text-sm font-semibold text-neutral-900 dark:text-neutral-100">
|
|
||||||
{item.title}
|
|
||||||
</h5>
|
|
||||||
<p className="line-clamp-1 text-sm text-neutral-600 dark:text-neutral-400">
|
|
||||||
{item.description}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<WidgetWrapper>
|
|
||||||
<TitleBar id={params.id} title="Add widget" />
|
|
||||||
<div className="flex-1 overflow-y-auto pb-10 scrollbar-none">
|
|
||||||
<div className="flex flex-col gap-6 px-3">
|
|
||||||
{DEFAULT_WIDGETS.map((row: WidgetGroup, index: number) =>
|
|
||||||
renderItem(row, index)
|
|
||||||
)}
|
|
||||||
<div className="border-t border-neutral-200 pt-6 dark:border-neutral-800">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
disabled
|
|
||||||
className="inline-flex h-14 w-full items-center justify-center gap-2.5 rounded-xl bg-neutral-50 text-sm font-medium text-neutral-900 dark:bg-neutral-950 dark:text-neutral-100"
|
|
||||||
>
|
|
||||||
Build your own widget{' '}
|
|
||||||
<div className="-rotate-3 transform-gpu rounded-md border border-neutral-200 bg-neutral-100 px-1.5 py-1 dark:border-neutral-800 dark:bg-neutral-900">
|
|
||||||
<span className="bg-gradient-to-r from-blue-400 via-red-400 to-orange-500 bg-clip-text text-xs text-transparent dark:from-blue-200 dark:via-red-200 dark:to-orange-300">
|
|
||||||
Coming soon
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</WidgetWrapper>
|
|
||||||
);
|
|
||||||
}
|
|
@ -34,7 +34,7 @@ export function NoteActions({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
addWidget.mutate({
|
addWidget.mutate({
|
||||||
kind: WIDGET_KIND.local.thread,
|
kind: WIDGET_KIND.thread,
|
||||||
title: 'Thread',
|
title: 'Thread',
|
||||||
content: id,
|
content: id,
|
||||||
})
|
})
|
||||||
|
@ -10,7 +10,7 @@ export function Hashtag({ tag }: { tag: string }) {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
addWidget.mutate({
|
addWidget.mutate({
|
||||||
kind: WIDGET_KIND.global.hashtag,
|
kind: WIDGET_KIND.hashtag,
|
||||||
title: tag,
|
title: tag,
|
||||||
content: tag.replace('#', ''),
|
content: tag.replace('#', ''),
|
||||||
})
|
})
|
||||||
|
@ -50,7 +50,7 @@ export const MentionNote = memo(function MentionNote({ id }: { id: string }) {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
addWidget.mutate({
|
addWidget.mutate({
|
||||||
kind: WIDGET_KIND.local.thread,
|
kind: WIDGET_KIND.thread,
|
||||||
title: 'Thread',
|
title: 'Thread',
|
||||||
content: data.id,
|
content: data.id,
|
||||||
})
|
})
|
||||||
|
@ -14,14 +14,7 @@ export const MentionUser = memo(function MentionUser({ pubkey }: { pubkey: strin
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
addWidget.mutate({
|
addWidget.mutate({
|
||||||
kind: WIDGET_KIND.local.user,
|
kind: WIDGET_KIND.user,
|
||||||
title: user?.name || user?.display_name || user?.displayName,
|
|
||||||
content: pubkey,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
onKeyDown={() =>
|
|
||||||
addWidget.mutate({
|
|
||||||
kind: WIDGET_KIND.local.user,
|
|
||||||
title: user?.name || user?.display_name || user?.displayName,
|
title: user?.name || user?.display_name || user?.displayName,
|
||||||
content: pubkey,
|
content: pubkey,
|
||||||
})
|
})
|
||||||
|
@ -91,7 +91,7 @@ export function NotifyNote({ event }: { event: NDKEvent }) {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
addWidget.mutate({
|
addWidget.mutate({
|
||||||
kind: WIDGET_KIND.local.thread,
|
kind: WIDGET_KIND.thread,
|
||||||
title: 'Thread',
|
title: 'Thread',
|
||||||
content: data.id,
|
content: data.id,
|
||||||
})
|
})
|
||||||
|
@ -30,7 +30,7 @@ export function TextNote({ event }: { event: NDKEvent }) {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
addWidget.mutate({
|
addWidget.mutate({
|
||||||
kind: WIDGET_KIND.local.thread,
|
kind: WIDGET_KIND.thread,
|
||||||
title: 'Thread',
|
title: 'Thread',
|
||||||
content: thread.rootEventId,
|
content: thread.rootEventId,
|
||||||
})
|
})
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
|
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
|
||||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||||
|
import { FetchFilter } from 'nostr-fetch';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { VList } from 'virtua';
|
import { VList } from 'virtua';
|
||||||
|
|
||||||
@ -15,12 +16,12 @@ import { FETCH_LIMIT } from '@stores/constants';
|
|||||||
|
|
||||||
import { Widget } from '@utils/types';
|
import { Widget } from '@utils/types';
|
||||||
|
|
||||||
export function LocalArticlesWidget({ params }: { params: Widget }) {
|
export function ArticleWidget({ widget }: { widget: Widget }) {
|
||||||
const { db } = useStorage();
|
const { db } = useStorage();
|
||||||
const { ndk, relayUrls, fetcher } = useNDK();
|
const { ndk, relayUrls, fetcher } = useNDK();
|
||||||
const { status, data, hasNextPage, isFetchingNextPage, fetchNextPage } =
|
const { status, data, hasNextPage, isFetchingNextPage, fetchNextPage } =
|
||||||
useInfiniteQuery({
|
useInfiniteQuery({
|
||||||
queryKey: ['local-articles'],
|
queryKey: ['widget-' + widget.id],
|
||||||
initialPageParam: 0,
|
initialPageParam: 0,
|
||||||
queryFn: async ({
|
queryFn: async ({
|
||||||
signal,
|
signal,
|
||||||
@ -29,15 +30,24 @@ export function LocalArticlesWidget({ params }: { params: Widget }) {
|
|||||||
signal: AbortSignal;
|
signal: AbortSignal;
|
||||||
pageParam: number;
|
pageParam: number;
|
||||||
}) => {
|
}) => {
|
||||||
const events = await fetcher.fetchLatestEvents(
|
let filter: FetchFilter;
|
||||||
relayUrls,
|
const content = JSON.parse(widget.content);
|
||||||
{
|
|
||||||
|
if (content.global) {
|
||||||
|
filter = {
|
||||||
|
kinds: [NDKKind.Article],
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
filter = {
|
||||||
kinds: [NDKKind.Article],
|
kinds: [NDKKind.Article],
|
||||||
authors: db.account.circles,
|
authors: db.account.circles,
|
||||||
},
|
};
|
||||||
FETCH_LIMIT,
|
}
|
||||||
{ asOf: pageParam === 0 ? undefined : pageParam, abortSignal: signal }
|
|
||||||
);
|
const events = await fetcher.fetchLatestEvents(relayUrls, filter, FETCH_LIMIT, {
|
||||||
|
asOf: pageParam === 0 ? undefined : pageParam,
|
||||||
|
abortSignal: signal,
|
||||||
|
});
|
||||||
|
|
||||||
const ndkEvents = events.map((event) => {
|
const ndkEvents = events.map((event) => {
|
||||||
return new NDKEvent(ndk, event);
|
return new NDKEvent(ndk, event);
|
||||||
@ -61,7 +71,7 @@ export function LocalArticlesWidget({ params }: { params: Widget }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<WidgetWrapper>
|
<WidgetWrapper>
|
||||||
<TitleBar id={params.id} title={params.title} />
|
<TitleBar id={widget.id} title={widget.title} />
|
||||||
<VList className="flex-1">
|
<VList className="flex-1">
|
||||||
{status === 'pending' ? (
|
{status === 'pending' ? (
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
<div className="flex h-full w-full items-center justify-center">
|
@ -1,5 +1,6 @@
|
|||||||
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
||||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||||
|
import { FetchFilter } from 'nostr-fetch';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { VList } from 'virtua';
|
import { VList } from 'virtua';
|
||||||
|
|
||||||
@ -15,12 +16,12 @@ import { FETCH_LIMIT } from '@stores/constants';
|
|||||||
|
|
||||||
import { Widget } from '@utils/types';
|
import { Widget } from '@utils/types';
|
||||||
|
|
||||||
export function LocalFilesWidget({ params }: { params: Widget }) {
|
export function FileWidget({ widget }: { widget: Widget }) {
|
||||||
const { db } = useStorage();
|
const { db } = useStorage();
|
||||||
const { ndk, relayUrls, fetcher } = useNDK();
|
const { ndk, relayUrls, fetcher } = useNDK();
|
||||||
const { status, data, hasNextPage, isFetchingNextPage, fetchNextPage } =
|
const { status, data, hasNextPage, isFetchingNextPage, fetchNextPage } =
|
||||||
useInfiniteQuery({
|
useInfiniteQuery({
|
||||||
queryKey: ['local-files'],
|
queryKey: ['widget-' + widget.id],
|
||||||
initialPageParam: 0,
|
initialPageParam: 0,
|
||||||
queryFn: async ({
|
queryFn: async ({
|
||||||
signal,
|
signal,
|
||||||
@ -29,15 +30,24 @@ export function LocalFilesWidget({ params }: { params: Widget }) {
|
|||||||
signal: AbortSignal;
|
signal: AbortSignal;
|
||||||
pageParam: number;
|
pageParam: number;
|
||||||
}) => {
|
}) => {
|
||||||
const events = await fetcher.fetchLatestEvents(
|
let filter: FetchFilter;
|
||||||
relayUrls,
|
const content = JSON.parse(widget.content);
|
||||||
{
|
|
||||||
|
if (content.global) {
|
||||||
|
filter = {
|
||||||
|
kinds: [1063],
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
filter = {
|
||||||
kinds: [1063],
|
kinds: [1063],
|
||||||
authors: db.account.circles,
|
authors: db.account.circles,
|
||||||
},
|
};
|
||||||
FETCH_LIMIT,
|
}
|
||||||
{ asOf: pageParam === 0 ? undefined : pageParam, abortSignal: signal }
|
|
||||||
);
|
const events = await fetcher.fetchLatestEvents(relayUrls, filter, FETCH_LIMIT, {
|
||||||
|
asOf: pageParam === 0 ? undefined : pageParam,
|
||||||
|
abortSignal: signal,
|
||||||
|
});
|
||||||
|
|
||||||
const ndkEvents = events.map((event) => {
|
const ndkEvents = events.map((event) => {
|
||||||
return new NDKEvent(ndk, event);
|
return new NDKEvent(ndk, event);
|
||||||
@ -61,7 +71,7 @@ export function LocalFilesWidget({ params }: { params: Widget }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<WidgetWrapper>
|
<WidgetWrapper>
|
||||||
<TitleBar id={params.id} title={params.title} />
|
<TitleBar id={widget.id} title={widget.title} />
|
||||||
<VList className="flex-1">
|
<VList className="flex-1">
|
||||||
{status === 'pending' ? (
|
{status === 'pending' ? (
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
<div className="flex h-full w-full items-center justify-center">
|
@ -1,106 +0,0 @@
|
|||||||
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
|
|
||||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
import { VList } from 'virtua';
|
|
||||||
|
|
||||||
import { useNDK } from '@libs/ndk/provider';
|
|
||||||
|
|
||||||
import { ArrowRightCircleIcon, LoaderIcon } from '@shared/icons';
|
|
||||||
import { MemoizedArticleNote } from '@shared/notes';
|
|
||||||
import { TitleBar } from '@shared/titleBar';
|
|
||||||
import { WidgetWrapper } from '@shared/widgets';
|
|
||||||
|
|
||||||
import { FETCH_LIMIT } from '@stores/constants';
|
|
||||||
|
|
||||||
import { Widget } from '@utils/types';
|
|
||||||
|
|
||||||
export function GlobalArticlesWidget({ params }: { params: Widget }) {
|
|
||||||
const { ndk, relayUrls, fetcher } = useNDK();
|
|
||||||
const { status, data, hasNextPage, isFetchingNextPage, fetchNextPage } =
|
|
||||||
useInfiniteQuery({
|
|
||||||
queryKey: ['global-articles'],
|
|
||||||
initialPageParam: 0,
|
|
||||||
queryFn: async ({
|
|
||||||
signal,
|
|
||||||
pageParam,
|
|
||||||
}: {
|
|
||||||
signal: AbortSignal;
|
|
||||||
pageParam: number;
|
|
||||||
}) => {
|
|
||||||
const events = await fetcher.fetchLatestEvents(
|
|
||||||
relayUrls,
|
|
||||||
{
|
|
||||||
kinds: [NDKKind.Article],
|
|
||||||
},
|
|
||||||
FETCH_LIMIT,
|
|
||||||
{ asOf: pageParam === 0 ? undefined : pageParam, abortSignal: signal }
|
|
||||||
);
|
|
||||||
|
|
||||||
const ndkEvents = events.map((event) => {
|
|
||||||
return new NDKEvent(ndk, event);
|
|
||||||
});
|
|
||||||
|
|
||||||
return ndkEvents.sort((a, b) => b.created_at - a.created_at);
|
|
||||||
},
|
|
||||||
getNextPageParam: (lastPage) => {
|
|
||||||
const lastEvent = lastPage.at(-1);
|
|
||||||
if (!lastEvent) return;
|
|
||||||
return lastEvent.created_at - 1;
|
|
||||||
},
|
|
||||||
refetchOnWindowFocus: false,
|
|
||||||
refetchOnReconnect: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const allEvents = useMemo(
|
|
||||||
() => (data ? data.pages.flatMap((page) => page) : []),
|
|
||||||
[data]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<WidgetWrapper>
|
|
||||||
<TitleBar id={params.id} title={params.title} />
|
|
||||||
<VList className="flex-1">
|
|
||||||
{status === 'pending' ? (
|
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
|
||||||
<LoaderIcon className="h-5 w-5 animate-spin" />
|
|
||||||
</div>
|
|
||||||
) : allEvents.length === 0 ? (
|
|
||||||
<div className="flex h-full w-full flex-col items-center justify-center px-3">
|
|
||||||
<div className="flex flex-col items-center gap-4">
|
|
||||||
<img src="/ghost.png" alt="empty feeds" className="h-16 w-16" />
|
|
||||||
<div className="text-center">
|
|
||||||
<h3 className="font-semibold leading-tight text-neutral-900 dark:text-neutral-100">
|
|
||||||
Oops, it looks like there are no articles.
|
|
||||||
</h3>
|
|
||||||
<p className="text-neutral-500 dark:text-neutral-400">
|
|
||||||
You can close this widget
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
allEvents.map((item) => <MemoizedArticleNote key={item.id} event={item} />)
|
|
||||||
)}
|
|
||||||
<div className="flex h-16 items-center justify-center px-3 pb-3">
|
|
||||||
{hasNextPage ? (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => fetchNextPage()}
|
|
||||||
disabled={!hasNextPage || isFetchingNextPage}
|
|
||||||
className="inline-flex h-10 w-max items-center justify-center gap-2 rounded-full bg-blue-500 px-6 font-medium text-white hover:bg-blue-600 focus:outline-none"
|
|
||||||
>
|
|
||||||
{isFetchingNextPage ? (
|
|
||||||
<LoaderIcon className="h-4 w-4 animate-spin" />
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<ArrowRightCircleIcon className="h-5 w-5" />
|
|
||||||
Load more
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</VList>
|
|
||||||
</WidgetWrapper>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,106 +0,0 @@
|
|||||||
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
|
||||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
import { VList } from 'virtua';
|
|
||||||
|
|
||||||
import { useNDK } from '@libs/ndk/provider';
|
|
||||||
|
|
||||||
import { ArrowRightCircleIcon, LoaderIcon } from '@shared/icons';
|
|
||||||
import { MemoizedFileNote } from '@shared/notes';
|
|
||||||
import { TitleBar } from '@shared/titleBar';
|
|
||||||
import { WidgetWrapper } from '@shared/widgets';
|
|
||||||
|
|
||||||
import { FETCH_LIMIT } from '@stores/constants';
|
|
||||||
|
|
||||||
import { Widget } from '@utils/types';
|
|
||||||
|
|
||||||
export function GlobalFilesWidget({ params }: { params: Widget }) {
|
|
||||||
const { ndk, relayUrls, fetcher } = useNDK();
|
|
||||||
const { status, data, hasNextPage, isFetchingNextPage, fetchNextPage } =
|
|
||||||
useInfiniteQuery({
|
|
||||||
queryKey: ['global-files'],
|
|
||||||
initialPageParam: 0,
|
|
||||||
queryFn: async ({
|
|
||||||
signal,
|
|
||||||
pageParam,
|
|
||||||
}: {
|
|
||||||
signal: AbortSignal;
|
|
||||||
pageParam: number;
|
|
||||||
}) => {
|
|
||||||
const events = await fetcher.fetchLatestEvents(
|
|
||||||
relayUrls,
|
|
||||||
{
|
|
||||||
kinds: [1063],
|
|
||||||
},
|
|
||||||
FETCH_LIMIT,
|
|
||||||
{ asOf: pageParam === 0 ? undefined : pageParam, abortSignal: signal }
|
|
||||||
);
|
|
||||||
|
|
||||||
const ndkEvents = events.map((event) => {
|
|
||||||
return new NDKEvent(ndk, event);
|
|
||||||
});
|
|
||||||
|
|
||||||
return ndkEvents.sort((a, b) => b.created_at - a.created_at);
|
|
||||||
},
|
|
||||||
getNextPageParam: (lastPage) => {
|
|
||||||
const lastEvent = lastPage.at(-1);
|
|
||||||
if (!lastEvent) return;
|
|
||||||
return lastEvent.created_at - 1;
|
|
||||||
},
|
|
||||||
refetchOnWindowFocus: false,
|
|
||||||
refetchOnReconnect: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const allEvents = useMemo(
|
|
||||||
() => (data ? data.pages.flatMap((page) => page) : []),
|
|
||||||
[data]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<WidgetWrapper>
|
|
||||||
<TitleBar id={params.id} title={params.title} />
|
|
||||||
<VList className="flex-1">
|
|
||||||
{status === 'pending' ? (
|
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
|
||||||
<LoaderIcon className="h-5 w-5 animate-spin" />
|
|
||||||
</div>
|
|
||||||
) : allEvents.length === 0 ? (
|
|
||||||
<div className="flex h-full w-full flex-col items-center justify-center px-3">
|
|
||||||
<div className="flex flex-col items-center gap-4">
|
|
||||||
<img src="/ghost.png" alt="empty feeds" className="h-16 w-16" />
|
|
||||||
<div className="text-center">
|
|
||||||
<h3 className="font-semibold leading-tight text-neutral-900 dark:text-neutral-100">
|
|
||||||
Oops, it looks like there are no files.
|
|
||||||
</h3>
|
|
||||||
<p className="text-neutral-500 dark:text-neutral-400">
|
|
||||||
You can close this widget
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
allEvents.map((item) => <MemoizedFileNote key={item.id} event={item} />)
|
|
||||||
)}
|
|
||||||
<div className="flex h-16 items-center justify-center px-3 pb-3">
|
|
||||||
{hasNextPage ? (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => fetchNextPage()}
|
|
||||||
disabled={!hasNextPage || isFetchingNextPage}
|
|
||||||
className="inline-flex h-10 w-max items-center justify-center gap-2 rounded-full bg-blue-500 px-6 font-medium text-white hover:bg-blue-600 focus:outline-none"
|
|
||||||
>
|
|
||||||
{isFetchingNextPage ? (
|
|
||||||
<LoaderIcon className="h-4 w-4 animate-spin" />
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<ArrowRightCircleIcon className="h-5 w-5" />
|
|
||||||
Load more
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</VList>
|
|
||||||
</WidgetWrapper>
|
|
||||||
);
|
|
||||||
}
|
|
@ -19,11 +19,11 @@ import { FETCH_LIMIT } from '@stores/constants';
|
|||||||
|
|
||||||
import { Widget } from '@utils/types';
|
import { Widget } from '@utils/types';
|
||||||
|
|
||||||
export function LocalFeedsWidget({ params }: { params: Widget }) {
|
export function GroupWidget({ widget }: { widget: Widget }) {
|
||||||
const { relayUrls, ndk, fetcher } = useNDK();
|
const { relayUrls, ndk, fetcher } = useNDK();
|
||||||
const { status, data, hasNextPage, isFetchingNextPage, fetchNextPage } =
|
const { status, data, hasNextPage, isFetchingNextPage, fetchNextPage } =
|
||||||
useInfiniteQuery({
|
useInfiniteQuery({
|
||||||
queryKey: ['group-feeds-' + params.id],
|
queryKey: ['widget-' + widget.id],
|
||||||
initialPageParam: 0,
|
initialPageParam: 0,
|
||||||
queryFn: async ({
|
queryFn: async ({
|
||||||
signal,
|
signal,
|
||||||
@ -32,11 +32,12 @@ export function LocalFeedsWidget({ params }: { params: Widget }) {
|
|||||||
signal: AbortSignal;
|
signal: AbortSignal;
|
||||||
pageParam: number;
|
pageParam: number;
|
||||||
}) => {
|
}) => {
|
||||||
|
const authors = JSON.parse(widget.content);
|
||||||
const events = await fetcher.fetchLatestEvents(
|
const events = await fetcher.fetchLatestEvents(
|
||||||
relayUrls,
|
relayUrls,
|
||||||
{
|
{
|
||||||
kinds: [NDKKind.Text, NDKKind.Repost],
|
kinds: [NDKKind.Text, NDKKind.Repost],
|
||||||
authors: JSON.parse(params.content),
|
authors: authors,
|
||||||
},
|
},
|
||||||
FETCH_LIMIT,
|
FETCH_LIMIT,
|
||||||
{ asOf: pageParam === 0 ? undefined : pageParam, abortSignal: signal }
|
{ asOf: pageParam === 0 ? undefined : pageParam, abortSignal: signal }
|
||||||
@ -78,7 +79,7 @@ export function LocalFeedsWidget({ params }: { params: Widget }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<WidgetWrapper>
|
<WidgetWrapper>
|
||||||
<TitleBar id={params.id} title={params.title} />
|
<TitleBar id={widget.id} title={widget.title} />
|
||||||
<VList className="flex-1">
|
<VList className="flex-1">
|
||||||
{status === 'pending' ? (
|
{status === 'pending' ? (
|
||||||
<div className="px-3 py-1.5">
|
<div className="px-3 py-1.5">
|
@ -14,11 +14,11 @@ import { FETCH_LIMIT } from '@stores/constants';
|
|||||||
|
|
||||||
import { Widget } from '@utils/types';
|
import { Widget } from '@utils/types';
|
||||||
|
|
||||||
export function GlobalHashtagWidget({ params }: { params: Widget }) {
|
export function HashtagWidget({ widget }: { widget: Widget }) {
|
||||||
const { ndk, relayUrls, fetcher } = useNDK();
|
const { ndk, relayUrls, fetcher } = useNDK();
|
||||||
const { status, data, hasNextPage, isFetchingNextPage, fetchNextPage } =
|
const { status, data, hasNextPage, isFetchingNextPage, fetchNextPage } =
|
||||||
useInfiniteQuery({
|
useInfiniteQuery({
|
||||||
queryKey: ['hashtag-' + params.title],
|
queryKey: ['widget-' + widget.id],
|
||||||
initialPageParam: 0,
|
initialPageParam: 0,
|
||||||
queryFn: async ({
|
queryFn: async ({
|
||||||
signal,
|
signal,
|
||||||
@ -31,7 +31,7 @@ export function GlobalHashtagWidget({ params }: { params: Widget }) {
|
|||||||
relayUrls,
|
relayUrls,
|
||||||
{
|
{
|
||||||
kinds: [NDKKind.Text, NDKKind.Repost],
|
kinds: [NDKKind.Text, NDKKind.Repost],
|
||||||
'#t': [params.content],
|
'#t': [widget.content],
|
||||||
},
|
},
|
||||||
FETCH_LIMIT,
|
FETCH_LIMIT,
|
||||||
{ asOf: pageParam === 0 ? undefined : pageParam, abortSignal: signal }
|
{ asOf: pageParam === 0 ? undefined : pageParam, abortSignal: signal }
|
||||||
@ -74,7 +74,7 @@ export function GlobalHashtagWidget({ params }: { params: Widget }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<WidgetWrapper>
|
<WidgetWrapper>
|
||||||
<TitleBar id={params.id} title={params.title} />
|
<TitleBar id={widget.id} title={widget.title} />
|
||||||
<VList className="flex-1">
|
<VList className="flex-1">
|
||||||
{status === 'pending' ? (
|
{status === 'pending' ? (
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
@ -86,7 +86,7 @@ export function GlobalHashtagWidget({ params }: { params: Widget }) {
|
|||||||
<img src="/ghost.png" alt="empty feeds" className="h-16 w-16" />
|
<img src="/ghost.png" alt="empty feeds" className="h-16 w-16" />
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<h3 className="font-semibold leading-tight text-neutral-900 dark:text-neutral-100">
|
<h3 className="font-semibold leading-tight text-neutral-900 dark:text-neutral-100">
|
||||||
Oops, it looks like there are no events related to {params.title}.
|
Oops, it looks like there are no events related to {widget.content}.
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-neutral-500 dark:text-neutral-400">
|
<p className="text-neutral-500 dark:text-neutral-400">
|
||||||
You can close this widget
|
You can close this widget
|
@ -1,17 +1,15 @@
|
|||||||
export * from './wrapper';
|
|
||||||
export * from './local/feeds';
|
|
||||||
export * from './local/user';
|
|
||||||
export * from './local/thread';
|
|
||||||
export * from './local/files';
|
|
||||||
export * from './local/articles';
|
|
||||||
export * from './global/articles';
|
|
||||||
export * from './global/files';
|
|
||||||
export * from './global/hashtag';
|
|
||||||
export * from './nostrBand/trendingNotes';
|
|
||||||
export * from './nostrBand/trendingAccounts';
|
|
||||||
export * from './tmp/feeds';
|
|
||||||
export * from './tmp/hashtag';
|
|
||||||
export * from './newsfeed';
|
export * from './newsfeed';
|
||||||
export * from './notification';
|
export * from './notification';
|
||||||
export * from './liveUpdater';
|
|
||||||
export * from './topic';
|
export * from './topic';
|
||||||
|
export * from './user';
|
||||||
|
export * from './article';
|
||||||
|
export * from './file';
|
||||||
|
export * from './hashtag';
|
||||||
|
export * from './thread';
|
||||||
|
export * from './group';
|
||||||
|
export * from './nostrBand/trendingAccounts';
|
||||||
|
export * from './nostrBand/trendingNotes';
|
||||||
|
export * from './other/wrapper';
|
||||||
|
export * from './other/liveUpdater';
|
||||||
|
export * from './other/toggleWidgetList';
|
||||||
|
export * from './other/widgetList';
|
||||||
|
@ -14,8 +14,7 @@ import {
|
|||||||
UnknownNote,
|
UnknownNote,
|
||||||
} from '@shared/notes';
|
} from '@shared/notes';
|
||||||
import { TitleBar } from '@shared/titleBar';
|
import { TitleBar } from '@shared/titleBar';
|
||||||
import { WidgetWrapper } from '@shared/widgets';
|
import { LiveUpdater, WidgetWrapper } from '@shared/widgets';
|
||||||
import { LiveUpdater } from '@shared/widgets';
|
|
||||||
|
|
||||||
import { FETCH_LIMIT } from '@stores/constants';
|
import { FETCH_LIMIT } from '@stores/constants';
|
||||||
|
|
||||||
|
@ -4,7 +4,10 @@ import { VList } from 'virtua';
|
|||||||
import { LoaderIcon } from '@shared/icons';
|
import { LoaderIcon } from '@shared/icons';
|
||||||
import { TitleBar } from '@shared/titleBar';
|
import { TitleBar } from '@shared/titleBar';
|
||||||
import { WidgetWrapper } from '@shared/widgets';
|
import { WidgetWrapper } from '@shared/widgets';
|
||||||
import { NostrBandUserProfile, type Profile } from '@shared/widgets/nostrBandUserProfile';
|
import {
|
||||||
|
NostrBandUserProfile,
|
||||||
|
type Profile,
|
||||||
|
} from '@shared/widgets/other/nostrBandUserProfile';
|
||||||
|
|
||||||
import { Widget } from '@utils/types';
|
import { Widget } from '@utils/types';
|
||||||
|
|
||||||
|
@ -13,9 +13,9 @@ interface Response {
|
|||||||
notes: Array<{ event: NDKEvent }>;
|
notes: Array<{ event: NDKEvent }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TrendingNotesWidget({ params }: { params: Widget }) {
|
export function TrendingNotesWidget({ widget }: { widget: Widget }) {
|
||||||
const { status, data } = useQuery({
|
const { status, data } = useQuery({
|
||||||
queryKey: ['trending-notes-widget'],
|
queryKey: ['widget-' + widget.id],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const res = await fetch('https://api.nostr.band/v0/trending/notes');
|
const res = await fetch('https://api.nostr.band/v0/trending/notes');
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
@ -33,7 +33,7 @@ export function TrendingNotesWidget({ params }: { params: Widget }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<WidgetWrapper>
|
<WidgetWrapper>
|
||||||
<TitleBar id={params.id} title="Trending Notes" />
|
<TitleBar id={widget.id} title="Trending Notes" />
|
||||||
<VList className="flex-1">
|
<VList className="flex-1">
|
||||||
{status === 'pending' ? (
|
{status === 'pending' ? (
|
||||||
<div className="flex h-full w-full items-center justify-center ">
|
<div className="flex h-full w-full items-center justify-center ">
|
||||||
|
@ -14,7 +14,7 @@ export function ToggleWidgetList() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
addWidget.mutate({ kind: WIDGET_KIND.tmp.list, title: '', content: '' })
|
addWidget.mutate({ kind: WIDGET_KIND.list, title: '', content: '' })
|
||||||
}
|
}
|
||||||
className="inline-flex h-9 items-center gap-2 rounded-full bg-neutral-200 px-3 text-neutral-900 hover:bg-neutral-300 dark:bg-neutral-800 dark:text-neutral-100 dark:hover:bg-neutral-700"
|
className="inline-flex h-9 items-center gap-2 rounded-full bg-neutral-200 px-3 text-neutral-900 hover:bg-neutral-300 dark:bg-neutral-800 dark:text-neutral-100 dark:hover:bg-neutral-700"
|
||||||
>
|
>
|
30
src/shared/widgets/other/widgetList.tsx
Normal file
30
src/shared/widgets/other/widgetList.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { TitleBar } from '@shared/titleBar';
|
||||||
|
import { WidgetWrapper } from '@shared/widgets';
|
||||||
|
|
||||||
|
import { Widget } from '@utils/types';
|
||||||
|
|
||||||
|
export function WidgetList({ widget }: { widget: Widget }) {
|
||||||
|
return (
|
||||||
|
<WidgetWrapper>
|
||||||
|
<TitleBar id={widget.id} title="Add widgets" />
|
||||||
|
<div className="flex-1 overflow-y-auto pb-10 scrollbar-none">
|
||||||
|
<div className="flex flex-col gap-6 px-3">
|
||||||
|
<div className="border-t border-neutral-200 pt-6 dark:border-neutral-800">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
disabled
|
||||||
|
className="inline-flex h-14 w-full items-center justify-center gap-2.5 rounded-xl bg-neutral-50 text-sm font-medium text-neutral-900 dark:bg-neutral-950 dark:text-neutral-100"
|
||||||
|
>
|
||||||
|
Build your own widget{' '}
|
||||||
|
<div className="-rotate-3 transform-gpu rounded-md border border-neutral-200 bg-neutral-100 px-1.5 py-1 dark:border-neutral-800 dark:bg-neutral-900">
|
||||||
|
<span className="bg-gradient-to-r from-blue-400 via-red-400 to-orange-500 bg-clip-text text-xs text-transparent dark:from-blue-200 dark:via-red-200 dark:to-orange-300">
|
||||||
|
Coming soon
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</WidgetWrapper>
|
||||||
|
);
|
||||||
|
}
|
@ -18,8 +18,8 @@ import { WidgetWrapper } from '@shared/widgets';
|
|||||||
import { useEvent } from '@utils/hooks/useEvent';
|
import { useEvent } from '@utils/hooks/useEvent';
|
||||||
import { Widget } from '@utils/types';
|
import { Widget } from '@utils/types';
|
||||||
|
|
||||||
export function LocalThreadWidget({ params }: { params: Widget }) {
|
export function ThreadWidget({ widget }: { widget: Widget }) {
|
||||||
const { status, data } = useEvent(params.content);
|
const { status, data } = useEvent(widget.content);
|
||||||
|
|
||||||
const renderKind = useCallback(
|
const renderKind = useCallback(
|
||||||
(event: NDKEvent) => {
|
(event: NDKEvent) => {
|
||||||
@ -39,7 +39,7 @@ export function LocalThreadWidget({ params }: { params: Widget }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<WidgetWrapper>
|
<WidgetWrapper>
|
||||||
<TitleBar id={params.id} title={params.title} />
|
<TitleBar id={widget.id} title={widget.title} />
|
||||||
<WVList className="flex-1 overflow-y-auto px-3 pb-5">
|
<WVList className="flex-1 overflow-y-auto px-3 pb-5">
|
||||||
{status === 'pending' ? (
|
{status === 'pending' ? (
|
||||||
<div className="flex h-16 items-center justify-center rounded-xl bg-neutral-50 px-3 py-3 dark:bg-neutral-950">
|
<div className="flex h-16 items-center justify-center rounded-xl bg-neutral-50 px-3 py-3 dark:bg-neutral-950">
|
||||||
@ -52,7 +52,7 @@ export function LocalThreadWidget({ params }: { params: Widget }) {
|
|||||||
{renderKind(data)}
|
{renderKind(data)}
|
||||||
<NoteActions id={data.id} pubkey={data.pubkey} />
|
<NoteActions id={data.id} pubkey={data.pubkey} />
|
||||||
</div>
|
</div>
|
||||||
<NoteReplyForm eventId={params.content} />
|
<NoteReplyForm eventId={widget.content} />
|
||||||
<ReplyList eventId={data.id} />
|
<ReplyList eventId={data.id} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
@ -28,7 +28,7 @@ export function XfeedsWidget({ params }: { params: Widget }) {
|
|||||||
|
|
||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
addWidget.mutate({
|
addWidget.mutate({
|
||||||
kind: WIDGET_KIND.local.feeds,
|
kind: WIDGET_KIND.group,
|
||||||
title: title || 'Group',
|
title: title || 'Group',
|
||||||
content: JSON.stringify(groups),
|
content: JSON.stringify(groups),
|
||||||
});
|
});
|
||||||
|
@ -39,7 +39,7 @@ export function XhashtagWidget({ params }: { params: Widget }) {
|
|||||||
const onSubmit = async (data: FormValues) => {
|
const onSubmit = async (data: FormValues) => {
|
||||||
try {
|
try {
|
||||||
addWidget.mutate({
|
addWidget.mutate({
|
||||||
kind: WIDGET_KIND.global.hashtag,
|
kind: WIDGET_KIND.hashtag,
|
||||||
title: data.hashtag,
|
title: data.hashtag,
|
||||||
content: data.hashtag.replace('#', ''),
|
content: data.hashtag.replace('#', ''),
|
||||||
});
|
});
|
||||||
|
@ -23,7 +23,7 @@ export function TopicWidget({ widget }: { widget: Widget }) {
|
|||||||
const { relayUrls, ndk, fetcher } = useNDK();
|
const { relayUrls, ndk, fetcher } = useNDK();
|
||||||
const { status, data, hasNextPage, isFetchingNextPage, fetchNextPage } =
|
const { status, data, hasNextPage, isFetchingNextPage, fetchNextPage } =
|
||||||
useInfiniteQuery({
|
useInfiniteQuery({
|
||||||
queryKey: [widget.title],
|
queryKey: ['widget-' + widget.id],
|
||||||
initialPageParam: 0,
|
initialPageParam: 0,
|
||||||
queryFn: async ({
|
queryFn: async ({
|
||||||
signal,
|
signal,
|
||||||
|
@ -18,17 +18,17 @@ import { WidgetWrapper } from '@shared/widgets';
|
|||||||
import { nHoursAgo } from '@utils/date';
|
import { nHoursAgo } from '@utils/date';
|
||||||
import { Widget } from '@utils/types';
|
import { Widget } from '@utils/types';
|
||||||
|
|
||||||
export function LocalUserWidget({ params }: { params: Widget }) {
|
export function UserWidget({ widget }: { widget: Widget }) {
|
||||||
const { ndk } = useNDK();
|
const { ndk } = useNDK();
|
||||||
const { status, data } = useQuery({
|
const { status, data } = useQuery({
|
||||||
queryKey: ['user-posts', params.content],
|
queryKey: ['widget-' + widget.id],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const rootIds = new Set();
|
const rootIds = new Set();
|
||||||
const dedupQueue = new Set();
|
const dedupQueue = new Set();
|
||||||
|
|
||||||
const events = await ndk.fetchEvents({
|
const events = await ndk.fetchEvents({
|
||||||
kinds: [NDKKind.Text, NDKKind.Repost],
|
kinds: [NDKKind.Text, NDKKind.Repost],
|
||||||
authors: [params.content],
|
authors: [widget.content],
|
||||||
since: nHoursAgo(24),
|
since: nHoursAgo(24),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -70,10 +70,10 @@ export function LocalUserWidget({ params }: { params: Widget }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<WidgetWrapper>
|
<WidgetWrapper>
|
||||||
<TitleBar id={params.id} title={params.title} />
|
<TitleBar id={widget.id} title={widget.title} />
|
||||||
<WVList className="flex-1 overflow-y-auto">
|
<WVList className="flex-1 overflow-y-auto">
|
||||||
<div className="px-3 pt-1.5">
|
<div className="px-3 pt-1.5">
|
||||||
<UserProfile pubkey={params.content} />
|
<UserProfile pubkey={widget.content} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="mb-3 mt-4 px-3 text-lg font-semibold text-neutral-900 dark:text-neutral-100">
|
<h3 className="mb-3 mt-4 px-3 text-lg font-semibold text-neutral-900 dark:text-neutral-100">
|
@ -42,35 +42,18 @@ export const HASHTAGS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export const WIDGET_KIND = {
|
export const WIDGET_KIND = {
|
||||||
local: {
|
user: 1,
|
||||||
network: 100,
|
thread: 2,
|
||||||
feeds: 101,
|
group: 3,
|
||||||
files: 102,
|
article: 4,
|
||||||
articles: 103,
|
file: 5,
|
||||||
user: 104,
|
trendingNotes: 6,
|
||||||
thread: 105,
|
trendingAccounts: 7,
|
||||||
follows: 106,
|
topic: 8,
|
||||||
notification: 107,
|
hashtag: 9,
|
||||||
},
|
notification: 9998,
|
||||||
global: {
|
newsfeed: 9999,
|
||||||
feeds: 1000,
|
|
||||||
files: 1001,
|
|
||||||
articles: 1002,
|
|
||||||
hashtag: 1003,
|
|
||||||
topic: 108,
|
|
||||||
},
|
|
||||||
nostrBand: {
|
|
||||||
trendingAccounts: 1,
|
|
||||||
trendingNotes: 2,
|
|
||||||
},
|
|
||||||
other: {
|
|
||||||
learnNostr: 90000,
|
|
||||||
},
|
|
||||||
tmp: {
|
|
||||||
list: 10000,
|
list: 10000,
|
||||||
xfeed: 10001,
|
|
||||||
xhashtag: 10002,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TOPICS = [
|
export const TOPICS = [
|
||||||
|
Loading…
Reference in New Issue
Block a user