aboutsummaryrefslogtreecommitdiff
path: root/apps/web/src
diff options
context:
space:
mode:
authoryxshv <[email protected]>2024-04-14 00:32:04 +0530
committeryxshv <[email protected]>2024-04-14 00:32:04 +0530
commite0461696fc9732c240f48d5be6e824a1d5fced44 (patch)
tree92e3e91099119db07398b1419f37d144c1d78ccc /apps/web/src
parentfix relaod bug (diff)
parentfix build fail (diff)
downloadsupermemory-e0461696fc9732c240f48d5be6e824a1d5fced44.tar.xz
supermemory-e0461696fc9732c240f48d5be6e824a1d5fced44.zip
Merge branch 'main' of https://github.com/dhravya/supermemory
Diffstat (limited to 'apps/web/src')
-rw-r--r--apps/web/src/actions/db.ts352
-rw-r--r--apps/web/src/app/api/spaces/route.ts23
-rw-r--r--apps/web/src/app/page.tsx35
-rw-r--r--apps/web/src/app/privacy/page.tsx2
-rw-r--r--apps/web/src/components/Main.tsx5
-rw-r--r--apps/web/src/components/Sidebar/AddMemoryDialog.tsx267
-rw-r--r--apps/web/src/components/Sidebar/FilterCombobox.tsx151
-rw-r--r--apps/web/src/components/Sidebar/MemoriesBar.tsx405
-rw-r--r--apps/web/src/components/ui/command.tsx13
-rw-r--r--apps/web/src/contexts/MemoryContext.tsx170
-rw-r--r--apps/web/src/hooks/useDebounce.ts2
-rw-r--r--apps/web/src/server/db/schema.ts22
-rw-r--r--apps/web/src/server/db/test.ts12
-rw-r--r--apps/web/src/server/helpers.ts36
14 files changed, 793 insertions, 702 deletions
diff --git a/apps/web/src/actions/db.ts b/apps/web/src/actions/db.ts
index 66fbc830..cd54c1bd 100644
--- a/apps/web/src/actions/db.ts
+++ b/apps/web/src/actions/db.ts
@@ -6,66 +6,79 @@ import {
sessions,
StoredContent,
storedContent,
- StoredSpace,
+ StoredSpace,
users,
- space,
+ space,
} from "@/server/db/schema";
import { SearchResult } from "@/contexts/MemoryContext";
import { like, eq, and, sql, exists, asc, notExists } from "drizzle-orm";
-import { union } from "drizzle-orm/sqlite-core"
+import { union } from "drizzle-orm/sqlite-core";
// @todo: (future) pagination not yet needed
-export async function searchMemoriesAndSpaces(query: string, opts?: { filter?: { memories?: boolean, spaces?: boolean }, range?: { offset: number, limit: number } }): Promise<SearchResult[]> {
-
- const user = await getUser()
-
- if (!user) {
- return []
- }
-
- try {
- const searchMemoriesQuery = db.select({
- type: sql<string>`'memory'`,
- space: sql`NULL`,
- memory: storedContent as any
- }).from(storedContent).where(and(
- eq(storedContent.user, user.id),
- like(storedContent.title, `%${query}%`)
- )).orderBy(asc(storedContent.savedAt));
-
- const searchSpacesQuery = db.select({
- type: sql<string>`'space'`,
- space: space as any,
- memory: sql`NULL`,
- }).from(space).where(
- and(
- eq(space.user, user.id),
- like(space.name, `%${query}%`)
- )
- ).orderBy(asc(space.name));
-
- let queries = [];
-
- console.log('adding');
-
- [undefined, true].includes(opts?.filter?.memories) && queries.push(searchMemoriesQuery);
- [undefined, true].includes(opts?.filter?.spaces) && queries.push(searchSpacesQuery);
-
- if (opts?.range) {
- queries = queries.map(q => q.offset(opts.range!.offset).limit(opts.range!.limit))
- } else {
- queries = queries.map(q => q.all())
- }
-
- const data = await Promise.all(queries)
-
- console.log('resp', data)
-
- return data.reduce((acc, i) => [...acc, ...i]) as SearchResult[]
- } catch {
- return []
- }
+export async function searchMemoriesAndSpaces(
+ query: string,
+ opts?: {
+ filter?: { memories?: boolean; spaces?: boolean };
+ range?: { offset: number; limit: number };
+ },
+): Promise<SearchResult[]> {
+ const user = await getUser();
+
+ if (!user) {
+ return [];
+ }
+ try {
+ const searchMemoriesQuery = db
+ .select({
+ type: sql<string>`'memory'`,
+ space: sql`NULL`,
+ memory: storedContent as any,
+ })
+ .from(storedContent)
+ .where(
+ and(
+ eq(storedContent.user, user.id),
+ like(storedContent.title, `%${query}%`),
+ ),
+ )
+ .orderBy(asc(storedContent.savedAt));
+
+ const searchSpacesQuery = db
+ .select({
+ type: sql<string>`'space'`,
+ space: space as any,
+ memory: sql`NULL`,
+ })
+ .from(space)
+ .where(and(eq(space.user, user.id), like(space.name, `%${query}%`)))
+ .orderBy(asc(space.name));
+
+ let queries = [];
+
+ console.log("adding");
+
+ [undefined, true].includes(opts?.filter?.memories) &&
+ queries.push(searchMemoriesQuery);
+ [undefined, true].includes(opts?.filter?.spaces) &&
+ queries.push(searchSpacesQuery);
+
+ if (opts?.range) {
+ queries = queries.map((q) =>
+ q.offset(opts.range!.offset).limit(opts.range!.limit),
+ );
+ } else {
+ queries = queries.map((q) => q.all());
+ }
+
+ const data = await Promise.all(queries);
+
+ console.log("resp", data);
+
+ return data.reduce((acc, i) => [...acc, ...i]) as SearchResult[];
+ } catch {
+ return [];
+ }
}
async function getUser() {
@@ -76,7 +89,7 @@ async function getUser() {
headers().get("Authorization")?.replace("Bearer ", "");
if (!token) {
- return null
+ return null;
}
const session = await db
@@ -85,7 +98,7 @@ async function getUser() {
.where(eq(sessions.sessionToken, token!));
if (!session || session.length === 0) {
- return null
+ return null;
}
const [userData] = await db
@@ -95,17 +108,17 @@ async function getUser() {
.limit(1);
if (!userData) {
- return null
+ return null;
}
- return userData
+ return userData;
}
export async function getMemory(title: string) {
const user = await getUser();
if (!user) {
- return null
+ return null;
}
return await db
@@ -119,34 +132,39 @@ export async function getMemory(title: string) {
);
}
-
export async function addSpace(name: string, memories: number[]) {
+ const user = await getUser();
- const user = await getUser();
-
- if (!user) {
- return null
- }
-
- const [addedSpace] = await db
- .insert(space)
- .values({
- name: name,
- user: user.id
- }).returning();
-
- const addedMemories = memories.length > 0 ? await db.insert(contentToSpace)
- .values(memories.map(m => ({
- contentId: m,
- spaceId: addedSpace.id
- }))).returning() : []
-
- return {
- space: addedSpace,
- addedMemories
- }
-}
+ if (!user) {
+ return null;
+ }
+ const [addedSpace] = await db
+ .insert(space)
+ .values({
+ name: name,
+ user: user.id,
+ })
+ .returning();
+
+ const addedMemories =
+ memories.length > 0
+ ? await db
+ .insert(contentToSpace)
+ .values(
+ memories.map((m) => ({
+ contentId: m,
+ spaceId: addedSpace.id,
+ })),
+ )
+ .returning()
+ : [];
+
+ return {
+ space: addedSpace,
+ addedMemories,
+ };
+}
export async function fetchContentForSpace(
spaceId: number,
@@ -155,115 +173,127 @@ export async function fetchContentForSpace(
limit: number;
},
) {
-
const query = db
.select()
.from(storedContent)
.where(
exists(
- db.select().from(contentToSpace).where(and(eq(contentToSpace.spaceId, spaceId), eq(contentToSpace.contentId, storedContent.id))),
+ db
+ .select()
+ .from(contentToSpace)
+ .where(
+ and(
+ eq(contentToSpace.spaceId, spaceId),
+ eq(contentToSpace.contentId, storedContent.id),
+ ),
+ ),
),
- ).orderBy(asc(storedContent.savedAt))
+ )
+ .orderBy(asc(storedContent.savedAt));
- return range ? await query.limit(range.limit).offset(range.offset) : await query.all()
+ return range
+ ? await query.limit(range.limit).offset(range.offset)
+ : await query.all();
}
-export async function fetchFreeMemories(
- range?: {
- offset: number;
- limit: number;
- }
-) {
-
- const user = await getUser()
+export async function fetchFreeMemories(range?: {
+ offset: number;
+ limit: number;
+}) {
+ const user = await getUser();
- if (!user) {
- return []
- }
+ if (!user) {
+ return [];
+ }
- const query = db
+ const query = db
.select()
.from(storedContent)
.where(
- and(
- notExists(
- db.select().from(contentToSpace).where(eq(contentToSpace.contentId, storedContent.id)),
- ),
- eq(storedContent.user, user.id),
- )
-
- ).orderBy(asc(storedContent.savedAt))
-
- return range ? await query.limit(range.limit).offset(range.offset) : await query.all()
+ and(
+ notExists(
+ db
+ .select()
+ .from(contentToSpace)
+ .where(eq(contentToSpace.contentId, storedContent.id)),
+ ),
+ eq(storedContent.user, user.id),
+ ),
+ )
+ .orderBy(asc(storedContent.savedAt));
+ return range
+ ? await query.limit(range.limit).offset(range.offset)
+ : await query.all();
}
-export async function addMemory(content: typeof storedContent.$inferInsert, spaces: number[]) {
-
- const user = await getUser()
-
- if (!user) {
- return null
- }
-
- const [addedMemory] = await db.insert(storedContent)
- .values({
- user: user.id,
- ...content
- })
- .returning();
-
- const addedToSpaces = spaces.length > 0 ? await db.insert(contentToSpace)
- .values(spaces.map(s => ({
- contentId: addedMemory.id,
- spaceId: s,
- })))
- .returning() : [];
+export async function addMemory(
+ content: typeof storedContent.$inferInsert,
+ spaces: number[],
+) {
+ const user = await getUser();
- return {
- memory: addedMemory,
- addedToSpaces
- }
+ if (!user) {
+ return null;
+ }
+ const [addedMemory] = await db
+ .insert(storedContent)
+ .values({
+ user: user.id,
+ ...content,
+ })
+ .returning();
+
+ const addedToSpaces =
+ spaces.length > 0
+ ? await db
+ .insert(contentToSpace)
+ .values(
+ spaces.map((s) => ({
+ contentId: addedMemory.id,
+ spaceId: s,
+ })),
+ )
+ .returning()
+ : [];
+
+ return {
+ memory: addedMemory,
+ addedToSpaces,
+ };
}
export async function deleteSpace(id: number) {
+ const user = await getUser();
- const user = await getUser()
-
- if (!user) {
- return null
- }
-
- await db.delete(contentToSpace)
- .where(eq(contentToSpace.spaceId, id));
-
- const [deleted] = await db.delete(space)
- .where(and(eq(space.user, user.id), eq(space.id, id)))
- .returning();
+ if (!user) {
+ return null;
+ }
+ await db.delete(contentToSpace).where(eq(contentToSpace.spaceId, id));
- return deleted
+ const [deleted] = await db
+ .delete(space)
+ .where(and(eq(space.user, user.id), eq(space.id, id)))
+ .returning();
+ return deleted;
}
-
export async function deleteMemory(id: number) {
+ const user = await getUser();
+ if (!user) {
+ return null;
+ }
- const user = await getUser()
-
- if (!user) {
- return null
- }
-
- await db.delete(contentToSpace)
- .where(eq(contentToSpace.contentId, id));
-
- const [deleted] = await db.delete(storedContent)
- .where(and(eq(storedContent.user, user.id), eq(storedContent.id, id)))
- .returning();
+ await db.delete(contentToSpace).where(eq(contentToSpace.contentId, id));
- return deleted
+ const [deleted] = await db
+ .delete(storedContent)
+ .where(and(eq(storedContent.user, user.id), eq(storedContent.id, id)))
+ .returning();
+ return deleted;
}
diff --git a/apps/web/src/app/api/spaces/route.ts b/apps/web/src/app/api/spaces/route.ts
index 3fe95870..d2685e9f 100644
--- a/apps/web/src/app/api/spaces/route.ts
+++ b/apps/web/src/app/api/spaces/route.ts
@@ -3,13 +3,7 @@ import { sessions, space, users } from "@/server/db/schema";
import { eq } from "drizzle-orm";
import { NextRequest, NextResponse } from "next/server";
-<<<<<<< HEAD
-export const runtime = "edge"
-
-export async function GET(req: NextRequest) {
-=======
export const runtime = "edge";
->>>>>>> 7648bdaa8cbe42a90f05865f8c555c9a3911af9b
export async function GET(req: NextRequest) {
const token =
@@ -40,7 +34,6 @@ export async function GET(req: NextRequest) {
.from(sessions)
.where(eq(sessions.sessionToken, token!));
-
if (!sessionData || sessionData.length === 0) {
return new Response(
JSON.stringify({ message: "Invalid Key, session not found." }),
@@ -63,27 +56,11 @@ export async function GET(req: NextRequest) {
const user = userData[0];
-<<<<<<< HEAD
- const spaces = await db
- .select()
- .from(space)
- .where(eq(space.user, user.id))
- .all();
-
-
- console.log('data', spaces)
-
- return NextResponse.json({
- message: "OK",
- data: spaces
- }, { status: 200 })
-=======
const spaces = await db
.select()
.from(space)
.where(eq(space.user, user.id))
.all();
->>>>>>> 7648bdaa8cbe42a90f05865f8c555c9a3911af9b
return NextResponse.json(
{
diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx
index 7f3abfee..1cc21adf 100644
--- a/apps/web/src/app/page.tsx
+++ b/apps/web/src/app/page.tsx
@@ -1,6 +1,6 @@
import { db } from "@/server/db";
import {
- ChachedSpaceContent,
+ ChachedSpaceContent,
contentToSpace,
sessions,
space,
@@ -57,37 +57,36 @@ export default async function Home() {
.select()
.from(space)
.where(eq(space.user, userData.id))
- .all();
+ .all();
- console.log(collectedSpaces)
+ console.log(collectedSpaces);
// Fetch only first 3 content of each spaces
let contents: ChachedSpaceContent[] = [];
- //console.log(await db.select().from(storedContent).)
-
+ //console.log(await db.select().from(storedContent).)
+
await Promise.all([
collectedSpaces.forEach(async (space) => {
- console.log("fetching ")
- const data = (await fetchContentForSpace(space.id, {
- offset: 0,
- limit: 3,
- })).map(data => ({
- ...data,
- space: space.id
- }))
- contents = [
- ...contents,
+ console.log("fetching ");
+ const data = (
+ await fetchContentForSpace(space.id, {
+ offset: 0,
+ limit: 3,
+ })
+ ).map((data) => ({
...data,
- ];
+ space: space.id,
+ }));
+ contents = [...contents, ...data];
}),
]);
- console.log(contents)
+ console.log(contents);
// freeMemories
const freeMemories = await fetchFreeMemories(userData.id);
- console.log('free',freeMemories)
+ console.log("free", freeMemories);
return (
<MemoryProvider
diff --git a/apps/web/src/app/privacy/page.tsx b/apps/web/src/app/privacy/page.tsx
index 8d126dff..5e40cbe9 100644
--- a/apps/web/src/app/privacy/page.tsx
+++ b/apps/web/src/app/privacy/page.tsx
@@ -2,6 +2,8 @@ import React from "react";
import Markdown from "react-markdown";
import { policy } from "./privacy";
+export const runtime = "edge";
+
function Page() {
return (
<div>
diff --git a/apps/web/src/components/Main.tsx b/apps/web/src/components/Main.tsx
index 3c883b67..e6b31de5 100644
--- a/apps/web/src/components/Main.tsx
+++ b/apps/web/src/components/Main.tsx
@@ -380,7 +380,10 @@ export function Chat({
loading={i === chatHistory.length - 1 ? isLoading : false}
sources={msg.answer.sources}
>
- {msg.answer.parts.map((part) => part.text).join(" ")}
+ {msg.answer.parts
+ .map((part) => part.text)
+ .join("")
+ .replace("</s>", "")}
</ChatAnswer>
</ChatMessage>
))}
diff --git a/apps/web/src/components/Sidebar/AddMemoryDialog.tsx b/apps/web/src/components/Sidebar/AddMemoryDialog.tsx
index f21a9683..39f088e3 100644
--- a/apps/web/src/components/Sidebar/AddMemoryDialog.tsx
+++ b/apps/web/src/components/Sidebar/AddMemoryDialog.tsx
@@ -15,7 +15,7 @@ import { useMemory } from "@/contexts/MemoryContext";
import { Loader, Plus, X } from "lucide-react";
import { StoredContent } from "@/server/db/schema";
import { cleanUrl } from "@/lib/utils";
-import { motion } from "framer-motion"
+import { motion } from "framer-motion";
import { getMetaData } from "@/server/helpers";
export function AddMemoryPage({ closeDialog }: { closeDialog: () => void }) {
@@ -39,29 +39,29 @@ export function AddMemoryPage({ closeDialog }: { closeDialog: () => void }) {
placeholder="Enter the URL of the page"
type="url"
data-modal-autofocus
- className="disabled:opacity-70 disabled:cursor-not-allowed bg-rgray-4 mt-2 w-full"
+ className="bg-rgray-4 mt-2 w-full disabled:cursor-not-allowed disabled:opacity-70"
value={url}
onChange={(e) => setUrl(e.target.value)}
- disabled={loading}
+ disabled={loading}
/>
<DialogFooter>
<FilterSpaces
selectedSpaces={selectedSpacesId}
setSelectedSpaces={setSelectedSpacesId}
- className="disabled:opacity-70 disabled:cursor-not-allowed hover:bg-rgray-5 mr-auto bg-white/5"
+ className="hover:bg-rgray-5 mr-auto bg-white/5 disabled:cursor-not-allowed disabled:opacity-70"
name={"Spaces"}
- disabled={loading}
+ disabled={loading}
/>
<button
type={"submit"}
- disabled={loading}
+ disabled={loading}
onClick={async () => {
- setLoading(true)
- const metadata = await getMetaData(url)
+ setLoading(true);
+ const metadata = await getMetaData(url);
await addMemory(
{
title: metadata.title,
- description: metadata.description,
+ description: metadata.description,
content: "",
type: "page",
url: url,
@@ -70,28 +70,28 @@ export function AddMemoryPage({ closeDialog }: { closeDialog: () => void }) {
},
selectedSpacesId,
);
- closeDialog()
+ closeDialog();
}}
- className="relative disabled:opacity-70 disabled:cursor-not-allowed bg-rgray-4 hover:bg-rgray-5 focus-visible:bg-rgray-5 focus-visible:ring-rgray-7 rounded-md px-4 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2"
+ className="bg-rgray-4 hover:bg-rgray-5 focus-visible:bg-rgray-5 focus-visible:ring-rgray-7 relative rounded-md px-4 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-70"
>
- <motion.div
- initial={{ x: '-50%', y: '-100%' }}
- animate={loading && { y: '-50%', x: '-50%', opacity: 1 }}
- className="opacity-0 absolute top-1/2 left-1/2 translate-y-[-100%] -translate-x-1/2"
- >
- <Loader className="w-5 h-5 animate-spin text-rgray-11" />
- </motion.div>
- <motion.div
- initial={{ y: '0%' }}
- animate={loading && { opacity: 0, y: '30%' }}
- >
- Add
- </motion.div>
+ <motion.div
+ initial={{ x: "-50%", y: "-100%" }}
+ animate={loading && { y: "-50%", x: "-50%", opacity: 1 }}
+ className="absolute left-1/2 top-1/2 -translate-x-1/2 translate-y-[-100%] opacity-0"
+ >
+ <Loader className="text-rgray-11 h-5 w-5 animate-spin" />
+ </motion.div>
+ <motion.div
+ initial={{ y: "0%" }}
+ animate={loading && { opacity: 0, y: "30%" }}
+ >
+ Add
+ </motion.div>
</button>
<DialogClose
- disabled={loading}
- className="disabled:opacity-70 disabled:cursor-not-allowed hover:bg-rgray-4 focus-visible:bg-rgray-4 focus-visible:ring-rgray-7 rounded-md px-3 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2"
- >
+ disabled={loading}
+ className="hover:bg-rgray-4 focus-visible:bg-rgray-4 focus-visible:ring-rgray-7 rounded-md px-3 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-70"
+ >
Cancel
</DialogClose>
</DialogFooter>
@@ -100,8 +100,7 @@ export function AddMemoryPage({ closeDialog }: { closeDialog: () => void }) {
}
export function NoteAddPage({ closeDialog }: { closeDialog: () => void }) {
-
- const { addMemory } = useMemory()
+ const { addMemory } = useMemory();
const [selectedSpacesId, setSelectedSpacesId] = useState<number[]>([]);
@@ -142,7 +141,7 @@ export function NoteAddPage({ closeDialog }: { closeDialog: () => void }) {
placeholder="Title of the note"
data-modal-autofocus
value={name}
- disabled={loading}
+ disabled={loading}
onChange={(e) => setName(e.target.value)}
/>
<Editor
@@ -165,39 +164,41 @@ export function NoteAddPage({ closeDialog }: { closeDialog: () => void }) {
<button
onClick={() => {
if (check()) {
- setLoading(true)
- addMemory({
- content,
- title: name,
- type: "note",
- url: "https://notes.supermemory.dhr.wtf/",
- image: '',
- savedAt: new Date()
- }, selectedSpacesId).then(closeDialog)
+ setLoading(true);
+ addMemory(
+ {
+ content,
+ title: name,
+ type: "note",
+ url: "https://notes.supermemory.dhr.wtf/",
+ image: "",
+ savedAt: new Date(),
+ },
+ selectedSpacesId,
+ ).then(closeDialog);
}
}}
- disabled={loading}
- className="relative disabled:opacity-70 disabled:cursor-not-allowed bg-rgray-4 hover:bg-rgray-5 focus-visible:bg-rgray-5 focus-visible:ring-rgray-7 rounded-md px-4 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2"
+ disabled={loading}
+ className="bg-rgray-4 hover:bg-rgray-5 focus-visible:bg-rgray-5 focus-visible:ring-rgray-7 relative rounded-md px-4 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-70"
>
-
- <motion.div
- initial={{ x: '-50%', y: '-100%' }}
- animate={loading && { y: '-50%', x: '-50%', opacity: 1 }}
- className="opacity-0 absolute top-1/2 left-1/2 translate-y-[-100%] -translate-x-1/2"
- >
- <Loader className="w-5 h-5 animate-spin text-rgray-11" />
- </motion.div>
- <motion.div
- initial={{ y: '0%' }}
- animate={loading && { opacity: 0, y: '30%' }}
- >
- Add
- </motion.div>
+ <motion.div
+ initial={{ x: "-50%", y: "-100%" }}
+ animate={loading && { y: "-50%", x: "-50%", opacity: 1 }}
+ className="absolute left-1/2 top-1/2 -translate-x-1/2 translate-y-[-100%] opacity-0"
+ >
+ <Loader className="text-rgray-11 h-5 w-5 animate-spin" />
+ </motion.div>
+ <motion.div
+ initial={{ y: "0%" }}
+ animate={loading && { opacity: 0, y: "30%" }}
+ >
+ Add
+ </motion.div>
</button>
<DialogClose
type={undefined}
- disabled={loading}
- className="disabled:opacity-70 disabled:cursor-not-allowed hover:bg-rgray-4 focus-visible:bg-rgray-4 focus-visible:ring-rgray-7 rounded-md px-3 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2"
+ disabled={loading}
+ className="hover:bg-rgray-4 focus-visible:bg-rgray-4 focus-visible:ring-rgray-7 rounded-md px-3 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-70"
>
Cancel
</DialogClose>
@@ -207,16 +208,14 @@ export function NoteAddPage({ closeDialog }: { closeDialog: () => void }) {
}
export function SpaceAddPage({ closeDialog }: { closeDialog: () => void }) {
-
- const { addSpace } = useMemory()
+ const { addSpace } = useMemory();
const inputRef = useRef<HTMLInputElement>(null);
const [name, setName] = useState("");
const [loading, setLoading] = useState(false);
- const [selected, setSelected] = useState<StoredContent[]>([]);
-
+ const [selected, setSelected] = useState<StoredContent[]>([]);
function check(): boolean {
const data = {
@@ -247,65 +246,73 @@ export function SpaceAddPage({ closeDialog }: { closeDialog: () => void }) {
</DialogHeader>
<Label className="mt-5 block">Name</Label>
<Input
- ref={inputRef}
+ ref={inputRef}
placeholder="Enter the name of the space"
type="url"
data-modal-autofocus
- value={name}
- disabled={loading}
- onChange={e => setName(e.target.value)}
- className="bg-rgray-4 mt-2 w-full focus-visible:data-[error=true]:ring-red-500/10 data-[error=true]:placeholder:text-red-400 placeholder:transition placeholder:duration-500"
+ value={name}
+ disabled={loading}
+ onChange={(e) => setName(e.target.value)}
+ className="bg-rgray-4 mt-2 w-full placeholder:transition placeholder:duration-500 data-[error=true]:placeholder:text-red-400 focus-visible:data-[error=true]:ring-red-500/10"
/>
{selected.length > 0 && (
- <>
- <Label className="mt-5 block">Add Memories</Label>
- <div className="flex min-h-5 py-2 flex-col justify-center items-center">
- {selected.map(i => (
- <MemorySelectedItem
- key={i.id}
- onRemove={() => setSelected(prev => prev.filter(p => p.id !== i.id))}
- {...i}
- />
- ))}
- </div>
- </>
- )}
+ <>
+ <Label className="mt-5 block">Add Memories</Label>
+ <div className="flex min-h-5 flex-col items-center justify-center py-2">
+ {selected.map((i) => (
+ <MemorySelectedItem
+ key={i.id}
+ onRemove={() =>
+ setSelected((prev) => prev.filter((p) => p.id !== i.id))
+ }
+ {...i}
+ />
+ ))}
+ </div>
+ </>
+ )}
<DialogFooter>
- <FilterMemories
- selected={selected}
- setSelected={setSelected}
- disabled={loading}
- className="mr-auto bg-white/5 hover:bg-rgray-4 focus-visible:bg-rgray-4 disabled:opacity-70 disabled:cursor-not-allowed"
- >
- <Plus className="w-5 h-5" />
- Memory
- </FilterMemories>
+ <FilterMemories
+ selected={selected}
+ setSelected={setSelected}
+ disabled={loading}
+ className="hover:bg-rgray-4 focus-visible:bg-rgray-4 mr-auto bg-white/5 disabled:cursor-not-allowed disabled:opacity-70"
+ >
+ <Plus className="h-5 w-5" />
+ Memory
+ </FilterMemories>
<button
type={undefined}
- onClick={() => {
- if (check()) {
- setLoading(true)
- addSpace(name, selected.map(s => s.id)).then(() => closeDialog())
- }
- }}
- disabled={loading}
- className="relative disabled:opacity-70 disabled:cursor-not-allowed bg-rgray-4 hover:bg-rgray-5 focus-visible:bg-rgray-5 focus-visible:ring-rgray-7 rounded-md px-4 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2"
+ onClick={() => {
+ if (check()) {
+ setLoading(true);
+ addSpace(
+ name,
+ selected.map((s) => s.id),
+ ).then(() => closeDialog());
+ }
+ }}
+ disabled={loading}
+ className="bg-rgray-4 hover:bg-rgray-5 focus-visible:bg-rgray-5 focus-visible:ring-rgray-7 relative rounded-md px-4 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-70"
>
- <motion.div
- initial={{ x: '-50%', y: '-100%' }}
- animate={loading && { y: '-50%', x: '-50%', opacity: 1 }}
- className="opacity-0 absolute top-1/2 left-1/2 translate-y-[-100%] -translate-x-1/2"
- >
- <Loader className="w-5 h-5 animate-spin text-rgray-11" />
- </motion.div>
- <motion.div
- initial={{ y: '0%' }}
- animate={loading && { opacity: 0, y: '30%' }}
- >
- Add
- </motion.div>
+ <motion.div
+ initial={{ x: "-50%", y: "-100%" }}
+ animate={loading && { y: "-50%", x: "-50%", opacity: 1 }}
+ className="absolute left-1/2 top-1/2 -translate-x-1/2 translate-y-[-100%] opacity-0"
+ >
+ <Loader className="text-rgray-11 h-5 w-5 animate-spin" />
+ </motion.div>
+ <motion.div
+ initial={{ y: "0%" }}
+ animate={loading && { opacity: 0, y: "30%" }}
+ >
+ Add
+ </motion.div>
</button>
- <DialogClose disabled={loading} className="disabled:opacity-70 disabled:cursor-not-allowed hover:bg-rgray-4 focus-visible:bg-rgray-4 focus-visible:ring-rgray-7 rounded-md px-3 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2">
+ <DialogClose
+ disabled={loading}
+ className="hover:bg-rgray-4 focus-visible:bg-rgray-4 focus-visible:ring-rgray-7 rounded-md px-3 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-70"
+ >
Cancel
</DialogClose>
</DialogFooter>
@@ -313,15 +320,33 @@ export function SpaceAddPage({ closeDialog }: { closeDialog: () => void }) {
);
}
-export function MemorySelectedItem({ id, title, url, type, image, onRemove }: StoredContent & { onRemove: () => void; }) {
- return (
- <div className="flex justify-start gap-2 p-1 px-2 w-full items-center text-sm rounded-md hover:bg-rgray-4 focus-within-bg-rgray-4 [&:hover>[data-icon]]:block [&:hover>img]:hidden">
- <img src={type === 'note'? '/note.svg' : image ?? "/icons/logo_without_bg.png"} className="h-5 w-5" />
- <button onClick={onRemove} data-icon className="w-5 h-5 p-0 m-0 hidden focus-visible:outline-none">
- <X className="w-5 h-5 scale-90" />
- </button>
- <span>{title}</span>
- <span className="ml-auto block opacity-50">{type ==='note' ? 'Note' : cleanUrl(url)}</span>
- </div>
- )
+export function MemorySelectedItem({
+ id,
+ title,
+ url,
+ type,
+ image,
+ onRemove,
+}: StoredContent & { onRemove: () => void }) {
+ return (
+ <div className="hover:bg-rgray-4 focus-within-bg-rgray-4 flex w-full items-center justify-start gap-2 rounded-md p-1 px-2 text-sm [&:hover>[data-icon]]:block [&:hover>img]:hidden">
+ <img
+ src={
+ type === "note" ? "/note.svg" : image ?? "/icons/logo_without_bg.png"
+ }
+ className="h-5 w-5"
+ />
+ <button
+ onClick={onRemove}
+ data-icon
+ className="m-0 hidden h-5 w-5 p-0 focus-visible:outline-none"
+ >
+ <X className="h-5 w-5 scale-90" />
+ </button>
+ <span>{title}</span>
+ <span className="ml-auto block opacity-50">
+ {type === "note" ? "Note" : cleanUrl(url)}
+ </span>
+ </div>
+ );
}
diff --git a/apps/web/src/components/Sidebar/FilterCombobox.tsx b/apps/web/src/components/Sidebar/FilterCombobox.tsx
index de2d5fe8..f93ae710 100644
--- a/apps/web/src/components/Sidebar/FilterCombobox.tsx
+++ b/apps/web/src/components/Sidebar/FilterCombobox.tsx
@@ -24,7 +24,8 @@ import { SearchResult, useMemory } from "@/contexts/MemoryContext";
import { useDebounce } from "@/hooks/useDebounce";
import { StoredContent } from "@/server/db/schema";
-export interface FilterSpacesProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
+export interface FilterSpacesProps
+ extends React.ButtonHTMLAttributes<HTMLButtonElement> {
side?: "top" | "bottom";
align?: "end" | "start" | "center";
onClose?: () => void;
@@ -153,7 +154,7 @@ export type FilterMemoriesProps = {
onClose?: () => void;
selected: StoredContent[];
setSelected: React.Dispatch<React.SetStateAction<StoredContent[]>>;
-} & React.ButtonHTMLAttributes<HTMLButtonElement>
+} & React.ButtonHTMLAttributes<HTMLButtonElement>;
export function FilterMemories({
className,
@@ -164,40 +165,39 @@ export function FilterMemories({
setSelected,
...props
}: FilterMemoriesProps) {
-
- const { search } = useMemory();
+ const { search } = useMemory();
const [open, setOpen] = React.useState(false);
- const [searchQuery, setSearchQuery] = React.useState("");
- const query = useDebounce(searchQuery, 500)
+ const [searchQuery, setSearchQuery] = React.useState("");
+ const query = useDebounce(searchQuery, 500);
- const [searchResults, setSearchResults] = React.useState<SearchResult[]>([]);
- const [isSearching, setIsSearching] = React.useState(false)
+ const [searchResults, setSearchResults] = React.useState<SearchResult[]>([]);
+ const [isSearching, setIsSearching] = React.useState(false);
- const results = React.useMemo(() => {
- return searchResults.map(r => r.memory)
- }, [searchResults])
+ const results = React.useMemo(() => {
+ return searchResults.map((r) => r.memory);
+ }, [searchResults]);
- console.log('memoized', results)
+ console.log("memoized", results);
- React.useEffect(() => {
- const q = query.trim()
- if (q.length > 0) {
- setIsSearching(true);
- (async () => {
- const results = await search(q, {
- filter: {
- memories: true,
- spaces: false
- }
- })
- setSearchResults(results)
- setIsSearching(false)
- })();
- } else {
- setSearchResults([])
- }
- }, [query])
+ React.useEffect(() => {
+ const q = query.trim();
+ if (q.length > 0) {
+ setIsSearching(true);
+ (async () => {
+ const results = await search(q, {
+ filter: {
+ memories: true,
+ spaces: false,
+ },
+ });
+ setSearchResults(results);
+ setIsSearching(false);
+ })();
+ } else {
+ setSearchResults([]);
+ }
+ }, [query]);
React.useEffect(() => {
if (!open) {
@@ -205,7 +205,7 @@ export function FilterMemories({
}
}, [open]);
- console.log(searchResults);
+ console.log(searchResults);
return (
<AnimatePresence mode="popLayout">
<LayoutGroup>
@@ -220,7 +220,7 @@ export function FilterMemories({
)}
{...props}
>
- {props.children}
+ {props.children}
</button>
</PopoverTrigger>
<PopoverContent
@@ -229,43 +229,58 @@ export function FilterMemories({
side={side}
className="w-[200px] p-0"
>
- <Command
- shouldFilter={false}
- >
- <CommandInput isSearching={isSearching} value={searchQuery} onValueChange={setSearchQuery} placeholder="Filter memories..." />
+ <Command shouldFilter={false}>
+ <CommandInput
+ isSearching={isSearching}
+ value={searchQuery}
+ onValueChange={setSearchQuery}
+ placeholder="Filter memories..."
+ />
<CommandList>
- <CommandGroup>
- <CommandEmpty className="text-rgray-11 text-sm text-center py-5">{isSearching ? "Searching..." : query.trim().length > 0 ? "Nothing Found" : "Search something"}</CommandEmpty>
- {results.map((m) => (
- <CommandItem
- key={m.id}
- value={m.id.toString()}
- onSelect={(val) => {
- setSelected((prev) =>
- prev.find(p => p.id === parseInt(val))
- ? prev.filter((v) => v.id !== parseInt(val))
- : [...prev, m],
- );
- }}
- asChild
- >
- <div
- className="text-rgray-11"
- >
- <img src={m.type === 'note' ? '/note.svg' : m.image ?? "/icons/logo_without_bg.png"} className="mr-2 h-4 w-4" />
- {m.title}
- <Check
- data-state-on={selected.find(i => i.id === m.id) !== undefined}
- className={cn(
- "on:opacity-100 ml-auto h-4 w-4 opacity-0",
- )}
- />
- </div>
- </CommandItem>
- ))}
-
- </CommandGroup>
- </CommandList>
+ <CommandGroup>
+ <CommandEmpty className="text-rgray-11 py-5 text-center text-sm">
+ {isSearching
+ ? "Searching..."
+ : query.trim().length > 0
+ ? "Nothing Found"
+ : "Search something"}
+ </CommandEmpty>
+ {results.map((m) => (
+ <CommandItem
+ key={m.id}
+ value={m.id.toString()}
+ onSelect={(val) => {
+ setSelected((prev) =>
+ prev.find((p) => p.id === parseInt(val))
+ ? prev.filter((v) => v.id !== parseInt(val))
+ : [...prev, m],
+ );
+ }}
+ asChild
+ >
+ <div className="text-rgray-11">
+ <img
+ src={
+ m.type === "note"
+ ? "/note.svg"
+ : m.image ?? "/icons/logo_without_bg.png"
+ }
+ className="mr-2 h-4 w-4"
+ />
+ {m.title}
+ <Check
+ data-state-on={
+ selected.find((i) => i.id === m.id) !== undefined
+ }
+ className={cn(
+ "on:opacity-100 ml-auto h-4 w-4 opacity-0",
+ )}
+ />
+ </div>
+ </CommandItem>
+ ))}
+ </CommandGroup>
+ </CommandList>
</Command>
</PopoverContent>
</Popover>
diff --git a/apps/web/src/components/Sidebar/MemoriesBar.tsx b/apps/web/src/components/Sidebar/MemoriesBar.tsx
index f474262a..6c640e26 100644
--- a/apps/web/src/components/Sidebar/MemoriesBar.tsx
+++ b/apps/web/src/components/Sidebar/MemoriesBar.tsx
@@ -44,7 +44,7 @@ import { DialogTrigger } from "@radix-ui/react-dialog";
import { AddMemoryPage, NoteAddPage, SpaceAddPage } from "./AddMemoryDialog";
import { ExpandedSpace } from "./ExpandedSpace";
import { StoredContent, StoredSpace } from "@/server/db/schema";
-import Image from "next/image"
+import Image from "next/image";
import { useDebounce } from "@/hooks/useDebounce";
export function MemoriesBar() {
@@ -57,11 +57,11 @@ export function MemoriesBar() {
>(null);
const [expandedSpace, setExpandedSpace] = useState<number | null>(null);
- const [searchQuery, setSearcyQuery] = useState("");
- const [searchLoading, setSearchLoading] = useState(false)
- const query = useDebounce(searchQuery, 500)
+ const [searchQuery, setSearcyQuery] = useState("");
+ const [searchLoading, setSearchLoading] = useState(false);
+ const query = useDebounce(searchQuery, 500);
- const [searchResults, setSearchResults] = useState<SearchResult[]>([])
+ const [searchResults, setSearchResults] = useState<SearchResult[]>([]);
if (expandedSpace) {
return (
@@ -72,20 +72,20 @@ export function MemoriesBar() {
);
}
- useEffect(() => {
- const q = query.trim()
- if (q.length < 1) {
- setSearchResults([])
- return
- }
+ useEffect(() => {
+ const q = query.trim();
+ if (q.length < 1) {
+ setSearchResults([]);
+ return;
+ }
- setSearchLoading(true);
+ setSearchLoading(true);
- (async () => {
- setSearchResults(await search(q))
- setSearchLoading(false)
- })();
- }, [query])
+ (async () => {
+ setSearchResults(await search(q));
+ setSearchLoading(false);
+ })();
+ }, [query]);
return (
<div className="text-rgray-11 flex w-full flex-col items-start py-8 text-left">
@@ -93,10 +93,16 @@ export function MemoriesBar() {
<h1 className="w-full text-2xl">Your Memories</h1>
<InputWithIcon
placeholder="Search"
- icon={searchLoading ? <Loader className="text-rgray-11 h-5 w-5 opacity-50 animate-spin" /> : <Search className="text-rgray-11 h-5 w-5 opacity-50" />}
+ icon={
+ searchLoading ? (
+ <Loader className="text-rgray-11 h-5 w-5 animate-spin opacity-50" />
+ ) : (
+ <Search className="text-rgray-11 h-5 w-5 opacity-50" />
+ )
+ }
className="bg-rgray-4 mt-2 w-full"
- value={searchQuery}
- onChange={(e) => setSearcyQuery(e.target.value)}
+ value={searchQuery}
+ onChange={(e) => setSearcyQuery(e.target.value)}
/>
</div>
<div className="mt-2 flex w-full px-8">
@@ -147,34 +153,32 @@ export function MemoriesBar() {
ref={parent}
className="grid w-full grid-flow-row grid-cols-3 gap-1 px-2 py-5"
>
- {query.trim().length > 0 ? (
- <>
- {searchResults.map(({ type, space, memory }, i) => (
- <>
- {type === "memory" && (
- <MemoryItem {...memory!} key={i} />
- )}
- {type === "space" && (
- <SpaceItem {...space!} key={i} onDelete={() => {}} />
- )}
- </>
- ))}
- </>
- ): (
- <>
- {spaces.map((space) => (
- <SpaceItem
- onDelete={() => deleteSpace(space.id)}
- key={space.id}
- //onClick={() => setExpandedSpace(space.id)}
- {...space}
- />
- ))}
- {freeMemories.map(m => (
- <MemoryItem {...m} key={m.id} />
- ))}
- </>
- )}
+ {query.trim().length > 0 ? (
+ <>
+ {searchResults.map(({ type, space, memory }, i) => (
+ <>
+ {type === "memory" && <MemoryItem {...memory!} key={i} />}
+ {type === "space" && (
+ <SpaceItem {...space!} key={i} onDelete={() => {}} />
+ )}
+ </>
+ ))}
+ </>
+ ) : (
+ <>
+ {spaces.map((space) => (
+ <SpaceItem
+ onDelete={() => deleteSpace(space.id)}
+ key={space.id}
+ //onClick={() => setExpandedSpace(space.id)}
+ {...space}
+ />
+ ))}
+ {freeMemories.map((m) => (
+ <MemoryItem {...m} key={m.id} />
+ ))}
+ </>
+ )}
</div>
</div>
);
@@ -190,44 +194,40 @@ const SpaceExitVariant: Variant = {
},
};
-export function MemoryItem({
- id,
- title,
- image,
- type
-}: StoredContent) {
+export function MemoryItem({ id, title, image, type }: StoredContent) {
+ const name = title
+ ? title.length > 10
+ ? title.slice(0, 10) + "..."
+ : title
+ : "<no title>";
- const name = title ? title.length > 10 ? title.slice(0, 10) + "..." : title : '<no title>';
-
- return (
- <div
-
- className="hover:bg-rgray-2 has-[[data-state='true']]:bg-rgray-2 has-[[data-space-text]:focus-visible]:bg-rgray-2 has-[[data-space-text]:focus-visible]:ring-rgray-7 [&:has-[[data-space-text]:focus-visible]>[data-more-button]]:opacity-100 relative flex select-none flex-col-reverse items-center justify-center rounded-md p-2 pb-4 text-center font-normal ring-transparent transition has-[[data-space-text]:focus-visible]:outline-none has-[[data-space-text]:focus-visible]:ring-2 md:has-[[data-state='true']]:bg-transparent [&:hover>[data-more-button]]:opacity-100"
- >
- <button data-space-text className="focus-visible:outline-none">
+ return (
+ <div className="hover:bg-rgray-2 has-[[data-state='true']]:bg-rgray-2 has-[[data-space-text]:focus-visible]:bg-rgray-2 has-[[data-space-text]:focus-visible]:ring-rgray-7 [&:has-[[data-space-text]:focus-visible]>[data-more-button]]:opacity-100 relative flex select-none flex-col-reverse items-center justify-center rounded-md p-2 pb-4 text-center font-normal ring-transparent transition has-[[data-space-text]:focus-visible]:outline-none has-[[data-space-text]:focus-visible]:ring-2 md:has-[[data-state='true']]:bg-transparent [&:hover>[data-more-button]]:opacity-100">
+ <button data-space-text className="focus-visible:outline-none">
{name}
</button>
-
- <div className="w-24 h-24 flex justify-center items-center">
- {type === "page" ? (
- <img
- className="h-16 w-16"
- id={id.toString()}
- src={image!}
- onError={(e) => {
- (e.target as HTMLImageElement).src = "/icons/white_without_bg.png"
- }}
- />
- ): type === "note" ? (
- <div className="shadow-md rounded-md bg-rgray-4 p-2 flex justify-center items-center">
- <Text className="w-10 h-10" />
- </div>
- ) : (
- <></>
- )}
- </div>
- </div>
- )
+
+ <div className="flex h-24 w-24 items-center justify-center">
+ {type === "page" ? (
+ <img
+ className="h-16 w-16"
+ id={id.toString()}
+ src={image!}
+ onError={(e) => {
+ (e.target as HTMLImageElement).src =
+ "/icons/white_without_bg.png";
+ }}
+ />
+ ) : type === "note" ? (
+ <div className="bg-rgray-4 flex items-center justify-center rounded-md p-2 shadow-md">
+ <Text className="h-10 w-10" />
+ </div>
+ ) : (
+ <></>
+ )}
+ </div>
+ </div>
+ );
}
export function SpaceItem({
@@ -236,8 +236,7 @@ export function SpaceItem({
onDelete,
onClick,
}: StoredSpace & { onDelete: () => void; onClick?: () => void }) {
-
- const { cachedMemories } = useMemory();
+ const { cachedMemories } = useMemory();
const [itemRef, animateItem] = useAnimate();
const { width } = useViewport();
@@ -250,11 +249,11 @@ export function SpaceItem({
},
});
- const spaceMemories = useMemo(() => {
- return cachedMemories.filter(m => m.space === id)
- }, [cachedMemories])
+ const spaceMemories = useMemo(() => {
+ return cachedMemories.filter((m) => m.space === id);
+ }, [cachedMemories]);
- const _name = name.length > 10 ? name.slice(0, 10) + "..." : name
+ const _name = name.length > 10 ? name.slice(0, 10) + "..." : name;
return (
<motion.div
ref={itemRef}
@@ -269,110 +268,120 @@ export function SpaceItem({
isOpen={moreDropdownOpen}
setIsOpen={setMoreDropdownOpen}
onDelete={() => {
- onDelete()
- return;
+ onDelete();
+ return;
if (!itemRef.current || width < 768) {
onDelete();
return;
}
- // 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 initial = {
- // x: scopeRect.left + scopeRect.width / 2,
- // y: scopeRect.top + scopeRect.height / 2,
- // };
- // const delta = {
- // x:
- // trashRect.left +
- // trashRect.width / 2 -
- // scopeRect.left +
- // scopeRect.width / 2,
- // y:
- // trashRect.top +
- // trashRect.height / 4 -
- // scopeRect.top +
- // scopeRect.height / 2,
- // };
- // const end = {
- // x: trashRect.left + trashRect.width / 2,
- // y: trashRect.top + trashRect.height / 4,
- // };
- // el.style.offsetPath = `path('M ${initial.x} ${initial.y} Q ${delta.x * 0.01} ${delta.y * 0.01} ${end.x} ${end.y}`;
- // animateItem(itemRef.current, SpaceExitVariant, {
- // duration: 0.2,
- // }).then(() => {
- // itemRef.current.style.scale = "0";
- // onDelete();
- // });
- // document.body.appendChild(el);
- // el.animate(
- // {
- // 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",
- // },
- // );
- // el.animate(
- // {
- // offsetDistance: ["0%", "100%"],
- // },
- // {
- // duration: 2000,
- // 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" },
- // ).onfinish = () => {
- // el.remove();
- // };
- // };
+ // 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 initial = {
+ // x: scopeRect.left + scopeRect.width / 2,
+ // y: scopeRect.top + scopeRect.height / 2,
+ // };
+ // const delta = {
+ // x:
+ // trashRect.left +
+ // trashRect.width / 2 -
+ // scopeRect.left +
+ // scopeRect.width / 2,
+ // y:
+ // trashRect.top +
+ // trashRect.height / 4 -
+ // scopeRect.top +
+ // scopeRect.height / 2,
+ // };
+ // const end = {
+ // x: trashRect.left + trashRect.width / 2,
+ // y: trashRect.top + trashRect.height / 4,
+ // };
+ // el.style.offsetPath = `path('M ${initial.x} ${initial.y} Q ${delta.x * 0.01} ${delta.y * 0.01} ${end.x} ${end.y}`;
+ // animateItem(itemRef.current, SpaceExitVariant, {
+ // duration: 0.2,
+ // }).then(() => {
+ // itemRef.current.style.scale = "0";
+ // onDelete();
+ // });
+ // document.body.appendChild(el);
+ // el.animate(
+ // {
+ // 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",
+ // },
+ // );
+ // el.animate(
+ // {
+ // offsetDistance: ["0%", "100%"],
+ // },
+ // {
+ // duration: 2000,
+ // 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" },
+ // ).onfinish = () => {
+ // el.remove();
+ // };
+ // };
}}
/>
{spaceMemories.length > 2 ? (
<MemoryWithImages3
className="h-24 w-24"
id={id.toString()}
- images={spaceMemories.map((c) => c.type === 'note' ? '/note.svg' : c.image).reverse() as string[]}
+ images={
+ spaceMemories
+ .map((c) => (c.type === "note" ? "/note.svg" : c.image))
+ .reverse() as string[]
+ }
/>
) : spaceMemories.length > 1 ? (
- <MemoryWithImages2
+ <MemoryWithImages2
className="h-24 w-24"
id={id.toString()}
- images={spaceMemories.map((c) => c.type === 'note' ? '/note.svg' : c.image).reverse() as string[]}
+ images={
+ spaceMemories
+ .map((c) => (c.type === "note" ? "/note.svg" : c.image))
+ .reverse() as string[]
+ }
/>
- ) : spaceMemories.length === 1 ? (
+ ) : spaceMemories.length === 1 ? (
<MemoryWithImage
className="h-24 w-24"
id={id.toString()}
- image={spaceMemories[0].type === 'note' ? '/note.svg' : spaceMemories[0].image!}
+ image={
+ spaceMemories[0].type === "note"
+ ? "/note.svg"
+ : spaceMemories[0].image!
+ }
/>
) : (
- <div className="bg-rgray-4 opacity-30 rounded-full w-24 h-24 scale-50 shadow-">
-
- </div>
- )}
+ <div className="bg-rgray-4 shadow- h-24 w-24 scale-50 rounded-full opacity-30"></div>
+ )}
</motion.div>
);
}
@@ -409,34 +418,32 @@ export function SpaceMoreButton({
<Edit3 className="mr-2 h-4 w-4" strokeWidth={1.5} />
Edit
</DropdownMenuItem>
- <DialogTrigger asChild>
- <DropdownMenuItem
- className="focus:bg-red-100 focus:text-red-400 dark:focus:bg-red-100/10"
- >
- <Trash2 className="mr-2 h-4 w-4" strokeWidth={1.5} />
- Delete
- </DropdownMenuItem>
- </DialogTrigger>
+ <DialogTrigger asChild>
+ <DropdownMenuItem className="focus:bg-red-100 focus:text-red-400 dark:focus:bg-red-100/10">
+ <Trash2 className="mr-2 h-4 w-4" strokeWidth={1.5} />
+ Delete
+ </DropdownMenuItem>
+ </DialogTrigger>
</DropdownMenuContent>
</DropdownMenu>
- <DialogContent>
- <DialogTitle className='text-xl'>Are you sure?</DialogTitle>
- <DialogDescription className='text-md'>You will not be able to recover this space</DialogDescription>
- <DialogFooter>
- <DialogClose
- type={undefined}
- onClick={onDelete}
- className="bg-red-500/40 focus-visible:bg-red-500/60 focus-visible:ring-red-500 hover:bg-red-500/60 ml-auto flex items-center justify-center rounded-md px-3 py-2 transition focus-visible:outline-none focus-visible:ring-2"
- >
- Delete
- </DialogClose>
- <DialogClose
- className="focus-visible:bg-rgray-4 focus-visible:ring-rgray-7 hover:bg-rgray-4 ml-auto flex items-center justify-center rounded-md px-3 py-2 transition focus-visible:outline-none focus-visible:ring-2"
- >
- Cancel
- </DialogClose>
- </DialogFooter>
- </DialogContent>
+ <DialogContent>
+ <DialogTitle className="text-xl">Are you sure?</DialogTitle>
+ <DialogDescription className="text-md">
+ You will not be able to recover this space
+ </DialogDescription>
+ <DialogFooter>
+ <DialogClose
+ type={undefined}
+ onClick={onDelete}
+ className="ml-auto flex items-center justify-center rounded-md bg-red-500/40 px-3 py-2 transition hover:bg-red-500/60 focus-visible:bg-red-500/60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-red-500"
+ >
+ Delete
+ </DialogClose>
+ <DialogClose className="focus-visible:bg-rgray-4 focus-visible:ring-rgray-7 hover:bg-rgray-4 ml-auto flex items-center justify-center rounded-md px-3 py-2 transition focus-visible:outline-none focus-visible:ring-2">
+ Cancel
+ </DialogClose>
+ </DialogFooter>
+ </DialogContent>
</Dialog>
);
}
diff --git a/apps/web/src/components/ui/command.tsx b/apps/web/src/components/ui/command.tsx
index f3534b55..afc2cf46 100644
--- a/apps/web/src/components/ui/command.tsx
+++ b/apps/web/src/components/ui/command.tsx
@@ -7,7 +7,6 @@ import { Loader, Search } from "lucide-react";
import { cn } from "@/lib/utils";
import { Dialog, DialogContent } from "@/components/ui/dialog";
-import { isSea } from "node:sea";
const Command = React.forwardRef<
React.ElementRef<typeof CommandPrimitive>,
@@ -40,13 +39,19 @@ const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
const CommandInput = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Input>,
- React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input> & { isSearching?: boolean }
->(({ className, isSearching = false ,...props }, ref) => (
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input> & {
+ isSearching?: boolean;
+ }
+>(({ className, isSearching = false, ...props }, ref) => (
<div
className="border-rgray-6 flex items-center border-b px-3"
cmdk-input-wrapper=""
>
- {isSearching ? <Loader className="mr-2 h-4 w-4 shrink-9 opacity-50 animate-spin" /> : <Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />}
+ {isSearching ? (
+ <Loader className="shrink-9 mr-2 h-4 w-4 animate-spin opacity-50" />
+ ) : (
+ <Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
+ )}
<CommandPrimitive.Input
ref={ref}
className={cn(
diff --git a/apps/web/src/contexts/MemoryContext.tsx b/apps/web/src/contexts/MemoryContext.tsx
index 67e6250e..e10984bb 100644
--- a/apps/web/src/contexts/MemoryContext.tsx
+++ b/apps/web/src/contexts/MemoryContext.tsx
@@ -1,14 +1,27 @@
"use client";
import React, { useCallback } from "react";
-import { ChachedSpaceContent, StoredContent, storedContent, StoredSpace } from "@/server/db/schema";
-import { addMemory, searchMemoriesAndSpaces, addSpace, fetchContentForSpace, deleteSpace, deleteMemory, fetchFreeMemories } from "@/actions/db";
+import {
+ ChachedSpaceContent,
+ StoredContent,
+ storedContent,
+ StoredSpace,
+} from "@/server/db/schema";
+import {
+ addMemory,
+ searchMemoriesAndSpaces,
+ addSpace,
+ fetchContentForSpace,
+ deleteSpace,
+ deleteMemory,
+ fetchFreeMemories,
+} from "@/actions/db";
import { User } from "next-auth";
export type SearchResult = {
- type: "memory" | "space",
- space: StoredSpace,
- memory: StoredContent
-}
+ type: "memory" | "space";
+ space: StoredSpace;
+ memory: StoredContent;
+};
// temperory (will change)
export const MemoryContext = React.createContext<{
@@ -17,107 +30,118 @@ export const MemoryContext = React.createContext<{
addSpace: typeof addSpace;
addMemory: typeof addMemory;
cachedMemories: ChachedSpaceContent[];
- search: typeof searchMemoriesAndSpaces;
- deleteSpace: typeof deleteSpace;
- deleteMemory: typeof deleteMemory;
+ search: typeof searchMemoriesAndSpaces;
+ deleteSpace: typeof deleteSpace;
+ deleteMemory: typeof deleteMemory;
}>({
spaces: [],
freeMemories: [],
- addMemory: (() => {}) as unknown as (typeof addMemory),
- addSpace: (async () => {}) as unknown as (typeof addSpace),
+ addMemory: (() => {}) as unknown as typeof addMemory,
+ addSpace: (async () => {}) as unknown as typeof addSpace,
cachedMemories: [],
- search: async () => [],
- deleteMemory: (() => {}) as unknown as (typeof deleteMemory),
- deleteSpace: (() => {}) as unknown as (typeof deleteSpace)
+ search: async () => [],
+ deleteMemory: (() => {}) as unknown as typeof deleteMemory,
+ deleteSpace: (() => {}) as unknown as typeof deleteSpace,
});
export const MemoryProvider: React.FC<
{
spaces: StoredSpace[];
freeMemories: StoredContent[];
- cachedMemories: ChachedSpaceContent[];
- user: User;
+ cachedMemories: ChachedSpaceContent[];
+ user: User;
} & React.PropsWithChildren
-> = ({ children, user, spaces: initalSpaces, freeMemories: initialFreeMemories, cachedMemories: initialCachedMemories }) => {
-
+> = ({
+ children,
+ user,
+ spaces: initalSpaces,
+ freeMemories: initialFreeMemories,
+ cachedMemories: initialCachedMemories,
+}) => {
const [spaces, setSpaces] = React.useState<StoredSpace[]>(initalSpaces);
const [freeMemories, setFreeMemories] =
React.useState<StoredContent[]>(initialFreeMemories);
- const [cachedMemories, setCachedMemories] = React.useState<ChachedSpaceContent[]>(
- initialCachedMemories
- );
-
- const _deleteSpace: typeof deleteSpace = async (...params) => {
- const deleted = (await deleteSpace(...params))!
+ const [cachedMemories, setCachedMemories] = React.useState<
+ ChachedSpaceContent[]
+ >(initialCachedMemories);
+
+ const _deleteSpace: typeof deleteSpace = async (...params) => {
+ const deleted = (await deleteSpace(...params))!;
+
+ setSpaces((prev) => prev.filter((i) => i.id !== deleted.id));
+ setCachedMemories((prev) => prev.filter((i) => i.space !== deleted.id));
- setSpaces(prev => prev.filter(i => i.id !== deleted.id))
- setCachedMemories(prev => prev.filter(i => i.space !== deleted.id))
+ setFreeMemories(await fetchFreeMemories());
- setFreeMemories(await fetchFreeMemories())
+ return deleted;
+ };
- return deleted
- }
+ const _deleteMemory: typeof deleteMemory = async (...params) => {
+ const deleted = (await deleteMemory(...params))!;
- const _deleteMemory: typeof deleteMemory = async (...params) => {
- const deleted = (await deleteMemory(...params))!
+ setCachedMemories((prev) => prev.filter((i) => i.id !== deleted.id));
+ setFreeMemories(await fetchFreeMemories());
- setCachedMemories(prev => prev.filter(i => i.id !== deleted.id))
- setFreeMemories(await fetchFreeMemories())
+ return deleted;
+ };
- return deleted
- }
-
// const fetchMemories = useCallback(async (query: string) => {
// const response = await fetch(`/api/memories?${query}`);
// }, []);
-
- const _addSpace: typeof addSpace = async (...params) => {
- const { space: addedSpace, addedMemories } = (await addSpace(...params))!;
-
- setSpaces(prev => [...prev, addedSpace])
- const cachedMemories = (await fetchContentForSpace(addedSpace.id, {
- offset: 0,
- limit: 3
- })).map(m => ({ ...m, space: addedSpace.id }))
-
- setCachedMemories(prev => [...prev, ...cachedMemories])
-
- setFreeMemories(await fetchFreeMemories())
-
- return {
- space: addedSpace, addedMemories
- }
- }
-
- const _addMemory: typeof addMemory = async (...params) => {
- const { memory: addedMemory, addedToSpaces } = (await addMemory(...params))!;
-
- addedToSpaces.length > 0 ? setCachedMemories(prev => [
- ...prev,
- ...addedToSpaces.map(s => ({
- ...addedMemory,
- space: s.spaceId
- }))
- ]) : setFreeMemories(prev => [...prev, addedMemory])
-
- return {
- memory: addedMemory,
- addedToSpaces
- }
- }
+ const _addSpace: typeof addSpace = async (...params) => {
+ const { space: addedSpace, addedMemories } = (await addSpace(...params))!;
+
+ setSpaces((prev) => [...prev, addedSpace]);
+ const cachedMemories = (
+ await fetchContentForSpace(addedSpace.id, {
+ offset: 0,
+ limit: 3,
+ })
+ ).map((m) => ({ ...m, space: addedSpace.id }));
+
+ setCachedMemories((prev) => [...prev, ...cachedMemories]);
+
+ setFreeMemories(await fetchFreeMemories());
+
+ return {
+ space: addedSpace,
+ addedMemories,
+ };
+ };
+
+ const _addMemory: typeof addMemory = async (...params) => {
+ const { memory: addedMemory, addedToSpaces } = (await addMemory(
+ ...params,
+ ))!;
+
+ addedToSpaces.length > 0
+ ? setCachedMemories((prev) => [
+ ...prev,
+ ...addedToSpaces.map((s) => ({
+ ...addedMemory,
+ space: s.spaceId,
+ })),
+ ])
+ : setFreeMemories((prev) => [...prev, addedMemory]);
+
+ return {
+ memory: addedMemory,
+ addedToSpaces,
+ };
+ };
return (
<MemoryContext.Provider
value={{
- search: searchMemoriesAndSpaces,
+ search: searchMemoriesAndSpaces,
spaces,
addSpace: _addSpace,
deleteSpace: _deleteSpace,
freeMemories,
cachedMemories,
- deleteMemory: _deleteMemory,
+ deleteMemory: _deleteMemory,
addMemory: _addMemory,
}}
>
diff --git a/apps/web/src/hooks/useDebounce.ts b/apps/web/src/hooks/useDebounce.ts
index 4a47de72..d133b1ae 100644
--- a/apps/web/src/hooks/useDebounce.ts
+++ b/apps/web/src/hooks/useDebounce.ts
@@ -1,4 +1,4 @@
-import { useEffect, useState } from 'react';
+import { useEffect, useState } from "react";
/**
* Use this hook when you need to debounce a value.
diff --git a/apps/web/src/server/db/schema.ts b/apps/web/src/server/db/schema.ts
index f3eafb94..cd2756f1 100644
--- a/apps/web/src/server/db/schema.ts
+++ b/apps/web/src/server/db/schema.ts
@@ -21,7 +21,7 @@ export const users = createTable("user", {
image: text("image", { length: 255 }),
});
-export type User = typeof users.$inferSelect
+export type User = typeof users.$inferSelect;
export const usersRelations = relations(users, ({ many }) => ({
accounts: many(accounts),
@@ -34,7 +34,7 @@ export const accounts = createTable(
id: integer("id").notNull().primaryKey({ autoIncrement: true }),
userId: text("userId", { length: 255 })
.notNull()
- .references(() => users.id, { onDelete: 'cascade' }),
+ .references(() => users.id, { onDelete: "cascade" }),
type: text("type", { length: 255 }).notNull(),
provider: text("provider", { length: 255 }).notNull(),
providerAccountId: text("providerAccountId", { length: 255 }).notNull(),
@@ -60,7 +60,7 @@ export const sessions = createTable(
sessionToken: text("sessionToken", { length: 255 }).notNull(),
userId: text("userId", { length: 255 })
.notNull()
- .references(() => users.id, { onDelete: 'cascade' }),
+ .references(() => users.id, { onDelete: "cascade" }),
expires: int("expires", { mode: "timestamp" }).notNull(),
},
(session) => ({
@@ -94,7 +94,9 @@ export const storedContent = createTable(
"page",
),
image: text("image", { length: 255 }),
- user: text("user", { length: 255 }).references(() => users.id, { onDelete: 'cascade' }),
+ user: text("user", { length: 255 }).references(() => users.id, {
+ onDelete: "cascade",
+ }),
},
(sc) => ({
urlIdx: index("storedContent_url_idx").on(sc.url),
@@ -109,10 +111,10 @@ export const contentToSpace = createTable(
{
contentId: integer("contentId")
.notNull()
- .references(() => storedContent.id, { onDelete: 'cascade' }),
+ .references(() => storedContent.id, { onDelete: "cascade" }),
spaceId: integer("spaceId")
.notNull()
- .references(() => space.id, { onDelete: 'cascade' }),
+ .references(() => space.id, { onDelete: "cascade" }),
},
(cts) => ({
compoundKey: primaryKey({ columns: [cts.contentId, cts.spaceId] }),
@@ -124,7 +126,9 @@ export const space = createTable(
{
id: integer("id").notNull().primaryKey({ autoIncrement: true }),
name: text("name").notNull().unique().default("none"),
- user: text("user", { length: 255 }).references(() => users.id, { onDelete: 'cascade' }),
+ user: text("user", { length: 255 }).references(() => users.id, {
+ onDelete: "cascade",
+ }),
},
(space) => ({
nameIdx: index("spaces_name_idx").on(space.name),
@@ -135,5 +139,5 @@ export const space = createTable(
export type StoredContent = Omit<typeof storedContent.$inferSelect, "user">;
export type StoredSpace = typeof space.$inferSelect;
export type ChachedSpaceContent = StoredContent & {
- space: number;
-}
+ space: number;
+};
diff --git a/apps/web/src/server/db/test.ts b/apps/web/src/server/db/test.ts
index 9cb8f2b5..37969e5e 100644
--- a/apps/web/src/server/db/test.ts
+++ b/apps/web/src/server/db/test.ts
@@ -1,10 +1,6 @@
-import { db } from "."
-import { space, user } from "./schema"
+import { db } from ".";
+import { space, user } from "./schema";
-const user = await db.select(user).all()
+const user = await db.select(user).all();
-await db.insert(space).values([
- {
-
- }
-])
+await db.insert(space).values([{}]);
diff --git a/apps/web/src/server/helpers.ts b/apps/web/src/server/helpers.ts
index f1ac078c..9a9a9607 100644
--- a/apps/web/src/server/helpers.ts
+++ b/apps/web/src/server/helpers.ts
@@ -1,28 +1,32 @@
-'use server';
-import * as cheerio from "cheerio"
+"use server";
+import * as cheerio from "cheerio";
export async function getMetaData(url: string) {
const response = await fetch(url);
const html = await response.text();
-
- const $ = cheerio.load(html)
+
+ const $ = cheerio.load(html);
// Extract the base URL
const baseUrl = new URL(url).origin;
// Extract title
- const title = $('title').text().trim()
-
- const description = $('meta[name=description]').attr('content') ?? ''
-
- const _favicon = $('link[rel=icon]').attr('href') ?? 'https://supermemory.dhr.wtf/web.svg';
-
- let favicon = _favicon.trim().length > 0 ? _favicon.trim() : 'https://supermemory.dhr.wtf/web.svg'
- if (favicon.startsWith("/")) {
- favicon = baseUrl + favicon
- } else if (favicon.startsWith("./")) {
- favicon = baseUrl + favicon.slice(1)
- }
+ const title = $("title").text().trim();
+
+ const description = $("meta[name=description]").attr("content") ?? "";
+
+ const _favicon =
+ $("link[rel=icon]").attr("href") ?? "https://supermemory.dhr.wtf/web.svg";
+
+ let favicon =
+ _favicon.trim().length > 0
+ ? _favicon.trim()
+ : "https://supermemory.dhr.wtf/web.svg";
+ if (favicon.startsWith("/")) {
+ favicon = baseUrl + favicon;
+ } else if (favicon.startsWith("./")) {
+ favicon = baseUrl + favicon.slice(1);
+ }
// Prepare the metadata object
const metadata = {