mirror of
https://github.com/luminous-devs/lume.git
synced 2024-09-19 11:43:30 +00:00
completed migrate to prisma-rust-client
This commit is contained in:
parent
3f87d510ab
commit
fc8dc8fd0d
@ -27,7 +27,7 @@ CREATE TABLE "Note" (
|
||||
"content" TEXT NOT NULL,
|
||||
"parent_id" TEXT NOT NULL,
|
||||
"parent_comment_id" TEXT NOT NULL,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"createdAt" INTEGER NOT NULL,
|
||||
"accountId" INTEGER NOT NULL,
|
||||
CONSTRAINT "Note_accountId_fkey" FOREIGN KEY ("accountId") REFERENCES "Account" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
@ -38,7 +38,7 @@ CREATE TABLE "Message" (
|
||||
"pubkey" TEXT NOT NULL,
|
||||
"content" TEXT NOT NULL,
|
||||
"tags" TEXT NOT NULL,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"createdAt" INTEGER NOT NULL,
|
||||
"accountId" INTEGER NOT NULL,
|
||||
CONSTRAINT "Message_accountId_fkey" FOREIGN KEY ("accountId") REFERENCES "Account" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
@ -0,0 +1,11 @@
|
||||
-- DropIndex
|
||||
DROP INDEX "Message_pubkey_idx";
|
||||
|
||||
-- DropIndex
|
||||
DROP INDEX "Note_eventId_idx";
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Message_pubkey_createdAt_idx" ON "Message"("pubkey", "createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Note_eventId_createdAt_idx" ON "Note"("eventId", "createdAt");
|
@ -44,12 +44,12 @@ model Note {
|
||||
content String
|
||||
parent_id String
|
||||
parent_comment_id String
|
||||
createdAt DateTime @default(now())
|
||||
createdAt Int
|
||||
|
||||
Account Account @relation(fields: [accountId], references: [id])
|
||||
accountId Int
|
||||
|
||||
@@index([eventId])
|
||||
@@index([eventId, createdAt])
|
||||
}
|
||||
|
||||
model Message {
|
||||
@ -57,12 +57,12 @@ model Message {
|
||||
pubkey String
|
||||
content String
|
||||
tags String
|
||||
createdAt DateTime @default(now())
|
||||
createdAt Int
|
||||
|
||||
Account Account @relation(fields: [accountId], references: [id])
|
||||
accountId Int
|
||||
|
||||
@@index([pubkey])
|
||||
@@index([pubkey, createdAt])
|
||||
}
|
||||
|
||||
model Relay {
|
||||
|
@ -7,7 +7,7 @@
|
||||
#[macro_use]
|
||||
extern crate objc;
|
||||
|
||||
use prisma_client_rust::raw;
|
||||
use prisma_client_rust::Direction;
|
||||
use tauri::{Manager, WindowEvent};
|
||||
#[cfg(target_os = "macos")]
|
||||
use window_ext::WindowExt;
|
||||
@ -54,12 +54,30 @@ struct CreateNoteData {
|
||||
content: String,
|
||||
parent_id: String,
|
||||
parent_comment_id: String,
|
||||
created_at: i32,
|
||||
account_id: i32,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Type)]
|
||||
struct GetNoteByIdData {
|
||||
event_id: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Type)]
|
||||
struct GetNoteData {
|
||||
date: i32,
|
||||
limit: i32,
|
||||
offset: i32,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Type)]
|
||||
struct GetLatestNoteData {
|
||||
date: i32,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn get_account(db: DbState<'_>) -> Result<Vec<account::Data>, ()> {
|
||||
async fn get_accounts(db: DbState<'_>) -> Result<Vec<account::Data>, ()> {
|
||||
db.account()
|
||||
.find_many(vec![account::active::equals(false)])
|
||||
.exec()
|
||||
@ -120,6 +138,7 @@ async fn create_note(db: DbState<'_>, data: CreateNoteData) -> Result<note::Data
|
||||
data.content,
|
||||
data.parent_id,
|
||||
data.parent_comment_id,
|
||||
data.created_at,
|
||||
account::id::equals(data.account_id),
|
||||
vec![],
|
||||
),
|
||||
@ -132,8 +151,12 @@ async fn create_note(db: DbState<'_>, data: CreateNoteData) -> Result<note::Data
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn get_notes(db: DbState<'_>) -> Result<Vec<note::Data>, ()> {
|
||||
db._query_raw(raw!("SELECT * FROM Note"))
|
||||
async fn get_notes(db: DbState<'_>, data: GetNoteData) -> Result<Vec<note::Data>, ()> {
|
||||
db.note()
|
||||
.find_many(vec![note::created_at::lte(data.date)])
|
||||
.order_by(note::created_at::order(Direction::Desc))
|
||||
.take(data.limit.into())
|
||||
.skip(data.offset.into())
|
||||
.exec()
|
||||
.await
|
||||
.map_err(|_| ())
|
||||
@ -141,15 +164,30 @@ async fn get_notes(db: DbState<'_>) -> Result<Vec<note::Data>, ()> {
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn check_note(db: DbState<'_>) -> Result<Vec<note::Data>, ()> {
|
||||
async fn get_latest_notes(db: DbState<'_>, data: GetLatestNoteData) -> Result<Vec<note::Data>, ()> {
|
||||
db.note()
|
||||
.find_many(vec![])
|
||||
.take(5)
|
||||
.find_many(vec![note::created_at::gt(data.date)])
|
||||
.order_by(note::created_at::order(Direction::Desc))
|
||||
.exec()
|
||||
.await
|
||||
.map_err(|_| ())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn get_note_by_id(db: DbState<'_>, data: GetNoteByIdData) -> Result<Option<note::Data>, ()> {
|
||||
db.note()
|
||||
.find_unique(note::event_id::equals(data.event_id))
|
||||
.exec()
|
||||
.await
|
||||
.map_err(|_| ())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn count_total_notes(db: DbState<'_>) -> Result<i64, ()> {
|
||||
db.note().count(vec![]).exec().await.map_err(|_| ())
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let db = PrismaClient::_builder().build().await.unwrap();
|
||||
@ -157,13 +195,14 @@ async fn main() {
|
||||
#[cfg(debug_assertions)]
|
||||
ts::export(
|
||||
collect_types![
|
||||
get_account,
|
||||
get_accounts,
|
||||
create_account,
|
||||
get_follows,
|
||||
create_follow,
|
||||
create_note,
|
||||
get_notes,
|
||||
check_note
|
||||
get_latest_notes,
|
||||
get_note_by_id
|
||||
],
|
||||
"../src/utils/bindings.ts",
|
||||
)
|
||||
@ -196,13 +235,15 @@ async fn main() {
|
||||
}
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
get_account,
|
||||
get_accounts,
|
||||
create_account,
|
||||
get_follows,
|
||||
create_follow,
|
||||
create_note,
|
||||
get_notes,
|
||||
check_note
|
||||
get_latest_notes,
|
||||
get_note_by_id,
|
||||
count_total_notes
|
||||
])
|
||||
.manage(Arc::new(db))
|
||||
.run(tauri::generate_context!())
|
||||
|
@ -2,8 +2,7 @@ import { RelayContext } from '@components/relaysProvider';
|
||||
|
||||
import { DEFAULT_AVATAR } from '@stores/constants';
|
||||
|
||||
import { createFollows } from '@utils/storage';
|
||||
import { tagsToArray } from '@utils/transform';
|
||||
import { fetchMetadata } from '@utils/metadata';
|
||||
|
||||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
|
||||
import { AvatarIcon, ExitIcon, GearIcon } from '@radix-ui/react-icons';
|
||||
@ -11,7 +10,7 @@ import { writeText } from '@tauri-apps/api/clipboard';
|
||||
import Image from 'next/image';
|
||||
import { useRouter } from 'next/router';
|
||||
import { nip19 } from 'nostr-tools';
|
||||
import { memo, useContext, useEffect } from 'react';
|
||||
import { memo, useCallback, useContext, useEffect } from 'react';
|
||||
|
||||
export const ActiveAccount = memo(function ActiveAccount({ user }: { user: any }) {
|
||||
const [pool, relays]: any = useContext(RelayContext);
|
||||
@ -27,6 +26,21 @@ export const ActiveAccount = memo(function ActiveAccount({ user }: { user: any }
|
||||
await writeText(nip19.npubEncode(user.id));
|
||||
};
|
||||
|
||||
const insertFollowsToStorage = useCallback(
|
||||
async (tags) => {
|
||||
const { createFollow } = await import('@utils/bindings');
|
||||
const activeAccount = JSON.parse(localStorage.getItem('activeAccount'));
|
||||
|
||||
for (const tag of tags) {
|
||||
const metadata: any = await fetchMetadata(tag[1], pool, relays);
|
||||
createFollow({ pubkey: tag[1], kind: 0, metadata: metadata.content, account_id: activeAccount.id }).catch(
|
||||
console.error
|
||||
);
|
||||
}
|
||||
},
|
||||
[pool, relays]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = pool.subscribe(
|
||||
[
|
||||
@ -38,7 +52,7 @@ export const ActiveAccount = memo(function ActiveAccount({ user }: { user: any }
|
||||
relays,
|
||||
(event: any) => {
|
||||
if (event.tags.length > 0) {
|
||||
createFollows(tagsToArray(event.tags), user.id, 0);
|
||||
insertFollowsToStorage(event.tags);
|
||||
}
|
||||
},
|
||||
undefined,
|
||||
@ -51,7 +65,7 @@ export const ActiveAccount = memo(function ActiveAccount({ user }: { user: any }
|
||||
return () => {
|
||||
unsubscribe;
|
||||
};
|
||||
}, [pool, relays, user.id]);
|
||||
}, [insertFollowsToStorage, pool, relays, user.id]);
|
||||
|
||||
return (
|
||||
<DropdownMenu.Root>
|
||||
|
@ -1,41 +1,37 @@
|
||||
import { ActiveAccount } from '@components/multiAccounts/activeAccount';
|
||||
import { InactiveAccount } from '@components/multiAccounts/inactiveAccount';
|
||||
|
||||
import { activeAccountAtom } from '@stores/account';
|
||||
import { APP_VERSION } from '@stores/constants';
|
||||
|
||||
import { getAccounts } from '@utils/storage';
|
||||
|
||||
import LumeSymbol from '@assets/icons/Lume';
|
||||
|
||||
import { PlusIcon } from '@radix-ui/react-icons';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import Link from 'next/link';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
export default function MultiAccounts() {
|
||||
const activeAccount: any = useAtomValue(activeAccountAtom);
|
||||
const [users, setUsers] = useState([]);
|
||||
|
||||
const renderAccount = useCallback(
|
||||
(user: { id: string }) => {
|
||||
const renderAccount = useCallback((user: { id: string }) => {
|
||||
const activeAccount = JSON.parse(localStorage.getItem('activeAccount'));
|
||||
|
||||
if (user.id === activeAccount.id) {
|
||||
return <ActiveAccount key={user.id} user={user} />;
|
||||
} else {
|
||||
return <InactiveAccount key={user.id} user={user} />;
|
||||
}
|
||||
},
|
||||
[activeAccount.id]
|
||||
);
|
||||
}, []);
|
||||
|
||||
const fetchAccounts = useCallback(async () => {
|
||||
const { getAccounts } = await import('@utils/bindings');
|
||||
const accounts = await getAccounts();
|
||||
// update state
|
||||
setUsers(accounts);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchAccount = async () => {
|
||||
const result: any = await getAccounts();
|
||||
setUsers(result);
|
||||
};
|
||||
|
||||
fetchAccount().catch(console.error);
|
||||
}, []);
|
||||
fetchAccounts().catch(console.error);
|
||||
}, [fetchAccounts]);
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col items-center justify-between px-2 pb-4 pt-3">
|
||||
|
@ -63,13 +63,13 @@ export const NoteBase = memo(function NoteBase({ event }: { event: any }) {
|
||||
|
||||
const getParent = useMemo(() => {
|
||||
if (event.parent_id) {
|
||||
if (event.parent_id !== event.id && !event.content.includes('#[0]')) {
|
||||
if (event.parent_id !== event.eventId && !event.content.includes('#[0]')) {
|
||||
return <NoteParent id={event.parent_id} />;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}, [event.content, event.id, event.parent_id]);
|
||||
}, [event.content, event.eventId, event.parent_id]);
|
||||
|
||||
const openThread = (e) => {
|
||||
const selection = window.getSelection();
|
||||
@ -87,7 +87,7 @@ export const NoteBase = memo(function NoteBase({ event }: { event: any }) {
|
||||
>
|
||||
<>{getParent}</>
|
||||
<div className="relative z-10 flex flex-col">
|
||||
<UserExtend pubkey={event.pubkey} time={event.created_at} />
|
||||
<UserExtend pubkey={event.pubkey} time={event.createdAt || 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">
|
||||
@ -97,10 +97,10 @@ export const NoteBase = memo(function NoteBase({ event }: { event: any }) {
|
||||
</div>
|
||||
<div onClick={(e) => e.stopPropagation()} className="mt-5 pl-[52px]">
|
||||
<NoteMetadata
|
||||
eventID={event.id}
|
||||
eventID={event.eventId}
|
||||
eventPubkey={event.pubkey}
|
||||
eventContent={event.content}
|
||||
eventTime={event.created_at}
|
||||
eventTime={event.createdAt || event.created_at}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -60,7 +60,7 @@ export const NoteComment = memo(function NoteComment({ event }: { event: any })
|
||||
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} />
|
||||
<UserExtend pubkey={event.pubkey} time={event.createdAt || 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">
|
||||
@ -70,10 +70,10 @@ export const NoteComment = memo(function NoteComment({ event }: { event: any })
|
||||
</div>
|
||||
<div onClick={(e) => e.stopPropagation()} className="mt-5 pl-[52px]">
|
||||
<NoteMetadata
|
||||
eventID={event.id}
|
||||
eventID={event.eventId}
|
||||
eventPubkey={event.pubkey}
|
||||
eventContent={event.content}
|
||||
eventTime={event.created_at}
|
||||
eventTime={event.createdAt || event.created_at}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,15 +1,14 @@
|
||||
import { RelayContext } from '@components/relaysProvider';
|
||||
|
||||
import { activeAccountAtom, lastLoginAtom } from '@stores/account';
|
||||
import { lastLoginAtom } from '@stores/account';
|
||||
import { hasNewerNoteAtom } from '@stores/note';
|
||||
|
||||
import { dateToUnix } from '@utils/getDate';
|
||||
import { createCacheNote, getAllFollowsByID } from '@utils/storage';
|
||||
import { pubkeyArray } from '@utils/transform';
|
||||
import { getParentID, pubkeyArray } from '@utils/transform';
|
||||
|
||||
import { TauriEvent } from '@tauri-apps/api/event';
|
||||
import { appWindow, getCurrent } from '@tauri-apps/api/window';
|
||||
import { useAtomValue, useSetAtom } from 'jotai';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { useCallback, useContext, useEffect, useRef, useState } from 'react';
|
||||
|
||||
export default function NoteConnector() {
|
||||
@ -17,14 +16,18 @@ export default function NoteConnector() {
|
||||
|
||||
const setLastLoginAtom = useSetAtom(lastLoginAtom);
|
||||
const setHasNewerNote = useSetAtom(hasNewerNoteAtom);
|
||||
const activeAccount: any = useAtomValue(activeAccountAtom);
|
||||
|
||||
const [isOnline] = useState(true);
|
||||
const now = useRef(new Date());
|
||||
|
||||
const subscribe = useCallback(() => {
|
||||
getAllFollowsByID(activeAccount.id).then((follows) => {
|
||||
pool.subscribe(
|
||||
const now = useRef(new Date());
|
||||
const unsubscribe = useRef(null);
|
||||
|
||||
const subscribe = useCallback(async () => {
|
||||
const { createNote } = await import('@utils/bindings');
|
||||
const activeAccount = JSON.parse(localStorage.getItem('activeAccount'));
|
||||
const follows = JSON.parse(localStorage.getItem('activeAccountFollows'));
|
||||
|
||||
unsubscribe.current = pool.subscribe(
|
||||
[
|
||||
{
|
||||
kinds: [1],
|
||||
@ -33,14 +36,25 @@ export default function NoteConnector() {
|
||||
},
|
||||
],
|
||||
relays,
|
||||
(event: any) => {
|
||||
(event) => {
|
||||
const parentID = getParentID(event.tags, event.id);
|
||||
// insert event to local database
|
||||
createCacheNote(event);
|
||||
createNote({
|
||||
event_id: event.id,
|
||||
pubkey: event.pubkey,
|
||||
kind: event.kind,
|
||||
tags: JSON.stringify(event.tags),
|
||||
content: event.content,
|
||||
parent_id: parentID,
|
||||
parent_comment_id: '',
|
||||
created_at: event.created_at,
|
||||
account_id: activeAccount.id,
|
||||
}).catch(console.error);
|
||||
// notify user reload to get newer note
|
||||
setHasNewerNote(true);
|
||||
}
|
||||
);
|
||||
});
|
||||
}, [activeAccount.id, pool, relays, setHasNewerNote]);
|
||||
}, [pool, relays, setHasNewerNote]);
|
||||
|
||||
useEffect(() => {
|
||||
subscribe();
|
||||
@ -48,10 +62,13 @@ export default function NoteConnector() {
|
||||
setLastLoginAtom(now.current);
|
||||
appWindow.close();
|
||||
});
|
||||
}, [activeAccount.id, pool, relays, setHasNewerNote, subscribe]);
|
||||
|
||||
return () => {
|
||||
unsubscribe.current;
|
||||
};
|
||||
}, [setHasNewerNote, setLastLoginAtom, subscribe]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="inline-flex items-center gap-1 rounded-md px-1.5 py-1 hover:bg-zinc-900">
|
||||
<span className="relative flex h-1.5 w-1.5">
|
||||
<span
|
||||
@ -65,6 +82,5 @@ export default function NoteConnector() {
|
||||
</span>
|
||||
<p className="text-xs font-medium text-zinc-500">{isOnline ? 'Online' : 'Offline'}</p>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ export const NoteExtend = memo(function NoteExtend({ event }: { event: any }) {
|
||||
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} />
|
||||
<UserLarge pubkey={event.pubkey} time={event.createdAt || 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">
|
||||
@ -70,10 +70,10 @@ export const NoteExtend = memo(function NoteExtend({ event }: { event: any }) {
|
||||
</div>
|
||||
<div className="mt-5 flex items-center border-b border-t border-zinc-800 py-2">
|
||||
<NoteMetadata
|
||||
eventID={event.id}
|
||||
eventID={event.eventId}
|
||||
eventPubkey={event.pubkey}
|
||||
eventContent={event.content}
|
||||
eventTime={event.created_at}
|
||||
eventTime={event.createdAt || event.created_at}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -6,7 +6,7 @@ import { RelayContext } from '@components/relaysProvider';
|
||||
import { UserExtend } from '@components/user/extend';
|
||||
import { UserMention } from '@components/user/mention';
|
||||
|
||||
import { createCacheNote, getNoteByID } from '@utils/storage';
|
||||
import { getParentID } from '@utils/transform';
|
||||
|
||||
import destr from 'destr';
|
||||
import { memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||
@ -18,7 +18,10 @@ export const NoteParent = memo(function NoteParent({ id }: { id: string }) {
|
||||
const [event, setEvent] = useState(null);
|
||||
const unsubscribe = useRef(null);
|
||||
|
||||
const fetchEvent = useCallback(() => {
|
||||
const fetchEvent = useCallback(async () => {
|
||||
const { createNote } = await import('@utils/bindings');
|
||||
const activeAccount = JSON.parse(localStorage.getItem('activeAccount'));
|
||||
|
||||
unsubscribe.current = pool.subscribe(
|
||||
[
|
||||
{
|
||||
@ -31,7 +34,19 @@ export const NoteParent = memo(function NoteParent({ id }: { id: string }) {
|
||||
// update state
|
||||
setEvent(event);
|
||||
// insert to database
|
||||
createCacheNote(event);
|
||||
const parentID = getParentID(event.tags, event.id);
|
||||
// insert event to local database
|
||||
createNote({
|
||||
event_id: event.id,
|
||||
pubkey: event.pubkey,
|
||||
kind: event.kind,
|
||||
tags: JSON.stringify(event.tags),
|
||||
content: event.content,
|
||||
parent_id: parentID,
|
||||
parent_comment_id: '',
|
||||
created_at: event.created_at,
|
||||
account_id: activeAccount.id,
|
||||
}).catch(console.error);
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
@ -41,19 +56,26 @@ export const NoteParent = memo(function NoteParent({ id }: { id: string }) {
|
||||
);
|
||||
}, [id, pool, relays]);
|
||||
|
||||
useEffect(() => {
|
||||
getNoteByID(id).then((res) => {
|
||||
const checkNoteExist = useCallback(async () => {
|
||||
const { getNoteById } = await import('@utils/bindings');
|
||||
getNoteById({ event_id: id })
|
||||
.then((res) => {
|
||||
if (res) {
|
||||
setEvent(res);
|
||||
} else {
|
||||
fetchEvent();
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(console.error);
|
||||
}, [fetchEvent, id]);
|
||||
|
||||
useEffect(() => {
|
||||
checkNoteExist();
|
||||
|
||||
return () => {
|
||||
unsubscribe.current;
|
||||
};
|
||||
}, [fetchEvent, id]);
|
||||
}, [checkNoteExist]);
|
||||
|
||||
const content = useMemo(() => {
|
||||
let parsedContent = event ? event.content : null;
|
||||
@ -110,7 +132,7 @@ export const NoteParent = memo(function NoteParent({ id }: { id: string }) {
|
||||
<div className="relative 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="relative z-10 flex flex-col">
|
||||
<UserExtend pubkey={event.pubkey} time={event.created_at} />
|
||||
<UserExtend pubkey={event.pubkey} time={event.createdAt || 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">
|
||||
@ -120,10 +142,10 @@ export const NoteParent = memo(function NoteParent({ id }: { id: string }) {
|
||||
</div>
|
||||
<div onClick={(e) => e.stopPropagation()} className="mt-5 pl-[52px]">
|
||||
<NoteMetadata
|
||||
eventID={event.id}
|
||||
eventID={event.eventId}
|
||||
eventPubkey={event.pubkey}
|
||||
eventContent={event.content}
|
||||
eventTime={event.created_at}
|
||||
eventTime={event.createdAt || event.created_at}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -2,7 +2,7 @@ import { RelayContext } from '@components/relaysProvider';
|
||||
import { UserExtend } from '@components/user/extend';
|
||||
import { UserMention } from '@components/user/mention';
|
||||
|
||||
import { createCacheNote, getNoteByID } from '@utils/storage';
|
||||
import { getParentID } from '@utils/transform';
|
||||
|
||||
import destr from 'destr';
|
||||
import { memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||
@ -14,7 +14,10 @@ export const NoteRepost = memo(function NoteRepost({ id }: { id: string }) {
|
||||
const [event, setEvent] = useState(null);
|
||||
const unsubscribe = useRef(null);
|
||||
|
||||
const fetchEvent = useCallback(() => {
|
||||
const fetchEvent = useCallback(async () => {
|
||||
const { createNote } = await import('@utils/bindings');
|
||||
const activeAccount = JSON.parse(localStorage.getItem('activeAccount'));
|
||||
|
||||
unsubscribe.current = pool.subscribe(
|
||||
[
|
||||
{
|
||||
@ -27,7 +30,19 @@ export const NoteRepost = memo(function NoteRepost({ id }: { id: string }) {
|
||||
// update state
|
||||
setEvent(event);
|
||||
// insert to database
|
||||
createCacheNote(event);
|
||||
const parentID = getParentID(event.tags, event.id);
|
||||
// insert event to local database
|
||||
createNote({
|
||||
event_id: event.id,
|
||||
pubkey: event.pubkey,
|
||||
kind: event.kind,
|
||||
tags: JSON.stringify(event.tags),
|
||||
content: event.content,
|
||||
parent_id: parentID,
|
||||
parent_comment_id: '',
|
||||
created_at: event.created_at,
|
||||
account_id: activeAccount.id,
|
||||
}).catch(console.error);
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
@ -37,19 +52,26 @@ export const NoteRepost = memo(function NoteRepost({ id }: { id: string }) {
|
||||
);
|
||||
}, [id, pool, relays]);
|
||||
|
||||
useEffect(() => {
|
||||
getNoteByID(id).then((res) => {
|
||||
const checkNoteExist = useCallback(async () => {
|
||||
const { getNoteById } = await import('@utils/bindings');
|
||||
getNoteById({ event_id: id })
|
||||
.then((res) => {
|
||||
if (res) {
|
||||
setEvent(res);
|
||||
} else {
|
||||
fetchEvent();
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(console.error);
|
||||
}, [fetchEvent, id]);
|
||||
|
||||
useEffect(() => {
|
||||
checkNoteExist();
|
||||
|
||||
return () => {
|
||||
unsubscribe.current;
|
||||
};
|
||||
}, [fetchEvent, id]);
|
||||
}, [checkNoteExist]);
|
||||
|
||||
const content = useMemo(() => {
|
||||
let parsedContent = event ? event.content : null;
|
||||
@ -89,7 +111,7 @@ export const NoteRepost = memo(function NoteRepost({ id }: { id: string }) {
|
||||
return (
|
||||
<div className="relative mb-2 mt-3 rounded-lg border border-zinc-700 bg-zinc-800 p-2 py-3">
|
||||
<div className="relative z-10 flex flex-col">
|
||||
<UserExtend pubkey={event.pubkey} time={event.created_at} />
|
||||
<UserExtend pubkey={event.pubkey} time={event.createdAt || 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">
|
||||
|
@ -6,17 +6,17 @@ import { DEFAULT_AVATAR } from '@stores/constants';
|
||||
import { truncate } from '@utils/truncate';
|
||||
|
||||
import { Author } from 'nostr-relaypool';
|
||||
import { memo, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { memo, useContext, useEffect, useState } from 'react';
|
||||
|
||||
export const UserBase = memo(function UserBase({ pubkey }: { pubkey: string }) {
|
||||
const [pool, relays]: any = useContext(RelayContext);
|
||||
|
||||
const [profile, setProfile] = useState(null);
|
||||
const user = useMemo(() => new Author(pool, relays, pubkey), [pubkey, pool, relays]);
|
||||
|
||||
useEffect(() => {
|
||||
const user = new Author(pool, relays, pubkey);
|
||||
user.metaData((res) => setProfile(JSON.parse(res.content)), 0);
|
||||
}, [user]);
|
||||
}, [pool, relays, pubkey]);
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
|
@ -10,16 +10,15 @@ import dayjs from 'dayjs';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
import { useRouter } from 'next/router';
|
||||
import { Author } from 'nostr-relaypool';
|
||||
import { memo, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { memo, useContext, useEffect, useState } from 'react';
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
export const UserExtend = memo(function UserExtend({ pubkey, time }: { pubkey: string; time: any }) {
|
||||
const [pool, relays]: any = useContext(RelayContext);
|
||||
const router = useRouter();
|
||||
const [pool, relays]: any = useContext(RelayContext);
|
||||
|
||||
const [profile, setProfile] = useState(null);
|
||||
const user = useMemo(() => new Author(pool, relays, pubkey), [pubkey, pool, relays]);
|
||||
|
||||
const openUserPage = (e) => {
|
||||
e.stopPropagation();
|
||||
@ -27,8 +26,9 @@ export const UserExtend = memo(function UserExtend({ pubkey, time }: { pubkey: s
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const user = new Author(pool, relays, pubkey);
|
||||
user.metaData((res) => setProfile(JSON.parse(res.content)), 0);
|
||||
}, [user]);
|
||||
}, [pool, relays, pubkey]);
|
||||
|
||||
return (
|
||||
<div className="group flex items-start gap-2">
|
||||
|
@ -1,33 +1,22 @@
|
||||
import { ImageWithFallback } from '@components/imageWithFallback';
|
||||
import { RelayContext } from '@components/relaysProvider';
|
||||
|
||||
import { DEFAULT_AVATAR } from '@stores/constants';
|
||||
|
||||
import { createCacheProfile } from '@utils/storage';
|
||||
import { truncate } from '@utils/truncate';
|
||||
|
||||
import { fetch } from '@tauri-apps/api/http';
|
||||
import destr from 'destr';
|
||||
import { memo, useCallback, useEffect, useState } from 'react';
|
||||
import { Author } from 'nostr-relaypool';
|
||||
import { memo, useContext, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
export const UserFollow = memo(function UserFollow({ pubkey }: { pubkey: string }) {
|
||||
const [profile, setProfile] = useState(null);
|
||||
const [pool, relays]: any = useContext(RelayContext);
|
||||
|
||||
const fetchProfile = useCallback(async (id: string) => {
|
||||
const res = await fetch(`https://rbr.bio/${id}/metadata.json`, {
|
||||
method: 'GET',
|
||||
timeout: 30,
|
||||
});
|
||||
return res.data;
|
||||
}, []);
|
||||
const [profile, setProfile] = useState(null);
|
||||
const user = useMemo(() => new Author(pool, relays, pubkey), [pubkey, pool, relays]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchProfile(pubkey)
|
||||
.then((res: any) => {
|
||||
setProfile(destr(res.content));
|
||||
createCacheProfile(res.pubkey, res.content);
|
||||
})
|
||||
.catch(console.error);
|
||||
}, [fetchProfile, pubkey]);
|
||||
user.metaData((res) => setProfile(JSON.parse(res.content)), 0);
|
||||
}, [user]);
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
|
@ -9,19 +9,18 @@ import { DotsHorizontalIcon } from '@radix-ui/react-icons';
|
||||
import dayjs from 'dayjs';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
import { Author } from 'nostr-relaypool';
|
||||
import { memo, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { memo, useContext, useEffect, useState } from 'react';
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
export const UserLarge = memo(function UserLarge({ pubkey, time }: { pubkey: string; time: any }) {
|
||||
const [pool, relays]: any = useContext(RelayContext);
|
||||
|
||||
const [profile, setProfile] = useState(null);
|
||||
const user = useMemo(() => new Author(pool, relays, pubkey), [pubkey, pool, relays]);
|
||||
|
||||
useEffect(() => {
|
||||
const user = new Author(pool, relays, pubkey);
|
||||
user.metaData((res) => setProfile(JSON.parse(res.content)), 0);
|
||||
}, [user]);
|
||||
}, [pool, relays, pubkey]);
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
|
@ -3,17 +3,16 @@ import { RelayContext } from '@components/relaysProvider';
|
||||
import { truncate } from '@utils/truncate';
|
||||
|
||||
import { Author } from 'nostr-relaypool';
|
||||
import { memo, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { memo, useContext, useEffect, useState } from 'react';
|
||||
|
||||
export const UserMention = memo(function UserMention({ pubkey }: { pubkey: string }) {
|
||||
const [pool, relays]: any = useContext(RelayContext);
|
||||
|
||||
const [profile, setProfile] = useState(null);
|
||||
const user = useMemo(() => new Author(pool, relays, pubkey), [pubkey, pool, relays]);
|
||||
|
||||
useEffect(() => {
|
||||
const user = new Author(pool, relays, pubkey);
|
||||
user.metaData((res) => setProfile(JSON.parse(res.content)), 0);
|
||||
}, [user]);
|
||||
}, [pool, relays, pubkey]);
|
||||
|
||||
return <span className="cursor-pointer text-fuchsia-500">@{profile?.name || truncate(pubkey, 16, ' .... ')}</span>;
|
||||
});
|
||||
|
@ -6,17 +6,16 @@ import { DEFAULT_AVATAR } from '@stores/constants';
|
||||
import { truncate } from '@utils/truncate';
|
||||
|
||||
import { Author } from 'nostr-relaypool';
|
||||
import { useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
|
||||
export const UserMini = ({ pubkey }: { pubkey: string }) => {
|
||||
const [pool, relays]: any = useContext(RelayContext);
|
||||
|
||||
const [profile, setProfile] = useState(null);
|
||||
const user = useMemo(() => new Author(pool, relays, pubkey), [pubkey, pool, relays]);
|
||||
|
||||
useEffect(() => {
|
||||
const user = new Author(pool, relays, pubkey);
|
||||
user.metaData((res) => setProfile(JSON.parse(res.content)), 0);
|
||||
}, [user]);
|
||||
}, [pool, relays, pubkey]);
|
||||
|
||||
if (profile) {
|
||||
return (
|
||||
|
@ -14,8 +14,8 @@ export default function Page() {
|
||||
const setActiveAccountFollows = useSetAtom(activeAccountFollowsAtom);
|
||||
|
||||
const fetchActiveAccount = useCallback(async () => {
|
||||
const { getAccount } = await import('@utils/bindings');
|
||||
return await getAccount();
|
||||
const { getAccounts } = await import('@utils/bindings');
|
||||
return await getAccounts();
|
||||
}, []);
|
||||
|
||||
const fetchFollowsByAccount = useCallback(async (id) => {
|
||||
|
@ -2,14 +2,12 @@ import BaseLayout from '@layouts/base';
|
||||
|
||||
import { RelayContext } from '@components/relaysProvider';
|
||||
|
||||
import { activeAccountAtom, activeAccountFollowsAtom, lastLoginAtom } from '@stores/account';
|
||||
|
||||
import { dateToUnix, hoursAgo } from '@utils/getDate';
|
||||
import { getParentID, pubkeyArray } from '@utils/transform';
|
||||
|
||||
import LumeSymbol from '@assets/icons/Lume';
|
||||
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { invoke } from '@tauri-apps/api/tauri';
|
||||
import { useRouter } from 'next/router';
|
||||
import {
|
||||
JSXElementConstructor,
|
||||
@ -27,23 +25,22 @@ export default function Page() {
|
||||
const router = useRouter();
|
||||
const [pool, relays]: any = useContext(RelayContext);
|
||||
|
||||
const activeAccount: any = useAtomValue(activeAccountAtom);
|
||||
const activeAccountFollows: any = useAtomValue(activeAccountFollowsAtom);
|
||||
const lastLogin: any = useAtomValue(lastLoginAtom);
|
||||
|
||||
const now = useRef(new Date());
|
||||
const unsubscribe = useRef(null);
|
||||
|
||||
const [eose, setEose] = useState(false);
|
||||
|
||||
const fetchData = useCallback(
|
||||
async (since) => {
|
||||
async (since: Date) => {
|
||||
const { createNote } = await import('@utils/bindings');
|
||||
const activeAccount = JSON.parse(localStorage.getItem('activeAccount'));
|
||||
const follows = JSON.parse(localStorage.getItem('activeAccountFollows'));
|
||||
|
||||
unsubscribe.current = pool.subscribe(
|
||||
[
|
||||
{
|
||||
kinds: [1],
|
||||
authors: pubkeyArray(activeAccountFollows),
|
||||
authors: pubkeyArray(follows),
|
||||
since: dateToUnix(since),
|
||||
until: dateToUnix(now.current),
|
||||
},
|
||||
@ -59,7 +56,8 @@ export default function Page() {
|
||||
tags: JSON.stringify(event.tags),
|
||||
content: event.content,
|
||||
parent_id: parentID,
|
||||
parent_comment_id: 'aaa',
|
||||
parent_comment_id: '',
|
||||
created_at: event.created_at,
|
||||
account_id: activeAccount.id,
|
||||
}).catch(console.error);
|
||||
},
|
||||
@ -69,22 +67,20 @@ export default function Page() {
|
||||
}
|
||||
);
|
||||
},
|
||||
[activeAccount.id, activeAccountFollows, pool, relays]
|
||||
[pool, relays]
|
||||
);
|
||||
|
||||
const isNoteExist = useCallback(async () => {
|
||||
const { checkNote } = await import('@utils/bindings');
|
||||
checkNote()
|
||||
.then((res) => {
|
||||
if (res.length === 5) {
|
||||
invoke('count_total_notes').then((res: number) => {
|
||||
if (res > 0) {
|
||||
const lastLogin = JSON.parse(localStorage.getItem('lastLogin'));
|
||||
const parseDate = new Date(lastLogin);
|
||||
fetchData(parseDate);
|
||||
} else {
|
||||
fetchData(hoursAgo(24, now.current));
|
||||
}
|
||||
})
|
||||
.catch(console.error);
|
||||
}, [fetchData, lastLogin]);
|
||||
});
|
||||
}, [fetchData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (eose === false) {
|
||||
|
@ -8,7 +8,7 @@ import { Placeholder } from '@components/note/placeholder';
|
||||
import { hasNewerNoteAtom } from '@stores/note';
|
||||
|
||||
import { dateToUnix } from '@utils/getDate';
|
||||
import { getLatestNotes, getNotes } from '@utils/storage';
|
||||
import { filteredData } from '@utils/transform';
|
||||
|
||||
import { ArrowUpIcon } from '@radix-ui/react-icons';
|
||||
import { useAtom } from 'jotai';
|
||||
@ -48,23 +48,36 @@ export default function Page() {
|
||||
);
|
||||
|
||||
const initialData = useCallback(async () => {
|
||||
const result: any = await getNotes(dateToUnix(now.current), limit.current, offset.current);
|
||||
setData((data) => [...data, ...result]);
|
||||
const { getNotes } = await import('@utils/bindings');
|
||||
const result: any = await getNotes({
|
||||
date: dateToUnix(now.current),
|
||||
limit: limit.current,
|
||||
offset: offset.current,
|
||||
});
|
||||
const filteredResult = filteredData(result);
|
||||
setData((data) => [...data, ...filteredResult]);
|
||||
}, []);
|
||||
|
||||
const loadMore = useCallback(async () => {
|
||||
const { getNotes } = await import('@utils/bindings');
|
||||
offset.current += limit.current;
|
||||
// next query
|
||||
const result: any = await getNotes(dateToUnix(now.current), limit.current, offset.current);
|
||||
setData((data) => [...data, ...result]);
|
||||
const result: any = await getNotes({
|
||||
date: dateToUnix(now.current),
|
||||
limit: limit.current,
|
||||
offset: offset.current,
|
||||
});
|
||||
const filteredResult = filteredData(result);
|
||||
setData((data) => [...data, ...filteredResult]);
|
||||
}, []);
|
||||
|
||||
const loadLatest = useCallback(async () => {
|
||||
offset.current += limit.current;
|
||||
const { getLatestNotes } = await import('@utils/bindings');
|
||||
// next query
|
||||
const result: any = await getLatestNotes(dateToUnix(now.current));
|
||||
const result: any = await getLatestNotes({ date: dateToUnix(now.current) });
|
||||
// update data
|
||||
setData((data) => [...result, ...data]);
|
||||
const filteredResult = filteredData(result);
|
||||
setData((data) => [...data, ...filteredResult]);
|
||||
// hide newer trigger
|
||||
setHasNewerNote(false);
|
||||
// scroll to top
|
||||
|
@ -11,4 +11,4 @@ const createMyJsonStorage = () => {
|
||||
|
||||
export const activeAccountAtom = atomWithStorage('activeAccount', {}, createMyJsonStorage());
|
||||
export const activeAccountFollowsAtom = atomWithStorage('activeAccountFollows', [], createMyJsonStorage());
|
||||
export const lastLoginAtom = atomWithStorage('lastLoginAtom', [], createMyJsonStorage());
|
||||
export const lastLoginAtom = atomWithStorage('lastLogin', [], createMyJsonStorage());
|
||||
|
@ -1,9 +0,0 @@
|
||||
import { isSSR } from '@utils/ssr';
|
||||
import { getAllRelays } from '@utils/storage';
|
||||
|
||||
import { atomWithCache } from 'jotai-cache';
|
||||
|
||||
export const relaysAtom = atomWithCache(async () => {
|
||||
const response = isSSR ? [] : await getAllRelays();
|
||||
return response;
|
||||
});
|
@ -8,8 +8,8 @@ declare global {
|
||||
|
||||
const invoke = window.__TAURI_INVOKE__;
|
||||
|
||||
export function getAccount() {
|
||||
return invoke<Account[]>('get_account');
|
||||
export function getAccounts() {
|
||||
return invoke<Account[]>('get_accounts');
|
||||
}
|
||||
|
||||
export function createAccount(data: CreateAccountData) {
|
||||
@ -28,15 +28,20 @@ export function createNote(data: CreateNoteData) {
|
||||
return invoke<Note>('create_note', { data });
|
||||
}
|
||||
|
||||
export function getNotes() {
|
||||
return invoke<Note[]>('get_notes');
|
||||
export function getNotes(data: GetNoteData) {
|
||||
return invoke<Note[]>('get_notes', { data });
|
||||
}
|
||||
|
||||
export function checkNote() {
|
||||
return invoke<Note[]>('check_note');
|
||||
export function getLatestNotes(data: GetLatestNoteData) {
|
||||
return invoke<Note[]>('get_latest_notes', { data });
|
||||
}
|
||||
|
||||
export function getNoteById(data: GetNoteByIdData) {
|
||||
return invoke<Note | null>('get_note_by_id', { data });
|
||||
}
|
||||
|
||||
export type GetFollowData = { account_id: number };
|
||||
export type GetNoteByIdData = { event_id: string };
|
||||
export type Note = {
|
||||
id: number;
|
||||
eventId: string;
|
||||
@ -46,9 +51,10 @@ export type Note = {
|
||||
content: string;
|
||||
parent_id: string;
|
||||
parent_comment_id: string;
|
||||
createdAt: string;
|
||||
createdAt: number;
|
||||
accountId: number;
|
||||
};
|
||||
export type GetNoteData = { date: number; limit: number; offset: number };
|
||||
export type CreateFollowData = { pubkey: string; kind: number; metadata: string; account_id: number };
|
||||
export type Account = { id: number; pubkey: string; privkey: string; active: boolean; metadata: string };
|
||||
export type CreateNoteData = {
|
||||
@ -59,7 +65,9 @@ export type CreateNoteData = {
|
||||
content: string;
|
||||
parent_id: string;
|
||||
parent_comment_id: string;
|
||||
created_at: number;
|
||||
account_id: number;
|
||||
};
|
||||
export type GetLatestNoteData = { date: number };
|
||||
export type CreateAccountData = { pubkey: string; privkey: string; metadata: string };
|
||||
export type Follow = { id: number; pubkey: string; kind: number; metadata: string; accountId: number };
|
||||
|
@ -45,3 +45,16 @@ export const getParentID = (arr, fallback) => {
|
||||
|
||||
return parentID;
|
||||
};
|
||||
|
||||
export const filteredData = (obj) => {
|
||||
const filteredArr = obj.reduce((item, current) => {
|
||||
const x = item.find((item) => item.parent_id === current.parent_id);
|
||||
if (!x) {
|
||||
return item.concat([current]);
|
||||
} else {
|
||||
return item;
|
||||
}
|
||||
}, []);
|
||||
|
||||
return filteredArr;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user