WikiTemplates gains two fields: an optional css code field injected on pages that use the template (selectors auto-scoped to a hash-based .ptf-tpl-* class so templates never collide with each other or the app shell), and a developer-only html field rendered verbatim (sanitized) in place of the markdown body when transcluded. - templateCss.ts scopes/sanitizes CSS, incl. nested at-rules; pages and the editor live preview inject scoped styles and drop the Card chrome in favor of a full-width article - templateHtml.ts sanitizes HTML and splits prepared markdown around expansion sentinels so raw HTML renders without rehype-raw - templates.ts adds extractTemplateNames/extractPlaceholders/ extractDirectiveNames; prepareMarkdown threads an htmlTemplates map - markdown pipeline gains rehype-highlight for fenced code blocks - styles.css adds wiki prose typography + infobox demo styling - docs: demo article and Infobox template authoring guide
140 lines
4.3 KiB
TypeScript
140 lines
4.3 KiB
TypeScript
import config from "@payload-config";
|
|
import Link from "next/link";
|
|
import { redirect } from "next/navigation";
|
|
import { headers as nextHeaders } from "next/headers";
|
|
import { getPayload } from "payload";
|
|
import { ArrowLeftIcon, PencilIcon } from "lucide-react";
|
|
import { Card, CardContent } from "@/components/ui/card";
|
|
import { LockdownBanner } from "@/components/frontend/wiki/LockdownBanner";
|
|
import { WikiEditor } from "@/components/frontend/wiki/WikiEditor";
|
|
import { listTemplates, templateMap } from "@/lib/wiki/service";
|
|
|
|
export const metadata = {
|
|
title: "Edit wiki page — Polaris Task Force",
|
|
};
|
|
|
|
function templateCssMap(
|
|
templates: readonly { name: string; css?: string | null }[],
|
|
): Record<string, string> {
|
|
const map: Record<string, string> = {};
|
|
for (const template of templates) {
|
|
if (template.css && template.css.trim().length > 0) map[template.name] = template.css;
|
|
}
|
|
return map;
|
|
}
|
|
|
|
function templateHtmlMap(
|
|
templates: readonly { name: string; html?: string | null }[],
|
|
): Record<string, string> {
|
|
const map: Record<string, string> = {};
|
|
for (const template of templates) {
|
|
if (template.html && template.html.trim().length > 0) map[template.name] = template.html;
|
|
}
|
|
return map;
|
|
}
|
|
|
|
export default async function EditWikiPage({
|
|
params,
|
|
}: {
|
|
readonly params: Promise<{ slug: string }>;
|
|
}) {
|
|
const { slug } = await params;
|
|
const payload = await getPayload({ config });
|
|
const headers = await nextHeaders();
|
|
const { user } = await payload.auth({ headers, canSetHeaders: false });
|
|
const pageResult = await payload.find({
|
|
collection: "wiki-pages",
|
|
where: { slug: { equals: slug } },
|
|
limit: 1,
|
|
depth: 1,
|
|
overrideAccess: true,
|
|
});
|
|
const page = pageResult.docs[0];
|
|
|
|
if (!page) {
|
|
redirect(`/wiki/new?title=${encodeURIComponent(slug)}`);
|
|
}
|
|
|
|
if (page.lockdown) {
|
|
return (
|
|
<div className="flex flex-col gap-6 p-5">
|
|
<Link
|
|
href={`/wiki/${page.slug}`}
|
|
className="flex w-fit items-center gap-1 text-sm text-muted-foreground transition-colors hover:text-primary"
|
|
>
|
|
<ArrowLeftIcon className="size-3" />
|
|
Back to page
|
|
</Link>
|
|
<div className="flex items-center gap-2">
|
|
<PencilIcon className="size-5 text-muted-foreground" />
|
|
<h1 className="text-lg font-semibold">Edit {page.title}</h1>
|
|
</div>
|
|
<LockdownBanner />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const [templates, pagesResult, hasEditedRevisions] = await Promise.all([
|
|
listTemplates(payload),
|
|
payload.find({
|
|
collection: "wiki-pages",
|
|
sort: "title",
|
|
limit: 200,
|
|
depth: 0,
|
|
overrideAccess: true,
|
|
}),
|
|
user
|
|
? payload
|
|
.find({
|
|
collection: "wiki-revisions",
|
|
where: { editor: { equals: user.id } },
|
|
limit: 1,
|
|
depth: 0,
|
|
overrideAccess: true,
|
|
})
|
|
.then((result) => result.docs.length > 0)
|
|
: Promise.resolve(true),
|
|
]);
|
|
const existingPages = pagesResult.docs.map((existingPage) => ({ title: existingPage.title }));
|
|
|
|
return (
|
|
<div className="flex flex-col gap-6 p-5">
|
|
<div className="flex flex-col gap-3">
|
|
<Link
|
|
href={`/wiki/${page.slug}`}
|
|
className="flex w-fit items-center gap-1 text-sm text-muted-foreground transition-colors hover:text-primary"
|
|
>
|
|
<ArrowLeftIcon className="size-3" />
|
|
Back to page
|
|
</Link>
|
|
<div className="flex items-center gap-2">
|
|
<PencilIcon className="size-5 text-muted-foreground" />
|
|
<h1 className="text-lg font-semibold">Edit {page.title}</h1>
|
|
</div>
|
|
<p className="text-sm text-muted-foreground">
|
|
Update the article and leave a short note for the revision history.
|
|
</p>
|
|
</div>
|
|
|
|
<Card>
|
|
<CardContent className="p-6">
|
|
<WikiEditor
|
|
mode="edit"
|
|
initial={{
|
|
id: page.id,
|
|
title: page.title,
|
|
category: page.category,
|
|
tags: page.tags ?? [],
|
|
body: page.body,
|
|
}}
|
|
templates={templateMap(templates)}
|
|
templateCssMap={templateCssMap(templates)}
|
|
templateHtmlMap={templateHtmlMap(templates)}
|
|
existingPages={existingPages}
|
|
isFirstEdit={!hasEditedRevisions}
|
|
/>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|