small UX fixes and multi space save actually works now

This commit is contained in:
Dhravya 2024-07-14 20:48:06 -05:00
parent dff1bdd295
commit 1fd0aae0be
8 changed files with 173 additions and 203 deletions

View file

@ -7,9 +7,4 @@ module.exports = {
parserOptions: {
project: true,
},
// ignore some rules
rules: {
"@": "off",
"import/no-unresolved": "off",
},
};

View file

@ -284,7 +284,10 @@ function LinkComponent({
<div className="flex items-center gap-2 text-xs">
<PaperclipIcon className="w-3 h-3" /> Page
</div>
<div className="text-lg text-[#fff] mt-4 line-clamp-2">{title}</div>
{/* remove `<---chunkId: ${vector.id}\n${content}\n---->` pattern from title */}
<div className="text-lg text-[#fff] mt-4 line-clamp-2">
{title.replace(/(<---chunkId: .*?\n.*?\n---->)/g, "")}
</div>
<div>
{url.replace("https://supermemory.ai", "").split("#")[0] ?? "/"}
</div>
@ -294,7 +297,9 @@ function LinkComponent({
<div className="flex items-center gap-2 text-xs">
<NotebookIcon className="w-3 h-3" /> Note
</div>
<div className="text-lg text-[#fff] mt-4 line-clamp-2">{title}</div>
<div className="text-lg text-[#fff] mt-4 line-clamp-2">
{title.replace(/(<---chunkId: .*?\n.*?\n---->)/g, "")}
</div>
<div className="line-clamp-3 mt-2">
{content.replace(title, "")}
</div>

View file

@ -17,34 +17,15 @@ import {
} from "@repo/ui/shadcn/dialog";
import { Label } from "@repo/ui/shadcn/label";
import { Textarea } from "@repo/ui/shadcn/textarea";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@repo/ui/shadcn/select";
import { toast } from "sonner";
import { getSpaces } from "../actions/fetchers";
import { Space } from "../actions/types";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@repo/ui/shadcn/tooltip";
import { InformationCircleIcon } from "@heroicons/react/24/outline";
import { HomeIcon } from "@heroicons/react/24/solid";
import { createMemory, createSpace } from "../actions/doers";
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(() => {
@ -109,7 +90,7 @@ function Menu() {
[spaces],
);
const handleSubmit = async (content?: string, space?: string) => {
const handleSubmit = async (content?: string, spaces?: number[]) => {
setDialogOpen(false);
toast.info("Creating memory...", {
@ -122,12 +103,15 @@ function Menu() {
return;
}
console.log(spaces);
const cont = await createMemory({
content: content,
spaces: space ? [space] : undefined,
spaces: spaces ?? undefined,
});
setContent("");
setSelectedSpaces([]);
if (cont.success) {
toast.success("Memory created", {
@ -187,148 +171,138 @@ function Menu() {
</div>
<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 }}
>
<form
ref={ref}
action={async (e: FormData) => {
const content = e.get("content")?.toString();
const space = e.get("space")?.toString();
<form
action={async (e: FormData) => {
const content = e.get("content")?.toString();
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>
await handleSubmit(content, selectedSpaces);
}}
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>
<div>
<Label htmlFor="name">Resource (URL or content)</Label>
<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, selectedSpaces);
}
}}
/>
</div>
<div>
<Label className="space-y-1" htmlFor="space">
<h3 className="font-semibold text-lg tracking-tight">
Spaces (Optional)
</h3>
<p className="leading-normal text-[#F2F3F5] text-sm">
A space is a collection of memories. It's a way to organise
your memories.
</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="Select or create a new space."
className="bg-[#2F353C] h-min rounded-md mt-4 mb-4"
/>
<div>
<Label htmlFor="name">Resource (URL or content)</Label>
<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);
}
}}
/>
</div>
{autoDetectedType != "none" && (
<div>
<Label className="space-y-2" htmlFor="space">
<h3 className="font-bold text-lg">Spaces (Optional)</h3>
<p className="leading-normal">
A space is a collection of memories. It's a way to
organise your memories.
</p>
</Label>
<ComboboxWithCreate
options={spaces.map((x) => ({
label: x.name,
value: x.id.toString(),
}))}
onSelect={(v) =>
setSelectedSpaces((prev) => {
if (v === "") {
return [];
{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),
)
}
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="select or create a new space."
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>
)}
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>
</div>
<DialogFooter>
<Button
disabled={autoDetectedType === "none"}
variant={"secondary"}
type="submit"
>
Save {autoDetectedType != "none" && autoDetectedType}
</Button>
</DialogFooter>
</form>
</motion.div>
<DialogFooter>
<Button
disabled={autoDetectedType === "none"}
variant={"secondary"}
type="submit"
>
Save {autoDetectedType != "none" && autoDetectedType}
</Button>
</DialogFooter>
</form>
</DialogContent>
{/* Mobile Menu */}

View file

@ -158,7 +158,7 @@ const getTweetData = async (tweetID: string) => {
export const createMemory = async (input: {
content: string;
spaces?: string[];
spaces?: number[];
}): ServerActionReturnType<number> => {
const data = await auth();
@ -239,7 +239,7 @@ export const createMemory = async (input: {
title: metadata.title,
description: metadata.description,
url: metadata.baseUrl,
spaces: storeToSpaces,
spaces: storeToSpaces.map((spaceId) => spaceId.toString()),
user: data.user.id,
type,
}),
@ -302,7 +302,7 @@ export const createMemory = async (input: {
const insertResponse = await db
.insert(storedContent)
.values({
content: response.chunkedInput,
content: pageContent,
title: metadata.title,
description: metadata.description,
url: saveToDbUrl,
@ -355,13 +355,7 @@ export const createMemory = async (input: {
.select()
.from(space)
.where(
and(
inArray(
space.id,
storeToSpaces.map((s) => parseInt(s)),
),
eq(space.user, data.user.id),
),
and(inArray(space.id, storeToSpaces), eq(space.user, data.user.id)),
)
.all();
@ -371,7 +365,7 @@ export const createMemory = async (input: {
.insert(contentToSpace)
.values({ contentId: contentId, spaceId: s.id });
db.update(space).set({ numItems: s.numItems + 1 });
await db.update(space).set({ numItems: s.numItems + 1 });
}),
);
}

View file

@ -15,7 +15,7 @@ import s from "./tweet-header.module.css";
import { VerifiedBadge } from "./verified-badge";
type Props = {
tweet: Tweet;
tweet: Tweet | { error: string };
components?: TwitterComponents;
};
@ -101,6 +101,10 @@ const TweetHeader = ({
};
export const MyTweet = ({ tweet: t, components }: Props) => {
if ("error" in t) {
return <div>{t.error}</div>;
}
const tweet = enrichTweet(t);
return (
<TweetContainer className="bg-transparent !m-0 !p-0 !z-0">

View file

@ -25,7 +25,7 @@
"eslint-plugin-next-on-pages": "^1.11.3",
"lint-staged": "^15.2.5",
"postcss": "^8.4.38",
"prettier": "^3.2.5",
"prettier": "^3.3.3",
"readline-sync": "^1.4.10",
"tailwindcss": "^3.4.3",
"tailwindcss-animate": "^1.0.7",
@ -79,6 +79,7 @@
"cheerio": "^1.0.0-rc.12",
"compromise": "^14.13.0",
"drizzle-orm": "0.30.0",
"eslint-config-turbo": "^2.0.6",
"framer-motion": "^11.2.6",
"geist": "^1.3.0",
"grammy": "^1.25.1",

View file

@ -12,15 +12,12 @@ export const tweetToMd = (tweet: Tweet) => {
export const getRawTweet = (tweet: string) => {
// Get the content inside the last <raw> tag, there can any number of <raw> tags in the tweet (or just one)
const rawTag = /<raw>(.*)<\/raw>/gs; // Use 's' flag to match across multiple lines
const rawTag = /<raw>(.*)<\/raw>/g;
const match = rawTag.exec(tweet);
if (match && match.length > 1) {
// Remove the specified item pattern
const cleanedContent = match[1]?.replace(
/<---chunkId:.*?\n.*?\n---->/gs,
"",
);
return cleanedContent;
if (match) {
return match[1];
}
return tweet;
return `{
"error": "No <raw> tag found"
}`;
};

View file

@ -52,23 +52,6 @@ const ComboboxWithCreate: React.FC<ComboboxWithCreateProps> = ({
value={inputValue}
/>
<CommandList className="z-10 translate-y-12 translate-x-5 opacity-0 absolute group-focus-within:opacity-100 bg-secondary p-2 rounded-b-xl max-w-64">
<CommandEmpty>
<Button
className="px-1"
type="button"
onClick={async () => onSubmit(inputValue)}
variant="link"
disabled={inputValue.length === 0}
>
{inputValue.length > 0 ? (
<>
{createNewMessage} "{inputValue}"
</>
) : (
<>Create a new space</>
)}
</Button>
</CommandEmpty>
<CommandGroup className="hidden group-focus-within:block">
{options.map((option, idx) => (
<CommandItem
@ -78,6 +61,23 @@ const ComboboxWithCreate: React.FC<ComboboxWithCreateProps> = ({
{option.label}
</CommandItem>
))}
{!options.map((opts) => opts.label).includes(inputValue) && (
<Button
className="px-1"
type="button"
onClick={async () => onSubmit(inputValue)}
variant="link"
disabled={inputValue.length === 0}
>
{inputValue.length > 0 ? (
<>
{createNewMessage} "{inputValue}"
</>
) : (
<>Type to create a new space</>
)}
</Button>
)}
</CommandGroup>
</CommandList>
</Command>