mirror of
https://github.com/luminous-devs/lume.git
synced 2024-09-18 11:13:30 +00:00
add topic widget
This commit is contained in:
parent
108ecafab7
commit
cb9006abb2
@ -6,7 +6,7 @@ import { useStorage } from '@libs/storage/provider';
|
|||||||
|
|
||||||
import { ArrowLeftIcon, CheckCircleIcon, LoaderIcon } from '@shared/icons';
|
import { ArrowLeftIcon, CheckCircleIcon, LoaderIcon } from '@shared/icons';
|
||||||
|
|
||||||
import { HASHTAGS, WidgetKinds } from '@stores/constants';
|
import { HASHTAGS, WIDGET_KIND } from '@stores/constants';
|
||||||
import { useOnboarding } from '@stores/onboarding';
|
import { useOnboarding } from '@stores/onboarding';
|
||||||
|
|
||||||
export function OnboardHashtagScreen() {
|
export function OnboardHashtagScreen() {
|
||||||
@ -35,7 +35,7 @@ export function OnboardHashtagScreen() {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
for (const tag of tags) {
|
for (const tag of tags) {
|
||||||
await db.createWidget(WidgetKinds.global.hashtag, tag, tag.replace('#', ''));
|
await db.createWidget(WIDGET_KIND.global.hashtag, tag, tag.replace('#', ''));
|
||||||
}
|
}
|
||||||
|
|
||||||
setHashtag();
|
setHashtag();
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { PlusIcon } from '@shared/icons';
|
import { PlusIcon } from '@shared/icons';
|
||||||
import { WidgetWrapper } from '@shared/widgets';
|
import { WidgetWrapper } from '@shared/widgets';
|
||||||
|
|
||||||
import { WidgetKinds } from '@stores/constants';
|
import { WIDGET_KIND } from '@stores/constants';
|
||||||
|
|
||||||
import { useWidget } from '@utils/hooks/useWidget';
|
import { useWidget } from '@utils/hooks/useWidget';
|
||||||
|
|
||||||
@ -14,7 +14,7 @@ export function ToggleWidgetList() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
addWidget.mutate({ kind: WidgetKinds.tmp.list, title: '', content: '' })
|
addWidget.mutate({ kind: WIDGET_KIND.tmp.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"
|
||||||
>
|
>
|
||||||
|
@ -13,7 +13,7 @@ import {
|
|||||||
import { TitleBar } from '@shared/titleBar';
|
import { TitleBar } from '@shared/titleBar';
|
||||||
import { WidgetWrapper } from '@shared/widgets';
|
import { WidgetWrapper } from '@shared/widgets';
|
||||||
|
|
||||||
import { DefaultWidgets, WidgetKinds } from '@stores/constants';
|
import { DEFAULT_WIDGETS, WIDGET_KIND } from '@stores/constants';
|
||||||
|
|
||||||
import { useWidget } from '@utils/hooks/useWidget';
|
import { useWidget } from '@utils/hooks/useWidget';
|
||||||
import { Widget, WidgetGroup, WidgetGroupItem } from '@utils/types';
|
import { Widget, WidgetGroup, WidgetGroupItem } from '@utils/types';
|
||||||
@ -22,55 +22,48 @@ export function WidgetList({ params }: { params: Widget }) {
|
|||||||
const { addWidget, removeWidget } = useWidget();
|
const { addWidget, removeWidget } = useWidget();
|
||||||
|
|
||||||
const open = (item: WidgetGroupItem) => {
|
const open = (item: WidgetGroupItem) => {
|
||||||
addWidget.mutate({ kind: item.kind, title: item.title, content: '' });
|
addWidget.mutate({
|
||||||
|
kind: item.kind,
|
||||||
|
title: item.title,
|
||||||
|
content: JSON.stringify(item.content),
|
||||||
|
});
|
||||||
removeWidget.mutate(params.id);
|
removeWidget.mutate(params.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderIcon = useCallback(
|
const renderIcon = useCallback((kind: number) => {
|
||||||
(kind: number) => {
|
switch (kind) {
|
||||||
switch (kind) {
|
case WIDGET_KIND.tmp.xfeed:
|
||||||
case WidgetKinds.tmp.xfeed:
|
return (
|
||||||
return (
|
<GroupFeedsIcon className="h-5 w-5 text-neutral-900 dark:text-neutral-100" />
|
||||||
<GroupFeedsIcon className="h-5 w-5 text-neutral-900 dark:text-neutral-100" />
|
);
|
||||||
);
|
case WIDGET_KIND.local.follows:
|
||||||
case WidgetKinds.local.follows:
|
return <FollowsIcon className="h-5 w-5 text-neutral-900 dark:text-neutral-100" />;
|
||||||
return (
|
case WIDGET_KIND.local.files:
|
||||||
<FollowsIcon className="h-5 w-5 text-neutral-900 dark:text-neutral-100" />
|
case WIDGET_KIND.global.files:
|
||||||
);
|
return <FileIcon className="h-5 w-5 text-neutral-900 dark:text-neutral-100" />;
|
||||||
case WidgetKinds.local.files:
|
case WIDGET_KIND.local.articles:
|
||||||
case WidgetKinds.global.files:
|
case WIDGET_KIND.global.articles:
|
||||||
return <FileIcon className="h-5 w-5 text-neutral-900 dark:text-neutral-100" />;
|
return <ArticleIcon className="h-5 w-5 text-neutral-900 dark:text-neutral-100" />;
|
||||||
case WidgetKinds.local.articles:
|
case WIDGET_KIND.tmp.xhashtag:
|
||||||
case WidgetKinds.global.articles:
|
return <HashtagIcon className="h-5 w-5 text-neutral-900 dark:text-neutral-100" />;
|
||||||
return (
|
case WIDGET_KIND.nostrBand.trendingAccounts:
|
||||||
<ArticleIcon className="h-5 w-5 text-neutral-900 dark:text-neutral-100" />
|
case WIDGET_KIND.nostrBand.trendingNotes:
|
||||||
);
|
return (
|
||||||
case WidgetKinds.tmp.xhashtag:
|
<TrendingIcon className="h-5 w-5 text-neutral-900 dark:text-neutral-100" />
|
||||||
return (
|
);
|
||||||
<HashtagIcon 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 WidgetKinds.nostrBand.trendingAccounts:
|
case WIDGET_KIND.other.learnNostr:
|
||||||
case WidgetKinds.nostrBand.trendingNotes:
|
return <ThreadsIcon className="h-5 w-5 text-neutral-900 dark:text-neutral-100" />;
|
||||||
return (
|
default:
|
||||||
<TrendingIcon className="h-5 w-5 text-neutral-900 dark:text-neutral-100" />
|
return null;
|
||||||
);
|
}
|
||||||
case WidgetKinds.local.notification:
|
}, []);
|
||||||
return <BellIcon className="h-5 w-5 text-neutral-900 dark:text-neutral-100" />;
|
|
||||||
case WidgetKinds.other.learnNostr:
|
|
||||||
return (
|
|
||||||
<ThreadsIcon className="h-5 w-5 text-neutral-900 dark:text-neutral-100" />
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[DefaultWidgets]
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderItem = useCallback((row: WidgetGroup, index: number) => {
|
const renderItem = useCallback((row: WidgetGroup, index: number) => {
|
||||||
return (
|
return (
|
||||||
<div key={index} className="flex flex-col gap-2">
|
<div key={index} className="flex flex-col gap-2">
|
||||||
<h3 className="text-sm font-semibold">{row.title}</h3>
|
<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">
|
<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) => (
|
{row.data.map((item, index) => (
|
||||||
<button
|
<button
|
||||||
@ -111,7 +104,7 @@ export function WidgetList({ params }: { params: Widget }) {
|
|||||||
<TitleBar id={params.id} title="Add widget" />
|
<TitleBar id={params.id} title="Add widget" />
|
||||||
<div className="flex-1 overflow-y-auto pb-10 scrollbar-none">
|
<div className="flex-1 overflow-y-auto pb-10 scrollbar-none">
|
||||||
<div className="flex flex-col gap-6 px-3">
|
<div className="flex flex-col gap-6 px-3">
|
||||||
{DefaultWidgets.map((row: WidgetGroup, index: number) =>
|
{DEFAULT_WIDGETS.map((row: WidgetGroup, index: number) =>
|
||||||
renderItem(row, index)
|
renderItem(row, index)
|
||||||
)}
|
)}
|
||||||
<div className="border-t border-neutral-200 pt-6 dark:border-neutral-800">
|
<div className="border-t border-neutral-200 pt-6 dark:border-neutral-800">
|
||||||
|
@ -25,7 +25,7 @@ import {
|
|||||||
XhashtagWidget,
|
XhashtagWidget,
|
||||||
} from '@shared/widgets';
|
} from '@shared/widgets';
|
||||||
|
|
||||||
import { WidgetKinds } from '@stores/constants';
|
import { WIDGET_KIND } from '@stores/constants';
|
||||||
|
|
||||||
import { Widget } from '@utils/types';
|
import { Widget } from '@utils/types';
|
||||||
|
|
||||||
@ -43,13 +43,13 @@ export function SpaceScreen() {
|
|||||||
id: '9998',
|
id: '9998',
|
||||||
title: 'Notification',
|
title: 'Notification',
|
||||||
content: '',
|
content: '',
|
||||||
kind: WidgetKinds.local.notification,
|
kind: WIDGET_KIND.local.notification,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '9999',
|
id: '9999',
|
||||||
title: 'Newsfeed',
|
title: 'Newsfeed',
|
||||||
content: '',
|
content: '',
|
||||||
kind: WidgetKinds.local.network,
|
kind: WIDGET_KIND.local.network,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -63,35 +63,35 @@ export function SpaceScreen() {
|
|||||||
|
|
||||||
const renderItem = useCallback((widget: Widget) => {
|
const renderItem = useCallback((widget: Widget) => {
|
||||||
switch (widget.kind) {
|
switch (widget.kind) {
|
||||||
case WidgetKinds.local.feeds:
|
case WIDGET_KIND.local.feeds:
|
||||||
return <LocalFeedsWidget key={widget.id} params={widget} />;
|
return <LocalFeedsWidget key={widget.id} params={widget} />;
|
||||||
case WidgetKinds.local.files:
|
case WIDGET_KIND.local.files:
|
||||||
return <LocalFilesWidget key={widget.id} params={widget} />;
|
return <LocalFilesWidget key={widget.id} params={widget} />;
|
||||||
case WidgetKinds.local.articles:
|
case WIDGET_KIND.local.articles:
|
||||||
return <LocalArticlesWidget key={widget.id} params={widget} />;
|
return <LocalArticlesWidget key={widget.id} params={widget} />;
|
||||||
case WidgetKinds.local.user:
|
case WIDGET_KIND.local.user:
|
||||||
return <LocalUserWidget key={widget.id} params={widget} />;
|
return <LocalUserWidget key={widget.id} params={widget} />;
|
||||||
case WidgetKinds.local.thread:
|
case WIDGET_KIND.local.thread:
|
||||||
return <LocalThreadWidget key={widget.id} params={widget} />;
|
return <LocalThreadWidget key={widget.id} params={widget} />;
|
||||||
case WidgetKinds.global.hashtag:
|
case WIDGET_KIND.global.hashtag:
|
||||||
return <GlobalHashtagWidget key={widget.id} params={widget} />;
|
return <GlobalHashtagWidget key={widget.id} params={widget} />;
|
||||||
case WidgetKinds.global.articles:
|
case WIDGET_KIND.global.articles:
|
||||||
return <GlobalArticlesWidget key={widget.id} params={widget} />;
|
return <GlobalArticlesWidget key={widget.id} params={widget} />;
|
||||||
case WidgetKinds.global.files:
|
case WIDGET_KIND.global.files:
|
||||||
return <GlobalFilesWidget key={widget.id} params={widget} />;
|
return <GlobalFilesWidget key={widget.id} params={widget} />;
|
||||||
case WidgetKinds.nostrBand.trendingAccounts:
|
case WIDGET_KIND.nostrBand.trendingAccounts:
|
||||||
return <TrendingAccountsWidget key={widget.id} params={widget} />;
|
return <TrendingAccountsWidget key={widget.id} params={widget} />;
|
||||||
case WidgetKinds.nostrBand.trendingNotes:
|
case WIDGET_KIND.nostrBand.trendingNotes:
|
||||||
return <TrendingNotesWidget key={widget.id} params={widget} />;
|
return <TrendingNotesWidget key={widget.id} params={widget} />;
|
||||||
case WidgetKinds.tmp.xfeed:
|
case WIDGET_KIND.tmp.xfeed:
|
||||||
return <XfeedsWidget key={widget.id} params={widget} />;
|
return <XfeedsWidget key={widget.id} params={widget} />;
|
||||||
case WidgetKinds.tmp.xhashtag:
|
case WIDGET_KIND.tmp.xhashtag:
|
||||||
return <XhashtagWidget key={widget.id} params={widget} />;
|
return <XhashtagWidget key={widget.id} params={widget} />;
|
||||||
case WidgetKinds.tmp.list:
|
case WIDGET_KIND.tmp.list:
|
||||||
return <WidgetList key={widget.id} params={widget} />;
|
return <WidgetList key={widget.id} params={widget} />;
|
||||||
case WidgetKinds.local.notification:
|
case WIDGET_KIND.local.notification:
|
||||||
return <NotificationWidget key={widget.id} />;
|
return <NotificationWidget key={widget.id} />;
|
||||||
case WidgetKinds.local.network:
|
case WIDGET_KIND.local.network:
|
||||||
return <NewsfeedWidget key={widget.id} />;
|
return <NewsfeedWidget key={widget.id} />;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
|
@ -6,7 +6,7 @@ import { NoteReply } from '@shared/notes/actions/reply';
|
|||||||
import { NoteRepost } from '@shared/notes/actions/repost';
|
import { NoteRepost } from '@shared/notes/actions/repost';
|
||||||
import { NoteZap } from '@shared/notes/actions/zap';
|
import { NoteZap } from '@shared/notes/actions/zap';
|
||||||
|
|
||||||
import { WidgetKinds } from '@stores/constants';
|
import { WIDGET_KIND } from '@stores/constants';
|
||||||
|
|
||||||
import { useWidget } from '@utils/hooks/useWidget';
|
import { useWidget } from '@utils/hooks/useWidget';
|
||||||
|
|
||||||
@ -34,7 +34,7 @@ export function NoteActions({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
addWidget.mutate({
|
addWidget.mutate({
|
||||||
kind: WidgetKinds.local.thread,
|
kind: WIDGET_KIND.local.thread,
|
||||||
title: 'Thread',
|
title: 'Thread',
|
||||||
content: id,
|
content: id,
|
||||||
})
|
})
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { WidgetKinds } from '@stores/constants';
|
import { WIDGET_KIND } from '@stores/constants';
|
||||||
|
|
||||||
import { useWidget } from '@utils/hooks/useWidget';
|
import { useWidget } from '@utils/hooks/useWidget';
|
||||||
|
|
||||||
@ -10,7 +10,7 @@ export function Hashtag({ tag }: { tag: string }) {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
addWidget.mutate({
|
addWidget.mutate({
|
||||||
kind: WidgetKinds.global.hashtag,
|
kind: WIDGET_KIND.global.hashtag,
|
||||||
title: tag,
|
title: tag,
|
||||||
content: tag.replace('#', ''),
|
content: tag.replace('#', ''),
|
||||||
})
|
})
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
} from '@shared/notes';
|
} from '@shared/notes';
|
||||||
import { User } from '@shared/user';
|
import { User } from '@shared/user';
|
||||||
|
|
||||||
import { WidgetKinds } from '@stores/constants';
|
import { WIDGET_KIND } from '@stores/constants';
|
||||||
|
|
||||||
import { useEvent } from '@utils/hooks/useEvent';
|
import { useEvent } from '@utils/hooks/useEvent';
|
||||||
import { useWidget } from '@utils/hooks/useWidget';
|
import { useWidget } from '@utils/hooks/useWidget';
|
||||||
@ -50,7 +50,7 @@ export const MentionNote = memo(function MentionNote({ id }: { id: string }) {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
addWidget.mutate({
|
addWidget.mutate({
|
||||||
kind: WidgetKinds.local.thread,
|
kind: WIDGET_KIND.local.thread,
|
||||||
title: 'Thread',
|
title: 'Thread',
|
||||||
content: data.id,
|
content: data.id,
|
||||||
})
|
})
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
|
||||||
import { WidgetKinds } from '@stores/constants';
|
import { WIDGET_KIND } from '@stores/constants';
|
||||||
|
|
||||||
import { useProfile } from '@utils/hooks/useProfile';
|
import { useProfile } from '@utils/hooks/useProfile';
|
||||||
import { useWidget } from '@utils/hooks/useWidget';
|
import { useWidget } from '@utils/hooks/useWidget';
|
||||||
@ -14,14 +14,14 @@ export const MentionUser = memo(function MentionUser({ pubkey }: { pubkey: strin
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
addWidget.mutate({
|
addWidget.mutate({
|
||||||
kind: WidgetKinds.local.user,
|
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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
onKeyDown={() =>
|
onKeyDown={() =>
|
||||||
addWidget.mutate({
|
addWidget.mutate({
|
||||||
kind: WidgetKinds.local.user,
|
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,
|
||||||
})
|
})
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
} from '@shared/notes';
|
} from '@shared/notes';
|
||||||
import { User } from '@shared/user';
|
import { User } from '@shared/user';
|
||||||
|
|
||||||
import { WidgetKinds } from '@stores/constants';
|
import { WIDGET_KIND } from '@stores/constants';
|
||||||
|
|
||||||
import { formatCreatedAt } from '@utils/createdAt';
|
import { formatCreatedAt } from '@utils/createdAt';
|
||||||
import { useEvent } from '@utils/hooks/useEvent';
|
import { useEvent } from '@utils/hooks/useEvent';
|
||||||
@ -91,7 +91,7 @@ export function NotifyNote({ event }: { event: NDKEvent }) {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
addWidget.mutate({
|
addWidget.mutate({
|
||||||
kind: WidgetKinds.local.thread,
|
kind: WIDGET_KIND.local.thread,
|
||||||
title: 'Thread',
|
title: 'Thread',
|
||||||
content: data.id,
|
content: data.id,
|
||||||
})
|
})
|
||||||
|
@ -4,7 +4,7 @@ import { memo } from 'react';
|
|||||||
import { ChildNote, NoteActions } from '@shared/notes';
|
import { ChildNote, NoteActions } from '@shared/notes';
|
||||||
import { User } from '@shared/user';
|
import { User } from '@shared/user';
|
||||||
|
|
||||||
import { WidgetKinds } from '@stores/constants';
|
import { WIDGET_KIND } from '@stores/constants';
|
||||||
|
|
||||||
import { useNostr } from '@utils/hooks/useNostr';
|
import { useNostr } from '@utils/hooks/useNostr';
|
||||||
import { useRichContent } from '@utils/hooks/useRichContent';
|
import { useRichContent } from '@utils/hooks/useRichContent';
|
||||||
@ -30,7 +30,7 @@ export function TextNote({ event }: { event: NDKEvent }) {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
addWidget.mutate({
|
addWidget.mutate({
|
||||||
kind: WidgetKinds.local.thread,
|
kind: WIDGET_KIND.local.thread,
|
||||||
title: 'Thread',
|
title: 'Thread',
|
||||||
content: thread.rootEventId,
|
content: thread.rootEventId,
|
||||||
})
|
})
|
||||||
|
@ -14,3 +14,4 @@ export * from './tmp/hashtag';
|
|||||||
export * from './newsfeed';
|
export * from './newsfeed';
|
||||||
export * from './notification';
|
export * from './notification';
|
||||||
export * from './liveUpdater';
|
export * from './liveUpdater';
|
||||||
|
export * from './topic';
|
||||||
|
@ -6,7 +6,7 @@ import { ArrowRightCircleIcon, CancelIcon, CheckCircleIcon } from '@shared/icons
|
|||||||
import { User } from '@shared/user';
|
import { User } from '@shared/user';
|
||||||
import { WidgetWrapper } from '@shared/widgets';
|
import { WidgetWrapper } from '@shared/widgets';
|
||||||
|
|
||||||
import { WidgetKinds } from '@stores/constants';
|
import { WIDGET_KIND } from '@stores/constants';
|
||||||
|
|
||||||
import { useWidget } from '@utils/hooks/useWidget';
|
import { useWidget } from '@utils/hooks/useWidget';
|
||||||
import { Widget } from '@utils/types';
|
import { Widget } from '@utils/types';
|
||||||
@ -28,7 +28,7 @@ export function XfeedsWidget({ params }: { params: Widget }) {
|
|||||||
|
|
||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
addWidget.mutate({
|
addWidget.mutate({
|
||||||
kind: WidgetKinds.local.feeds,
|
kind: WIDGET_KIND.local.feeds,
|
||||||
title: title || 'Group',
|
title: title || 'Group',
|
||||||
content: JSON.stringify(groups),
|
content: JSON.stringify(groups),
|
||||||
});
|
});
|
||||||
|
@ -3,7 +3,7 @@ import { Resolver, useForm } from 'react-hook-form';
|
|||||||
import { ArrowRightCircleIcon, CancelIcon } from '@shared/icons';
|
import { ArrowRightCircleIcon, CancelIcon } from '@shared/icons';
|
||||||
import { WidgetWrapper } from '@shared/widgets';
|
import { WidgetWrapper } from '@shared/widgets';
|
||||||
|
|
||||||
import { HASHTAGS, WidgetKinds } from '@stores/constants';
|
import { HASHTAGS, WIDGET_KIND } from '@stores/constants';
|
||||||
|
|
||||||
import { useWidget } from '@utils/hooks/useWidget';
|
import { useWidget } from '@utils/hooks/useWidget';
|
||||||
import { Widget } from '@utils/types';
|
import { Widget } from '@utils/types';
|
||||||
@ -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: WidgetKinds.global.hashtag,
|
kind: WIDGET_KIND.global.hashtag,
|
||||||
title: data.hashtag,
|
title: data.hashtag,
|
||||||
content: data.hashtag.replace('#', ''),
|
content: data.hashtag.replace('#', ''),
|
||||||
});
|
});
|
||||||
|
128
src/shared/widgets/topic.tsx
Normal file
128
src/shared/widgets/topic.tsx
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
|
||||||
|
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||||
|
import { useCallback, useMemo } from 'react';
|
||||||
|
import { VList } from 'virtua';
|
||||||
|
|
||||||
|
import { useNDK } from '@libs/ndk/provider';
|
||||||
|
|
||||||
|
import { ArrowRightCircleIcon, LoaderIcon } from '@shared/icons';
|
||||||
|
import {
|
||||||
|
MemoizedRepost,
|
||||||
|
MemoizedTextNote,
|
||||||
|
NoteSkeleton,
|
||||||
|
UnknownNote,
|
||||||
|
} 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 TopicWidget({ widget }: { widget: Widget }) {
|
||||||
|
const { relayUrls, ndk, fetcher } = useNDK();
|
||||||
|
const { status, data, hasNextPage, isFetchingNextPage, fetchNextPage } =
|
||||||
|
useInfiniteQuery({
|
||||||
|
queryKey: [widget.title],
|
||||||
|
initialPageParam: 0,
|
||||||
|
queryFn: async ({
|
||||||
|
signal,
|
||||||
|
pageParam,
|
||||||
|
}: {
|
||||||
|
signal: AbortSignal;
|
||||||
|
pageParam: number;
|
||||||
|
}) => {
|
||||||
|
const hashtags: string[] = JSON.parse(widget.content as string);
|
||||||
|
const rootIds = new Set();
|
||||||
|
const dedupQueue = new Set();
|
||||||
|
|
||||||
|
const events = await fetcher.fetchLatestEvents(
|
||||||
|
relayUrls,
|
||||||
|
{
|
||||||
|
kinds: [NDKKind.Text, NDKKind.Repost],
|
||||||
|
'#t': hashtags,
|
||||||
|
},
|
||||||
|
FETCH_LIMIT,
|
||||||
|
{ asOf: pageParam === 0 ? undefined : pageParam, abortSignal: signal }
|
||||||
|
);
|
||||||
|
|
||||||
|
const ndkEvents = events.map((event) => {
|
||||||
|
return new NDKEvent(ndk, event);
|
||||||
|
});
|
||||||
|
|
||||||
|
ndkEvents.forEach((event) => {
|
||||||
|
const tags = event.tags.filter((el) => el[0] === 'e');
|
||||||
|
if (tags && tags.length > 0) {
|
||||||
|
const rootId = tags.filter((el) => el[3] === 'root')[1] ?? tags[0][1];
|
||||||
|
if (rootIds.has(rootId)) return dedupQueue.add(event.id);
|
||||||
|
rootIds.add(rootId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ndkEvents
|
||||||
|
.filter((event) => !dedupQueue.has(event.id))
|
||||||
|
.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,
|
||||||
|
});
|
||||||
|
|
||||||
|
const allEvents = useMemo(
|
||||||
|
() => (data ? data.pages.flatMap((page) => page) : []),
|
||||||
|
[data]
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderItem = useCallback(
|
||||||
|
(event: NDKEvent) => {
|
||||||
|
switch (event.kind) {
|
||||||
|
case NDKKind.Text:
|
||||||
|
return <MemoizedTextNote key={event.id} event={event} />;
|
||||||
|
case NDKKind.Repost:
|
||||||
|
return <MemoizedRepost key={event.id} event={event} />;
|
||||||
|
default:
|
||||||
|
return <UnknownNote key={event.id} event={event} />;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[data]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<WidgetWrapper>
|
||||||
|
<TitleBar id={widget.id} title={widget.title} />
|
||||||
|
<VList className="flex-1" overscan={2}>
|
||||||
|
{status === 'pending' ? (
|
||||||
|
<div className="px-3 py-1.5">
|
||||||
|
<div className="rounded-xl bg-neutral-100 px-3 py-3 dark:bg-neutral-900">
|
||||||
|
<NoteSkeleton />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
allEvents.map((item) => renderItem(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>
|
||||||
|
);
|
||||||
|
}
|
@ -41,7 +41,7 @@ export const HASHTAGS = [
|
|||||||
{ hashtag: '#primal' },
|
{ hashtag: '#primal' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const WidgetKinds = {
|
export const WIDGET_KIND = {
|
||||||
local: {
|
local: {
|
||||||
network: 100,
|
network: 100,
|
||||||
feeds: 101,
|
feeds: 101,
|
||||||
@ -57,6 +57,7 @@ export const WidgetKinds = {
|
|||||||
files: 1001,
|
files: 1001,
|
||||||
articles: 1002,
|
articles: 1002,
|
||||||
hashtag: 1003,
|
hashtag: 1003,
|
||||||
|
topic: 108,
|
||||||
},
|
},
|
||||||
nostrBand: {
|
nostrBand: {
|
||||||
trendingAccounts: 1,
|
trendingAccounts: 1,
|
||||||
@ -72,24 +73,293 @@ export const WidgetKinds = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DefaultWidgets: Array<WidgetGroup> = [
|
export const TOPICS = [
|
||||||
|
{
|
||||||
|
title: 'Gaming',
|
||||||
|
description: '',
|
||||||
|
kind: WIDGET_KIND.global.topic,
|
||||||
|
content: [
|
||||||
|
'#gamestr',
|
||||||
|
'#gaming',
|
||||||
|
'#gamer',
|
||||||
|
'#ps',
|
||||||
|
'#playstation',
|
||||||
|
'#videogames',
|
||||||
|
'#game',
|
||||||
|
'#xbox',
|
||||||
|
'#games',
|
||||||
|
'#twitch',
|
||||||
|
'#fortnite',
|
||||||
|
'#pc',
|
||||||
|
'#memes',
|
||||||
|
'#pcgaming',
|
||||||
|
'#gamers',
|
||||||
|
'#gamingcommunity',
|
||||||
|
'#youtube',
|
||||||
|
'#switch',
|
||||||
|
'#gamergirl',
|
||||||
|
'#nintendo',
|
||||||
|
'#gta',
|
||||||
|
'#callofduty',
|
||||||
|
'#streamer',
|
||||||
|
'#follow',
|
||||||
|
'#pubg',
|
||||||
|
'#videogame',
|
||||||
|
'#esports',
|
||||||
|
'#bhfyp',
|
||||||
|
'#meme',
|
||||||
|
'#twitchstreamer',
|
||||||
|
'#art',
|
||||||
|
'#genshinimpact',
|
||||||
|
'#honkaiimpact',
|
||||||
|
'#warthunder',
|
||||||
|
'#hovoverse',
|
||||||
|
'#arknights',
|
||||||
|
'#soul',
|
||||||
|
'#eldenring',
|
||||||
|
'#steam',
|
||||||
|
'#pubg',
|
||||||
|
'#cs2',
|
||||||
|
'#apexlegends',
|
||||||
|
'#baldurgate3',
|
||||||
|
'#starfield',
|
||||||
|
'#gta6',
|
||||||
|
'#gameoftheyear',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Music',
|
||||||
|
description: '',
|
||||||
|
kind: WIDGET_KIND.global.topic,
|
||||||
|
content: [
|
||||||
|
'#audiostr',
|
||||||
|
'#musicstr',
|
||||||
|
'#music',
|
||||||
|
'#love',
|
||||||
|
'#hiphop',
|
||||||
|
'#rap',
|
||||||
|
'#art',
|
||||||
|
'#musician',
|
||||||
|
'#artist',
|
||||||
|
'#musica',
|
||||||
|
'#instagood',
|
||||||
|
'#singer',
|
||||||
|
'#dj',
|
||||||
|
'#follow',
|
||||||
|
'#rock',
|
||||||
|
'#like',
|
||||||
|
'#dance',
|
||||||
|
'#guitar',
|
||||||
|
'#s',
|
||||||
|
'#photography',
|
||||||
|
'#song',
|
||||||
|
'#bhfyp',
|
||||||
|
'#newmusic',
|
||||||
|
'#producer',
|
||||||
|
'#life',
|
||||||
|
'#rapper',
|
||||||
|
'#party',
|
||||||
|
'#fashion',
|
||||||
|
'#explorepage',
|
||||||
|
'#viral',
|
||||||
|
'#beats',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Photography',
|
||||||
|
description: '',
|
||||||
|
kind: WIDGET_KIND.global.topic,
|
||||||
|
content: [
|
||||||
|
'#photography',
|
||||||
|
'#photooftheday',
|
||||||
|
'#love',
|
||||||
|
'#photo',
|
||||||
|
'#nature',
|
||||||
|
'#picoftheday',
|
||||||
|
'#like',
|
||||||
|
'#photographer',
|
||||||
|
'#beautiful',
|
||||||
|
'#follow',
|
||||||
|
'#art',
|
||||||
|
'#fashion',
|
||||||
|
'#travel',
|
||||||
|
'#bhfyp',
|
||||||
|
'#photoshoot',
|
||||||
|
'#likeforlikes',
|
||||||
|
'#instadaily',
|
||||||
|
'#naturephotography',
|
||||||
|
'#model',
|
||||||
|
'#me',
|
||||||
|
'#smile',
|
||||||
|
'#style',
|
||||||
|
'#instalike',
|
||||||
|
'#happy',
|
||||||
|
'#likes',
|
||||||
|
'#myself',
|
||||||
|
'#followme',
|
||||||
|
'#followforfollowback',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Art',
|
||||||
|
description: '',
|
||||||
|
kind: WIDGET_KIND.global.topic,
|
||||||
|
content: [
|
||||||
|
'#artstr',
|
||||||
|
'#art',
|
||||||
|
'#artist',
|
||||||
|
'#love',
|
||||||
|
'#drawing',
|
||||||
|
'#photography',
|
||||||
|
'#artwork',
|
||||||
|
'#instagood',
|
||||||
|
'#photooftheday',
|
||||||
|
'#painting',
|
||||||
|
'#fashion',
|
||||||
|
'#like',
|
||||||
|
'#artistsoninstagram',
|
||||||
|
'#beautiful',
|
||||||
|
'#illustration',
|
||||||
|
'#digitalart',
|
||||||
|
'#follow',
|
||||||
|
'#design',
|
||||||
|
'#nature',
|
||||||
|
'#picoftheday',
|
||||||
|
'#photo',
|
||||||
|
'#bhfyp',
|
||||||
|
'#sketch',
|
||||||
|
'#style',
|
||||||
|
'#arte',
|
||||||
|
'#happy',
|
||||||
|
'#cute',
|
||||||
|
'#draw',
|
||||||
|
'#artoftheday',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Movie',
|
||||||
|
description: '',
|
||||||
|
kind: WIDGET_KIND.global.topic,
|
||||||
|
content: [
|
||||||
|
'#filmstr',
|
||||||
|
'#moviestr',
|
||||||
|
'#movies',
|
||||||
|
'#movie',
|
||||||
|
'#film',
|
||||||
|
'#cinema',
|
||||||
|
'#films',
|
||||||
|
'#hollywood',
|
||||||
|
'#actor',
|
||||||
|
'#love',
|
||||||
|
'#s',
|
||||||
|
'#art',
|
||||||
|
'#cinematography',
|
||||||
|
'#actress',
|
||||||
|
'#netflix',
|
||||||
|
'#moviescenes',
|
||||||
|
'#music',
|
||||||
|
'#filmmaking',
|
||||||
|
'#horror',
|
||||||
|
'#instagood',
|
||||||
|
'#bollywood',
|
||||||
|
'#movienight',
|
||||||
|
'#photography',
|
||||||
|
'#comedy',
|
||||||
|
'#cinephile',
|
||||||
|
'#cine',
|
||||||
|
'#tv',
|
||||||
|
'#director',
|
||||||
|
'#horrormovies',
|
||||||
|
'#drama',
|
||||||
|
'#filmmaker',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Technology',
|
||||||
|
description: '',
|
||||||
|
kind: WIDGET_KIND.global.topic,
|
||||||
|
content: [
|
||||||
|
'#apple',
|
||||||
|
'#xiaomi',
|
||||||
|
'#huawei',
|
||||||
|
'#ai',
|
||||||
|
'#oppo',
|
||||||
|
'#nostr',
|
||||||
|
'#technology',
|
||||||
|
'#tech',
|
||||||
|
'#innovation',
|
||||||
|
'#engineering',
|
||||||
|
'#business',
|
||||||
|
'#iphone',
|
||||||
|
'#technews',
|
||||||
|
'#science',
|
||||||
|
'#design',
|
||||||
|
'#gadgets',
|
||||||
|
'#electronics',
|
||||||
|
'#android',
|
||||||
|
'#software',
|
||||||
|
'#programming',
|
||||||
|
'#smartphone',
|
||||||
|
'#bhfyp',
|
||||||
|
'#samsung',
|
||||||
|
'#instagood',
|
||||||
|
'#coding',
|
||||||
|
'#computer',
|
||||||
|
'#pro',
|
||||||
|
'#education',
|
||||||
|
'#security',
|
||||||
|
'#gadget',
|
||||||
|
'#mobile',
|
||||||
|
'#technologynews',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Anime',
|
||||||
|
description: '',
|
||||||
|
kind: WIDGET_KIND.global.topic,
|
||||||
|
content: [
|
||||||
|
'#animestr',
|
||||||
|
'#anime',
|
||||||
|
'#manga',
|
||||||
|
'#ntr',
|
||||||
|
'#otaku',
|
||||||
|
'#animeart',
|
||||||
|
'#animegirl',
|
||||||
|
'#cosplay',
|
||||||
|
'#kawaii',
|
||||||
|
'#weeb',
|
||||||
|
'#onepiece',
|
||||||
|
'#demonslayer',
|
||||||
|
'#animeworld',
|
||||||
|
'#aot',
|
||||||
|
'#hentai',
|
||||||
|
'#fanart',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const DEFAULT_WIDGETS: Array<WidgetGroup> = [
|
||||||
|
{
|
||||||
|
title: 'Topics',
|
||||||
|
data: TOPICS,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'Local',
|
title: 'Local',
|
||||||
data: [
|
data: [
|
||||||
{
|
{
|
||||||
kind: WidgetKinds.tmp.xfeed,
|
kind: WIDGET_KIND.tmp.xfeed,
|
||||||
title: 'Group feeds',
|
title: 'Group feeds',
|
||||||
description: 'All posts from specific people you want to keep up with',
|
description: 'All posts from specific people you want to keep up with',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: WidgetKinds.local.files,
|
kind: WIDGET_KIND.local.files,
|
||||||
title: 'Files',
|
title: 'Files',
|
||||||
description: 'All files shared by people in your circle',
|
description: 'All files shared by people you follow',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: WidgetKinds.local.articles,
|
kind: WIDGET_KIND.local.articles,
|
||||||
title: 'Articles',
|
title: 'Articles',
|
||||||
description: 'All articles shared by people in your circle',
|
description: 'All articles shared by people you follow',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -97,17 +367,17 @@ export const DefaultWidgets: Array<WidgetGroup> = [
|
|||||||
title: 'Global',
|
title: 'Global',
|
||||||
data: [
|
data: [
|
||||||
{
|
{
|
||||||
kind: WidgetKinds.tmp.xhashtag,
|
kind: WIDGET_KIND.tmp.xhashtag,
|
||||||
title: 'Hashtag',
|
title: 'Hashtag',
|
||||||
description: 'All posts have a specific hashtag',
|
description: 'All posts have a specific hashtag',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: WidgetKinds.global.files,
|
kind: WIDGET_KIND.global.files,
|
||||||
title: 'Files',
|
title: 'Files',
|
||||||
description: 'All files shared by people in your current relay set',
|
description: 'All files shared by people in your current relay set',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: WidgetKinds.global.articles,
|
kind: WIDGET_KIND.global.articles,
|
||||||
title: 'Articles',
|
title: 'Articles',
|
||||||
description: 'All articles shared by people in your current relay set',
|
description: 'All articles shared by people in your current relay set',
|
||||||
},
|
},
|
||||||
@ -117,12 +387,12 @@ export const DefaultWidgets: Array<WidgetGroup> = [
|
|||||||
title: 'nostr.band',
|
title: 'nostr.band',
|
||||||
data: [
|
data: [
|
||||||
{
|
{
|
||||||
kind: WidgetKinds.nostrBand.trendingAccounts,
|
kind: WIDGET_KIND.nostrBand.trendingAccounts,
|
||||||
title: 'Accounts',
|
title: 'Accounts',
|
||||||
description: 'Trending accounts from the last 24 hours',
|
description: 'Trending accounts from the last 24 hours',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: WidgetKinds.nostrBand.trendingNotes,
|
kind: WIDGET_KIND.nostrBand.trendingNotes,
|
||||||
title: 'Notes',
|
title: 'Notes',
|
||||||
description: 'Trending notes from the last 24 hours',
|
description: 'Trending notes from the last 24 hours',
|
||||||
},
|
},
|
||||||
@ -132,7 +402,7 @@ export const DefaultWidgets: Array<WidgetGroup> = [
|
|||||||
title: 'Other',
|
title: 'Other',
|
||||||
data: [
|
data: [
|
||||||
{
|
{
|
||||||
kind: WidgetKinds.local.notification,
|
kind: WIDGET_KIND.local.notification,
|
||||||
title: 'Notification',
|
title: 'Notification',
|
||||||
description: 'Everything happens around you',
|
description: 'Everything happens around you',
|
||||||
},
|
},
|
||||||
|
1
src/utils/types.d.ts
vendored
1
src/utils/types.d.ts
vendored
@ -43,6 +43,7 @@ export interface WidgetGroup {
|
|||||||
export interface WidgetGroupItem {
|
export interface WidgetGroupItem {
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
content: string;
|
||||||
kind: number;
|
kind: number;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user