diff --git a/web/src/components/associations/AssociationHeader.tsx b/web/src/components/associations/AssociationHeader.tsx index 8ffd753..1202253 100644 --- a/web/src/components/associations/AssociationHeader.tsx +++ b/web/src/components/associations/AssociationHeader.tsx @@ -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 = ({

{association.title}

{association.lead && ( -
+
+ +
)} {association.websiteUrl && ( diff --git a/web/src/components/blocks/ContactSection.tsx b/web/src/components/blocks/ContactSection.tsx index e58ec58..7344a70 100644 --- a/web/src/components/blocks/ContactSection.tsx +++ b/web/src/components/blocks/ContactSection.tsx @@ -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 = ({

{block.title}

{block.text && ( -

+

+ +
)}
@@ -56,10 +56,9 @@ export const ContactSubsectionBlock = ({

{block.title}

{block.text && ( -

+

+ +
)}
diff --git a/web/src/components/blocks/FactBoxBlock.tsx b/web/src/components/blocks/FactBoxBlock.tsx index 3252b0c..f6ec25a 100644 --- a/web/src/components/blocks/FactBoxBlock.tsx +++ b/web/src/components/blocks/FactBoxBlock.tsx @@ -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 ?? ""} > -
+
+ +
); diff --git a/web/src/components/blocks/FeaturedBlock.tsx b/web/src/components/blocks/FeaturedBlock.tsx index 537f51d..e0506ad 100644 --- a/web/src/components/blocks/FeaturedBlock.tsx +++ b/web/src/components/blocks/FeaturedBlock.tsx @@ -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 (

{block.title}

-
- +
+ +
+ {block.linkText} →
diff --git a/web/src/components/blocks/ScheduleBlock.tsx b/web/src/components/blocks/ScheduleBlock.tsx index 0686865..1bacd8f 100644 --- a/web/src/components/blocks/ScheduleBlock.tsx +++ b/web/src/components/blocks/ScheduleBlock.tsx @@ -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 = ({
{scheduleItem.time}
-
+
+ +
{scheduleItem.description && ( -
+
+ +
)}
diff --git a/web/src/components/events/EventPageView.tsx b/web/src/components/events/EventPageView.tsx index 0574d03..280acd2 100644 --- a/web/src/components/events/EventPageView.tsx +++ b/web/src/components/events/EventPageView.tsx @@ -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) { {event.lead && ( -
+
+ +
)} diff --git a/web/src/components/general/PageHeader.tsx b/web/src/components/general/PageHeader.tsx index 6f9ba5c..835c00b 100644 --- a/web/src/components/general/PageHeader.tsx +++ b/web/src/components/general/PageHeader.tsx @@ -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 = ({

{heading}

{lead && ( -
+
+ +
)}
); diff --git a/web/src/components/general/RichText.tsx b/web/src/components/general/RichText.tsx index 5b104d0..fab5832 100644 --- a/web/src/components/general/RichText.tsx +++ b/web/src/components/general/RichText.tsx @@ -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 - * (`
` 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 (`` 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 ( - + {domToReact(node.children as DOMNode[], options)} ); } + if (internal) { + return ( + + {domToReact(node.children as DOMNode[], options)} + + ); + } }, }; diff --git a/web/src/components/news/NewsPageView.tsx b/web/src/components/news/NewsPageView.tsx index 60805df..b310e06 100644 --- a/web/src/components/news/NewsPageView.tsx +++ b/web/src/components/news/NewsPageView.tsx @@ -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) { />

{news.title}

{news.lead && ( -
+
+ +
)} {featuredImage && ( {

{name}

{text && ( -

+

+ +
)} {website && (

diff --git a/web/src/components/studio/StudioHeader.tsx b/web/src/components/studio/StudioHeader.tsx index 3c4ab36..29cb8e7 100644 --- a/web/src/components/studio/StudioHeader.tsx +++ b/web/src/components/studio/StudioHeader.tsx @@ -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 = ({

{lead && ( -
+
+ +
)}
); diff --git a/web/src/lib/links.ts b/web/src/lib/links.ts new file mode 100644 index 0000000..4092f44 --- /dev/null +++ b/web/src/lib/links.ts @@ -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; +}