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
6 changes: 4 additions & 2 deletions apps/sites/app/sites/[teamId]/(site)/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export async function generateMetadata(
alternates: { canonical: url },
openGraph: {
url,
type: "website",
title: seo.title,
description: seo.description ?? undefined,
images: seo.imageUrl ? [{ url: seo.imageUrl }] : undefined,
Expand Down Expand Up @@ -89,8 +90,9 @@ export default async function SitePage({ params }: Props) {
dangerouslySetInnerHTML={{
__html: serializeJsonLd(
organizationAndWebsiteJsonLd({
siteName: page.name,
logoUrl: null,
siteName: settings?.title || page.title || "Website",
description: settings?.subtitle || page.description || null,
logoUrl: (settings?.logo?.url as string | undefined) ?? null,
canonicalHost,
}),
),
Expand Down
3 changes: 2 additions & 1 deletion apps/sites/app/sites/[teamId]/(site)/docs/[...slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ async function apiMetadata(
title,
description,
alternates: { canonical: url },
openGraph: { url, title, description },
openGraph: { url, type: "article", title, description },
robots: { index, follow: index },
};
}
Expand Down Expand Up @@ -113,6 +113,7 @@ export async function generateMetadata(
alternates: { canonical: url },
openGraph: {
url,
type: "article",
title: seo.title,
description: seo.description ?? undefined,
images: seo.imageUrl ? [{ url: seo.imageUrl }] : undefined,
Expand Down
1 change: 1 addition & 0 deletions apps/sites/app/sites/[teamId]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export async function generateMetadata({
// wins). A non-canonical host gets `X-Robots-Tag` from the proxy.
robots: settings?.hideFromSearch ? { index: false, follow: false } : undefined,
openGraph: {
type: "website",
siteName: siteTitle,
images: logoUrl ? [{ url: logoUrl }] : undefined,
},
Expand Down
13 changes: 13 additions & 0 deletions apps/sites/lib/json-ld.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ describe("json-ld builders", () => {
it("builds Organization + WebSite for the homepage", () => {
const [org, site] = organizationAndWebsiteJsonLd({
siteName: "Acme",
description: "Rockets for everyone",
logoUrl: "https://cdn/logo.png",
canonicalHost: "acme.frontlit.app",
});
expect(org).toMatchObject({
"@type": "Organization",
name: "Acme",
url: "https://acme.frontlit.app",
description: "Rockets for everyone",
logo: "https://cdn/logo.png",
});
expect(site).toMatchObject({
Expand All @@ -27,6 +29,17 @@ describe("json-ld builders", () => {
});
});

it("omits description and logo when the site has none", () => {
const [org] = organizationAndWebsiteJsonLd({
siteName: "Acme",
description: null,
logoUrl: null,
canonicalHost: "acme.frontlit.app",
});
expect(org).not.toHaveProperty("description");
expect(org).not.toHaveProperty("logo");
});

it("builds BlogPosting with dateModified falling back to datePublished", () => {
const ld = blogPostingJsonLd({
title: "Hello",
Expand Down
3 changes: 3 additions & 0 deletions apps/sites/lib/json-ld.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

export function organizationAndWebsiteJsonLd(input: {
siteName: string;
description: string | null;
logoUrl: string | null;
canonicalHost: string;
}) {
Expand All @@ -16,13 +17,15 @@ export function organizationAndWebsiteJsonLd(input: {
"@type": "Organization",
name: input.siteName,
url,
...(input.description ? { description: input.description } : {}),
...(input.logoUrl ? { logo: input.logoUrl } : {}),
},
{
"@context": "https://schema.org",
"@type": "WebSite",
name: input.siteName,
url,
...(input.description ? { description: input.description } : {}),
},
];
}
Expand Down
6 changes: 6 additions & 0 deletions packages/page-builder/src/blocks/_shared/title.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,9 @@ export function renderTitle(title?: string, theme?: ThemeStyle): React.ReactNode
: part,
);
}

/** Heading element for item titles under a block title (cards, steps),
* one level below it so the outline never skips a level. */
export function itemHeadingLevel(titleLevel: HeadingLevel = "h2"): "h2" | "h3" | "h4" {
return titleLevel === "h1" ? "h2" : titleLevel === "h2" ? "h3" : "h4";
}
11 changes: 8 additions & 3 deletions packages/page-builder/src/blocks/featured/widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as Icons from "lucide-react";
import type { LucideIcon } from "lucide-react";
import type { ThemeStyle, WidgetProps } from "../../models";
import type { FeaturedSettings } from "./settings";
import { renderTitle, titleProps } from "../_shared/title";
import { itemHeadingLevel, renderTitle, titleProps } from "../_shared/title";
import { TextRenderer } from "../../components/text-renderer";
import {
Header1,
Expand Down Expand Up @@ -48,6 +48,7 @@ export function FeaturedWidget({
overriddenTheme.structure.page.width = maxWidth || theme.theme.structure.page.width;
overriddenTheme.structure.section.padding.y =
verticalPadding || theme.theme.structure.section.padding.y;
const itemLevel = itemHeadingLevel(titleLevel);

return (
<Section theme={overriddenTheme} id={cssId} background={background}>
Expand All @@ -74,7 +75,9 @@ export function FeaturedWidget({
<span className="text-3xl font-bold text-primary/30">
{String(index + 1).padStart(2, "0")}
</span>
<Header4 theme={overriddenTheme}>{item.title}</Header4>
<Header4 theme={overriddenTheme} as={itemLevel}>
{item.title}
</Header4>
{item.description && (
<Text2 theme={overriddenTheme}>{item.description}</Text2>
)}
Expand Down Expand Up @@ -119,7 +122,9 @@ export function FeaturedWidget({
layout === "compact" && "items-center gap-1",
)}
>
<Header4 theme={overriddenTheme}>{item.title}</Header4>
<Header4 theme={overriddenTheme} as={itemLevel}>
{item.title}
</Header4>
{item.description && layout !== "compact" && (
<Text2 theme={overriddenTheme}>{item.description}</Text2>
)}
Expand Down
26 changes: 21 additions & 5 deletions packages/page-builder/src/blocks/grid/widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import clsx from "clsx";
import * as Icons from "lucide-react";
import type { LucideIcon } from "lucide-react";
import type { ThemeStyle, WidgetProps } from "../../models";
import type { HeadingTag } from "../../primitives/heading";
import { getMediaUrl, getMediaAlt } from "../../models/media";
import type { GridItem, GridSettings } from "./settings";
import { renderTitle, titleProps } from "../_shared/title";
import { itemHeadingLevel, renderTitle, titleProps } from "../_shared/title";
import { Image } from "../../components/image";
import { NavLink } from "../../components/nav-link";
import { TextRenderer } from "../../components/text-renderer";
Expand All @@ -25,7 +26,15 @@ function resolveIcon(name?: string): LucideIcon | null {
return (Icons as unknown as Record<string, LucideIcon>)[name] ?? null;
}

function GridItemCard({ item, theme }: { item: GridItem; theme: ThemeStyle }) {
function GridItemCard({
item,
theme,
headingLevel,
}: {
item: GridItem;
theme: ThemeStyle;
headingLevel: HeadingTag;
}) {
const Icon = resolveIcon(item.icon);
const mediaUrl = getMediaUrl(item.media);
const mediaAlt = getMediaAlt(item.media);
Expand All @@ -50,7 +59,7 @@ function GridItemCard({ item, theme }: { item: GridItem; theme: ThemeStyle }) {
)}
<div className="absolute inset-0 bg-gradient-to-t from-black/70 via-black/10 to-transparent" />
<div className="absolute inset-x-0 bottom-0 p-4">
<Header4 theme={theme} className="text-white">
<Header4 theme={theme} as={headingLevel} className="text-white">
{item.title}
</Header4>
</div>
Expand Down Expand Up @@ -79,7 +88,9 @@ function GridItemCard({ item, theme }: { item: GridItem; theme: ThemeStyle }) {

const text = (
<div className="flex flex-col gap-2">
<Header4 theme={theme}>{item.title}</Header4>
<Header4 theme={theme} as={headingLevel}>
{item.title}
</Header4>
{item.description && <Text2 theme={theme}>{item.description}</Text2>}
{item.buttonAction && item.buttonCaption && (
<NavLink href={item.buttonAction} className="mt-2">
Expand Down Expand Up @@ -164,7 +175,12 @@ export function GridWidget({
)}
>
{items.map((item, index) => (
<GridItemCard key={index} item={item} theme={overriddenTheme} />
<GridItemCard
key={index}
item={item}
theme={overriddenTheme}
headingLevel={itemHeadingLevel(titleLevel)}
/>
))}
</div>
)}
Expand Down
2 changes: 1 addition & 1 deletion packages/page-builder/src/blocks/pricing/widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export function PricingWidget({
<Preheader theme={overriddenTheme}>{tier.name}</Preheader>
{tier.highlighted && <Badge theme={overriddenTheme}>Most popular</Badge>}
</div>
<PageCardHeader theme={overriddenTheme}>
<PageCardHeader theme={overriddenTheme} as="p">
{pricingSwitcher ? (
<>
<span className={monthlyOnly}>{tier.price}</span>
Expand Down
8 changes: 7 additions & 1 deletion packages/page-builder/src/primitives/card.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type React from "react";
import type { ThemeStyle } from "../models";
import { Header4 } from "./header4";
import type { HeadingTag } from "./heading";
import { cn } from "./lib/utils";

export interface PageCardProps extends React.HTMLAttributes<HTMLDivElement> {
Expand All @@ -27,6 +28,8 @@ export interface PageCardHeaderProps extends React.HTMLAttributes<HTMLDivElement
children: React.ReactNode;
className?: string;
theme?: ThemeStyle;
/** Element for the header text; defaults to `h4`. */
as?: HeadingTag;
}

export const PageCard: React.FC<PageCardProps> = ({
Expand Down Expand Up @@ -102,6 +105,7 @@ export const PageCardHeader: React.FC<PageCardHeaderProps> = ({
children,
className = "",
theme,
as,
...props
}) => {
const classes = cn(
Expand All @@ -112,7 +116,9 @@ export const PageCardHeader: React.FC<PageCardHeaderProps> = ({

return (
<div className={classes} {...props}>
<Header4 theme={theme}>{children}</Header4>
<Header4 theme={theme} as={as}>
{children}
</Header4>
</div>
);
};
Loading