Skip to content

Commit

Permalink
fix: scoping of breadcrumb links, add unified function that resolves …
Browse files Browse the repository at this point in the history
…urls for site
  • Loading branch information
manuelblum committed Jan 13, 2025
1 parent ce8dc9d commit f0d7e1c
Show file tree
Hide file tree
Showing 10 changed files with 145 additions and 20 deletions.
7 changes: 6 additions & 1 deletion demo/site/src/app/[domain]/[language]/[[...path]]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ExternalLinkBlockData, InternalLinkBlockData, RedirectsLinkBlockData }
import { documentTypes } from "@src/documents";
import { GQLPageTreeNodeScope, GQLPageTreeNodeScopeInput } from "@src/graphql.generated";
import { createGraphQLFetch } from "@src/util/graphQLClient";
import { resolveUrl } from "@src/util/resolveUrl";
import { getSiteConfigForDomain } from "@src/util/siteConfig";
import type { Metadata, ResolvingMetadata } from "next";
import { notFound, redirect } from "next/navigation";
Expand Down Expand Up @@ -86,7 +87,10 @@ export default async function Page({ params }: Props) {
case "internal": {
const internalLink = target.block.props as InternalLinkBlockData;
if (internalLink.targetPage) {
destination = `/${(internalLink.targetPage.scope as GQLPageTreeNodeScope).language}/${internalLink.targetPage.path}`;
destination = resolveUrl({
path: internalLink.targetPage.path,
scope: { language: (internalLink.targetPage.scope as GQLPageTreeNodeScope).language },
});
}
break;
}
Expand All @@ -95,6 +99,7 @@ export default async function Page({ params }: Props) {
break;
}
}

if (destination) {
redirect(destination);
}
Expand Down
10 changes: 9 additions & 1 deletion demo/site/src/app/[domain]/sitemap.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { resolveUrl } from "@src/util/resolveUrl";

export const dynamic = "force-dynamic"; // don't generate at build time

