244 lines
6.5 KiB
TypeScript
244 lines
6.5 KiB
TypeScript
"use client";
|
|
|
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
import { PortableText, createDataAttribute, stegaClean } from "next-sanity";
|
|
import {
|
|
getFileAsset,
|
|
getImageAsset,
|
|
getImageDimensions,
|
|
SanityFileAsset,
|
|
} from "@sanity/asset-utils";
|
|
import type {
|
|
BlockContent,
|
|
Button as SanityButton,
|
|
SanityImageAsset,
|
|
} from "@/sanity/sanity.types";
|
|
import { client } from "@/sanity/client";
|
|
import LinkButton from "./link-button";
|
|
import Image from "next/image";
|
|
import Link from "next/link";
|
|
import { DownloadIcon } from "lucide-react";
|
|
import { useDeconstructLink } from "@/lib/link-client";
|
|
import { dynamicHeight, generateImageUrl } from "@/lib/image-url";
|
|
import { Button } from "./ui/button";
|
|
import { sanityConnection } from "@repo/sanity-connection";
|
|
|
|
const { projectId, dataset } = client.config();
|
|
|
|
export function Callout(props: any) {
|
|
if (!props) {
|
|
return null;
|
|
}
|
|
return (
|
|
<div className="bg-secondary-500 text-secondary-50 shadow-lg rounded-md p-6 border my-6 border-gray-200">
|
|
<PortableText
|
|
value={props.value.content}
|
|
components={{
|
|
block: {
|
|
h1: ({ children }: any) => <h1>{children}</h1>,
|
|
h2: ({ children }: any) => <h2>{children}</h2>,
|
|
h3: ({ children }: any) => <h3>{children}</h3>,
|
|
h4: ({ children }: any) => <h4>{children}</h4>,
|
|
h5: ({ children }: any) => <h5>{children}</h5>,
|
|
h6: ({ children }: any) => <h6>{children}</h6>,
|
|
},
|
|
}}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function PortableButton(value: { value: SanityButton }) {
|
|
const linkData = useDeconstructLink(value.value.link);
|
|
return (
|
|
<div className="flex justify-left">
|
|
<LinkButton
|
|
className="px-5"
|
|
text={value.value.text ?? ""}
|
|
color={"default"}
|
|
linkData={linkData}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function PortableImage({
|
|
value,
|
|
isInline,
|
|
}: {
|
|
value: SanityImageAsset;
|
|
isInline: boolean;
|
|
}) {
|
|
const { width, height } = getImageDimensions(value);
|
|
const imageAsset = getImageAsset(value, {
|
|
projectId: sanityConnection.projectId ?? "",
|
|
dataset: sanityConnection.dataset ?? "",
|
|
});
|
|
const attr = createDataAttribute({
|
|
id: imageAsset._id,
|
|
type: imageAsset._type,
|
|
workspace: "production",
|
|
});
|
|
|
|
return (
|
|
<Image
|
|
data-sanity={attr("body")}
|
|
src={generateImageUrl(value)}
|
|
width={
|
|
isInline ? (width >= 100 ? 100 : width) : width >= 1200 ? 1200 : width
|
|
}
|
|
height={dynamicHeight(height, width, isInline)}
|
|
alt={value.altText || " "}
|
|
loading="lazy"
|
|
className="border rounded-md shadow-md"
|
|
style={{
|
|
display: isInline ? "inline-block" : "block",
|
|
aspectRatio: width / height,
|
|
maxHeight: "400px",
|
|
maxWidth: "100%",
|
|
width: "auto",
|
|
}}
|
|
/>
|
|
);
|
|
}
|
|
|
|
export function PortableFile({ value }: { value: SanityFileAsset }) {
|
|
const fileAsset = getFileAsset(value, {
|
|
projectId: projectId ?? "",
|
|
dataset: dataset ?? "",
|
|
});
|
|
const attr = createDataAttribute({
|
|
id: fileAsset._id,
|
|
type: fileAsset._type,
|
|
workspace: "production",
|
|
});
|
|
|
|
const filename: string =
|
|
fileAsset?.originalFilename ||
|
|
`${fileAsset.extension.charAt(0).toUpperCase() + fileAsset.extension?.slice(1)} herunterladen`;
|
|
|
|
return (
|
|
<div className="flex justify-left">
|
|
<LinkButton
|
|
linkData={{ href: fileAsset.url, target: "" }}
|
|
data-sanity={attr("body")}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
download
|
|
showIcon={false}
|
|
extraIcon={DownloadIcon}
|
|
text={
|
|
filename.length > 20
|
|
? `${filename.slice(0, 10)}...${filename.slice(-10)}`
|
|
: filename
|
|
}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function CustomLink(props: { children: React.ReactNode; value?: any }) {
|
|
const { value, children } = props;
|
|
|
|
let linkUrl = null;
|
|
let target = "_self";
|
|
|
|
if (value?.href) {
|
|
const href = value.href;
|
|
|
|
if (href.type === "external" && href.url) {
|
|
linkUrl = href.url;
|
|
target = href.blank ? "_blank" : "_self";
|
|
} else if (href.type === "internal" || href._type === "reference") {
|
|
const resolved = useDeconstructLink(href);
|
|
if (resolved) {
|
|
const ButtonComponent = Button as any;
|
|
return (
|
|
<ButtonComponent variant="link" className="p-0 h-auto font-normal" asChild>
|
|
<Link href={resolved}>
|
|
{children}
|
|
</Link>
|
|
</ButtonComponent>
|
|
);
|
|
}
|
|
} else if (typeof href === "string") {
|
|
linkUrl = href;
|
|
}
|
|
}
|
|
|
|
if (linkUrl) {
|
|
const ButtonComponent = Button as any;
|
|
return (
|
|
<ButtonComponent variant="link" className="p-0 h-auto font-normal" asChild>
|
|
<Link
|
|
href={linkUrl}
|
|
target={target}
|
|
rel={target === "_blank" ? "noopener noreferrer" : undefined}
|
|
>
|
|
{children}
|
|
</Link>
|
|
</ButtonComponent>
|
|
);
|
|
}
|
|
|
|
return <>{children}</>;
|
|
}
|
|
|
|
const components = {
|
|
types: {
|
|
image: PortableImage,
|
|
button: PortableButton,
|
|
callout: Callout,
|
|
file: PortableFile,
|
|
},
|
|
block: {
|
|
h1: ({ children }: any) => <h1>{children}</h1>,
|
|
h2: ({ children }: any) => <h2>{children}</h2>,
|
|
h3: ({ children }: any) => <h3>{children}</h3>,
|
|
h4: ({ children }: any) => <h4>{children}</h4>,
|
|
h5: ({ children }: any) => <h5>{children}</h5>,
|
|
h6: ({ children }: any) => <h6>{children}</h6>,
|
|
},
|
|
marks: {
|
|
left: ({ children }: { children: React.ReactNode }) => (
|
|
<span style={{ textAlign: "left", width: "100%", display: "block" }}>
|
|
{children}
|
|
</span>
|
|
),
|
|
center: ({ children }: { children: React.ReactNode }) => (
|
|
<span style={{ textAlign: "center", width: "100%", display: "block" }}>
|
|
{children}
|
|
</span>
|
|
),
|
|
right: ({ children }: { children: React.ReactNode }) => (
|
|
<span style={{ textAlign: "right", width: "100%", display: "block" }}>
|
|
{children}
|
|
</span>
|
|
),
|
|
textColor: ({
|
|
children,
|
|
value = { value: "" },
|
|
}: {
|
|
children: React.ReactNode;
|
|
value?: { value: string };
|
|
}) => <span style={{ color: stegaClean(value.value) }}>{children}</span>,
|
|
highlightColor: ({
|
|
children,
|
|
value = { value: "" },
|
|
}: {
|
|
children: React.ReactNode;
|
|
value?: { value: string };
|
|
}) => (
|
|
<span style={{ background: stegaClean(value.value) }}>{children}</span>
|
|
),
|
|
link: CustomLink,
|
|
},
|
|
};
|
|
|
|
export default function SanityBlock({ body }: { body: BlockContent }) {
|
|
if (!body) {
|
|
return null;
|
|
}
|
|
return <PortableText value={body} components={components} />;
|
|
}
|