mirror of
https://github.com/lumehq/lume.git
synced 2025-10-02 18:17:43 +02:00
fix the mess I started
This commit is contained in:
@@ -4,7 +4,7 @@ import Navigation from '@lume/shared/navigation';
|
|||||||
|
|
||||||
export function LayoutChannel({ children }: { children: React.ReactNode }) {
|
export function LayoutChannel({ children }: { children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<div className="h-screen w-screen bg-zinc-50 text-zinc-900 dark:bg-black dark:text-white">
|
<div className="h-screen w-screen bg-zinc-50 text-zinc-900 dark:bg-zinc-950 dark:text-white">
|
||||||
<div className="flex h-screen w-full flex-col">
|
<div className="flex h-screen w-full flex-col">
|
||||||
<div
|
<div
|
||||||
data-tauri-drag-region
|
data-tauri-drag-region
|
||||||
|
@@ -1,9 +1,7 @@
|
|||||||
import { AccountContext } from '@lume/shared/accountProvider';
|
|
||||||
import { ChannelBlackList } from '@lume/shared/channels/channelBlackList';
|
import { ChannelBlackList } from '@lume/shared/channels/channelBlackList';
|
||||||
import { ChannelProfile } from '@lume/shared/channels/channelProfile';
|
import { ChannelProfile } from '@lume/shared/channels/channelProfile';
|
||||||
import { UpdateChannelModal } from '@lume/shared/channels/updateChannelModal';
|
import { UpdateChannelModal } from '@lume/shared/channels/updateChannelModal';
|
||||||
import { FormChannel } from '@lume/shared/form/channel';
|
import { FormChannel } from '@lume/shared/form/channel';
|
||||||
import { RelayContext } from '@lume/shared/relaysProvider';
|
|
||||||
import { channelMessagesAtom, channelReplyAtom } from '@lume/stores/channel';
|
import { channelMessagesAtom, channelReplyAtom } from '@lume/stores/channel';
|
||||||
import { FULL_RELAYS } from '@lume/stores/constants';
|
import { FULL_RELAYS } from '@lume/stores/constants';
|
||||||
import { dateToUnix, hoursAgo } from '@lume/utils/getDate';
|
import { dateToUnix, hoursAgo } from '@lume/utils/getDate';
|
||||||
@@ -12,18 +10,20 @@ import { arrayObjToPureArr } from '@lume/utils/transform';
|
|||||||
|
|
||||||
import { useSetAtom } from 'jotai';
|
import { useSetAtom } from 'jotai';
|
||||||
import { useResetAtom } from 'jotai/utils';
|
import { useResetAtom } from 'jotai/utils';
|
||||||
import { Suspense, lazy, useContext, useRef } from 'react';
|
import { RelayPool } from 'nostr-relaypool';
|
||||||
|
import { Suspense, lazy, useRef } from 'react';
|
||||||
import useSWRSubscription from 'swr/subscription';
|
import useSWRSubscription from 'swr/subscription';
|
||||||
|
|
||||||
const ChannelMessages = lazy(() => import('@lume/shared/channels/messages'));
|
const ChannelMessages = lazy(() => import('@lume/shared/channels/messages'));
|
||||||
|
|
||||||
let mutedList: any = [];
|
let mutedList: any = [];
|
||||||
|
let activeAccount: any = {};
|
||||||
let activeMutedList: any = [];
|
let activeMutedList: any = [];
|
||||||
let activeHidedList: any = [];
|
let activeHidedList: any = [];
|
||||||
|
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
const { getBlacklist, getActiveBlacklist, getActiveAccount } = await import('@lume/utils/storage');
|
const { getBlacklist, getActiveBlacklist, getActiveAccount } = await import('@lume/utils/storage');
|
||||||
const activeAccount = await getActiveAccount();
|
activeAccount = await getActiveAccount();
|
||||||
activeHidedList = await getActiveBlacklist(activeAccount.id, 43);
|
activeHidedList = await getActiveBlacklist(activeAccount.id, 43);
|
||||||
activeMutedList = await getActiveBlacklist(activeAccount.id, 44);
|
activeMutedList = await getActiveBlacklist(activeAccount.id, 44);
|
||||||
mutedList = await getBlacklist(activeAccount.id, 44);
|
mutedList = await getBlacklist(activeAccount.id, 44);
|
||||||
@@ -33,12 +33,9 @@ export function Page() {
|
|||||||
const pageContext = usePageContext();
|
const pageContext = usePageContext();
|
||||||
const searchParams: any = pageContext.urlParsed.search;
|
const searchParams: any = pageContext.urlParsed.search;
|
||||||
|
|
||||||
const id = searchParams.id;
|
const channelID = searchParams.id;
|
||||||
const channelPubkey = searchParams.pubkey;
|
const channelPubkey = searchParams.pubkey;
|
||||||
|
|
||||||
const pool: any = useContext(RelayContext);
|
|
||||||
const activeAccount: any = useContext(AccountContext);
|
|
||||||
|
|
||||||
const setChannelMessages = useSetAtom(channelMessagesAtom);
|
const setChannelMessages = useSetAtom(channelMessagesAtom);
|
||||||
const resetChannelMessages = useResetAtom(channelMessagesAtom);
|
const resetChannelMessages = useResetAtom(channelMessagesAtom);
|
||||||
const resetChannelReply = useResetAtom(channelReplyAtom);
|
const resetChannelReply = useResetAtom(channelReplyAtom);
|
||||||
@@ -47,16 +44,17 @@ export function Page() {
|
|||||||
const hided = arrayObjToPureArr(activeHidedList);
|
const hided = arrayObjToPureArr(activeHidedList);
|
||||||
const muted = arrayObjToPureArr(activeMutedList);
|
const muted = arrayObjToPureArr(activeMutedList);
|
||||||
|
|
||||||
useSWRSubscription(id, () => {
|
useSWRSubscription(channelID, () => {
|
||||||
// reset channel reply
|
// reset channel reply
|
||||||
resetChannelReply();
|
resetChannelReply();
|
||||||
// reset channel messages
|
// reset channel messages
|
||||||
resetChannelMessages();
|
resetChannelMessages();
|
||||||
// subscribe for new messages
|
// subscribe for new messages
|
||||||
|
const pool = new RelayPool(FULL_RELAYS);
|
||||||
const unsubscribe = pool.subscribe(
|
const unsubscribe = pool.subscribe(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
'#e': [id],
|
'#e': [channelID],
|
||||||
kinds: [42],
|
kinds: [42],
|
||||||
since: dateToUnix(hoursAgo(48, now.current)),
|
since: dateToUnix(hoursAgo(48, now.current)),
|
||||||
},
|
},
|
||||||
@@ -82,11 +80,11 @@ export function Page() {
|
|||||||
<div className="flex h-full flex-col justify-between gap-2">
|
<div className="flex h-full flex-col justify-between gap-2">
|
||||||
<div className="flex h-11 w-full shrink-0 items-center justify-between">
|
<div className="flex h-11 w-full shrink-0 items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<ChannelProfile id={id} pubkey={channelPubkey} />
|
<ChannelProfile id={channelID} pubkey={channelPubkey} />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<ChannelBlackList blacklist={mutedList} />
|
<ChannelBlackList blacklist={mutedList} />
|
||||||
{activeAccount.pubkey === channelPubkey && <UpdateChannelModal id={id} />}
|
{activeAccount.pubkey === channelPubkey && <UpdateChannelModal id={activeAccount} />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="relative flex w-full flex-1 flex-col justify-between rounded-lg border border-zinc-800 bg-zinc-900 shadow-input shadow-black/20">
|
<div className="relative flex w-full flex-1 flex-col justify-between rounded-lg border border-zinc-800 bg-zinc-900 shadow-input shadow-black/20">
|
||||||
@@ -94,7 +92,7 @@ export function Page() {
|
|||||||
<ChannelMessages />
|
<ChannelMessages />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
<div className="shrink-0 p-3">
|
<div className="shrink-0 p-3">
|
||||||
<FormChannel eventId={id} />
|
<FormChannel eventId={channelID} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -4,7 +4,7 @@ import Navigation from '@lume/shared/navigation';
|
|||||||
|
|
||||||
export function LayoutChat({ children }: { children: React.ReactNode }) {
|
export function LayoutChat({ children }: { children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<div className="h-screen w-screen bg-zinc-50 text-zinc-900 dark:bg-black dark:text-white">
|
<div className="h-screen w-screen bg-zinc-50 text-zinc-900 dark:bg-zinc-950 dark:text-white">
|
||||||
<div className="flex h-screen w-full flex-col">
|
<div className="flex h-screen w-full flex-col">
|
||||||
<div
|
<div
|
||||||
data-tauri-drag-region
|
data-tauri-drag-region
|
||||||
|
56
src/app/newsfeed/components/contentParser.tsx
Normal file
56
src/app/newsfeed/components/contentParser.tsx
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { ImagePreview } from '@lume/app/newsfeed/components/note/preview/image';
|
||||||
|
import { VideoPreview } from '@lume/app/newsfeed/components/note/preview/video';
|
||||||
|
import { YoutubePreview } from '@lume/app/newsfeed/components/note/preview/youtube';
|
||||||
|
import { NoteQuote } from '@lume/app/newsfeed/components/note/quote';
|
||||||
|
import { NoteMentionUser } from '@lume/app/newsfeed/components/user/mention';
|
||||||
|
|
||||||
|
import destr from 'destr';
|
||||||
|
import reactStringReplace from 'react-string-replace';
|
||||||
|
|
||||||
|
export const contentParser = (noteContent: any, noteTags: any) => {
|
||||||
|
let parsedContent = noteContent.trim();
|
||||||
|
|
||||||
|
// get data tags
|
||||||
|
const tags = destr(noteTags);
|
||||||
|
// handle urls
|
||||||
|
parsedContent = reactStringReplace(parsedContent, /(https?:\/\/\S+)/g, (match, i) => {
|
||||||
|
if (match.match(/\.(jpg|jpeg|gif|png|webp)$/i)) {
|
||||||
|
// image url
|
||||||
|
return <ImagePreview key={match + i} url={match} size="large" />;
|
||||||
|
} else if (match.match(/(http:|https:)?(\/\/)?(www\.)?(youtube.com|youtu.be)\/(watch|embed)?(\?v=|\/)?(\S+)?/)) {
|
||||||
|
// youtube
|
||||||
|
return <YoutubePreview key={match + i} url={match} />;
|
||||||
|
} else if (match.match(/\.(mp4|webm)$/i)) {
|
||||||
|
// video
|
||||||
|
return <VideoPreview key={match + i} url={match} />;
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<a key={match + i} href={match} className="cursor-pointer text-fuchsia-500" target="_blank" rel="noreferrer">
|
||||||
|
{match}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// handle #-hashtags
|
||||||
|
parsedContent = reactStringReplace(parsedContent, /#(\w+)/g, (match, i) => (
|
||||||
|
<span key={match + i} className="cursor-pointer text-fuchsia-500">
|
||||||
|
#{match}
|
||||||
|
</span>
|
||||||
|
));
|
||||||
|
// handle mentions
|
||||||
|
if (tags && tags.length > 0) {
|
||||||
|
parsedContent = reactStringReplace(parsedContent, /\#\[(\d+)\]/gm, (match, i) => {
|
||||||
|
if (tags[match][0] === 'p') {
|
||||||
|
// @-mentions
|
||||||
|
return <NoteMentionUser key={tags[match][1] + i} pubkey={tags[match][1]} />;
|
||||||
|
} else if (tags[match][0] === 'e') {
|
||||||
|
// note-quotes
|
||||||
|
return <NoteQuote key={tags[match][1] + i} id={tags[match][1]} />;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsedContent;
|
||||||
|
};
|
@@ -1,32 +1,31 @@
|
|||||||
import { AccountContext } from '@lume/shared/accountProvider';
|
|
||||||
import { ImagePicker } from '@lume/shared/form/imagePicker';
|
import { ImagePicker } from '@lume/shared/form/imagePicker';
|
||||||
import { RelayContext } from '@lume/shared/relaysProvider';
|
|
||||||
import { WRITEONLY_RELAYS } from '@lume/stores/constants';
|
import { WRITEONLY_RELAYS } from '@lume/stores/constants';
|
||||||
import { noteContentAtom } from '@lume/stores/note';
|
import { noteContentAtom } from '@lume/stores/note';
|
||||||
import { dateToUnix } from '@lume/utils/getDate';
|
import { dateToUnix } from '@lume/utils/getDate';
|
||||||
|
import { useActiveAccount } from '@lume/utils/hooks/useActiveAccount';
|
||||||
|
|
||||||
import { useAtom } from 'jotai';
|
import { useAtom } from 'jotai';
|
||||||
import { useResetAtom } from 'jotai/utils';
|
import { useResetAtom } from 'jotai/utils';
|
||||||
|
import { RelayPool } from 'nostr-relaypool';
|
||||||
import { getEventHash, signEvent } from 'nostr-tools';
|
import { getEventHash, signEvent } from 'nostr-tools';
|
||||||
import { useContext } from 'react';
|
|
||||||
|
|
||||||
export default function FormBase() {
|
|
||||||
const pool: any = useContext(RelayContext);
|
|
||||||
const activeAccount: any = useContext(AccountContext);
|
|
||||||
|
|
||||||
|
export default function NoteForm() {
|
||||||
|
const { account, isLoading, isError } = useActiveAccount();
|
||||||
const [value, setValue] = useAtom(noteContentAtom);
|
const [value, setValue] = useAtom(noteContentAtom);
|
||||||
const resetValue = useResetAtom(noteContentAtom);
|
const resetValue = useResetAtom(noteContentAtom);
|
||||||
|
|
||||||
const submitEvent = () => {
|
const submitEvent = () => {
|
||||||
|
if (!isLoading && !isError && account) {
|
||||||
|
const pool = new RelayPool(WRITEONLY_RELAYS);
|
||||||
const event: any = {
|
const event: any = {
|
||||||
content: value,
|
content: value,
|
||||||
created_at: dateToUnix(),
|
created_at: dateToUnix(),
|
||||||
kind: 1,
|
kind: 1,
|
||||||
pubkey: activeAccount.pubkey,
|
pubkey: account.pubkey,
|
||||||
tags: [],
|
tags: [],
|
||||||
};
|
};
|
||||||
event.id = getEventHash(event);
|
event.id = getEventHash(event);
|
||||||
event.sig = signEvent(event, activeAccount.privkey);
|
event.sig = signEvent(event, account.privkey);
|
||||||
|
|
||||||
// publish note
|
// publish note
|
||||||
pool.publish(event, WRITEONLY_RELAYS);
|
pool.publish(event, WRITEONLY_RELAYS);
|
||||||
@@ -34,6 +33,9 @@ export default function FormBase() {
|
|||||||
resetValue();
|
resetValue();
|
||||||
// send notification
|
// send notification
|
||||||
// sendNotification('Note has been published successfully');
|
// sendNotification('Note has been published successfully');
|
||||||
|
} else {
|
||||||
|
console.log('Cannot publish note');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
@@ -1,7 +1,6 @@
|
|||||||
import { NoteMetadata } from '@lume/shared/note/metadata';
|
import { contentParser } from '@lume/app/newsfeed/components/contentParser';
|
||||||
import { NoteParent } from '@lume/shared/note/parent';
|
import { NoteParent } from '@lume/app/newsfeed/components/note/parent';
|
||||||
import { UserExtend } from '@lume/shared/user/extend';
|
import { NoteDefaultUser } from '@lume/app/newsfeed/components/user/default';
|
||||||
import { contentParser } from '@lume/utils/parser';
|
|
||||||
|
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { navigate } from 'vite-plugin-ssr/client/router';
|
import { navigate } from 'vite-plugin-ssr/client/router';
|
||||||
@@ -40,19 +39,12 @@ export const NoteBase = memo(function NoteBase({ event }: { event: any }) {
|
|||||||
{parentNote()}
|
{parentNote()}
|
||||||
<div className="relative z-10 flex flex-col">
|
<div className="relative z-10 flex flex-col">
|
||||||
<div onClick={(e) => openUserPage(e)}>
|
<div onClick={(e) => openUserPage(e)}>
|
||||||
<UserExtend pubkey={event.pubkey} time={event.created_at} />
|
<NoteDefaultUser pubkey={event.pubkey} time={event.created_at} />
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-1 pl-[52px]">
|
<div className="mt-1 pl-[52px]">
|
||||||
<div className="whitespace-pre-line break-words text-[15px] leading-tight text-zinc-100">{content}</div>
|
<div className="whitespace-pre-line break-words text-[15px] leading-tight text-zinc-100">{content}</div>
|
||||||
</div>
|
</div>
|
||||||
<div onClick={(e) => e.stopPropagation()} className="mt-5 pl-[52px]">
|
<div onClick={(e) => e.stopPropagation()} className="mt-5 pl-[52px]"></div>
|
||||||
<NoteMetadata
|
|
||||||
eventID={event.event_id}
|
|
||||||
eventPubkey={event.pubkey}
|
|
||||||
eventContent={event.content}
|
|
||||||
eventTime={event.created_at}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
24
src/app/newsfeed/components/note/comment.tsx
Normal file
24
src/app/newsfeed/components/note/comment.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { contentParser } from '@lume/app/newsfeed/components/contentParser';
|
||||||
|
import { NoteDefaultUser } from '@lume/app/newsfeed/components/user/default';
|
||||||
|
|
||||||
|
import { memo } from 'react';
|
||||||
|
|
||||||
|
export const NoteComment = memo(function NoteComment({ event }: { event: any }) {
|
||||||
|
const content = contentParser(event.content, event.tags);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative z-10 flex h-min min-h-min w-full select-text flex-col border-b border-zinc-800 px-3 py-5 hover:bg-black/20">
|
||||||
|
<div className="relative z-10 flex flex-col">
|
||||||
|
<NoteDefaultUser pubkey={event.pubkey} time={event.created_at} />
|
||||||
|
<div className="-mt-5 pl-[52px]">
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<div className="prose prose-zinc max-w-none break-words text-[15px] leading-tight dark:prose-invert prose-p:m-0 prose-p:text-[15px] prose-p:leading-tight prose-a:font-normal prose-a:text-fuchsia-500 prose-a:no-underline prose-img:m-0 prose-video:m-0">
|
||||||
|
{content}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div onClick={(e) => e.stopPropagation()} className="mt-5 pl-[52px]"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
@@ -1,15 +1,12 @@
|
|||||||
import { NoteMetadata } from '@lume/shared/note/metadata';
|
import { contentParser } from '@lume/app/newsfeed/components/contentParser';
|
||||||
import { RelayContext } from '@lume/shared/relaysProvider';
|
import { NoteDefaultUser } from '@lume/app/newsfeed/components/user/default';
|
||||||
import { UserExtend } from '@lume/shared/user/extend';
|
|
||||||
import { READONLY_RELAYS } from '@lume/stores/constants';
|
import { READONLY_RELAYS } from '@lume/stores/constants';
|
||||||
import { contentParser } from '@lume/utils/parser';
|
|
||||||
|
|
||||||
import { memo, useContext } from 'react';
|
import { RelayPool } from 'nostr-relaypool';
|
||||||
|
import { memo } from 'react';
|
||||||
import useSWRSubscription from 'swr/subscription';
|
import useSWRSubscription from 'swr/subscription';
|
||||||
|
|
||||||
export const NoteParent = memo(function NoteParent({ id }: { id: string }) {
|
export const NoteParent = memo(function NoteParent({ id }: { id: string }) {
|
||||||
const pool: any = useContext(RelayContext);
|
|
||||||
|
|
||||||
const { data, error } = useSWRSubscription(
|
const { data, error } = useSWRSubscription(
|
||||||
id
|
id
|
||||||
? [
|
? [
|
||||||
@@ -20,6 +17,7 @@ export const NoteParent = memo(function NoteParent({ id }: { id: string }) {
|
|||||||
]
|
]
|
||||||
: null,
|
: null,
|
||||||
(key, { next }) => {
|
(key, { next }) => {
|
||||||
|
const pool = new RelayPool(READONLY_RELAYS);
|
||||||
const unsubscribe = pool.subscribe(
|
const unsubscribe = pool.subscribe(
|
||||||
key,
|
key,
|
||||||
READONLY_RELAYS,
|
READONLY_RELAYS,
|
||||||
@@ -70,20 +68,13 @@ export const NoteParent = memo(function NoteParent({ id }: { id: string }) {
|
|||||||
<>
|
<>
|
||||||
<div className="absolute left-[21px] top-0 h-full w-0.5 bg-gradient-to-t from-zinc-800 to-zinc-600"></div>
|
<div className="absolute left-[21px] top-0 h-full w-0.5 bg-gradient-to-t from-zinc-800 to-zinc-600"></div>
|
||||||
<div className="relative z-10 flex flex-col">
|
<div className="relative z-10 flex flex-col">
|
||||||
<UserExtend pubkey={data.pubkey} time={data.created_at} />
|
<NoteDefaultUser pubkey={data.pubkey} time={data.created_at} />
|
||||||
<div className="mt-1 pl-[52px]">
|
<div className="mt-1 pl-[52px]">
|
||||||
<div className="whitespace-pre-line break-words text-[15px] leading-tight text-zinc-100">
|
<div className="whitespace-pre-line break-words text-[15px] leading-tight text-zinc-100">
|
||||||
{contentParser(data.content, data.tags)}
|
{contentParser(data.content, data.tags)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div onClick={(e) => e.stopPropagation()} className="mt-5 pl-[52px]">
|
<div onClick={(e) => e.stopPropagation()} className="mt-5 pl-[52px]"></div>
|
||||||
<NoteMetadata
|
|
||||||
eventID={data.event_id}
|
|
||||||
eventPubkey={data.pubkey}
|
|
||||||
eventContent={data.content}
|
|
||||||
eventTime={data.created_at}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
@@ -1,14 +1,12 @@
|
|||||||
import { RelayContext } from '@lume/shared/relaysProvider';
|
import { contentParser } from '@lume/app/newsfeed/components/contentParser';
|
||||||
import { UserExtend } from '@lume/shared/user/extend';
|
import { NoteDefaultUser } from '@lume/app/newsfeed/components/user/default';
|
||||||
import { READONLY_RELAYS } from '@lume/stores/constants';
|
import { READONLY_RELAYS } from '@lume/stores/constants';
|
||||||
import { contentParser } from '@lume/utils/parser';
|
|
||||||
|
|
||||||
import { memo, useContext } from 'react';
|
import { RelayPool } from 'nostr-relaypool';
|
||||||
|
import { memo } from 'react';
|
||||||
import useSWRSubscription from 'swr/subscription';
|
import useSWRSubscription from 'swr/subscription';
|
||||||
|
|
||||||
export const NoteQuote = memo(function NoteQuote({ id }: { id: string }) {
|
export const NoteQuote = memo(function NoteQuote({ id }: { id: string }) {
|
||||||
const pool: any = useContext(RelayContext);
|
|
||||||
|
|
||||||
const { data, error } = useSWRSubscription(
|
const { data, error } = useSWRSubscription(
|
||||||
id
|
id
|
||||||
? [
|
? [
|
||||||
@@ -19,6 +17,7 @@ export const NoteQuote = memo(function NoteQuote({ id }: { id: string }) {
|
|||||||
]
|
]
|
||||||
: null,
|
: null,
|
||||||
(key, { next }) => {
|
(key, { next }) => {
|
||||||
|
const pool = new RelayPool(READONLY_RELAYS);
|
||||||
const unsubscribe = pool.subscribe(
|
const unsubscribe = pool.subscribe(
|
||||||
key,
|
key,
|
||||||
READONLY_RELAYS,
|
READONLY_RELAYS,
|
||||||
@@ -45,7 +44,7 @@ export const NoteQuote = memo(function NoteQuote({ id }: { id: string }) {
|
|||||||
<div className="h-6 w-full animate-pulse select-text flex-col rounded bg-zinc-800"></div>
|
<div className="h-6 w-full animate-pulse select-text flex-col rounded bg-zinc-800"></div>
|
||||||
) : (
|
) : (
|
||||||
<div className="relative z-10 flex flex-col">
|
<div className="relative z-10 flex flex-col">
|
||||||
<UserExtend pubkey={data.pubkey} time={data.created_at} />
|
<NoteDefaultUser pubkey={data.pubkey} time={data.created_at} />
|
||||||
<div className="mt-1 pl-[52px]">
|
<div className="mt-1 pl-[52px]">
|
||||||
<div className="whitespace-pre-line break-words text-[15px] leading-tight text-zinc-100">
|
<div className="whitespace-pre-line break-words text-[15px] leading-tight text-zinc-100">
|
||||||
{contentParser(data.content, data.tags)}
|
{contentParser(data.content, data.tags)}
|
@@ -1,5 +1,5 @@
|
|||||||
import { RootNote } from '@lume/shared/note/rootNote';
|
import { RootNote } from '@lume/app/newsfeed/components/note/rootNote';
|
||||||
import { UserQuoteRepost } from '@lume/shared/user/quoteRepost';
|
import { NoteRepostUser } from '@lume/app/newsfeed/components/user/repost';
|
||||||
import { getQuoteID } from '@lume/utils/transform';
|
import { getQuoteID } from '@lume/utils/transform';
|
||||||
|
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
@@ -11,7 +11,7 @@ export const NoteQuoteRepost = memo(function NoteQuoteRepost({ event }: { event:
|
|||||||
<div className="relative z-10 flex h-min min-h-min w-full select-text flex-col border-b border-zinc-800 px-3 py-5 hover:bg-black/20">
|
<div className="relative z-10 flex h-min min-h-min w-full select-text flex-col border-b border-zinc-800 px-3 py-5 hover:bg-black/20">
|
||||||
<div className="relative z-10 flex flex-col pb-5">
|
<div className="relative z-10 flex flex-col pb-5">
|
||||||
<div className="absolute left-[21px] top-0 h-full w-0.5 bg-gradient-to-t from-zinc-800 to-zinc-600"></div>
|
<div className="absolute left-[21px] top-0 h-full w-0.5 bg-gradient-to-t from-zinc-800 to-zinc-600"></div>
|
||||||
<UserQuoteRepost pubkey={event.pubkey} time={event.created_at} />
|
<NoteRepostUser pubkey={event.pubkey} time={event.created_at} />
|
||||||
</div>
|
</div>
|
||||||
<RootNote id={rootID} />
|
<RootNote id={rootID} />
|
||||||
</div>
|
</div>
|
@@ -1,16 +1,13 @@
|
|||||||
import { NoteMetadata } from '@lume/shared/note/metadata';
|
import { contentParser } from '@lume/app/newsfeed/components/contentParser';
|
||||||
import { RelayContext } from '@lume/shared/relaysProvider';
|
import { NoteDefaultUser } from '@lume/app/newsfeed/components/user/default';
|
||||||
import { UserExtend } from '@lume/shared/user/extend';
|
|
||||||
import { READONLY_RELAYS } from '@lume/stores/constants';
|
import { READONLY_RELAYS } from '@lume/stores/constants';
|
||||||
import { contentParser } from '@lume/utils/parser';
|
|
||||||
|
|
||||||
import { memo, useContext } from 'react';
|
import { RelayPool } from 'nostr-relaypool';
|
||||||
|
import { memo } from 'react';
|
||||||
import useSWRSubscription from 'swr/subscription';
|
import useSWRSubscription from 'swr/subscription';
|
||||||
import { navigate } from 'vite-plugin-ssr/client/router';
|
import { navigate } from 'vite-plugin-ssr/client/router';
|
||||||
|
|
||||||
export const RootNote = memo(function RootNote({ id }: { id: string }) {
|
export const RootNote = memo(function RootNote({ id }: { id: string }) {
|
||||||
const pool: any = useContext(RelayContext);
|
|
||||||
|
|
||||||
const openThread = (e) => {
|
const openThread = (e) => {
|
||||||
const selection = window.getSelection();
|
const selection = window.getSelection();
|
||||||
if (selection.toString().length === 0) {
|
if (selection.toString().length === 0) {
|
||||||
@@ -30,6 +27,7 @@ export const RootNote = memo(function RootNote({ id }: { id: string }) {
|
|||||||
]
|
]
|
||||||
: null,
|
: null,
|
||||||
(key, { next }) => {
|
(key, { next }) => {
|
||||||
|
const pool = new RelayPool(READONLY_RELAYS);
|
||||||
const unsubscribe = pool.subscribe(
|
const unsubscribe = pool.subscribe(
|
||||||
key,
|
key,
|
||||||
READONLY_RELAYS,
|
READONLY_RELAYS,
|
||||||
@@ -56,20 +54,13 @@ export const RootNote = memo(function RootNote({ id }: { id: string }) {
|
|||||||
<div className="h-6 w-full animate-pulse select-text flex-col rounded bg-zinc-800"></div>
|
<div className="h-6 w-full animate-pulse select-text flex-col rounded bg-zinc-800"></div>
|
||||||
) : (
|
) : (
|
||||||
<div onClick={(e) => openThread(e)} className="relative z-10 flex flex-col">
|
<div onClick={(e) => openThread(e)} className="relative z-10 flex flex-col">
|
||||||
<UserExtend pubkey={data.pubkey} time={data.created_at} />
|
<NoteDefaultUser pubkey={data.pubkey} time={data.created_at} />
|
||||||
<div className="mt-1 pl-[52px]">
|
<div className="mt-1 pl-[52px]">
|
||||||
<div className="whitespace-pre-line break-words text-[15px] leading-tight text-zinc-100">
|
<div className="whitespace-pre-line break-words text-[15px] leading-tight text-zinc-100">
|
||||||
{contentParser(data.content, data.tags)}
|
{contentParser(data.content, data.tags)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div onClick={(e) => e.stopPropagation()} className="mt-5 pl-[52px]">
|
<div onClick={(e) => e.stopPropagation()} className="mt-5 pl-[52px]"></div>
|
||||||
<NoteMetadata
|
|
||||||
eventID={data.id}
|
|
||||||
eventPubkey={data.pubkey}
|
|
||||||
eventContent={data.content}
|
|
||||||
eventTime={data.created_at}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
@@ -7,7 +7,7 @@ import relativeTime from 'dayjs/plugin/relativeTime';
|
|||||||
|
|
||||||
dayjs.extend(relativeTime);
|
dayjs.extend(relativeTime);
|
||||||
|
|
||||||
export const UserExtend = ({ pubkey, time }: { pubkey: string; time: number }) => {
|
export const NoteDefaultUser = ({ pubkey, time }: { pubkey: string; time: number }) => {
|
||||||
const profile = useProfile(pubkey);
|
const profile = useProfile(pubkey);
|
||||||
|
|
||||||
return (
|
return (
|
@@ -1,7 +1,7 @@
|
|||||||
import { useProfile } from '@lume/utils/hooks/useProfile';
|
import { useProfile } from '@lume/utils/hooks/useProfile';
|
||||||
import { shortenKey } from '@lume/utils/shortenKey';
|
import { shortenKey } from '@lume/utils/shortenKey';
|
||||||
|
|
||||||
export const UserMention = ({ pubkey }: { pubkey: string }) => {
|
export const NoteMentionUser = ({ pubkey }: { pubkey: string }) => {
|
||||||
const profile = useProfile(pubkey);
|
const profile = useProfile(pubkey);
|
||||||
return (
|
return (
|
||||||
<span className="cursor-pointer text-fuchsia-500">@{profile?.name || profile?.username || shortenKey(pubkey)}</span>
|
<span className="cursor-pointer text-fuchsia-500">@{profile?.name || profile?.username || shortenKey(pubkey)}</span>
|
@@ -7,7 +7,7 @@ import relativeTime from 'dayjs/plugin/relativeTime';
|
|||||||
|
|
||||||
dayjs.extend(relativeTime);
|
dayjs.extend(relativeTime);
|
||||||
|
|
||||||
export const UserQuoteRepost = ({ pubkey, time }: { pubkey: string; time: number }) => {
|
export const NoteRepostUser = ({ pubkey, time }: { pubkey: string; time: number }) => {
|
||||||
const profile = useProfile(pubkey);
|
const profile = useProfile(pubkey);
|
||||||
|
|
||||||
return (
|
return (
|
@@ -4,7 +4,7 @@ import Navigation from '@lume/shared/navigation';
|
|||||||
|
|
||||||
export function LayoutNewsfeed({ children }: { children: React.ReactNode }) {
|
export function LayoutNewsfeed({ children }: { children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<div className="h-screen w-screen bg-zinc-50 text-zinc-900 dark:bg-black dark:text-white">
|
<div className="h-screen w-screen bg-zinc-50 text-zinc-900 dark:bg-zinc-950 dark:text-white">
|
||||||
<div className="flex h-screen w-full flex-col">
|
<div className="flex h-screen w-full flex-col">
|
||||||
<div
|
<div
|
||||||
data-tauri-drag-region
|
data-tauri-drag-region
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import FormBase from '@lume/shared/form/base';
|
import NoteForm from '@lume/app/newsfeed/components/form';
|
||||||
import { NoteBase } from '@lume/shared/note/base';
|
import { NoteBase } from '@lume/app/newsfeed/components/note/base';
|
||||||
import { Placeholder } from '@lume/shared/note/placeholder';
|
import { Placeholder } from '@lume/app/newsfeed/components/note/placeholder';
|
||||||
import { NoteQuoteRepost } from '@lume/shared/note/quoteRepost';
|
import { NoteQuoteRepost } from '@lume/app/newsfeed/components/note/quoteRepost';
|
||||||
import { hasNewerNoteAtom } from '@lume/stores/note';
|
import { hasNewerNoteAtom } from '@lume/stores/note';
|
||||||
import { countTotalNotes, getNotes } from '@lume/utils/storage';
|
import { countTotalNotes, getNotes } from '@lume/utils/storage';
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ export function Page() {
|
|||||||
<div>{error.message}</div>
|
<div>{error.message}</div>
|
||||||
) : (
|
) : (
|
||||||
<div ref={parentRef} className="scrollbar-hide h-full w-full overflow-y-auto" style={{ contain: 'strict' }}>
|
<div ref={parentRef} className="scrollbar-hide h-full w-full overflow-y-auto" style={{ contain: 'strict' }}>
|
||||||
<FormBase />
|
<NoteForm />
|
||||||
<div
|
<div
|
||||||
className="relative w-full"
|
className="relative w-full"
|
||||||
style={{
|
style={{
|
||||||
|
@@ -94,8 +94,9 @@ export function Page() {
|
|||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
|
const followsIncludeSelf = follows.concat([onboarding.pubkey]);
|
||||||
// insert to database
|
// insert to database
|
||||||
createAccount(onboarding.pubkey, onboarding.privkey, onboarding.metadata, arrayToNIP02(follows), 1)
|
createAccount(onboarding.pubkey, onboarding.privkey, onboarding.metadata, arrayToNIP02(followsIncludeSelf), 1)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res) {
|
if (res) {
|
||||||
for (const tag of follows) {
|
for (const tag of follows) {
|
||||||
|
@@ -53,8 +53,9 @@ export function Page() {
|
|||||||
// show loading indicator
|
// show loading indicator
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
|
const follows = onboarding.follows.concat([['p', pubkey]]);
|
||||||
// insert to database
|
// insert to database
|
||||||
createAccount(pubkey, onboarding.privkey, onboarding.metadata, onboarding.follows, 1)
|
createAccount(pubkey, onboarding.privkey, onboarding.metadata, follows, 1)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res) {
|
if (res) {
|
||||||
for (const tag of onboarding.follows) {
|
for (const tag of onboarding.follows) {
|
||||||
|
@@ -45,10 +45,9 @@ export default function AppHeader({ collector }: { collector: boolean }) {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div data-tauri-drag-region className="flex h-full w-full items-center justify-between">
|
<div data-tauri-drag-region className="flex h-full w-full items-center justify-between">
|
||||||
<div className="flex h-full items-center divide-x divide-zinc-900 px-4 pt-px">
|
<div className="flex h-full items-center divide-x divide-zinc-900 px-4 pt-px"></div>
|
||||||
{collector && <EventCollector />}
|
{collector && <EventCollector />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -1,24 +1,26 @@
|
|||||||
import { AccountContext } from '@lume/shared/accountProvider';
|
|
||||||
import { NetworkStatusIndicator } from '@lume/shared/networkStatusIndicator';
|
import { NetworkStatusIndicator } from '@lume/shared/networkStatusIndicator';
|
||||||
import { RelayContext } from '@lume/shared/relaysProvider';
|
|
||||||
import { READONLY_RELAYS } from '@lume/stores/constants';
|
import { READONLY_RELAYS } from '@lume/stores/constants';
|
||||||
import { hasNewerNoteAtom } from '@lume/stores/note';
|
import { hasNewerNoteAtom } from '@lume/stores/note';
|
||||||
import { dateToUnix } from '@lume/utils/getDate';
|
import { dateToUnix } from '@lume/utils/getDate';
|
||||||
|
import { useActiveAccount } from '@lume/utils/hooks/useActiveAccount';
|
||||||
import { createChat, createNote, updateAccount } from '@lume/utils/storage';
|
import { createChat, createNote, updateAccount } from '@lume/utils/storage';
|
||||||
import { getParentID, nip02ToArray } from '@lume/utils/transform';
|
import { getParentID, nip02ToArray } from '@lume/utils/transform';
|
||||||
|
|
||||||
import { useSetAtom } from 'jotai';
|
import { useSetAtom } from 'jotai';
|
||||||
import { useCallback, useContext, useEffect, useRef } from 'react';
|
import { RelayPool } from 'nostr-relaypool';
|
||||||
|
import { useRef } from 'react';
|
||||||
|
import useSWRSubscription from 'swr/subscription';
|
||||||
|
|
||||||
export default function EventCollector() {
|
export default function EventCollector() {
|
||||||
const pool: any = useContext(RelayContext);
|
|
||||||
const activeAccount: any = useContext(AccountContext);
|
|
||||||
|
|
||||||
const setHasNewerNote = useSetAtom(hasNewerNoteAtom);
|
const setHasNewerNote = useSetAtom(hasNewerNoteAtom);
|
||||||
const now = useRef(new Date());
|
const now = useRef(new Date());
|
||||||
|
|
||||||
const subscribe = useCallback(async () => {
|
const { account, isLoading, isError } = useActiveAccount();
|
||||||
const follows = activeAccount.follows ? JSON.parse(activeAccount.follows) : [];
|
|
||||||
|
useSWRSubscription(!isLoading && !isError ? account : null, () => {
|
||||||
|
const follows = nip02ToArray(JSON.parse(account.follows));
|
||||||
|
|
||||||
|
const pool = new RelayPool(READONLY_RELAYS);
|
||||||
const unsubscribe = pool.subscribe(
|
const unsubscribe = pool.subscribe(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@@ -28,16 +30,16 @@ export default function EventCollector() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
kinds: [0, 3],
|
kinds: [0, 3],
|
||||||
authors: [activeAccount.pubkey],
|
authors: [account.pubkey],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kinds: [4],
|
kinds: [4],
|
||||||
'#p': [activeAccount.pubkey],
|
'#p': [account.pubkey],
|
||||||
since: dateToUnix(now.current),
|
since: dateToUnix(now.current),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
READONLY_RELAYS,
|
READONLY_RELAYS,
|
||||||
(event: { kind: number; tags: string[]; id: string; pubkey: string; content: string; created_at: number }) => {
|
(event: any) => {
|
||||||
switch (event.kind) {
|
switch (event.kind) {
|
||||||
// metadata
|
// metadata
|
||||||
case 0:
|
case 0:
|
||||||
@@ -48,7 +50,7 @@ export default function EventCollector() {
|
|||||||
const parentID = getParentID(event.tags, event.id);
|
const parentID = getParentID(event.tags, event.id);
|
||||||
createNote(
|
createNote(
|
||||||
event.id,
|
event.id,
|
||||||
activeAccount.id,
|
account.id,
|
||||||
event.pubkey,
|
event.pubkey,
|
||||||
event.kind,
|
event.kind,
|
||||||
event.tags,
|
event.tags,
|
||||||
@@ -61,21 +63,20 @@ export default function EventCollector() {
|
|||||||
break;
|
break;
|
||||||
// contacts
|
// contacts
|
||||||
case 3:
|
case 3:
|
||||||
const arr = nip02ToArray(event.tags);
|
|
||||||
// update account's folllows with NIP-02 tag list
|
// update account's folllows with NIP-02 tag list
|
||||||
updateAccount('follows', arr, event.pubkey);
|
updateAccount('follows', event.tags, event.pubkey);
|
||||||
break;
|
break;
|
||||||
// chat
|
// chat
|
||||||
case 4:
|
case 4:
|
||||||
if (event.pubkey !== activeAccount.pubkey) {
|
if (event.pubkey !== account.pubkey) {
|
||||||
createChat(activeAccount.id, event.pubkey, event.created_at);
|
createChat(account.id, event.pubkey, event.created_at);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
// repost
|
// repost
|
||||||
case 6:
|
case 6:
|
||||||
createNote(
|
createNote(
|
||||||
event.id,
|
event.id,
|
||||||
activeAccount.id,
|
account.id,
|
||||||
event.pubkey,
|
event.pubkey,
|
||||||
event.kind,
|
event.kind,
|
||||||
event.tags,
|
event.tags,
|
||||||
@@ -93,19 +94,7 @@ export default function EventCollector() {
|
|||||||
return () => {
|
return () => {
|
||||||
unsubscribe();
|
unsubscribe();
|
||||||
};
|
};
|
||||||
}, [activeAccount.id, activeAccount.pubkey, activeAccount.follows, pool, setHasNewerNote]);
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let ignore = false;
|
|
||||||
|
|
||||||
if (!ignore) {
|
|
||||||
subscribe();
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
ignore = true;
|
|
||||||
};
|
|
||||||
}, [subscribe]);
|
|
||||||
|
|
||||||
return <NetworkStatusIndicator />;
|
return <NetworkStatusIndicator />;
|
||||||
}
|
}
|
||||||
|
@@ -1,31 +0,0 @@
|
|||||||
import AppHeader from '@lume/shared/appHeader';
|
|
||||||
import MultiAccounts from '@lume/shared/multiAccounts';
|
|
||||||
import Navigation from '@lume/shared/navigation';
|
|
||||||
|
|
||||||
export default function ChannelLayout({ children }: { children: React.ReactNode }) {
|
|
||||||
return (
|
|
||||||
<div className="h-screen w-screen bg-zinc-50 text-zinc-900 dark:bg-black dark:text-white">
|
|
||||||
<div className="flex h-screen w-full flex-col">
|
|
||||||
<div
|
|
||||||
data-tauri-drag-region
|
|
||||||
className="relative h-11 shrink-0 border-b border-zinc-100 bg-white dark:border-zinc-900 dark:bg-black"
|
|
||||||
>
|
|
||||||
<AppHeader collector={true} />
|
|
||||||
</div>
|
|
||||||
<div className="relative flex min-h-0 w-full flex-1">
|
|
||||||
<div className="relative w-[68px] shrink-0 border-r border-zinc-900">
|
|
||||||
<MultiAccounts />
|
|
||||||
</div>
|
|
||||||
<div className="grid w-full grid-cols-4 xl:grid-cols-5">
|
|
||||||
<div className="scrollbar-hide col-span-1 overflow-y-auto overflow-x-hidden border-r border-zinc-900">
|
|
||||||
<Navigation />
|
|
||||||
</div>
|
|
||||||
<div className="col-span-3 m-3 overflow-hidden xl:col-span-4 xl:mr-1.5">
|
|
||||||
<div className="h-full w-full rounded-lg">{children}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@@ -1,29 +0,0 @@
|
|||||||
import AppHeader from '@lume/shared/appHeader';
|
|
||||||
import MultiAccounts from '@lume/shared/multiAccounts';
|
|
||||||
import Navigation from '@lume/shared/navigation';
|
|
||||||
|
|
||||||
export default function NewsfeedLayout({ children }: { children: React.ReactNode }) {
|
|
||||||
return (
|
|
||||||
<div className="h-screen w-screen bg-zinc-50 text-zinc-900 dark:bg-black dark:text-white">
|
|
||||||
<div className="flex h-screen w-full flex-col">
|
|
||||||
<div
|
|
||||||
data-tauri-drag-region
|
|
||||||
className="relative h-11 shrink-0 border-b border-zinc-100 bg-white dark:border-zinc-900 dark:bg-black"
|
|
||||||
>
|
|
||||||
<AppHeader collector={true} />
|
|
||||||
</div>
|
|
||||||
<div className="relative flex min-h-0 w-full flex-1">
|
|
||||||
<div className="relative w-[68px] shrink-0 border-r border-zinc-900">
|
|
||||||
<MultiAccounts />
|
|
||||||
</div>
|
|
||||||
<div className="grid w-full grid-cols-4 xl:grid-cols-5">
|
|
||||||
<div className="scrollbar-hide col-span-1 overflow-y-auto overflow-x-hidden border-r border-zinc-900">
|
|
||||||
<Navigation />
|
|
||||||
</div>
|
|
||||||
<div className="col-span-3 m-3 overflow-hidden xl:col-span-4">{children}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@@ -1,17 +0,0 @@
|
|||||||
import AppHeader from '@lume/shared/appHeader';
|
|
||||||
|
|
||||||
export default function OnboardingLayout({ children }: { children: React.ReactNode }) {
|
|
||||||
return (
|
|
||||||
<div className="h-screen w-screen bg-zinc-50 text-zinc-900 dark:bg-black dark:text-white">
|
|
||||||
<div className="flex h-screen w-full flex-col">
|
|
||||||
<div
|
|
||||||
data-tauri-drag-region
|
|
||||||
className="relative h-11 shrink-0 border-b border-zinc-100 bg-white dark:border-zinc-900 dark:bg-black"
|
|
||||||
>
|
|
||||||
<AppHeader collector={false} />
|
|
||||||
</div>
|
|
||||||
<div className="relative flex min-h-0 w-full flex-1">{children}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@@ -24,7 +24,7 @@ export default function Navigation() {
|
|||||||
</Disclosure.Button>
|
</Disclosure.Button>
|
||||||
<Disclosure.Panel className="flex flex-col text-zinc-400">
|
<Disclosure.Panel className="flex flex-col text-zinc-400">
|
||||||
<ActiveLink
|
<ActiveLink
|
||||||
href="/newsfeed/following"
|
href="/app/newsfeed/following"
|
||||||
className="flex h-8 items-center gap-2.5 rounded-md px-2.5 text-sm font-medium hover:text-zinc-200"
|
className="flex h-8 items-center gap-2.5 rounded-md px-2.5 text-sm font-medium hover:text-zinc-200"
|
||||||
activeClassName="dark:bg-zinc-900 dark:text-zinc-100 hover:dark:bg-zinc-800"
|
activeClassName="dark:bg-zinc-900 dark:text-zinc-100 hover:dark:bg-zinc-800"
|
||||||
>
|
>
|
||||||
@@ -32,7 +32,7 @@ export default function Navigation() {
|
|||||||
<span>Following</span>
|
<span>Following</span>
|
||||||
</ActiveLink>
|
</ActiveLink>
|
||||||
<ActiveLink
|
<ActiveLink
|
||||||
href="/newsfeed/circle"
|
href="/app/newsfeed/circle"
|
||||||
className="flex h-8 items-center gap-2.5 rounded-md px-2.5 text-sm font-medium hover:text-zinc-200"
|
className="flex h-8 items-center gap-2.5 rounded-md px-2.5 text-sm font-medium hover:text-zinc-200"
|
||||||
activeClassName="dark:bg-zinc-900 dark:text-zinc-100 hover:dark:bg-zinc-800"
|
activeClassName="dark:bg-zinc-900 dark:text-zinc-100 hover:dark:bg-zinc-800"
|
||||||
>
|
>
|
||||||
|
@@ -1,82 +0,0 @@
|
|||||||
import { NoteMetadata } from '@lume/shared/note/metadata';
|
|
||||||
import { ImagePreview } from '@lume/shared/note/preview/image';
|
|
||||||
import { VideoPreview } from '@lume/shared/note/preview/video';
|
|
||||||
import { NoteQuote } from '@lume/shared/note/quote';
|
|
||||||
import { UserExtend } from '@lume/shared/user/extend';
|
|
||||||
import { UserMention } from '@lume/shared/user/mention';
|
|
||||||
|
|
||||||
import destr from 'destr';
|
|
||||||
import { memo, useMemo } from 'react';
|
|
||||||
import reactStringReplace from 'react-string-replace';
|
|
||||||
|
|
||||||
export const NoteComment = memo(function NoteComment({ event }: { event: any }) {
|
|
||||||
const content = useMemo(() => {
|
|
||||||
let parsedContent = event.content;
|
|
||||||
// get data tags
|
|
||||||
const tags = destr(event.tags);
|
|
||||||
// handle urls
|
|
||||||
parsedContent = reactStringReplace(parsedContent, /(https?:\/\/\S+)/g, (match, i) => {
|
|
||||||
if (match.match(/\.(jpg|jpeg|gif|png|webp)$/i)) {
|
|
||||||
// image url
|
|
||||||
return <ImagePreview key={match + i} url={match} size="large" />;
|
|
||||||
} else if (match.match(/(www\.)?(youtube\.com\/watch\?v=|youtu\.be\/)([a-zA-Z0-9_-]{11})/i)) {
|
|
||||||
// youtube
|
|
||||||
return <VideoPreview key={match + i} url={match} />;
|
|
||||||
} else if (match.match(/\.(mp4|webm)$/i)) {
|
|
||||||
// video
|
|
||||||
return <VideoPreview key={match + i} url={match} />;
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<a key={match + i} href={match} target="_blank" rel="noreferrer">
|
|
||||||
{match}
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// handle #-hashtags
|
|
||||||
parsedContent = reactStringReplace(parsedContent, /#(\w+)/g, (match, i) => (
|
|
||||||
<span key={match + i} className="cursor-pointer text-fuchsia-500">
|
|
||||||
#{match}
|
|
||||||
</span>
|
|
||||||
));
|
|
||||||
// handle mentions
|
|
||||||
if (tags.length > 0) {
|
|
||||||
parsedContent = reactStringReplace(parsedContent, /\#\[(\d+)\]/gm, (match, i) => {
|
|
||||||
if (tags[match][0] === 'p') {
|
|
||||||
// @-mentions
|
|
||||||
return <UserMention key={match + i} pubkey={tags[match][1]} />;
|
|
||||||
} else if (tags[match][0] === 'e') {
|
|
||||||
// note-quotes
|
|
||||||
return <NoteQuote key={match + i} id={tags[match][1]} />;
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return parsedContent;
|
|
||||||
}, [event.content, event.tags]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="relative z-10 flex h-min min-h-min w-full select-text flex-col border-b border-zinc-800 px-3 py-5 hover:bg-black/20">
|
|
||||||
<div className="relative z-10 flex flex-col">
|
|
||||||
<UserExtend pubkey={event.pubkey} time={event.created_at} />
|
|
||||||
<div className="-mt-5 pl-[52px]">
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<div className="prose prose-zinc max-w-none break-words text-[15px] leading-tight dark:prose-invert prose-p:m-0 prose-p:text-[15px] prose-p:leading-tight prose-a:font-normal prose-a:text-fuchsia-500 prose-a:no-underline prose-img:m-0 prose-video:m-0">
|
|
||||||
{content}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div onClick={(e) => e.stopPropagation()} className="mt-5 pl-[52px]">
|
|
||||||
<NoteMetadata
|
|
||||||
eventID={event.event_id}
|
|
||||||
eventPubkey={event.pubkey}
|
|
||||||
eventContent={event.content}
|
|
||||||
eventTime={event.created_at}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
@@ -1,82 +0,0 @@
|
|||||||
import { NoteMetadata } from '@lume/shared/note/metadata';
|
|
||||||
import { ImagePreview } from '@lume/shared/note/preview/image';
|
|
||||||
import { VideoPreview } from '@lume/shared/note/preview/video';
|
|
||||||
import { NoteQuote } from '@lume/shared/note/quote';
|
|
||||||
import { UserLarge } from '@lume/shared/user/large';
|
|
||||||
import { UserMention } from '@lume/shared/user/mention';
|
|
||||||
|
|
||||||
import destr from 'destr';
|
|
||||||
import { memo, useMemo } from 'react';
|
|
||||||
import reactStringReplace from 'react-string-replace';
|
|
||||||
|
|
||||||
export const NoteExtend = memo(function NoteExtend({ event }: { event: any }) {
|
|
||||||
const content = useMemo(() => {
|
|
||||||
let parsedContent = event.content;
|
|
||||||
// get data tags
|
|
||||||
const tags = destr(event.tags);
|
|
||||||
// handle urls
|
|
||||||
parsedContent = reactStringReplace(parsedContent, /(https?:\/\/\S+)/g, (match, i) => {
|
|
||||||
if (match.match(/\.(jpg|jpeg|gif|png|webp)$/i)) {
|
|
||||||
// image url
|
|
||||||
return <ImagePreview key={match + i} url={match} size="large" />;
|
|
||||||
} else if (match.match(/(www\.)?(youtube\.com\/watch\?v=|youtu\.be\/)([a-zA-Z0-9_-]{11})/i)) {
|
|
||||||
// youtube
|
|
||||||
return <VideoPreview key={match + i} url={match} />;
|
|
||||||
} else if (match.match(/\.(mp4|webm)$/i)) {
|
|
||||||
// video
|
|
||||||
return <VideoPreview key={match + i} url={match} />;
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<a key={match + i} href={match} target="_blank" rel="noreferrer">
|
|
||||||
{match}
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// handle #-hashtags
|
|
||||||
parsedContent = reactStringReplace(parsedContent, /#(\w+)/g, (match, i) => (
|
|
||||||
<span key={match + i} className="cursor-pointer text-fuchsia-500">
|
|
||||||
#{match}
|
|
||||||
</span>
|
|
||||||
));
|
|
||||||
// handle mentions
|
|
||||||
if (tags.length > 0) {
|
|
||||||
parsedContent = reactStringReplace(parsedContent, /\#\[(\d+)\]/gm, (match, i) => {
|
|
||||||
if (tags[match][0] === 'p') {
|
|
||||||
// @-mentions
|
|
||||||
return <UserMention key={match + i} pubkey={tags[match][1]} />;
|
|
||||||
} else if (tags[match][0] === 'e') {
|
|
||||||
// note-quotes
|
|
||||||
return <NoteQuote key={match + i} id={tags[match][1]} />;
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return parsedContent;
|
|
||||||
}, [event.content, event.tags]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="relative z-10 flex h-min min-h-min w-full select-text flex-col">
|
|
||||||
<div className="relative z-10 flex flex-col">
|
|
||||||
<UserLarge pubkey={event.pubkey} time={event.created_at} />
|
|
||||||
<div className="mt-2">
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<div className="prose prose-zinc max-w-none break-words text-[15px] leading-tight dark:prose-invert prose-p:m-0 prose-p:text-[15px] prose-p:leading-tight prose-a:font-normal prose-a:text-fuchsia-500 prose-a:no-underline prose-img:m-0 prose-video:m-0">
|
|
||||||
{content}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mt-5 flex items-center border-b border-t border-zinc-800 py-2">
|
|
||||||
<NoteMetadata
|
|
||||||
eventID={event.event_id}
|
|
||||||
eventPubkey={event.pubkey}
|
|
||||||
eventContent={event.content}
|
|
||||||
eventTime={event.created_at}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
15
src/utils/hooks/useActiveAccount.tsx
Normal file
15
src/utils/hooks/useActiveAccount.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { getActiveAccount } from '@lume/utils/storage';
|
||||||
|
|
||||||
|
import useSWR from 'swr';
|
||||||
|
|
||||||
|
const fetcher = () => getActiveAccount();
|
||||||
|
|
||||||
|
export const useActiveAccount = () => {
|
||||||
|
const { data, error, isLoading } = useSWR('activeAcount', fetcher);
|
||||||
|
|
||||||
|
return {
|
||||||
|
account: data,
|
||||||
|
isLoading,
|
||||||
|
isError: error,
|
||||||
|
};
|
||||||
|
};
|
@@ -1,7 +1,7 @@
|
|||||||
import destr from 'destr';
|
import destr from 'destr';
|
||||||
|
|
||||||
// convert NIP-02 to array of pubkey
|
// convert NIP-02 to array of pubkey
|
||||||
export const nip02ToArray = (tags: string[]) => {
|
export const nip02ToArray = (tags: any) => {
|
||||||
const arr = [];
|
const arr = [];
|
||||||
tags.forEach((item) => {
|
tags.forEach((item) => {
|
||||||
arr.push(item[1]);
|
arr.push(item[1]);
|
||||||
|
Reference in New Issue
Block a user