import { gql } from "@comet/cms-site";
Expand Down Expand Up @@ -40,7 +42,13 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const seoBlock = pageTreeNode.document.seo;
if (!seoBlock.noIndex) {
sitemap.push({
url: `${siteConfig.url}/${language}${pageTreeNode.path}`,
url: resolveUrl({
baseUrl: `${siteConfig.url}/`,
scope: {
language: language,
},
path: pageTreeNode.path,
}),
priority: Number(seoBlock.priority.replace("_", ".")),
changeFrequency: seoBlock.changeFrequency,
lastModified: pageTreeNode.document.updatedAt,
Expand Down
24 changes: 15 additions & 9 deletions demo/site/src/common/blocks/InternalLinkBlock.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client";
import { PropsWithData } from "@comet/cms-site";
import { InternalLinkBlockData } from "@src/blocks.generated";
import { resolveUrl } from "@src/util/resolveUrl";
import Link from "next/link";
import { PropsWithChildren } from "react";

Expand All @@ -14,16 +15,21 @@ export function InternalLinkBlock({ data: { targetPage, targetPageAnchor }, chil
return <span className={className}>{children}</span>;
}

let href = targetPageAnchor !== undefined ? `${targetPage.path}#${targetPageAnchor}` : targetPage.path;
if (targetPage.scope) {
const language = (targetPage.scope as Record<string, string>).language;
if (language) {
href = `/${language}${href}`;
}
}

return (
<Link href={href} title={title} className={className}>
<Link
href={resolveUrl({
scope:
targetPage.scope != null
? {
language: (targetPage.scope as Record<string, string>).language,
}
: null,
path: targetPage.path,
anchor: targetPageAnchor,
})}
title={title}
className={className}
>
{children}
</Link>
);
Expand Down
30 changes: 27 additions & 3 deletions demo/site/src/common/components/Breadcrumbs.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,48 @@
"use client";
import { resolveUrl } from "@src/util/resolveUrl";
import { Fragment } from "react";

import { GQLBreadcrumbsFragment } from "./Breadcrumbs.fragment.generated";
import * as sc from "./Breadcrumbs.sc";
import { GridRoot } from "./Breadcrumbs.sc";

const Breadcrumbs = ({ name, path, parentNodes }: GQLBreadcrumbsFragment) => {
type BreadcrumbsProps = GQLBreadcrumbsFragment & {
scope: {
language: string;
};
};
const Breadcrumbs = ({ scope, name, path, parentNodes }: BreadcrumbsProps) => {
return (
<GridRoot>
{parentNodes.length > 0 && (
<sc.Container>
{parentNodes.map((parentNode) => (
<Fragment key={parentNode.path}>
<sc.StyledLink href={parentNode.path}> {parentNode.name}</sc.StyledLink>
<sc.StyledLink
href={resolveUrl({
path: parentNode.path,
scope: {
language: scope.language,
},
})}
>
{parentNode.name}
</sc.StyledLink>

<sc.Divider />
</Fragment>
))}

<sc.StyledLink href={path}> {name}</sc.StyledLink>
<sc.StyledLink
href={resolveUrl({
path: path,
scope: {
language: scope.language,
},
})}
>
{name}
</sc.StyledLink>
</sc.Container>
)}
</GridRoot>
Expand Down
2 changes: 1 addition & 1 deletion demo/site/src/documents/pages/Page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ export async function Page({ pageTreeNodeId, scope }: { pageTreeNodeId: string;
)}
<TopNavigation data={data.topMenu} />
<Header header={data.header} />
<Breadcrumbs {...data.pageContent} />
<Breadcrumbs {...data.pageContent} scope={scope} />
<div>
<StageBlock data={document.stage} />
<PageContentBlock data={data.pageContent.document.content} />
Expand Down
13 changes: 11 additions & 2 deletions demo/site/src/layout/header/PageLink.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use client";
import { LinkBlock } from "@src/common/blocks/LinkBlock";
import { resolveUrl } from "@src/util/resolveUrl";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { PropsWithChildren } from "react";
Expand Down Expand Up @@ -34,13 +35,21 @@ function PageLink({ page, children, className: passedClassName, activeClassName
);
} else if (page.documentType === "Page") {
return (
<Link href={`/${page.scope.language}${page.path}`} className={className}>
<Link
href={resolveUrl({
path: page.path,
scope: {
language: page.scope.language,
},
})}
className={className}
>
{children}
</Link>
);
} else if (page.documentType === "PredefinedPage") {
return (
<Link href={`/${page.scope.language}${page.path}`} className={className}>
<Link href={resolveUrl({ path: page.path, scope: page.scope })} className={className}>
{children}
</Link>
);
Expand Down
11 changes: 10 additions & 1 deletion demo/site/src/news/NewsList.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use client";
import { DamImageBlock } from "@src/common/blocks/DamImageBlock";
import { resolveUrl } from "@src/util/resolveUrl";
import Link from "next/link";
import styled from "styled-components";

Expand All @@ -11,7 +12,15 @@ export function NewsList({ newsList }: { newsList: GQLNewsListFragment }) {
<h1>News</h1>
<CardList>
{newsList.nodes.map((news) => (
<Card key={news.id} href={`/${news.scope.language}/news/${news.slug}`}>
<Card
key={news.id}
href={resolveUrl({
scope: {
language: news.scope.language,
},
path: `/news/${news.slug}`,
})}
>
<DamImageBlock data={news.image} aspectRatio="4x3" />
<h2>{news.title}</h2>
{/* <p><FormattedDate value={news.createdAt} /></p> */}
Expand Down
12 changes: 11 additions & 1 deletion demo/site/src/news/blocks/NewsLinkBlock.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { PropsWithData } from "@comet/cms-site";
import { NewsLinkBlockData } from "@src/blocks.generated";
import { resolveUrl } from "@src/util/resolveUrl";
import Link from "next/link";
import { PropsWithChildren } from "react";

Expand All @@ -11,7 +12,16 @@ function NewsLinkBlock({ data: { news }, children, title, className }: PropsWith
}

return (
<Link href={`/${news.scope.language}/news/${news.slug}`} title={title} className={className}>
<Link
href={resolveUrl({
scope: {
language: news.scope.language,
},
path: `/news/${news.slug}`,
})}
title={title}
className={className}
>
{children}
</Link>
);
Expand Down
12 changes: 11 additions & 1 deletion demo/site/src/news/blocks/NewsListBlock.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { PropsWithData, withPreview } from "@comet/cms-site";
import { NewsListBlockData } from "@src/blocks.generated";
import { resolveUrl } from "@src/util/resolveUrl";
import Link from "next/link";

import { LoadedData } from "./NewsListBlock.loader";
Expand All @@ -14,7 +15,16 @@ export const NewsListBlock = withPreview(
<ol>
{newsList.map((news) => (
<li key={news.id}>
<Link href={`/${news.scope.language}/news/${news.slug}`}>{news.title}</Link>
<Link
href={resolveUrl({
scope: {
language: news.scope.language,
},
path: `/news/${news.slug}`,
})}
>
{news.title}
</Link>
</li>
))}
</ol>
Expand Down
44 changes: 44 additions & 0 deletions demo/site/src/util/resolveUrl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
type ResolveUrlOptions = {
/**
* If provided, the baseUrl will be prepended to the resulting path
*
* Sample: "http://localhost:3000"
* @default: "/"
*/
baseUrl?: string;

/**
* path with leading /
*
* Sample: "/test/to/my/page"
*/
path: string;

scope: {
language: string;
} | null;

/**
* If provided, the anchor will be appended to the resulting URL
*
* Sample: "my-anchor"
*/
anchor?: string;
};

/**
* Resolves a path, scope and a baseUrl to a real URL which can be navigated to.
*
* @return {string} The resolved URL: /{scope.language}/test/to/my/page#my-anchor
*/
export const resolveUrl = ({ baseUrl = "/", path, scope, anchor }: ResolveUrlOptions) => {
let safeScope = "";
if (scope != null) {
safeScope = scope.language;
}
let anchorPostfix = "";
if (anchor) {
anchorPostfix = `#${anchor}`;
}
return `${baseUrl}${safeScope}${path}${anchorPostfix}`;
};

0 comments on commit f0d7e1c

Please sign in to comment.