Remove title field from notes and update tests to reflect changes
Refactors the notes feature by removing the `title` field and updating all related tests and UI components to use `content` instead. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 7464969c-726a-4ec2-a3b2-8ff63f91f6ed Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/591TuZw Replit-Helium-Checkpoint-Created: true
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
@@ -431,9 +431,6 @@ export function IncomingNotePopup() {
|
||||
saveStoredPos(userId, next);
|
||||
};
|
||||
|
||||
const bodyTitle = replyPayload
|
||||
? replyPayload.noteTitle?.trim() || t("notes.popup.untitled")
|
||||
: current.title?.trim() || t("notes.popup.untitled");
|
||||
const bodyContent = replyPayload
|
||||
? replyPayload.replyContent
|
||||
: current.content;
|
||||
@@ -536,11 +533,6 @@ export function IncomingNotePopup() {
|
||||
className="p-4 space-y-3 max-h-[50vh] overflow-y-auto"
|
||||
data-testid="incoming-note-popup-body"
|
||||
>
|
||||
{bodyTitle && (
|
||||
<div className="font-semibold text-sm text-foreground break-words">
|
||||
{bodyTitle}
|
||||
</div>
|
||||
)}
|
||||
{isChecklistNote ? (
|
||||
<PopupChecklistPreview
|
||||
items={checklistItems}
|
||||
|
||||
@@ -305,10 +305,7 @@ export default function NotesPage() {
|
||||
if (folderSelection.kind === "folder" && n.folderId !== folderSelection.id)
|
||||
return false;
|
||||
if (!q) return true;
|
||||
return (
|
||||
n.title.toLowerCase().includes(q) ||
|
||||
n.content.toLowerCase().includes(q)
|
||||
);
|
||||
return n.content.toLowerCase().includes(q);
|
||||
});
|
||||
}, [notes, search, labelFilter, folderSelection]);
|
||||
|
||||
@@ -411,20 +408,14 @@ export default function NotesPage() {
|
||||
const filteredReceived = useMemo(() => {
|
||||
const q = search.trim().toLowerCase();
|
||||
if (!q) return receivedNotes;
|
||||
return receivedNotes.filter(
|
||||
(n) =>
|
||||
n.title.toLowerCase().includes(q) ||
|
||||
n.content.toLowerCase().includes(q),
|
||||
);
|
||||
return receivedNotes.filter((n) => n.content.toLowerCase().includes(q));
|
||||
}, [receivedNotes, search]);
|
||||
|
||||
const filteredSent: SentNote[] = useMemo(() => {
|
||||
const q = search.trim().toLowerCase();
|
||||
if (!q) return sentNotes;
|
||||
return sentNotes.filter(
|
||||
(n: SentNote) =>
|
||||
n.title.toLowerCase().includes(q) ||
|
||||
n.content.toLowerCase().includes(q),
|
||||
return sentNotes.filter((n: SentNote) =>
|
||||
n.content.toLowerCase().includes(q),
|
||||
);
|
||||
}, [sentNotes, search]);
|
||||
|
||||
@@ -805,10 +796,7 @@ export default function NotesPage() {
|
||||
archivedReceived={archivedReceivedNotes.filter((n) => {
|
||||
const q = search.trim().toLowerCase();
|
||||
if (!q) return true;
|
||||
return (
|
||||
n.title.toLowerCase().includes(q) ||
|
||||
n.content.toLowerCase().includes(q)
|
||||
);
|
||||
return n.content.toLowerCase().includes(q);
|
||||
})}
|
||||
onOpenThread={(id) => setOpenThreadId(id)}
|
||||
/>
|
||||
@@ -1151,11 +1139,6 @@ function NoteCard({
|
||||
>
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="flex-1 min-w-0">
|
||||
{note.title && (
|
||||
<div className="font-semibold text-sm text-foreground break-words">
|
||||
{note.title}
|
||||
</div>
|
||||
)}
|
||||
{note.kind === "checklist" ? (
|
||||
<ChecklistView
|
||||
items={note.items ?? []}
|
||||
@@ -1813,11 +1796,6 @@ function ReceivedList({
|
||||
</div>
|
||||
<StatusBadge status={n.status} />
|
||||
</div>
|
||||
{n.title && (
|
||||
<div className="font-semibold text-sm text-foreground break-words">
|
||||
{n.title}
|
||||
</div>
|
||||
)}
|
||||
{n.kind === "checklist" ? (
|
||||
<ChecklistView
|
||||
items={n.items ?? []}
|
||||
@@ -1913,11 +1891,6 @@ function SentList({
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
{n.title && (
|
||||
<div className="font-semibold text-sm text-foreground break-words">
|
||||
{n.title}
|
||||
</div>
|
||||
)}
|
||||
{n.kind === "checklist" ? (
|
||||
<ChecklistView
|
||||
items={n.items ?? []}
|
||||
@@ -2289,7 +2262,7 @@ function ThreadDialog({
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-base">
|
||||
{thread?.title || t("notes.editTitle", "Edit note")}
|
||||
{t("notes.editTitle", "Edit note")}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
{isLoading || !thread ? (
|
||||
@@ -2891,7 +2864,6 @@ function Composer({
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [title, setTitle] = useState("");
|
||||
const [content, setContent] = useState("");
|
||||
const [color, setColor] = useState("default");
|
||||
const [labelIds, setLabelIds] = useState<number[]>([]);
|
||||
@@ -2941,10 +2913,9 @@ function Composer({
|
||||
document.addEventListener("mousedown", onClick);
|
||||
return () => document.removeEventListener("mousedown", onClick);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [open, title, content, color, labelIds, kind, items]);
|
||||
}, [open, content, color, labelIds, kind, items]);
|
||||
|
||||
const reset = () => {
|
||||
setTitle("");
|
||||
setContent("");
|
||||
setColor("default");
|
||||
setLabelIds([]);
|
||||
@@ -2954,10 +2925,10 @@ function Composer({
|
||||
};
|
||||
|
||||
// Build the create-note payload from current composer state and a
|
||||
// boolean indicating whether the composer is empty (no title and no
|
||||
// body / checklist items). Shared by save() and sendNew() so the
|
||||
// "save before send" path uses the exact same data the bare Save
|
||||
// button would have submitted.
|
||||
// boolean indicating whether the composer is empty (no body / checklist
|
||||
// items). Shared by save() and sendNew() so the "save before send"
|
||||
// path uses the exact same data the bare Save button would have
|
||||
// submitted.
|
||||
const buildPayload = () => {
|
||||
const cleanItems =
|
||||
kind === "checklist"
|
||||
@@ -2966,12 +2937,10 @@ function Composer({
|
||||
.filter((it) => it.text.length > 0)
|
||||
: [];
|
||||
const isEmpty =
|
||||
!title.trim() &&
|
||||
(kind === "checklist" ? cleanItems.length === 0 : !content.trim());
|
||||
kind === "checklist" ? cleanItems.length === 0 : !content.trim();
|
||||
return {
|
||||
isEmpty,
|
||||
payload: {
|
||||
title: title.trim(),
|
||||
content: kind === "checklist" ? "" : content.trim(),
|
||||
color,
|
||||
labelIds,
|
||||
@@ -3010,10 +2979,9 @@ function Composer({
|
||||
|
||||
const sendDisabled =
|
||||
create.isPending ||
|
||||
(!title.trim() &&
|
||||
(kind === "checklist"
|
||||
? items.every((it) => !it.text.trim())
|
||||
: !content.trim()));
|
||||
(kind === "checklist"
|
||||
? items.every((it) => !it.text.trim())
|
||||
: !content.trim());
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -3025,16 +2993,8 @@ function Composer({
|
||||
>
|
||||
{open ? (
|
||||
<>
|
||||
<Input
|
||||
autoFocus
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
placeholder={t("notes.titlePlaceholder", "Title")}
|
||||
className="border-0 bg-transparent shadow-none focus-visible:ring-0 px-1 font-semibold"
|
||||
data-testid="notes-composer-title"
|
||||
/>
|
||||
{kind === "checklist" ? (
|
||||
<div className="mt-2 px-1">
|
||||
<div className="px-1">
|
||||
<ChecklistEditor
|
||||
items={items}
|
||||
onChange={setItems}
|
||||
@@ -3043,6 +3003,7 @@ function Composer({
|
||||
</div>
|
||||
) : (
|
||||
<Textarea
|
||||
autoFocus
|
||||
ref={contentRef}
|
||||
value={content}
|
||||
onChange={(e) => setContent(e.target.value)}
|
||||
@@ -3056,7 +3017,7 @@ function Composer({
|
||||
contentFocused.current = false;
|
||||
}}
|
||||
placeholder={t("notes.contentPlaceholder", "Take a note...")}
|
||||
className="border-0 bg-transparent shadow-none focus-visible:ring-0 px-1 mt-1 min-h-[80px] resize-none"
|
||||
className="border-0 bg-transparent shadow-none focus-visible:ring-0 px-1 min-h-[80px] resize-none"
|
||||
data-testid="notes-composer-content"
|
||||
/>
|
||||
)}
|
||||
@@ -3127,7 +3088,6 @@ function EditNoteDialog({
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [title, setTitle] = useState(note.title);
|
||||
const [content, setContent] = useState(note.content);
|
||||
const [color, setColor] = useState(note.color);
|
||||
const [labelIds, setLabelIds] = useState<number[]>(note.labelIds);
|
||||
@@ -3145,7 +3105,6 @@ function EditNoteDialog({
|
||||
update.mutate(
|
||||
{
|
||||
id: note.id,
|
||||
title: title.trim(),
|
||||
content: kind === "checklist" ? "" : content.trim(),
|
||||
color,
|
||||
labelIds,
|
||||
@@ -3170,12 +3129,6 @@ function EditNoteDialog({
|
||||
{t("notes.editTitle", "Edit note")}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<Input
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
placeholder={t("notes.titlePlaceholder", "Title")}
|
||||
className="border-0 bg-transparent shadow-none focus-visible:ring-0 px-1 font-semibold text-base"
|
||||
/>
|
||||
{kind === "checklist" ? (
|
||||
<div className="px-1 min-h-[160px]" data-testid="notes-edit-checklist-wrap">
|
||||
<ChecklistEditor
|
||||
@@ -3429,7 +3382,6 @@ function SharedFolderView({
|
||||
const q = search.trim().toLowerCase();
|
||||
if (!q) return data.notes;
|
||||
return data.notes.filter((n) => {
|
||||
if (n.title?.toLowerCase().includes(q)) return true;
|
||||
if (n.content?.toLowerCase().includes(q)) return true;
|
||||
if (n.kind === "checklist" && Array.isArray(n.items)) {
|
||||
return n.items.some((it) => it.text?.toLowerCase().includes(q));
|
||||
@@ -3559,11 +3511,6 @@ function SharedFolderView({
|
||||
n.color,
|
||||
)}`}
|
||||
>
|
||||
{n.title && (
|
||||
<div className="font-semibold text-sm text-foreground break-words pe-8">
|
||||
{n.title}
|
||||
</div>
|
||||
)}
|
||||
{n.kind === "checklist" ? (
|
||||
<ChecklistView
|
||||
items={n.items ?? []}
|
||||
|
||||
@@ -95,9 +95,9 @@ test.describe("note card actions on touch devices", () => {
|
||||
);
|
||||
expect(noHover).toBe(true);
|
||||
|
||||
const title = `touch-test ${uniq()}`;
|
||||
const content = `touch-test ${uniq()}`;
|
||||
await page.getByTestId("notes-composer").click();
|
||||
await page.getByTestId("notes-composer-title").fill(title);
|
||||
await page.getByTestId("notes-composer-content").fill(content);
|
||||
const savePromise = page.waitForResponse(
|
||||
(r) =>
|
||||
new URL(r.url()).pathname === "/api/notes" &&
|
||||
|
||||
@@ -78,11 +78,8 @@ test("composer creates a checklist note that persists across reloads", async ({
|
||||
await page.goto("/notes");
|
||||
await expect(page.getByTestId("notes-page")).toBeVisible();
|
||||
|
||||
const title = `checklist-test ${uniq()}`;
|
||||
|
||||
// Open composer, set title, switch to checklist.
|
||||
// Open composer and switch to checklist.
|
||||
await page.getByTestId("notes-composer").click();
|
||||
await page.getByTestId("notes-composer-title").fill(title);
|
||||
await page.getByTestId("notes-composer-kind-toggle").click();
|
||||
|
||||
// Add two items via the "Add item" button.
|
||||
@@ -104,7 +101,6 @@ test("composer creates a checklist note that persists across reloads", async ({
|
||||
await page.getByTestId("notes-composer-save").click();
|
||||
const resp = await savePromise;
|
||||
const note = await resp.json();
|
||||
expect(note.title).toBe(title);
|
||||
expect(note.kind).toBe("checklist");
|
||||
expect(Array.isArray(note.items)).toBe(true);
|
||||
expect(note.items.map((i) => i.text)).toEqual(["Buy milk", "Walk the dog"]);
|
||||
|
||||
@@ -77,10 +77,10 @@ test("composer persists chosen color when saving via outside click", async ({
|
||||
await page.goto("/notes");
|
||||
await expect(page.getByTestId("notes-page")).toBeVisible();
|
||||
|
||||
// Open the composer and type a title.
|
||||
const title = `color-test ${uniq()}`;
|
||||
// Open the composer and type some content.
|
||||
const content = `color-test ${uniq()}`;
|
||||
await page.getByTestId("notes-composer").click();
|
||||
await page.getByTestId("notes-composer-title").fill(title);
|
||||
await page.getByTestId("notes-composer-content").fill(content);
|
||||
|
||||
// Open the color popover and pick a non-default swatch.
|
||||
await page
|
||||
@@ -105,6 +105,6 @@ test("composer persists chosen color when saving via outside click", async ({
|
||||
const resp = await savePromise;
|
||||
const note = await resp.json();
|
||||
|
||||
expect(note.title).toBe(title);
|
||||
expect(note.content).toBe(content);
|
||||
expect(note.color).toBe("red");
|
||||
});
|
||||
|
||||
@@ -68,9 +68,9 @@ async function loginViaUi(page, username) {
|
||||
]);
|
||||
}
|
||||
|
||||
async function createNoteViaComposer(page, title) {
|
||||
async function createNoteViaComposer(page, content) {
|
||||
await page.getByTestId("notes-composer").click();
|
||||
await page.getByTestId("notes-composer-title").fill(title);
|
||||
await page.getByTestId("notes-composer-content").fill(content);
|
||||
const savePromise = page.waitForResponse(
|
||||
(r) =>
|
||||
new URL(r.url()).pathname === "/api/notes" &&
|
||||
|
||||
@@ -105,11 +105,9 @@ test("sender → recipient → reply flow updates Inbox + Sent tabs", async ({
|
||||
await page.goto("/notes");
|
||||
await expect(page.getByTestId("notes-page")).toBeVisible();
|
||||
|
||||
const noteTitle = `E2E Note ${uniq()}`;
|
||||
const noteContent = "Please review attached.";
|
||||
const noteContent = `Please review attached. ${uniq()}`;
|
||||
|
||||
await page.getByTestId("notes-composer-open").click();
|
||||
await page.getByTestId("notes-composer-title").fill(noteTitle);
|
||||
await page.getByTestId("notes-composer-content").fill(noteContent);
|
||||
|
||||
const createPromise = page.waitForResponse(
|
||||
@@ -162,7 +160,7 @@ test("sender → recipient → reply flow updates Inbox + Sent tabs", async ({
|
||||
await page.getByTestId("notes-overflow-received").click();
|
||||
const inboxCard = page.getByTestId(`received-note-card-${createdNote.id}`);
|
||||
await expect(inboxCard).toBeVisible();
|
||||
await expect(inboxCard).toContainText(noteTitle);
|
||||
await expect(inboxCard).toContainText(noteContent);
|
||||
await expect(inboxCard).toContainText(sender.displayEn);
|
||||
await expect(
|
||||
inboxCard.getByTestId("status-badge-unread"),
|
||||
@@ -209,7 +207,7 @@ test("sender → recipient → reply flow updates Inbox + Sent tabs", async ({
|
||||
|
||||
const sentCard = page.getByTestId(`sent-note-card-${createdNote.id}`);
|
||||
await expect(sentCard).toBeVisible();
|
||||
await expect(sentCard).toContainText(noteTitle);
|
||||
await expect(sentCard).toContainText(noteContent);
|
||||
await expect(sentCard).toContainText(recipient.displayEn);
|
||||
// Per-recipient status pill flipped to "replied".
|
||||
const recipientPill = page.getByTestId(
|
||||
|
||||
@@ -83,7 +83,6 @@ test("recipient ticks a checklist item in the popup; sender sees the update", as
|
||||
|
||||
// Compose a checklist note with two items.
|
||||
await senderPage.getByTestId("notes-composer-open").click();
|
||||
await senderPage.getByTestId("notes-composer-title").fill(`Collab ${uniq()}`);
|
||||
await senderPage.getByTestId("notes-composer-kind-toggle").click();
|
||||
const list = senderPage.getByTestId("notes-composer-checklist-list");
|
||||
await expect(list).toBeVisible();
|
||||
|
||||
@@ -118,10 +118,8 @@ test("recipient sees a popup modal when a note arrives in real time", async ({
|
||||
await senderPage.goto("/notes");
|
||||
await expect(senderPage.getByTestId("notes-page")).toBeVisible();
|
||||
|
||||
const noteTitle = `Popup Note ${uniq()}`;
|
||||
const noteContent = "Drop everything and read this.";
|
||||
const noteContent = `Drop everything and read this. ${uniq()}`;
|
||||
await senderPage.getByTestId("notes-composer-open").click();
|
||||
await senderPage.getByTestId("notes-composer-title").fill(noteTitle);
|
||||
await senderPage.getByTestId("notes-composer-content").fill(noteContent);
|
||||
const createPromise = senderPage.waitForResponse(
|
||||
(r) =>
|
||||
@@ -167,7 +165,6 @@ test("recipient sees a popup modal when a note arrives in real time", async ({
|
||||
const popup = recipientPage.getByTestId("incoming-note-popup");
|
||||
await expect(popup).toBeVisible({ timeout: 3_000 });
|
||||
expect(Date.now() - sendCompletedAt).toBeLessThan(3_000);
|
||||
await expect(popup).toContainText(noteTitle);
|
||||
await expect(popup).toContainText(noteContent);
|
||||
await expect(popup).toContainText(sender.displayEn);
|
||||
// Floating card — NOT a modal: must not render as an alertdialog
|
||||
@@ -252,10 +249,8 @@ test("sender sees a floating reply card when the recipient replies", async ({
|
||||
await senderPage.goto("/notes");
|
||||
await expect(senderPage.getByTestId("notes-page")).toBeVisible();
|
||||
|
||||
const noteTitle = `Reply Note ${uniq()}`;
|
||||
const noteContent = "Please reply with your thoughts.";
|
||||
const noteContent = `Please reply with your thoughts. ${uniq()}`;
|
||||
await senderPage.getByTestId("notes-composer-open").click();
|
||||
await senderPage.getByTestId("notes-composer-title").fill(noteTitle);
|
||||
await senderPage.getByTestId("notes-composer-content").fill(noteContent);
|
||||
const createPromise = senderPage.waitForResponse(
|
||||
(r) =>
|
||||
@@ -403,9 +398,7 @@ test("recipient popup renders checklist items for a checklist note", async ({
|
||||
await senderPage.goto("/notes");
|
||||
await expect(senderPage.getByTestId("notes-page")).toBeVisible();
|
||||
|
||||
const noteTitle = `Checklist Note ${uniq()}`;
|
||||
await senderPage.getByTestId("notes-composer-open").click();
|
||||
await senderPage.getByTestId("notes-composer-title").fill(noteTitle);
|
||||
// Switch composer to checklist mode and add two items.
|
||||
await senderPage.getByTestId("notes-composer-kind-toggle").click();
|
||||
const list = senderPage.getByTestId("notes-composer-checklist-list");
|
||||
@@ -462,7 +455,6 @@ test("recipient popup renders checklist items for a checklist note", async ({
|
||||
|
||||
const popup = recipientPage.getByTestId("incoming-note-popup");
|
||||
await expect(popup).toBeVisible({ timeout: 5_000 });
|
||||
await expect(popup).toContainText(noteTitle);
|
||||
// Checklist branch: dedicated container is rendered, with each item
|
||||
// as a row, and the plain text content branch is NOT used.
|
||||
const checklist = recipientPage.getByTestId(
|
||||
|
||||
@@ -171,10 +171,8 @@ test("recipient on a touch device can tap Reply on the floating note popup", asy
|
||||
await senderPage.goto("/notes");
|
||||
await expect(senderPage.getByTestId("notes-page")).toBeVisible();
|
||||
|
||||
const noteTitle = `Touch Note ${uniq()}`;
|
||||
const noteContent = "Tap reply on this card.";
|
||||
const noteContent = `Tap reply on this card. ${uniq()}`;
|
||||
await senderPage.getByTestId("notes-composer-open").click();
|
||||
await senderPage.getByTestId("notes-composer-title").fill(noteTitle);
|
||||
await senderPage.getByTestId("notes-composer-content").fill(noteContent);
|
||||
const createPromise = senderPage.waitForResponse(
|
||||
(r) =>
|
||||
@@ -317,12 +315,10 @@ test("popup + meeting alert simultaneously: tap on popup still works", async ({
|
||||
await senderPage.goto("/notes");
|
||||
await expect(senderPage.getByTestId("notes-page")).toBeVisible();
|
||||
|
||||
const noteTitle = `Stack Note ${uniq()}`;
|
||||
await senderPage.getByTestId("notes-composer-open").click();
|
||||
await senderPage.getByTestId("notes-composer-title").fill(noteTitle);
|
||||
await senderPage
|
||||
.getByTestId("notes-composer-content")
|
||||
.fill("Tap reply with the meeting alert visible too.");
|
||||
.fill(`Tap reply with the meeting alert visible too. ${uniq()}`);
|
||||
const createPromise = senderPage.waitForResponse(
|
||||
(r) =>
|
||||
new URL(r.url()).pathname === "/api/notes" &&
|
||||
|
||||
@@ -38,7 +38,7 @@ export default defineConfig({
|
||||
await import("@replit/vite-plugin-cartographer").then((m) =>
|
||||
m.cartographer({
|
||||
root: path.resolve(import.meta.dirname, ".."),
|
||||
}),أحذ العنوان من الملاحظات
|
||||
}),
|
||||
),
|
||||
await import("@replit/vite-plugin-dev-banner").then((m) =>
|
||||
m.devBanner(),
|
||||
|
||||
Reference in New Issue
Block a user