Compare commits
6 Commits
e5a797718f
...
renovate/n
Author | SHA1 | Date | |
---|---|---|---|
|
f956ce3d77 | ||
5acb6a4f81 | |||
|
7b7a3cf2a0 | ||
ee25889139 | |||
7cf1b4d16e | |||
e1b7beadbd |
@ -1,5 +1,6 @@
|
||||
{
|
||||
"siteName": "Pulse App",
|
||||
"ogApiUrl": "https://docs.pulseapp.cc/api/og?title={title}",
|
||||
"metadata": {
|
||||
"title": {
|
||||
"default": "Pulse Docs",
|
||||
|
@ -20,6 +20,7 @@
|
||||
"@radix-ui/react-separator": "^1.1.0",
|
||||
"@radix-ui/react-slot": "^1.1.0",
|
||||
"@radix-ui/react-tooltip": "^1.1.3",
|
||||
"@vercel/og": "^0.6.3",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "1.0.0",
|
||||
@ -27,7 +28,7 @@
|
||||
"lucide-react": "^0.452.0",
|
||||
"luxon": "^3.5.0",
|
||||
"next": "^15.0.0-canary.179",
|
||||
"next-themes": "^0.3.0",
|
||||
"next-themes": "^0.4.0",
|
||||
"react": "^19.0.0-rc-1460d67c-20241003",
|
||||
"react-dom": "^19.0.0-rc-1460d67c-20241003",
|
||||
"remark-gfm": "^4.0.0",
|
||||
@ -42,7 +43,7 @@
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.2.15",
|
||||
"eslint-config-next": "15.0.0",
|
||||
"postcss": "^8",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5"
|
||||
|
@ -14,6 +14,7 @@ import { Metadata } from "next";
|
||||
import Embed from "@/components/embed";
|
||||
import DocsFooter from "@/components/docs-footer";
|
||||
import OnThisPage from "@/components/on-this-page";
|
||||
import config from "@/config";
|
||||
|
||||
/**
|
||||
* The page to render the documentation markdown content.
|
||||
@ -99,17 +100,19 @@ export const generateMetadata = async ({
|
||||
}): Promise<Metadata | undefined> => {
|
||||
const slug: string = (((await params).slug as string[]) || undefined)?.join(
|
||||
"/"
|
||||
); // The slug of the content
|
||||
);
|
||||
if (slug) {
|
||||
const content: DocsContentMetadata | undefined = (
|
||||
await getDocsContent()
|
||||
).find(
|
||||
(metadata: DocsContentMetadata): boolean => metadata.slug === slug
|
||||
); // Get the content based on the provided slug
|
||||
if (content) {
|
||||
const pages: DocsContentMetadata[] = await getDocsContent();
|
||||
const decodedSlug: string = decodeURIComponent(slug || "");
|
||||
const page: DocsContentMetadata | undefined = pages.find(
|
||||
(metadata: DocsContentMetadata): boolean =>
|
||||
metadata.slug === (decodedSlug || pages[0].slug)
|
||||
);
|
||||
if (page) {
|
||||
return Embed({
|
||||
title: content.title,
|
||||
description: content.summary,
|
||||
title: page.title,
|
||||
description: page.summary,
|
||||
thumbnail: config.ogApiUrl.replace("{title}", page.title),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
30
src/app/api/og/route.tsx
Normal file
30
src/app/api/og/route.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import { ImageResponse } from "next/og";
|
||||
import config from "@/config";
|
||||
|
||||
export const GET = async (request: Request) => {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const title: string | undefined = searchParams.has("title")
|
||||
? searchParams.get("title")?.slice(0, 100)
|
||||
: "Hello World (:";
|
||||
|
||||
return new ImageResponse(
|
||||
(
|
||||
<div tw="w-full h-full flex flex-col justify-center items-center bg-black/95 text-white">
|
||||
{/* Logo */}
|
||||
<img
|
||||
src={(config.metadata.openGraph?.images as any)[0].url}
|
||||
alt={`${config.siteName} Logo`}
|
||||
width={96}
|
||||
height={96}
|
||||
/>
|
||||
|
||||
{/* Title */}
|
||||
<h1 tw="text-5xl font-bold">{title}</h1>
|
||||
</div>
|
||||
),
|
||||
{
|
||||
width: 1200,
|
||||
height: 630,
|
||||
}
|
||||
);
|
||||
};
|
@ -6,6 +6,11 @@ export type Config = {
|
||||
*/
|
||||
siteName: string;
|
||||
|
||||
/**
|
||||
* The URL to use for generating OG images.
|
||||
*/
|
||||
ogApiUrl: string;
|
||||
|
||||
/**
|
||||
* The metadata for this app.
|
||||
*/
|
||||
|
@ -13,6 +13,11 @@ type EmbedProps = {
|
||||
* The description of the embed.
|
||||
*/
|
||||
description: string;
|
||||
|
||||
/**
|
||||
* The optional thumbnail image of the embed.
|
||||
*/
|
||||
thumbnail?: string | undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -21,13 +26,25 @@ type EmbedProps = {
|
||||
* @param props the embed props
|
||||
* @returns the embed jsx
|
||||
*/
|
||||
const Embed = ({ title, description }: EmbedProps): Metadata => {
|
||||
const Embed = ({ title, description, thumbnail }: EmbedProps): Metadata => {
|
||||
return {
|
||||
title: title,
|
||||
openGraph: {
|
||||
title: `${title}`,
|
||||
description: description,
|
||||
...(thumbnail && {
|
||||
images: [
|
||||
{
|
||||
url: thumbnail,
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
...(thumbnail && {
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
},
|
||||
}),
|
||||
};
|
||||
};
|
||||
export default Embed;
|
||||
|
@ -18,6 +18,7 @@ type Header = {
|
||||
};
|
||||
|
||||
const OnThisPage = ({ page }: { page: DocsContentMetadata }): ReactElement => {
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [headers, setHeaders] = useState<Header[]>([]);
|
||||
const [activeHeader, setActiveHeader] = useState<string | undefined>(
|
||||
undefined
|
||||
@ -45,6 +46,7 @@ const OnThisPage = ({ page }: { page: DocsContentMetadata }): ReactElement => {
|
||||
}
|
||||
|
||||
setHeaders(extractedHeaders);
|
||||
setLoading(false);
|
||||
}, [page.content]);
|
||||
|
||||
useEffect(() => {
|
||||
@ -94,8 +96,10 @@ const OnThisPage = ({ page }: { page: DocsContentMetadata }): ReactElement => {
|
||||
|
||||
{/* Headers */}
|
||||
<ul className="relative">
|
||||
{headers.length === 0 ? (
|
||||
{loading ? (
|
||||
<Skeleton className="w-full h-5 bg-accent rounded-lg" />
|
||||
) : headers.length === 0 ? (
|
||||
<span className="opacity-75">Nothing ):</span>
|
||||
) : (
|
||||
headers.map((header: Header) => (
|
||||
<li
|
||||
|
@ -44,7 +44,7 @@ const getDocsDirectory = cache(async (): Promise<string> => {
|
||||
|
||||
// Pull the latest changes from the repo if we don't have it
|
||||
if (!fs.existsSync(cacheDir) || fs.readdirSync(cacheDir).length < 1) {
|
||||
console.log("Fetching initial docs from Git...");
|
||||
console.log("Cloning initial docs content from Git...");
|
||||
try {
|
||||
await simpleGit().clone(DOCS_DIR, cacheDir, { "--depth": 1 });
|
||||
storeUpdatedRepoTime();
|
||||
@ -55,7 +55,7 @@ const getDocsDirectory = cache(async (): Promise<string> => {
|
||||
}
|
||||
} else if (shouldUpdateRepo()) {
|
||||
// Pull the latest changes from Git
|
||||
console.log("Updating docs content from Git...");
|
||||
console.log("Pulling docs content from Git...");
|
||||
await simpleGit(cacheDir)
|
||||
.reset(["--hard"]) // Reset any local changes
|
||||
.pull(); // Pull latest changes
|
||||
|
Reference in New Issue
Block a user