Skip to content

Commit 494dc76

Browse files
authored
FIX | Title updates to pages (#41)
* Custom block for Page title * loading data fixes * Title finally working * title working * remove ai package * remove unnecessary code
1 parent 1c16225 commit 494dc76

File tree

19 files changed

+706
-323
lines changed

19 files changed

+706
-323
lines changed

apps/web/src/app/(app)/@appSidebar/_components/app-sidebar-page-item.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export function AppSidebarPageItem(props: AppSidebarPageItemProps) {
3434
href={`/pages/${page?.id}`}
3535
className="line-clamp-1 min-w-0 flex-1 truncate hover:underline"
3636
>
37-
{page?.title}
37+
{page?.title || "New Page"}
3838
</Link>
3939
{!!page && (
4040
<DeletePageButton
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
"use client";
2+
3+
import { BlockEditor } from "~/components/editor/block-editor";
4+
5+
type PageBlocksProps = {
6+
parentId: string;
7+
parentType: "page" | "block";
8+
};
9+
10+
export function PageBlocks({ parentId, parentType }: PageBlocksProps) {
11+
return <BlockEditor parentId={parentId} parentType={parentType} />;
12+
}

apps/web/src/app/(app)/pages/_components/page-editor.tsx

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,14 @@
11
"use client";
22

3-
import { useQuery } from "@tanstack/react-query";
43
import { LazyBlockEditor } from "~/components/editor/lazy-block-editor";
5-
import { useTRPC } from "~/trpc/react";
6-
import { PageSkeleton } from "./page-skeleton";
7-
import { PageTitle } from "./page-title";
84

95
type PageEditorProps = {
106
id: string;
117
};
128

139
export function PageEditor({ id }: PageEditorProps) {
14-
const trpc = useTRPC();
15-
16-
const { data: page, isLoading } = useQuery({
17-
...trpc.pages.getById.queryOptions({ id }),
18-
// Refetch once when navigating to a page (not continuously)
19-
refetchOnMount: true,
20-
refetchOnWindowFocus: true,
21-
staleTime: 1000,
22-
});
23-
24-
// Show loading indicator when initially loading OR when refetching
25-
if (isLoading) {
26-
return <PageSkeleton />;
27-
}
28-
29-
if (!page) {
30-
return (
31-
<div className="flex h-full items-center justify-center">
32-
<p className="text-muted-foreground">Page not found</p>
33-
</div>
34-
);
35-
}
36-
3710
return (
38-
<div className="flex min-h-full flex-col gap-4 p-4">
39-
<PageTitle id={id} initialTitle={page.title ?? ""} />
11+
<div className="flex h-full flex-col gap-4 p-4">
4012
<div className="min-h-0 flex-1">
4113
<LazyBlockEditor parentId={id} parentType="page" />
4214
</div>

apps/web/src/app/(app)/pages/_components/page-skeleton.tsx

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,37 @@
11
import { Skeleton } from "~/components/ui/skeleton";
22
import { cn } from "~/components/utils";
33

4-
type PageSkeletonProps = Omit<React.ComponentProps<"div">, "children"> & {
5-
hasContent?: boolean;
6-
};
4+
type PageSkeletonProps = Omit<React.ComponentProps<"div">, "children">;
75

8-
export function PageSkeleton({
9-
className,
10-
hasContent = true,
11-
...rest
12-
}: PageSkeletonProps) {
6+
export function PageSkeleton({ className, ...rest }: PageSkeletonProps) {
137
return (
148
<div className={cn("flex flex-col gap-4 p-4", className)} {...rest}>
159
{/* Title skeleton */}
1610
<Skeleton className="h-10 w-3/5" />
1711

1812
{/* Content skeleton */}
1913
<div className="space-y-3">
20-
{hasContent ? (
21-
<>
22-
<Skeleton className="h-4 w-full" />
23-
<Skeleton className="h-4 w-11/12" />
24-
<Skeleton className="h-4 w-4/5" />
14+
<div className="flex flex-col gap-4 p-6">
15+
{/* Title skeleton */}
16+
<Skeleton className="h-8 w-2/3" />
17+
18+
{/* Content blocks skeleton */}
19+
<div className="space-y-4">
2520
<Skeleton className="h-4 w-full" />
26-
<Skeleton className="h-4 w-3/4" />
2721
<Skeleton className="h-4 w-5/6" />
28-
<Skeleton className="h-4 w-2/3" />
29-
</>
30-
) : (
31-
<Skeleton className="h-4 w-1/3" />
32-
)}
22+
<Skeleton className="h-4 w-4/5" />
23+
<div className="space-y-2">
24+
<Skeleton className="h-4 w-full" />
25+
<Skeleton className="h-4 w-3/4" />
26+
</div>
27+
<Skeleton className="h-32 w-full" />
28+
<div className="space-y-2">
29+
<Skeleton className="h-4 w-full" />
30+
<Skeleton className="h-4 w-2/3" />
31+
<Skeleton className="h-4 w-5/6" />
32+
</div>
33+
</div>
34+
</div>
3335
</div>
3436
</div>
3537
);

apps/web/src/app/(app)/pages/_components/page-title.tsx

Lines changed: 0 additions & 98 deletions
This file was deleted.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"use client";
2+
3+
import { BlockNoteSchema, defaultBlockSpecs } from "@blocknote/core";
4+
import { BlockNoteView } from "@blocknote/mantine";
5+
import { useCreateBlockNote } from "@blocknote/react";
6+
import { AlertBlock } from "~/components/ui/custom-blocks/alert-block";
7+
8+
// Minimal schema with NO custom blocks
9+
const schema = BlockNoteSchema.create({
10+
blockSpecs: {
11+
...defaultBlockSpecs,
12+
alert: AlertBlock,
13+
},
14+
});
15+
16+
export default function TestComponent() {
17+
const editor = useCreateBlockNote({
18+
initialContent: [
19+
{
20+
content: "Test paragraph",
21+
type: "paragraph",
22+
},
23+
{
24+
content: "This is an example alert",
25+
type: "alert",
26+
},
27+
{
28+
content: "Click the '!' icon to change the alert type",
29+
type: "paragraph",
30+
},
31+
],
32+
schema,
33+
});
34+
35+
return (
36+
<div style={{ minHeight: "400px", padding: "20px" }}>
37+
<h1>BlockNote Test Page</h1>
38+
<div style={{ border: "1px solid red", padding: "10px" }}>
39+
<BlockNoteView editor={editor} />
40+
</div>
41+
</div>
42+
);
43+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
"use client";
2+
3+
import TestComponent from "./component";
4+
5+
export default function TestBlockNotePage() {
6+
return <TestComponent />;
7+
}
Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
"use client";
22

3+
import { MantineProvider } from "@mantine/core";
34
import { ThreadRuntime } from "~/components/ai/thread-runtime";
45

56
export function AppProviders({ children }: { children: React.ReactNode }) {
67
return (
7-
<ThreadRuntime api="/api/chat" initialMessages={[]}>
8-
{children}
9-
</ThreadRuntime>
8+
<MantineProvider>
9+
<ThreadRuntime api="/api/chat" initialMessages={[]}>
10+
{children}
11+
</ThreadRuntime>
12+
</MantineProvider>
1013
);
1114
}
Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
"use client";
22

3+
import type { BlockIdentifier } from "@blocknote/core";
34
import { BlockNoteView } from "@blocknote/mantine";
45
import { useTheme } from "next-themes";
6+
import { useMemo, useRef } from "react";
7+
import { EditorTitle } from "~/components/editor/editor-title";
58
import { useBlockEditor } from "./hooks/use-block-editor";
69
import type { BlockNoteEditorProps } from "./types";
710

@@ -10,8 +13,11 @@ export function BlockEditor({
1013
parentId,
1114
parentType,
1215
isFullyLoaded,
16+
title,
17+
titlePlaceholder = "New page",
1318
}: BlockNoteEditorProps) {
1419
const { theme, systemTheme } = useTheme();
20+
const titleRef = useRef<HTMLInputElement>(null);
1521

1622
// Use the main editor hook that contains all the logic
1723
const { editor, handleEditorChange } = useBlockEditor(
@@ -21,21 +27,55 @@ export function BlockEditor({
2127
isFullyLoaded,
2228
);
2329

30+
const firstBlock = useMemo(() => {
31+
return editor.document[0];
32+
}, [editor.document]);
33+
2434
const resolvedTheme = theme === "system" ? systemTheme : theme;
2535

36+
const handleTitleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
37+
if (e.key === "Enter" || e.key === "ArrowDown") {
38+
e.preventDefault();
39+
if (editor && firstBlock) {
40+
editor.setTextCursorPosition(firstBlock.id as BlockIdentifier, "end");
41+
editor.focus();
42+
}
43+
}
44+
};
45+
46+
const handleEditorKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
47+
if (e.key === "ArrowUp" && parentType === "page") {
48+
const cursorPos = editor.getTextCursorPosition();
49+
if (cursorPos.block.id === firstBlock?.id) {
50+
titleRef.current?.focus();
51+
setTimeout(() => {
52+
const length = titleRef.current?.value.length || 0;
53+
titleRef.current?.setSelectionRange(length, length);
54+
}, 0);
55+
}
56+
}
57+
};
58+
2659
return (
27-
<div className="blocknote-editor" data-color-scheme={resolvedTheme}>
28-
<BlockNoteView
29-
editor={editor}
30-
onChange={isFullyLoaded ? handleEditorChange : undefined}
31-
editable={isFullyLoaded}
32-
theme={resolvedTheme as "light" | "dark"}
60+
<>
61+
<EditorTitle
62+
ref={titleRef}
63+
parentId={parentId}
64+
parentType={parentType}
65+
title={title}
66+
placeholder={titlePlaceholder}
67+
onKeyDown={handleTitleKeyDown}
68+
className="mb-4 pl-13"
3369
/>
34-
{!isFullyLoaded && (
35-
<div className="mb-2 text-muted-foreground text-sm">
36-
Loading blocks...
37-
</div>
38-
)}
39-
</div>
70+
<div className="blocknote-editor" data-color-scheme={resolvedTheme}>
71+
<BlockNoteView
72+
editor={editor}
73+
onChange={isFullyLoaded ? handleEditorChange : undefined}
74+
editable={isFullyLoaded}
75+
theme={resolvedTheme as "light" | "dark"}
76+
onKeyDown={handleEditorKeyDown}
77+
/>
78+
</div>
79+
</>
4080
);
4181
}

0 commit comments

Comments
 (0)