mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-10-09 03:18:04 +00:00
memories that show up are legit now
This commit is contained in:
parent
f38ef77f37
commit
db78e5b718
12 changed files with 166 additions and 332 deletions
|
|
@ -24,23 +24,25 @@ CREATE TABLE `session` (
|
|||
FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `spaces` (
|
||||
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
`name` text DEFAULT 'all' NOT NULL,
|
||||
`description` text(255)
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `storedContent` (
|
||||
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
`content` text NOT NULL,
|
||||
`title` text(255),
|
||||
`description` text(255),
|
||||
`url` text NOT NULL,
|
||||
`space` text(255),
|
||||
`space` text(255) DEFAULT 'all',
|
||||
`savedAt` integer NOT NULL,
|
||||
`baseUrl` text(255),
|
||||
`image` text(255)
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `userStoredContent` (
|
||||
`userId` text NOT NULL,
|
||||
`contentId` integer NOT NULL,
|
||||
FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action,
|
||||
FOREIGN KEY (`contentId`) REFERENCES `storedContent`(`id`) ON UPDATE no action ON DELETE no action
|
||||
`image` text(255),
|
||||
`user` text(255),
|
||||
FOREIGN KEY (`space`) REFERENCES `spaces`(`name`) ON UPDATE no action ON DELETE no action,
|
||||
FOREIGN KEY (`user`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `user` (
|
||||
|
|
@ -60,10 +62,9 @@ CREATE TABLE `verificationToken` (
|
|||
--> statement-breakpoint
|
||||
CREATE INDEX `account_userId_idx` ON `account` (`userId`);--> statement-breakpoint
|
||||
CREATE INDEX `session_userId_idx` ON `session` (`userId`);--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `storedContent_url_unique` ON `storedContent` (`url`);--> statement-breakpoint
|
||||
CREATE INDEX `spaces_name_idx` ON `spaces` (`name`);--> statement-breakpoint
|
||||
CREATE INDEX `storedContent_url_idx` ON `storedContent` (`url`);--> statement-breakpoint
|
||||
CREATE INDEX `storedContent_savedAt_idx` ON `storedContent` (`savedAt`);--> statement-breakpoint
|
||||
CREATE INDEX `storedContent_title_idx` ON `storedContent` (`title`);--> statement-breakpoint
|
||||
CREATE INDEX `storedContent_space_idx` ON `storedContent` (`space`);--> statement-breakpoint
|
||||
CREATE INDEX `userStoredContent_idx` ON `userStoredContent` (`userId`,`contentId`);--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `unique_user_content` ON `userStoredContent` (`userId`,`contentId`);
|
||||
CREATE INDEX `storedContent_user_idx` ON `storedContent` (`user`);
|
||||
|
|
@ -8,11 +8,7 @@ function MessagePoster({ jwt }: { jwt: string }) {
|
|||
window.postMessage({ jwt }, '*');
|
||||
}, [jwt]);
|
||||
|
||||
return (
|
||||
<button onClick={() => window.postMessage({ jwt }, '*')}>
|
||||
Send auth token to extension
|
||||
</button>
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
export default MessagePoster;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { db } from "@/server/db";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { sessions, storedContent, userStoredContent, users } from "@/server/db/schema";
|
||||
import { sessions, storedContent, users } from "@/server/db/schema";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { env } from "@/env";
|
||||
import { getMetaData } from "@/server/helpers";
|
||||
|
|
@ -38,32 +38,19 @@ export async function POST(req: NextRequest) {
|
|||
|
||||
let id: number | undefined = undefined;
|
||||
|
||||
const storedCont = await db.select().from(storedContent).where(eq(storedContent.url, data.url)).limit(1)
|
||||
const storedContentId = await db.insert(storedContent).values({
|
||||
content: data.pageContent,
|
||||
title: metadata.title,
|
||||
description: metadata.description,
|
||||
url: data.url,
|
||||
baseUrl: metadata.baseUrl,
|
||||
image: metadata.image,
|
||||
savedAt: new Date(),
|
||||
space: "all",
|
||||
user: session.user.id
|
||||
})
|
||||
|
||||
if (storedCont.length > 0) {
|
||||
id = storedCont[0].id;
|
||||
} else {
|
||||
const storedContentId = await db.insert(storedContent).values({
|
||||
content: data.pageContent,
|
||||
title: metadata.title,
|
||||
description: metadata.description,
|
||||
url: data.url,
|
||||
baseUrl: metadata.baseUrl,
|
||||
image: metadata.image,
|
||||
savedAt: new Date()
|
||||
})
|
||||
|
||||
id = storedContentId.meta.last_row_id;
|
||||
}
|
||||
|
||||
try {
|
||||
await db.insert(userStoredContent).values({
|
||||
userId: session.user.id,
|
||||
contentId: id
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
id = storedContentId.meta.last_row_id;
|
||||
|
||||
const res = await Promise.race([
|
||||
fetch("https://cf-ai-backend.dhravya.workers.dev/add", {
|
||||
|
|
@ -78,10 +65,6 @@ export async function POST(req: NextRequest) {
|
|||
)
|
||||
]) as Response
|
||||
|
||||
const _ = await res.text();
|
||||
|
||||
console.log(_)
|
||||
|
||||
if (res.status !== 200) {
|
||||
return NextResponse.json({ message: "Error", error: "Error in CF function" }, { status: 500 });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,12 @@
|
|||
import { CardContent, Card } from '@/components/ui/card';
|
||||
import { db } from '@/server/db';
|
||||
import {
|
||||
sessions,
|
||||
storedContent,
|
||||
userStoredContent,
|
||||
users,
|
||||
} from '@/server/db/schema';
|
||||
import { sessions, storedContent, users } from '@/server/db/schema';
|
||||
import { eq, inArray } from 'drizzle-orm';
|
||||
import { cookies, headers } from 'next/headers';
|
||||
import { redirect } from 'next/navigation';
|
||||
import Image from 'next/image';
|
||||
import QueryAI from '@/components/QueryAI';
|
||||
import Sidebar from '@/components/Sidebar/index';
|
||||
import Main from '@/components/Main';
|
||||
import MessagePoster from './MessagePoster';
|
||||
import { transformContent } from '../../types/memory';
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
|
|
@ -26,6 +21,13 @@ export default async function Home() {
|
|||
return redirect('/api/auth/signin');
|
||||
}
|
||||
|
||||
const selectedItem = cookies().get('selectedItem')?.value;
|
||||
|
||||
const setSelectedItem = async (selectedItem: string | null) => {
|
||||
'use server';
|
||||
cookies().set('selectedItem', selectedItem!);
|
||||
};
|
||||
|
||||
const session = await db
|
||||
.select()
|
||||
.from(sessions)
|
||||
|
|
@ -35,101 +37,28 @@ export default async function Home() {
|
|||
return redirect('/api/auth/signin');
|
||||
}
|
||||
|
||||
const userContent = await db
|
||||
.select()
|
||||
.from(userStoredContent)
|
||||
.where(eq(userStoredContent.userId, session[0].userId));
|
||||
|
||||
const userData = await db
|
||||
const [userData] = await db
|
||||
.select()
|
||||
.from(users)
|
||||
.where(eq(users.id, session[0].userId))
|
||||
.limit(1);
|
||||
|
||||
if (!userData || userData.length === 0) {
|
||||
if (!userData) {
|
||||
return redirect('/api/auth/signin');
|
||||
}
|
||||
|
||||
const listOfContent =
|
||||
userContent.map((content) => content.contentId).length > 0
|
||||
? userContent.map((content) => content.contentId)
|
||||
: [1];
|
||||
|
||||
const posts = await db
|
||||
.select()
|
||||
.from(storedContent)
|
||||
.where(inArray(storedContent.id, listOfContent));
|
||||
.where(eq(storedContent.user, userData.id));
|
||||
|
||||
const collectedSpaces = transformContent(posts);
|
||||
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex flex-col items-center">
|
||||
<div className="flex flex-col mt-16">
|
||||
<div className="flex flex-col md:flex-row gap-4">
|
||||
<Image
|
||||
className="rounded-2xl"
|
||||
src="/logo.png"
|
||||
width={120}
|
||||
height={120}
|
||||
alt="logo"
|
||||
/>
|
||||
<div className="mt-4 text-gray-400 max-w-md">
|
||||
<h1 className="text-xl font-bold text-white">SuperMemory</h1>
|
||||
Remember that one thing you read a while ago? We got you covered.
|
||||
Add the extension, click a button and I'll remember it for you.{' '}
|
||||
<a
|
||||
href="https://github.com/dhravyashah/anycontext"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-sky-500"
|
||||
>
|
||||
Get the Extension
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<QueryAI />
|
||||
|
||||
<div className="flex w-screen">
|
||||
<Sidebar selectChange={setSelectedItem} spaces={collectedSpaces} />
|
||||
<Main sidebarOpen={selectedItem !== null} />
|
||||
<MessagePoster jwt={token} />
|
||||
|
||||
{/* TODO: LABEL THE WEBSITES USING A CLASSIFICATION MODEL */}
|
||||
{/* <nav className="flex space-x-2 my-4">
|
||||
<Badge variant="secondary">Technology (2)</Badge>
|
||||
<Badge variant="secondary">Business & Finance (1)</Badge>
|
||||
<Badge variant="secondary">Education & Career (1)</Badge>
|
||||
</nav> */}
|
||||
<main className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mt-16">
|
||||
{posts.reverse().map((post) => (
|
||||
<a
|
||||
href={post.url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="hover:scale-105 ease-in-out transition-transform duration-300"
|
||||
>
|
||||
<Card className="w-full">
|
||||
<img
|
||||
alt="Not found"
|
||||
className="w-full h-48 object-cover rounded-md"
|
||||
height="200"
|
||||
src={
|
||||
post.image && post.image !== 'Image not found'
|
||||
? post.image
|
||||
: '/placeholder.svg'
|
||||
}
|
||||
style={{
|
||||
aspectRatio: '300/200',
|
||||
objectFit: 'cover',
|
||||
}}
|
||||
width="300"
|
||||
/>
|
||||
<CardContent>
|
||||
<h3 className="text-lg font-semibold mt-4">{post.title}</h3>
|
||||
<p className="text-sm text-gray-600">{post.baseUrl}</p>
|
||||
<p className="text-sm">{post.description}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</a>
|
||||
))}
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export default function Home() {
|
|||
|
||||
return (
|
||||
<div className="flex w-screen">
|
||||
<Sidebar selectChange={setSelectedItem} />
|
||||
{/* <Sidebar selectChange={setSelectedItem} spaces={spaces} /> */}
|
||||
<Main sidebarOpen={selectedItem !== null} />
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,22 +1,22 @@
|
|||
"use client";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { FilterCombobox } from "./Sidebar/FilterCombobox";
|
||||
import { Textarea2 } from "./ui/textarea";
|
||||
import { ArrowRight } from "lucide-react";
|
||||
import { MemoryDrawer } from "./MemoryDrawer";
|
||||
import useViewport from "@/hooks/useViewport";
|
||||
import { motion } from "framer-motion";
|
||||
import { cn } from "@/lib/utils";
|
||||
'use client';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { FilterCombobox } from './Sidebar/FilterCombobox';
|
||||
import { Textarea2 } from './ui/textarea';
|
||||
import { ArrowRight } from 'lucide-react';
|
||||
import { MemoryDrawer } from './MemoryDrawer';
|
||||
import useViewport from '@/hooks/useViewport';
|
||||
import { motion } from 'framer-motion';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
|
||||
const [hide, setHide] = useState(false);
|
||||
const [value, setValue] = useState("");
|
||||
const [value, setValue] = useState('');
|
||||
const { width } = useViewport();
|
||||
|
||||
const textArea = useRef<HTMLTextAreaElement>(null);
|
||||
const main = useRef<HTMLDivElement>(null);
|
||||
|
||||
console.log("main px", sidebarOpen);
|
||||
console.log('main px', sidebarOpen);
|
||||
|
||||
useEffect(() => {
|
||||
function onResize() {
|
||||
|
|
@ -41,9 +41,9 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
|
|||
}
|
||||
}
|
||||
|
||||
window.visualViewport?.addEventListener("resize", onResize);
|
||||
window.visualViewport?.addEventListener('resize', onResize);
|
||||
return () => {
|
||||
window.visualViewport?.removeEventListener("resize", onResize);
|
||||
window.visualViewport?.removeEventListener('resize', onResize);
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
|
@ -54,10 +54,10 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
|
|||
className={cn(
|
||||
"sidebar flex w-full flex-col items-end justify-center gap-5 px-5 pt-5 transition-[padding-left,padding-top,padding-right] delay-200 duration-200 md:items-center md:px-72 [&[data-sidebar-open='true']]:pr-10 [&[data-sidebar-open='true']]:delay-0 md:[&[data-sidebar-open='true']]:pl-[calc(2.5rem+30vw)]",
|
||||
hide
|
||||
? "pb-5"
|
||||
: CSS.supports("height: 100dvh")
|
||||
? "pb-[13vh]"
|
||||
: "pb-[20vh]",
|
||||
? 'pb-5'
|
||||
: CSS.supports('height: 100dvh')
|
||||
? 'pb-[13vh]'
|
||||
: 'pb-[20vh]',
|
||||
)}
|
||||
>
|
||||
<h1 className="text-rgray-11 mt-auto w-full text-center text-3xl">
|
||||
|
|
@ -67,9 +67,9 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
|
|||
ref={textArea}
|
||||
className="mt-auto h-max max-h-[30em] min-h-[3em] resize-y flex-row items-start justify-center overflow-auto py-5 md:h-[20vh] md:resize-none md:flex-col md:items-center md:justify-center md:p-2 md:pb-2 md:pt-2"
|
||||
textAreaProps={{
|
||||
placeholder: "Ask your SuperMemory...",
|
||||
placeholder: 'Ask your SuperMemory...',
|
||||
className:
|
||||
"h-auto overflow-auto md:h-full md:resize-none text-lg py-0 px-2 md:py-0 md:p-5 resize-y text-rgray-11 w-full min-h-[1em]",
|
||||
'h-auto overflow-auto md:h-full md:resize-none text-lg py-0 px-2 md:py-0 md:p-5 resize-y text-rgray-11 w-full min-h-[1em]',
|
||||
value,
|
||||
autoFocus: true,
|
||||
onChange: (e) => setValue(e.target.value),
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import { useAutoAnimate } from '@formkit/auto-animate/react';
|
||||
import {
|
||||
MemoryWithImage,
|
||||
MemoryWithImages3,
|
||||
MemoryWithImages2,
|
||||
} from "@/assets/MemoryWithImages";
|
||||
import { type Space } from "../../../types/memory";
|
||||
import { InputWithIcon } from "../ui/input";
|
||||
} from '@/assets/MemoryWithImages';
|
||||
import { type CollectedSpaces } from '../../../types/memory';
|
||||
import { InputWithIcon } from '../ui/input';
|
||||
import {
|
||||
ArrowUpRight,
|
||||
Edit3,
|
||||
MoreHorizontal,
|
||||
Search,
|
||||
Trash2,
|
||||
} from "lucide-react";
|
||||
} from 'lucide-react';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "../ui/dropdown-menu";
|
||||
} from '../ui/dropdown-menu';
|
||||
import {
|
||||
animate,
|
||||
AnimatePresence,
|
||||
|
|
@ -26,105 +26,15 @@ import {
|
|||
motion,
|
||||
useAnimate,
|
||||
Variant,
|
||||
} from "framer-motion";
|
||||
import { useRef, useState } from "react";
|
||||
} from 'framer-motion';
|
||||
import { useRef, useState } from 'react';
|
||||
|
||||
const spaces: Space[] = [
|
||||
{
|
||||
id: 1,
|
||||
title: "Cool Tech",
|
||||
description: "Really cool mind blowing tech",
|
||||
content: [
|
||||
{
|
||||
id: 1,
|
||||
title: "Perplexity",
|
||||
description: "A good ui",
|
||||
content: "",
|
||||
image: "https://perplexity.ai/favicon.ico",
|
||||
url: "https://perplexity.ai",
|
||||
savedAt: new Date(),
|
||||
baseUrl: "https://perplexity.ai",
|
||||
space: "Cool tech",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "Pi.ai",
|
||||
description: "A good ui",
|
||||
content: "",
|
||||
image: "https://pi.ai/pi-logo-192.png?v=2",
|
||||
url: "https://pi.ai",
|
||||
savedAt: new Date(),
|
||||
baseUrl: "https://pi.ai",
|
||||
space: "Cool tech",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "Visual Studio Code",
|
||||
description: "A good ui",
|
||||
content: "",
|
||||
image: "https://code.visualstudio.com/favicon.ico",
|
||||
url: "https://code.visualstudio.com",
|
||||
savedAt: new Date(),
|
||||
baseUrl: "https://code.visualstudio.com",
|
||||
space: "Cool tech",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "Cool Courses",
|
||||
description: "Amazng",
|
||||
content: [
|
||||
{
|
||||
id: 1,
|
||||
title: "Animation on the web",
|
||||
description: "A good ui",
|
||||
content: "",
|
||||
image: "https://animations.dev/favicon.ico",
|
||||
url: "https://animations.dev",
|
||||
savedAt: new Date(),
|
||||
baseUrl: "https://animations.dev",
|
||||
space: "Cool courses",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "Tailwind Course",
|
||||
description: "A good ui",
|
||||
content: "",
|
||||
image:
|
||||
"https://tailwindcss.com/_next/static/media/tailwindcss-mark.3c5441fc7a190fb1800d4a5c7f07ba4b1345a9c8.svg",
|
||||
url: "https://tailwindcss.com",
|
||||
savedAt: new Date(),
|
||||
baseUrl: "https://tailwindcss.com",
|
||||
space: "Cool courses",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "Cool Libraries",
|
||||
description: "Really cool mind blowing tech",
|
||||
content: [
|
||||
{
|
||||
id: 1,
|
||||
title: "Perplexity",
|
||||
description: "A good ui",
|
||||
content: "",
|
||||
image: "https://yashverma.me/logo.jpg",
|
||||
url: "https://perplexity.ai",
|
||||
savedAt: new Date(),
|
||||
baseUrl: "https://perplexity.ai",
|
||||
space: "Cool libraries",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export function MemoriesBar() {
|
||||
export function MemoriesBar({ spaces }: { spaces: CollectedSpaces[] }) {
|
||||
const [parent, enableAnimations] = useAutoAnimate();
|
||||
const [currentSpaces, setCurrentSpaces] = useState(spaces);
|
||||
|
||||
console.log("currentSpaces: ", currentSpaces);
|
||||
console.log('currentSpaces: ', currentSpaces);
|
||||
|
||||
return (
|
||||
<div className="text-rgray-11 flex w-full flex-col items-start py-8 text-left">
|
||||
|
|
@ -157,8 +67,8 @@ export function MemoriesBar() {
|
|||
const SpaceExitVariant: Variant = {
|
||||
opacity: 0,
|
||||
scale: 0,
|
||||
borderRadius: "50%",
|
||||
background: "var(--gray-1)",
|
||||
borderRadius: '50%',
|
||||
background: 'var(--gray-1)',
|
||||
transition: {
|
||||
duration: 0.2,
|
||||
},
|
||||
|
|
@ -170,7 +80,7 @@ export function SpaceItem({
|
|||
content,
|
||||
id,
|
||||
onDelete,
|
||||
}: Space & { onDelete: () => void }) {
|
||||
}: CollectedSpaces & { onDelete: () => void }) {
|
||||
const [itemRef, animateItem] = useAnimate();
|
||||
|
||||
return (
|
||||
|
|
@ -184,22 +94,22 @@ export function SpaceItem({
|
|||
<SpaceMoreButton
|
||||
onDelete={() => {
|
||||
if (!itemRef.current) return;
|
||||
const trash = document.querySelector("#trash")! as HTMLDivElement;
|
||||
const trashBin = document.querySelector("#trash-button")!;
|
||||
const trash = document.querySelector('#trash')! as HTMLDivElement;
|
||||
const trashBin = document.querySelector('#trash-button')!;
|
||||
const trashRect = trashBin.getBoundingClientRect();
|
||||
const scopeRect = itemRef.current.getBoundingClientRect();
|
||||
const el = document.createElement("div");
|
||||
el.style.position = "fixed";
|
||||
el.style.top = "0";
|
||||
el.style.left = "0";
|
||||
el.style.width = "15px";
|
||||
el.style.height = "15px";
|
||||
el.style.backgroundColor = "var(--gray-7)";
|
||||
el.style.zIndex = "60";
|
||||
el.style.borderRadius = "50%";
|
||||
el.style.transform = "scale(5)";
|
||||
el.style.opacity = "0";
|
||||
trash.dataset["open"] = "true";
|
||||
const el = document.createElement('div');
|
||||
el.style.position = 'fixed';
|
||||
el.style.top = '0';
|
||||
el.style.left = '0';
|
||||
el.style.width = '15px';
|
||||
el.style.height = '15px';
|
||||
el.style.backgroundColor = 'var(--gray-7)';
|
||||
el.style.zIndex = '60';
|
||||
el.style.borderRadius = '50%';
|
||||
el.style.transform = 'scale(5)';
|
||||
el.style.opacity = '0';
|
||||
trash.dataset['open'] = 'true';
|
||||
const initial = {
|
||||
x: scopeRect.left + scopeRect.width / 2,
|
||||
y: scopeRect.top + scopeRect.height / 2,
|
||||
|
|
@ -224,35 +134,35 @@ export function SpaceItem({
|
|||
animateItem(itemRef.current, SpaceExitVariant, {
|
||||
duration: 0.2,
|
||||
}).then(() => {
|
||||
itemRef.current.style.scale = "0";
|
||||
itemRef.current.style.scale = '0';
|
||||
onDelete();
|
||||
});
|
||||
document.body.appendChild(el);
|
||||
el.animate(
|
||||
{
|
||||
transform: ["scale(5)", "scale(1)"],
|
||||
transform: ['scale(5)', 'scale(1)'],
|
||||
opacity: [0, 0.3, 1],
|
||||
},
|
||||
{
|
||||
duration: 200,
|
||||
easing: "cubic-bezier(0.64, 0.57, 0.67, 1.53)",
|
||||
fill: "forwards",
|
||||
easing: 'cubic-bezier(0.64, 0.57, 0.67, 1.53)',
|
||||
fill: 'forwards',
|
||||
},
|
||||
);
|
||||
el.animate(
|
||||
{
|
||||
offsetDistance: ["0%", "100%"],
|
||||
offsetDistance: ['0%', '100%'],
|
||||
},
|
||||
{
|
||||
duration: 2000,
|
||||
easing: "cubic-bezier(0.64, 0.57, 0.67, 1.53)",
|
||||
fill: "forwards",
|
||||
easing: 'cubic-bezier(0.64, 0.57, 0.67, 1.53)',
|
||||
fill: 'forwards',
|
||||
delay: 200,
|
||||
},
|
||||
).onfinish = () => {
|
||||
el.animate(
|
||||
{ transform: "scale(0)", opacity: 0 },
|
||||
{ duration: 200, fill: "forwards" },
|
||||
{ transform: 'scale(0)', opacity: 0 },
|
||||
{ duration: 200, fill: 'forwards' },
|
||||
).onfinish = () => {
|
||||
el.remove();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,25 +2,18 @@
|
|||
import { StoredContent } from '@/server/db/schema';
|
||||
import { MemoryIcon } from '../../assets/Memories';
|
||||
import { Trash2, User2 } from 'lucide-react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { ElementType, useEffect, useState } from 'react';
|
||||
import { MemoriesBar } from './MemoriesBar';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { Bin } from '@/assets/Bin';
|
||||
import { CollectedSpaces } from '../../../types/memory';
|
||||
|
||||
export type MenuItem = {
|
||||
icon: React.ReactNode | React.ReactNode[];
|
||||
label: string;
|
||||
content?: React.FC;
|
||||
content?: React.ReactElement;
|
||||
};
|
||||
|
||||
const menuItemsTop: Array<MenuItem> = [
|
||||
{
|
||||
icon: <MemoryIcon className="h-10 w-10" />,
|
||||
label: 'Memories',
|
||||
content: MemoriesBar,
|
||||
},
|
||||
];
|
||||
|
||||
const menuItemsBottom: Array<MenuItem> = [
|
||||
{
|
||||
icon: <Trash2 strokeWidth={1.3} className="h-6 w-6" />,
|
||||
|
|
@ -34,9 +27,18 @@ const menuItemsBottom: Array<MenuItem> = [
|
|||
|
||||
export default function Sidebar({
|
||||
selectChange,
|
||||
spaces,
|
||||
}: {
|
||||
selectChange?: (selectedItem: string | null) => Promise<void>;
|
||||
spaces: CollectedSpaces[];
|
||||
}) {
|
||||
const menuItemsTop: Array<MenuItem> = [
|
||||
{
|
||||
icon: <MemoryIcon className="h-10 w-10" />,
|
||||
label: 'Memories',
|
||||
content: <MemoriesBar spaces={spaces} />,
|
||||
},
|
||||
];
|
||||
const menuItems = [...menuItemsTop, ...menuItemsBottom];
|
||||
const [selectedItem, setSelectedItem] = useState<string | null>(null);
|
||||
|
||||
|
|
@ -55,7 +57,7 @@ export default function Sidebar({
|
|||
item={{
|
||||
label: 'Memories',
|
||||
icon: <MemoryIcon className="h-10 w-10" />,
|
||||
content: MemoriesBar,
|
||||
content: <MemoriesBar spaces={spaces} />,
|
||||
}}
|
||||
selectedItem={selectedItem}
|
||||
setSelectedItem={setSelectedItem}
|
||||
|
|
@ -82,11 +84,11 @@ export default function Sidebar({
|
|||
/>
|
||||
</div>
|
||||
<AnimatePresence>
|
||||
{selectedItem && (
|
||||
<SubSidebar>
|
||||
<Subbar />
|
||||
</SubSidebar>
|
||||
)}
|
||||
{/* @yxshv idk why this is giving typeerror
|
||||
when used as <Subbar/> it says it's not valid element type
|
||||
*/}
|
||||
{/* @ts-ignore */}
|
||||
{selectedItem && <SubSidebar>{Subbar}</SubSidebar>}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
|||
12
apps/web/src/lib/searchParams.ts
Normal file
12
apps/web/src/lib/searchParams.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import {
|
||||
createSearchParamsCache,
|
||||
parseAsInteger,
|
||||
parseAsString
|
||||
} from 'nuqs/server'
|
||||
// Note: import from 'nuqs/server' to avoid the "use client" directive
|
||||
|
||||
export const searchParamsCache = createSearchParamsCache({
|
||||
// List your search param keys and associated parsers here:
|
||||
q: parseAsString.withDefault(''),
|
||||
maxResults: parseAsInteger.withDefault(10)
|
||||
})
|
||||
|
|
@ -6,15 +6,9 @@ import {
|
|||
sqliteTableCreator,
|
||||
text,
|
||||
integer,
|
||||
unique,
|
||||
unique
|
||||
} from "drizzle-orm/sqlite-core";
|
||||
|
||||
/**
|
||||
* This is an example of how to use the multi-project schema feature of Drizzle ORM. Use the same
|
||||
* database instance for multiple projects.
|
||||
*
|
||||
* @see https://orm.drizzle.team/docs/goodies#multi-project-schema
|
||||
*/
|
||||
export const createTable = sqliteTableCreator((name) => `${name}`);
|
||||
|
||||
export const users = createTable("user", {
|
||||
|
|
@ -84,27 +78,6 @@ export const verificationTokens = createTable(
|
|||
}),
|
||||
);
|
||||
|
||||
export const userStoredContent = createTable(
|
||||
"userStoredContent",
|
||||
{
|
||||
userId: text("userId")
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
contentId: integer("contentId")
|
||||
.notNull()
|
||||
.references(() => storedContent.id),
|
||||
},
|
||||
(usc) => ({
|
||||
userContentIdx: index("userStoredContent_idx").on(
|
||||
usc.userId,
|
||||
usc.contentId,
|
||||
),
|
||||
uniqueUserContent: unique("unique_user_content").on(
|
||||
usc.userId,
|
||||
usc.contentId,
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
export const storedContent = createTable(
|
||||
"storedContent",
|
||||
|
|
@ -113,18 +86,32 @@ export const storedContent = createTable(
|
|||
content: text("content").notNull(),
|
||||
title: text("title", { length: 255 }),
|
||||
description: text("description", { length: 255 }),
|
||||
url: text("url").notNull().unique(),
|
||||
space: text("space", { length: 255 }),
|
||||
url: text("url").notNull(),
|
||||
space: text("space", { length: 255 }).references(() => spaces.name).default('all'),
|
||||
savedAt: int("savedAt", { mode: "timestamp" }).notNull(),
|
||||
baseUrl: text("baseUrl", { length: 255 }),
|
||||
image: text("image", { length: 255 }),
|
||||
user: text("user", { length: 255 }).references(() => users.id),
|
||||
},
|
||||
(sc) => ({
|
||||
urlIdx: index("storedContent_url_idx").on(sc.url),
|
||||
savedAtIdx: index("storedContent_savedAt_idx").on(sc.savedAt),
|
||||
titleInx: index("storedContent_title_idx").on(sc.title),
|
||||
spaceIdx: index("storedContent_space_idx").on(sc.space),
|
||||
userIdx: index("storedContent_user_idx").on(sc.user),
|
||||
}),
|
||||
);
|
||||
|
||||
export type StoredContent = typeof storedContent.$inferSelect;
|
||||
export const spaces = createTable(
|
||||
"spaces",
|
||||
{
|
||||
id: integer("id").notNull().primaryKey({ autoIncrement: true }),
|
||||
name: text('name').notNull().default('all'),
|
||||
description: text("description", { length: 255 }),
|
||||
},
|
||||
(space) => ({
|
||||
nameIdx: index("spaces_name_idx").on(space.name),
|
||||
}),
|
||||
);
|
||||
|
||||
export type StoredContent = Omit<typeof storedContent.$inferSelect, 'user'>
|
||||
|
|
@ -1,6 +1,19 @@
|
|||
import { StoredContent } from "@/server/db/schema";
|
||||
import { StoredContent } from '@/server/db/schema';
|
||||
|
||||
export type Space = {
|
||||
export const transformContent = (content: StoredContent[]): CollectedSpaces[] => {
|
||||
const spaces = Array.from(new Set(content.map((c) => c.space)));
|
||||
|
||||
const spaceContent = spaces.map((space, i) => ({
|
||||
title: space!,
|
||||
id: i + 1,
|
||||
description: '',
|
||||
content: content.filter((c) => c.space === space),
|
||||
}));
|
||||
|
||||
return spaceContent;
|
||||
};
|
||||
|
||||
export type CollectedSpaces = {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@
|
|||
"html-metadata-parser": "^2.0.4",
|
||||
"lucide-react": "^0.343.0",
|
||||
"next-auth": "beta",
|
||||
"nuqs": "^1.17.1",
|
||||
"react-markdown": "^9.0.1",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"tailwindcss-animate": "^1.0.7"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue