use next/link for internal links, even if absolute from the cms
This commit is contained in:
@@ -2,6 +2,7 @@ import { AssociationFragment } from "@/gql/graphql";
|
||||
import { Image } from "@/components/general/Image";
|
||||
import styles from './associationHeader.module.scss';
|
||||
import { Breadcrumb } from "../general/Breadcrumb";
|
||||
import { RichText } from "@/components/general/RichText";
|
||||
import { Icon } from "../general/Icon";
|
||||
|
||||
export const AssociationHeader = ({
|
||||
@@ -15,10 +16,9 @@ export const AssociationHeader = ({
|
||||
<Breadcrumb link="/foreninger" text={association.associationType ? association.associationType : "Foreninger"} />
|
||||
<h1>{association.title}</h1>
|
||||
{association.lead && (
|
||||
<div
|
||||
className="lead"
|
||||
dangerouslySetInnerHTML={{ __html: association.lead }}
|
||||
/>
|
||||
<div className="lead">
|
||||
<RichText content={association.lead} />
|
||||
</div>
|
||||
)}
|
||||
{association.websiteUrl && (
|
||||
<a className="button" href={association.websiteUrl} target="_blank">
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
type ContactSectionBlockFragment,
|
||||
type ContactSubsectionBlockFragment,
|
||||
} from "@/gql/graphql";
|
||||
import { RichText } from "@/components/general/RichText";
|
||||
import styles from "./contactSection.module.scss";
|
||||
import { Blocks } from "./Blocks";
|
||||
|
||||
@@ -37,10 +38,9 @@ export const ContactSectionBlock = ({
|
||||
<section className={styles.contactSection}>
|
||||
<h2 className={styles.heading}>{block.title}</h2>
|
||||
{block.text && (
|
||||
<p
|
||||
className={styles.intro}
|
||||
dangerouslySetInnerHTML={{ __html: block.text }}
|
||||
/>
|
||||
<div className={styles.intro}>
|
||||
<RichText content={block.text} />
|
||||
</div>
|
||||
)}
|
||||
<Blocks blocks={block.blocks} />
|
||||
</section>
|
||||
@@ -56,10 +56,9 @@ export const ContactSubsectionBlock = ({
|
||||
<section className={styles.contactSubsection}>
|
||||
<h3 className={styles.heading}>{block.title}</h3>
|
||||
{block.text && (
|
||||
<p
|
||||
className={styles.intro}
|
||||
dangerouslySetInnerHTML={{ __html: block.text }}
|
||||
/>
|
||||
<div className={styles.intro}>
|
||||
<RichText content={block.text} />
|
||||
</div>
|
||||
)}
|
||||
<Blocks blocks={block.blocks} />
|
||||
</section>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { graphql } from "@/gql";
|
||||
import { type FactBoxBlockFragment } from "@/gql/graphql";
|
||||
import { RichText } from "@/components/general/RichText";
|
||||
import styles from "./factBoxBlock.module.scss";
|
||||
|
||||
const FactBoxBlockFragmentDefinition = graphql(`
|
||||
@@ -23,10 +24,9 @@ export const FactBoxBlock = ({
|
||||
className={styles.factBox}
|
||||
data-background-color={block.backgroundColor ?? ""}
|
||||
>
|
||||
<div
|
||||
className={styles.factBoxContent}
|
||||
dangerouslySetInnerHTML={{ __html: block.factBoxBody }}
|
||||
/>
|
||||
<div className={styles.factBoxContent}>
|
||||
<RichText content={block.factBoxBody} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -2,7 +2,9 @@ import { graphql, unmaskFragment } from "@/gql";
|
||||
import { type FeaturedBlockFragment } from "@/gql/graphql";
|
||||
import Link from "next/link";
|
||||
import { Image } from "@/components/general/Image";
|
||||
import { RichText } from "@/components/general/RichText";
|
||||
import { ImageFragmentDefinition } from "@/lib/common";
|
||||
import { internalHref } from "@/lib/links";
|
||||
import styles from "./featuredBlock.module.scss";
|
||||
|
||||
const FeaturedBlockFragmentDefinition = graphql(`
|
||||
@@ -44,6 +46,9 @@ export const FeaturedBlock = ({
|
||||
);
|
||||
// TODO: fetch image from target page
|
||||
|
||||
const pageUrl = block.featuredPage.url;
|
||||
const featuredHref = pageUrl ? internalHref(pageUrl) ?? pageUrl : "#";
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.featuredBlock}
|
||||
@@ -51,8 +56,10 @@ export const FeaturedBlock = ({
|
||||
>
|
||||
<div className={styles.text}>
|
||||
<h2>{block.title}</h2>
|
||||
<div dangerouslySetInnerHTML={{ __html: block.featuredBlockText }} />
|
||||
<Link href={block.featuredPage.url ?? "#"}>
|
||||
<div>
|
||||
<RichText content={block.featuredBlockText} />
|
||||
</div>
|
||||
<Link href={featuredHref}>
|
||||
{block.linkText} →
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { graphql, unmaskFragment } from "@/gql";
|
||||
import { type ScheduleBlockFragment } from "@/gql/graphql";
|
||||
import { RichText } from "@/components/general/RichText";
|
||||
import styles from "./scheduleBlock.module.scss";
|
||||
|
||||
const ScheduleItemFragmentDefinition = graphql(`
|
||||
@@ -45,19 +46,13 @@ export const ScheduleBlock = ({
|
||||
<div key={index} className={styles.item}>
|
||||
<dt className={styles.time}>{scheduleItem.time}</dt>
|
||||
<dd className={styles.body}>
|
||||
<div
|
||||
className={styles.title}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: scheduleItem.scheduleItemTitle,
|
||||
}}
|
||||
/>
|
||||
<div className={styles.title}>
|
||||
<RichText content={scheduleItem.scheduleItemTitle} />
|
||||
</div>
|
||||
{scheduleItem.description && (
|
||||
<div
|
||||
className={styles.description}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: scheduleItem.description,
|
||||
}}
|
||||
/>
|
||||
<div className={styles.description}>
|
||||
<RichText content={scheduleItem.description} />
|
||||
</div>
|
||||
)}
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { EventDetails } from "@/components/events/EventDetails";
|
||||
import { EventHeader } from "@/components/events/EventHeader";
|
||||
import { BgPig } from "@/components/general/BgPig";
|
||||
import { PageContent } from "@/components/general/PageContent";
|
||||
import { RichText } from "@/components/general/RichText";
|
||||
import { getEventPig } from "@/lib/event";
|
||||
|
||||
const eventBySlugQuery = graphql(`
|
||||
@@ -45,10 +46,9 @@ export function EventPageView({ event }: EventPageViewProps) {
|
||||
<EventHeader event={event} />
|
||||
<EventDetails event={event} />
|
||||
{event.lead && (
|
||||
<div
|
||||
className="lead event-lead"
|
||||
dangerouslySetInnerHTML={{ __html: event.lead }}
|
||||
/>
|
||||
<div className="lead event-lead">
|
||||
<RichText content={event.lead} />
|
||||
</div>
|
||||
)}
|
||||
<PageContent blocks={event.body} />
|
||||
</main>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { RichText } from "@/components/general/RichText";
|
||||
import styles from "./pageHeader.module.scss";
|
||||
|
||||
export const PageHeader = ({
|
||||
@@ -13,7 +14,9 @@ export const PageHeader = ({
|
||||
<div className={`${styles.pageHeader} ${align && styles[align]}`}>
|
||||
<h1 className={styles.title}>{heading}</h1>
|
||||
{lead && (
|
||||
<div className="lead" dangerouslySetInnerHTML={{ __html: lead }} />
|
||||
<div className="lead">
|
||||
<RichText content={lead} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -4,26 +4,39 @@ import parse, {
|
||||
type DOMNode,
|
||||
type HTMLReactParserOptions,
|
||||
} from "html-react-parser";
|
||||
import Link from "next/link";
|
||||
import { ButtonLink } from "@/components/general/ButtonLink";
|
||||
import { internalHref } from "@/lib/links";
|
||||
|
||||
/**
|
||||
* Renders CMS rich text (HTML) as React, swapping inline button markers
|
||||
* (`<a class="button">` from the button-link Draftail feature) for the
|
||||
* ButtonLink component. Everything else renders as plain HTML.
|
||||
* Renders CMS rich text (HTML) as React. Everything renders as plain HTML,
|
||||
* except:
|
||||
*
|
||||
* - inline button markers (`<a class="button">` from the button-link Draftail
|
||||
* feature) become the ButtonLink component
|
||||
* - links pointing at this site become next/link
|
||||
*/
|
||||
const options: HTMLReactParserOptions = {
|
||||
replace: (node) => {
|
||||
if (
|
||||
node instanceof Element &&
|
||||
node.name === "a" &&
|
||||
node.attribs.class?.split(/\s+/).includes("button")
|
||||
) {
|
||||
if (!(node instanceof Element) || node.name !== "a") {
|
||||
return;
|
||||
}
|
||||
const href = node.attribs.href ?? "#";
|
||||
const internal = internalHref(href);
|
||||
if (node.attribs.class?.split(/\s+/).includes("button")) {
|
||||
return (
|
||||
<ButtonLink href={node.attribs.href ?? "#"}>
|
||||
<ButtonLink href={internal ?? href}>
|
||||
{domToReact(node.children as DOMNode[], options)}
|
||||
</ButtonLink>
|
||||
);
|
||||
}
|
||||
if (internal) {
|
||||
return (
|
||||
<Link href={internal}>
|
||||
{domToReact(node.children as DOMNode[], options)}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { getClient } from "@/app/client";
|
||||
import { Breadcrumb } from "@/components/general/Breadcrumb";
|
||||
import { ImageFigure } from "@/components/general/Image";
|
||||
import { PageContent } from "@/components/general/PageContent";
|
||||
import { RichText } from "@/components/general/RichText";
|
||||
import { formatDate } from "@/lib/date";
|
||||
|
||||
const newsBySlugQuery = graphql(`
|
||||
@@ -47,10 +48,9 @@ export function NewsPageView({ news }: NewsPageViewProps) {
|
||||
/>
|
||||
<h1 className="news-title">{news.title}</h1>
|
||||
{news.lead && (
|
||||
<div
|
||||
className="lead"
|
||||
dangerouslySetInnerHTML={{ __html: news.lead }}
|
||||
/>
|
||||
<div className="lead">
|
||||
<RichText content={news.lead} />
|
||||
</div>
|
||||
)}
|
||||
{featuredImage && (
|
||||
<ImageFigure
|
||||
|
||||
@@ -2,6 +2,7 @@ import { graphql, unmaskFragment } from "@/gql";
|
||||
import { type SponsorFragment } from "@/gql/graphql";
|
||||
import { Image } from "../general/Image";
|
||||
import { ImageFragmentDefinition } from "@/lib/common";
|
||||
import { RichText } from "@/components/general/RichText";
|
||||
import styles from "./sponsorList.module.scss";
|
||||
|
||||
export const SponsorFragmentDefinition = graphql(`
|
||||
@@ -35,10 +36,9 @@ const SponsorItem = ({ sponsor }: { sponsor: SponsorFragment }) => {
|
||||
<div className={styles.text}>
|
||||
<h2>{name}</h2>
|
||||
{text && (
|
||||
<p
|
||||
className={styles.sponsorText}
|
||||
dangerouslySetInnerHTML={{ __html: text }}
|
||||
/>
|
||||
<div className={styles.sponsorText}>
|
||||
<RichText content={text} />
|
||||
</div>
|
||||
)}
|
||||
{website && (
|
||||
<p className={styles.website}>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { RichText } from "@/components/general/RichText";
|
||||
import styles from "./studioHeader.module.scss";
|
||||
|
||||
export const StudioHeader = ({
|
||||
@@ -54,7 +55,9 @@ export const StudioHeader = ({
|
||||
</div>
|
||||
</div>
|
||||
{lead && (
|
||||
<div className={`lead ${styles.lead}`} dangerouslySetInnerHTML={{ __html: lead }} />
|
||||
<div className={`lead ${styles.lead}`}>
|
||||
<RichText content={lead} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
const INTERNAL_ORIGINS = [
|
||||
process.env.URL,
|
||||
"https://neuf.no",
|
||||
].filter(Boolean) as string[];
|
||||
|
||||
/** Root-relative href if the URL points at this site, else null. */
|
||||
export function internalHref(href: string): string | null {
|
||||
if (href.startsWith("/")) {
|
||||
return href;
|
||||
}
|
||||
const origin = INTERNAL_ORIGINS.find(
|
||||
(o) => href === o || href.startsWith(o + "/"),
|
||||
);
|
||||
if (!origin) {
|
||||
return null;
|
||||
}
|
||||
const url = new URL(href);
|
||||
return url.pathname + url.search + url.hash;
|
||||
}
|
||||
Reference in New Issue
Block a user