Skip to content

Commit 71c544e

Browse files
authored
Improve editor UX/UI and functionality (#14)
* Add validated t3 environment * Add missing file * Modernise editor and asset manager * Add syntax highlighting to markdown editor * Add ScrollArea to asset manager * Add search modal and make search ui better, with pagination * Improved tagging functionality * Fix transaction type linter error Added cache time back for images
1 parent ff14b00 commit 71c544e

38 files changed

+3807
-1247
lines changed

package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,12 @@
2222
"dependencies": {
2323
"@auth/drizzle-adapter": "^1.8.0",
2424
"@codemirror/lang-markdown": "^6.3.2",
25+
"@codemirror/language": "^6.11.0",
26+
"@codemirror/language-data": "^6.5.1",
2527
"@codemirror/state": "^6.5.2",
28+
"@lezer/highlight": "^1.2.1",
2629
"@neondatabase/serverless": "^1.0.0",
30+
"@radix-ui/react-dialog": "^1.1.10",
2731
"@radix-ui/react-icons": "^1.3.2",
2832
"@radix-ui/react-label": "^2.1.3",
2933
"@radix-ui/react-popover": "^1.1.7",
@@ -44,9 +48,11 @@
4448
"@types/pg": "^8.11.13",
4549
"@types/react-syntax-highlighter": "^15.5.13",
4650
"@uiw/codemirror-theme-tokyo-night-storm": "^4.23.10",
51+
"@uiw/codemirror-theme-xcode": "^4.23.10",
4752
"@uiw/codemirror-themes": "^4.23.10",
4853
"@uiw/react-codemirror": "^4.23.10",
4954
"bcrypt": "^5.1.1",
55+
"cmdk": "^1.1.1",
5056
"date-fns": "^4.1.0",
5157
"dotenv": "^16.5.0",
5258
"drizzle-orm": "^0.42.0",

pnpm-lock.yaml

Lines changed: 507 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/app/admin/assets/page.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export default function AssetsAdminPage() {
3838
);
3939

4040
// Handle asset deletion
41-
const handleDeleteAsset = (assetId: number) => {
41+
const handleDeleteAsset = (assetId: string) => {
4242
if (confirm("Are you sure you want to delete this asset?")) {
4343
deleteAssetMutation.mutate({ id: assetId });
4444
}
@@ -191,9 +191,9 @@ export default function AssetsAdminPage() {
191191
{asset.uploadedBy?.name || "Unknown"}
192192
</td>
193193
<td className="px-4 py-2">
194-
{asset.pageId ? (
194+
{asset.id ? (
195195
<a
196-
href={`/admin/wiki/${asset.pageId}`}
196+
href={`/admin/wiki/${asset.id}`}
197197
className="text-blue-600 hover:underline"
198198
>
199199
View Page

src/app/api/assets/[id]/route.ts

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,54 @@
11
import { NextResponse } from "next/server";
22
import { assetService } from "~/lib/services";
3+
import { z } from "zod";
4+
5+
const ParamsSchema = z.object({
6+
id: z.string().uuid("Invalid asset ID format"),
7+
});
38

49
export async function GET(
510
request: Request,
6-
{ params }: { params: Promise<{ id: string }> }
11+
context: { params: Promise<{ id: string }> }
712
) {
8-
const resolvedParams = await params;
9-
void resolvedParams;
1013
try {
11-
const id = parseInt(resolvedParams.id, 10);
12-
if (isNaN(id)) {
13-
return NextResponse.json({ error: "Invalid asset ID" }, { status: 400 });
14+
// Access params from the context
15+
const params = context.params;
16+
const resolvedParams = await params;
17+
18+
// Validate the ID format using Zod
19+
const validationResult = ParamsSchema.safeParse(resolvedParams);
20+
if (!validationResult.success) {
21+
console.error("Zod Validation Error:", validationResult.error.format()); // Log detailed Zod error
22+
return NextResponse.json(
23+
{ error: validationResult.error.errors[0].message },
24+
{ status: 400 }
25+
);
1426
}
1527

28+
const { id } = validationResult.data;
29+
1630
// Get asset from database using service
1731
const asset = await assetService.getById(id);
1832

1933
if (!asset) {
2034
return NextResponse.json({ error: "Asset not found" }, { status: 404 });
2135
}
2236

37+
// Extract base64 data if it includes the data URI prefix
38+
let base64Data = asset.data;
39+
const prefixMatch = asset.data.match(/^data:.*;base64,/);
40+
if (prefixMatch) {
41+
base64Data = asset.data.substring(prefixMatch[0].length);
42+
}
43+
2344
// Decode base64 data
24-
const buffer = Buffer.from(asset.data, "base64");
45+
const buffer = Buffer.from(base64Data, "base64");
2546

26-
// Create response with appropriate Content-Type
47+
// Create response with appropriate Content-Type and Cache-Control
2748
return new NextResponse(buffer, {
2849
headers: {
2950
"Content-Type": asset.fileType,
3051
"Content-Disposition": `inline; filename="${asset.fileName}"`,
31-
"Cache-Control": "public, max-age=31536000", // Cache for 1 year
3252
},
3353
});
3454
} catch (error) {

src/app/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
} from "~/components/ui/card";
1111
import { WikiBrowser } from "~/components/wiki/WikiBrowser";
1212
import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs";
13-
import { WikiPageList } from "~/components/wiki/WikiPageList";
13+
import { AllPagesList } from "~/components/wiki/AllPagesList";
1414

1515
export default async function Home() {
1616
// Server-side data fetching - using our centralized service
@@ -49,7 +49,7 @@ export default async function Home() {
4949
</TabsContent>
5050

5151
<TabsContent value="all">
52-
<WikiPageList initialSortBy="title" initialSortOrder="asc" />
52+
<AllPagesList />
5353
</TabsContent>
5454
</Tabs>
5555

src/app/tags/[tag]/page.tsx

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,33 @@ export default async function TagPage({
1212

1313
const filteredPages = await dbService.tags.getPagesByTagName(tag);
1414

15+
// Extract the actual page data from the relations
16+
// Need to ensure the shape matches WikiPageListItem (dates might need conversion if inconsistent)
17+
const pagesToDisplay =
18+
filteredPages?.pages
19+
.map((relation) => relation.page)
20+
.filter(Boolean) // Filter out any potentially null pages
21+
.map((page) => ({
22+
...page,
23+
// Convert Date objects to ISO strings to match the expected type
24+
createdAt: page.createdAt?.toISOString() || null,
25+
updatedAt: page.updatedAt?.toISOString() || null,
26+
lockedAt: page.lockedAt?.toISOString() || null, // Add conversion for lockedAt
27+
lockExpiresAt: page.lockExpiresAt?.toISOString() || null, // Add conversion for lockExpiresAt
28+
// Convert dates within the nested tags array
29+
tags:
30+
page.tags?.map((tagRelation) => ({
31+
...tagRelation,
32+
tag: {
33+
...tagRelation.tag,
34+
createdAt: tagRelation.tag.createdAt?.toISOString() || null,
35+
},
36+
})) || [],
37+
})) || [];
38+
1539
return (
1640
<MainLayout>
17-
<div className="space-y-6">
41+
<div className="p-4 space-y-6">
1842
<div className="flex items-center justify-between">
1943
<div>
2044
<h1 className="flex items-center text-2xl font-bold">
@@ -31,7 +55,7 @@ export default async function TagPage({
3155
</div>
3256
</div>
3357

34-
<WikiPageList />
58+
<WikiPageList pages={pagesToDisplay} isLoading={false} />
3559
</div>
3660
</MainLayout>
3761
);

src/components/layout/Header.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { UserMenu } from "../auth/UserMenu";
2-
import { SearchBar } from "~/components/layout/SearchBar";
32
import { Suspense } from "react";
43
import { ThemeToggle } from "~/components/layout/theme-toggle";
54
import { AdminButton } from "~/components/layout/AdminButton";
@@ -8,9 +7,7 @@ export function Header() {
87
return (
98
<header className="flex items-center justify-between h-16 px-4 border-b shadow-sm border-border-default bg-background-paper">
109
<div className="flex items-center flex-1 max-w-xl">
11-
<Suspense fallback={<div>Loading search bar...</div>}>
12-
<SearchBar />
13-
</Suspense>
10+
<Suspense fallback={<div>Loading search bar...</div>}></Suspense>
1411
</div>
1512

1613
<div className="flex items-center space-x-4">

src/components/layout/SearchBar.tsx

Lines changed: 0 additions & 181 deletions
This file was deleted.

0 commit comments

Comments
 (0)