Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions src/app/[...puckPath]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ export async function generateMetadata({
const { puckPath = [] } = await params;
const path = `/${puckPath.join("/")}`;

console.log("Generating metadata for path:", path);

return {
title: (await getDocumentByPath(path))?.root.props?.title,
};
Expand Down
1 change: 0 additions & 1 deletion src/app/editor/ResourceCard.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { ReactNode } from "react";
import Link from "next/link";
import { Card, CardContent, CardFooter } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";

export function formatRelativeTime(date: Date): string {
Expand Down
46 changes: 13 additions & 33 deletions src/app/editor/[id]/[slug]/EditorPage.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import "@puckeditor/core/puck.css";
import type { Data } from "@puckeditor/core";
import { redirect } from "next/navigation";
import { notFound } from "next/navigation";
import { Client } from "./client";
import { getDocumentById } from "../../../../lib/documents/queries";
import { getEditorSlug, getEditorUrl } from "../../../../lib/editor-url";
import { getVersionContent } from "../../../../lib/documents/queries";
import { createEmptyPuckData } from "../../../../lib/puck/utils";
import { resolveEditorVersionId } from "./version-selection";
import { loadDocument } from "./params";

export default async function EditorPage({
documentId,
Expand All @@ -16,18 +15,17 @@ export default async function EditorPage({
slug: string;
versionId?: number;
}) {
const document = await getDocumentById(documentId);
const document = await loadDocument(documentId, slug, { versionId });

if (!document) {
notFound();
}
const targetVersionId = resolveEditorVersionId(document.versions, versionId);

const expectedSlug = getEditorSlug(document.name);
if (slug !== expectedSlug) {
redirect(getEditorUrl(documentId, document.name, versionId));
if (versionId !== undefined && targetVersionId === undefined) {
notFound();
}

const resolved = resolveVersion(versionId, document.versions);
const data = targetVersionId
? (await getVersionContent(targetVersionId)) ?? createEmptyPuckData()
: createEmptyPuckData();

const versions = document.versions.map((v) => ({
id: v.id,
Expand All @@ -37,32 +35,14 @@ export default async function EditorPage({

return (
<Client
key={`${documentId}-${resolved.versionId || "no-version"}`}
key={`${documentId}-${targetVersionId || "no-version"}`}
documentId={documentId}
documentName={document.name}
data={resolved.data}
versionId={resolved.versionId}
data={data}
versionId={targetVersionId}
publishedVersionId={document.publishedVersionId || undefined}
versions={versions}
isArchived={document.archivedAt !== null}
/>
);
}

function resolveVersion(
versionId: number | undefined,
versions: { id: number; documentId: number; content: unknown }[],
): { data: Data; versionId?: number } {
if (versionId !== undefined && !isNaN(versionId)) {
const match = versions.find((v) => v.id === versionId);
if (match) {
return { data: match.content as Data, versionId: match.id };
}
}

if (versions.length > 0) {
return { data: versions[0].content as Data, versionId: versions[0].id };
}

return { data: createEmptyPuckData() };
}
33 changes: 33 additions & 0 deletions src/app/editor/[id]/[slug]/PreviewPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { notFound } from "next/navigation";
import { Client } from "../../../[...puckPath]/client";
import { getVersionContent } from "../../../../lib/documents/queries";
import { resolvePreviewVersionId } from "./version-selection";
import { loadDocument } from "./params";

export default async function PreviewPage({
documentId,
slug,
versionId,
}: {
documentId: number;
slug: string;
versionId?: number;
}) {
const document = await loadDocument(documentId, slug, {
versionId,
redirectSuffix: "/preview",
});

const targetVersionId = resolvePreviewVersionId({
versions: document.versions,
publishedVersionId: document.publishedVersionId,
requestedVersionId: versionId,
});

if (!targetVersionId) notFound();

const data = await getVersionContent(targetVersionId);
if (!data) notFound();

return <Client data={data} />;
}
134 changes: 90 additions & 44 deletions src/app/editor/[id]/[slug]/VersionListPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Eye } from "lucide-react";
import { cn } from "../../../../lib/utils";
import type { Version } from "../../../../lib/types";

export interface VersionListPanelProps {
Expand All @@ -7,6 +9,7 @@ export interface VersionListPanelProps {
publishedVersionId?: number | null;
onLoadVersion: (versionId: number) => void;
onPublishVersion: (versionId: number) => void;
previewBaseUrl: string;
isPublishing?: boolean;
isPublishDisabled?: boolean;
}
Expand All @@ -18,13 +21,87 @@ function formatVersionLabel(version: Version) {
return { label, time };
}

function VersionEntry({
version,
isCurrent,
isPublished,
onLoad,
onPublish,
previewBaseUrl,
isPublishing,
isPublishDisabled,
}: {
version: Version;
isCurrent: boolean;
isPublished: boolean;
onLoad: () => void;
onPublish: () => void;
previewBaseUrl: string;
isPublishing?: boolean;
isPublishDisabled?: boolean;
}) {
const { label, time } = formatVersionLabel(version);

return (
<div
onClick={onLoad}
className={cn(
"px-3 py-2 rounded text-xs cursor-pointer transition",
isCurrent
? "bg-gray-100 font-semibold"
: "bg-white hover:bg-gray-50",
)}
>
<div className="flex items-start justify-between gap-2">
<div className="flex-1 min-w-0">
<span>{label}</span>

<div className="text-xs text-gray-500 mt-1">
{time}
</div>
</div>

<div className="flex items-center gap-1 shrink-0">
{isPublished ? (
<span className="px-2 py-0.5 bg-green-500 text-white rounded text-xs">
Published
</span>
) : (
<button
onClick={(e) => {
e.stopPropagation();
onPublish();
}}
disabled={isPublishing || isPublishDisabled}
className="px-2 py-0.5 text-xs text-gray-600 border border-gray-300 rounded hover:bg-gray-100 disabled:opacity-50 disabled:cursor-not-allowed transition"
>
Publish
</button>
)}
<a
href={`${previewBaseUrl}/${version.id}/preview`}
target="_blank"
rel="noopener noreferrer"
onClick={(e) => e.stopPropagation()}
className="p-1 text-gray-400 rounded hover:text-gray-600 hover:bg-gray-100 transition"
title="Preview"
>
<Eye size={14} />
</a>
</div>
</div>
</div>
);
}

export function VersionListPanel({
versions,
isLoading,
currentVersionId,
publishedVersionId,
onLoadVersion,
onPublishVersion,
previewBaseUrl,
isPublishing,
isPublishDisabled,
}: VersionListPanelProps) {
Expand All @@ -47,50 +124,19 @@ export function VersionListPanel({
) :
(
<div className="space-y-1 max-h-96">
{versions.map((version) => {
const isPublished = version.id === publishedVersionId;
const isCurrent = version.id === currentVersionId;
const { label, time } = formatVersionLabel(version);

return (
<div
key={version.id}
onClick={() => onLoadVersion(version.id)}
className={`px-3 py-2 rounded text-xs cursor-pointer transition ${
isCurrent
? "bg-gray-100 font-semibold"
: "bg-white hover:bg-gray-50"
}`}
>
<div className="flex items-start justify-between gap-2">
<div className="flex-1 min-w-0">
<span>{label}</span>

<div className="text-xs text-gray-500 mt-1">
{time}
</div>
</div>

{isPublished ? (
<span className="px-2 py-0.5 bg-green-500 text-white rounded text-xs shrink-0">
Published
</span>
) : (
<button
onClick={(e) => {
e.stopPropagation();
onPublishVersion(version.id);
}}
disabled={isPublishing || isPublishDisabled}
className="px-2 py-0.5 text-xs text-gray-600 border border-gray-300 rounded hover:bg-gray-100 disabled:opacity-50 disabled:cursor-not-allowed transition shrink-0"
>
Publish
</button>
)}
</div>
</div>
);
})}
{versions.map((version) => (
<VersionEntry
key={version.id}
version={version}
isCurrent={version.id === currentVersionId}
isPublished={version.id === publishedVersionId}
onLoad={() => onLoadVersion(version.id)}
onPublish={() => onPublishVersion(version.id)}
previewBaseUrl={previewBaseUrl}
isPublishing={isPublishing}
isPublishDisabled={isPublishDisabled}
/>
))}
</div>
)}
</div>
Expand Down
1 change: 1 addition & 0 deletions src/app/editor/[id]/[slug]/VersionPluginContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export function VersionPluginContainer() {
publishedVersionId={publishedVersionId}
onLoadVersion={handleLoadVersion}
onPublishVersion={handlePublishVersion}
previewBaseUrl={getEditorUrl(documentId, documentName)}
isPublishing={isPublishing}
isPublishDisabled={isArchived}
/>
Expand Down
31 changes: 4 additions & 27 deletions src/app/editor/[id]/[slug]/[versionId]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,7 @@
import type { Metadata } from "next";
import { notFound } from "next/navigation";
import EditorPage from "../EditorPage";
import { getDocumentName } from "../../../../../lib/documents/queries";

interface PageProps {
params: Promise<{ id: string; slug: string; versionId: string }>;
}

export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
const { id } = await params;
const name = await getDocumentName(parseInt(id, 10));
return {
title: name ? `Edit: ${name}` : "Document not found",
};
}

export default async function Page({ params }: PageProps) {
const { id, slug, versionId: versionIdParam } = await params;
const documentId = parseInt(id, 10);
const versionId = parseInt(versionIdParam, 10);

if (isNaN(documentId) || isNaN(versionId)) {
notFound();
}

return <EditorPage documentId={documentId} slug={slug} versionId={versionId} />;
}
import { createDocumentRoute } from "../params";

const { generateMetadata, Page } = createDocumentRoute(EditorPage, "Edit");
export { generateMetadata };
export default Page;
export const dynamic = "force-dynamic";
7 changes: 7 additions & 0 deletions src/app/editor/[id]/[slug]/[versionId]/preview/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import PreviewPage from "../../PreviewPage";
import { createDocumentRoute } from "../../params";

const { generateMetadata, Page } = createDocumentRoute(PreviewPage, "Preview");
export { generateMetadata };
export default Page;
export const dynamic = "force-dynamic";
30 changes: 4 additions & 26 deletions src/app/editor/[id]/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,7 @@
import type { Metadata } from "next";
import { notFound } from "next/navigation";
import EditorPage from "./EditorPage";
import { getDocumentName } from "../../../../lib/documents/queries";

interface PageProps {
params: Promise<{ id: string; slug: string }>;
}

export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
const { id } = await params;
const name = await getDocumentName(parseInt(id, 10));
return {
title: name ? `Edit: ${name}` : "Document not found",
};
}

export default async function Page({ params }: PageProps) {
const { id, slug } = await params;
const documentId = parseInt(id, 10);

if (isNaN(documentId)) {
notFound();
}

return <EditorPage documentId={documentId} slug={slug} />;
}
import { createDocumentRoute } from "./params";

const { generateMetadata, Page } = createDocumentRoute(EditorPage, "Edit");
export { generateMetadata };
export default Page;
export const dynamic = "force-dynamic";
Loading
Loading