mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-08-28 05:25:33 +00:00
commit
7576567af8
3 changed files with 148 additions and 145 deletions
|
|
@ -33,6 +33,7 @@ import { Button } from "@repo/ui/shadcn/button";
|
|||
import { addUserToSpace, deleteItem, moveItem } from "@/app/actions/doers";
|
||||
import { toast } from "sonner";
|
||||
import { Input } from "@repo/ui/shadcn/input";
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
export function MemoriesPage({
|
||||
memoriesAndSpaces,
|
||||
|
|
@ -269,7 +270,9 @@ function LinkComponent({
|
|||
id: number;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
className={`bg-secondary group relative border-2 border-border rounded-xl ${type === "tweet" ? "" : "p-4"} hover:scale-105 transition duration-200`}
|
||||
>
|
||||
<Link
|
||||
|
|
@ -353,7 +356,7 @@ function LinkComponent({
|
|||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -362,7 +365,7 @@ const SpaceFilterMethods = ["All", "Pages", "Notes", "Tweet"];
|
|||
function Filters({
|
||||
setFilter,
|
||||
filter,
|
||||
filterMethods
|
||||
filterMethods,
|
||||
}: {
|
||||
setFilter: (i: string) => void;
|
||||
filter: string;
|
||||
|
|
|
|||
|
|
@ -40,8 +40,11 @@ import { Input } from "@repo/ui/shadcn/input";
|
|||
import ComboboxWithCreate from "@repo/ui/shadcn/combobox";
|
||||
import { StoredSpace } from "@/server/db/schema";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import useMeasure from "react-use-measure";
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
function Menu() {
|
||||
const [ref, bounds] = useMeasure();
|
||||
const [spaces, setSpaces] = useState<StoredSpace[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -183,158 +186,154 @@ function Menu() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<DialogContent className="sm:max-w-[425px] rounded-2xl bg-background z-[39] backdrop-blur-md">
|
||||
<form
|
||||
action={async (e: FormData) => {
|
||||
const content = e.get("content")?.toString();
|
||||
const space = e.get("space")?.toString();
|
||||
|
||||
await handleSubmit(content, space);
|
||||
}}
|
||||
className="flex flex-col gap-4"
|
||||
<DialogContent className="sm:max-w-[475px] text-[#F2F3F5] rounded-2xl bg-background z-[39] backdrop-blur-md">
|
||||
<motion.div
|
||||
className="overflow-hidden max-h-[70vh]"
|
||||
animate={{ height: bounds.height + 10 }}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Add memory</DialogTitle>
|
||||
<DialogDescription>
|
||||
A "Memory" is a bookmark, something you want to remember.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<form
|
||||
ref={ref}
|
||||
action={async (e: FormData) => {
|
||||
const content = e.get("content")?.toString();
|
||||
const space = e.get("space")?.toString();
|
||||
|
||||
<div>
|
||||
<Label className="text-[#858B92]" htmlFor="name">
|
||||
Resource (URL or content)
|
||||
</Label>
|
||||
<Textarea
|
||||
className="bg-[#2F353C] focus-visible:ring-0 border-none focus-visible:ring-offset-0 mt-2"
|
||||
id="content"
|
||||
name="content"
|
||||
placeholder="Start typing a note or paste a URL here. I'll remember it."
|
||||
value={content}
|
||||
onChange={(e) => setContent(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
handleSubmit(content);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
await handleSubmit(content, space);
|
||||
}}
|
||||
className="flex flex-col gap-4"
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Add memory</DialogTitle>
|
||||
<DialogDescription className="text-[#F2F3F5]">
|
||||
A "Memory" is a bookmark, something you want to remember.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
{autoDetectedType != "none" && (
|
||||
<div>
|
||||
<Label
|
||||
className="text-[#858B92] flex items-center gap-1 duration-200 transform transition-transform"
|
||||
htmlFor="space"
|
||||
>
|
||||
Spaces
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<span>
|
||||
<InformationCircleIcon className="w-4 h-4" />
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>
|
||||
A space is a collection of memories. You can create a
|
||||
space and then chat/write/ideate with it.
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<Label htmlFor="name">
|
||||
Resource (URL or content)
|
||||
</Label>
|
||||
|
||||
<ComboboxWithCreate
|
||||
options={spaces.map((x) => ({
|
||||
label: x.name,
|
||||
value: x.id.toString(),
|
||||
}))}
|
||||
onSelect={(v) =>
|
||||
setSelectedSpaces((prev) => {
|
||||
if (v === "") {
|
||||
return [];
|
||||
}
|
||||
return [...prev, parseInt(v)];
|
||||
})
|
||||
}
|
||||
onSubmit={async (spaceName) => {
|
||||
const space = options.find((x) => x.label === spaceName);
|
||||
toast.info("Creating space...");
|
||||
|
||||
if (space) {
|
||||
toast.error("A space with that name already exists.");
|
||||
}
|
||||
|
||||
const creationTask = await createSpace(spaceName);
|
||||
if (creationTask.success && creationTask.data) {
|
||||
toast.success("Space created " + creationTask.data);
|
||||
setSpaces?.((prev) => [
|
||||
...prev,
|
||||
{
|
||||
name: spaceName,
|
||||
id: creationTask.data!,
|
||||
createdAt: new Date(),
|
||||
user: null,
|
||||
numItems: 0,
|
||||
},
|
||||
]);
|
||||
setSelectedSpaces((prev) => [
|
||||
...prev,
|
||||
creationTask.data!,
|
||||
]);
|
||||
} else {
|
||||
toast.error(
|
||||
"Space creation failed: " + creationTask.error ??
|
||||
"Unknown error",
|
||||
);
|
||||
<Textarea
|
||||
className={`bg-[#2F353C] text-[#DBDEE1] max-h-[35vh] overflow-auto focus-visible:ring-0 border-none focus-visible:ring-offset-0 mt-2 ${/^https?:\/\/\S+$/i.test(content) && "text-[#1D9BF0] underline underline-offset-2"}`}
|
||||
id="content"
|
||||
name="content"
|
||||
rows={8}
|
||||
placeholder="Start typing a note or paste a URL here. I'll remember it."
|
||||
value={content}
|
||||
onChange={(e) => setContent(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
handleSubmit(content);
|
||||
}
|
||||
}}
|
||||
placeholder="Save or create space by typing."
|
||||
className="bg-[#2F353C] h-min rounded-md mt-2 mb-4"
|
||||
/>
|
||||
|
||||
<div>
|
||||
{selectedSpaces.length > 0 && (
|
||||
<div className="flex flex-row flex-wrap gap-0.5 h-min">
|
||||
{selectedSpaces.map((x, idx) => (
|
||||
<button
|
||||
key={x}
|
||||
type="button"
|
||||
onClick={() =>
|
||||
setSelectedSpaces((prev) =>
|
||||
prev.filter((y) => y !== x),
|
||||
)
|
||||
}
|
||||
className={`relative group p-2 py-3 bg-[#3C464D] max-w-32 ${
|
||||
idx === selectedSpaces.length - 1
|
||||
? "rounded-br-xl"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<p className="line-clamp-1">
|
||||
{spaces.find((y) => y.id === x)?.name}
|
||||
</p>
|
||||
<div className="absolute h-full right-0 top-0 p-1 opacity-0 group-hover:opacity-100 items-center">
|
||||
<MinusIcon className="w-6 h-6 rounded-full bg-secondary" />
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<DialogFooter>
|
||||
<Button
|
||||
disabled={autoDetectedType === "none"}
|
||||
variant={"secondary"}
|
||||
type="submit"
|
||||
>
|
||||
Save {autoDetectedType != "none" && autoDetectedType}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
{autoDetectedType != "none" && (
|
||||
<div>
|
||||
<Label
|
||||
className="space-y-2"
|
||||
htmlFor="space"
|
||||
>
|
||||
<h3 className="font-bold text-lg">Spaces</h3>
|
||||
<p className="leading-normal">
|
||||
A space is a collection of memories. You can create a
|
||||
space and then chat/write/ideate with it.
|
||||
</p>
|
||||
</Label>
|
||||
|
||||
<ComboboxWithCreate
|
||||
options={spaces.map((x) => ({
|
||||
label: x.name,
|
||||
value: x.id.toString(),
|
||||
}))}
|
||||
onSelect={(v) =>
|
||||
setSelectedSpaces((prev) => {
|
||||
if (v === "") {
|
||||
return [];
|
||||
}
|
||||
return [...prev, parseInt(v)];
|
||||
})
|
||||
}
|
||||
onSubmit={async (spaceName) => {
|
||||
const space = options.find((x) => x.label === spaceName);
|
||||
toast.info("Creating space...");
|
||||
|
||||
if (space) {
|
||||
toast.error("A space with that name already exists.");
|
||||
}
|
||||
|
||||
const creationTask = await createSpace(spaceName);
|
||||
if (creationTask.success && creationTask.data) {
|
||||
toast.success("Space created " + creationTask.data);
|
||||
setSpaces?.((prev) => [
|
||||
...prev,
|
||||
{
|
||||
name: spaceName,
|
||||
id: creationTask.data!,
|
||||
createdAt: new Date(),
|
||||
user: null,
|
||||
numItems: 0,
|
||||
},
|
||||
]);
|
||||
setSelectedSpaces((prev) => [
|
||||
...prev,
|
||||
creationTask.data!,
|
||||
]);
|
||||
} else {
|
||||
toast.error(
|
||||
"Space creation failed: " + creationTask.error ??
|
||||
"Unknown error",
|
||||
);
|
||||
}
|
||||
}}
|
||||
placeholder="Save or create space by typing."
|
||||
className="bg-[#2F353C] h-min rounded-md mt-4 mb-4"
|
||||
/>
|
||||
|
||||
<div>
|
||||
{selectedSpaces.length > 0 && (
|
||||
<div className="flex flex-row flex-wrap gap-0.5 h-min">
|
||||
{selectedSpaces.map((x, idx) => (
|
||||
<button
|
||||
key={x}
|
||||
type="button"
|
||||
onClick={() =>
|
||||
setSelectedSpaces((prev) =>
|
||||
prev.filter((y) => y !== x),
|
||||
)
|
||||
}
|
||||
className={`relative group p-2 py-3 bg-[#3C464D] max-w-32 ${
|
||||
idx === selectedSpaces.length - 1
|
||||
? "rounded-br-xl"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<p className="line-clamp-1">
|
||||
{spaces.find((y) => y.id === x)?.name}
|
||||
</p>
|
||||
<div className="absolute h-full right-0 top-0 p-1 opacity-0 group-hover:opacity-100 items-center">
|
||||
<MinusIcon className="w-6 h-6 rounded-full bg-secondary" />
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<DialogFooter>
|
||||
<Button
|
||||
disabled={autoDetectedType === "none"}
|
||||
variant={"secondary"}
|
||||
type="submit"
|
||||
>
|
||||
Save {autoDetectedType != "none" && autoDetectedType}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</motion.div>
|
||||
</DialogContent>
|
||||
|
||||
{/* Mobile Menu */}
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@
|
|||
"react-markdown": "^9.0.1",
|
||||
"react-responsive-masonry": "^2.2.1",
|
||||
"react-tweet": "^3.2.1",
|
||||
"react-use-measure": "^2.1.1",
|
||||
"react-web-share": "^2.0.2",
|
||||
"rehype-highlight": "^7.0.0",
|
||||
"rehype-katex": "^7.0.0",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue