From 7a41f129c60c344436954f060a13411bcc389bbf Mon Sep 17 00:00:00 2001 From: Ken Date: Wed, 7 May 2025 16:22:30 +0200 Subject: [PATCH 1/7] :WIP: Sitemap --- aksel.nav.no/website/app/_sanity/queries.ts | 19 +++++ .../website/app/_sanity/query-types.ts | 26 ++++++- aksel.nav.no/website/app/_sanity/utils.ts | 69 ++++++++++++++++++- aksel.nav.no/website/app/dev/sitemap.ts | 21 ++++++ package.json | 2 +- yarn.lock | 22 +++++- 6 files changed, 153 insertions(+), 6 deletions(-) create mode 100644 aksel.nav.no/website/app/dev/sitemap.ts diff --git a/aksel.nav.no/website/app/_sanity/queries.ts b/aksel.nav.no/website/app/_sanity/queries.ts index 92d460048f..cb5692f5ba 100644 --- a/aksel.nav.no/website/app/_sanity/queries.ts +++ b/aksel.nav.no/website/app/_sanity/queries.ts @@ -157,6 +157,7 @@ const SLUG_BY_TYPE_QUERY = defineQuery(` const GOD_PRAKSIS_ALL_TEMA_QUERY = defineQuery(`*[_type == "gp.tema"] | order(lower(title)){ title, + _updatedAt, description, pictogram, "slug": slug.current, @@ -262,6 +263,22 @@ const DOCUMENT_BY_ID_FOR_SLACK_QUERY = defineQuery(`*[_id == $id][0]{ "contacts": undertema[]->tema->contacts[]->email }`); +/* --------------------------------- Sitemap -------------------------------- */ +const SITEMAP_LANDINGPAGES_QUERY = defineQuery(` +{ + "frontpage": *[_type == "aksel_forside"][0]._updatedAt, + "godpraksis": *[_type == "godpraksis_landingsside"][0]._updatedAt, + "blogg": *[_type == "blogg_landingsside"][0]._updatedAt, +} + `); + +const SITEMAP_ARTICLES_BY_TYPE_QUERY = defineQuery(` + *[_type in $doctypes]{ + "slug": slug.current, + _updatedAt + } + `); + /* --------------------------------- Exports -------------------------------- */ export { DESIGNSYSTEM_SIDEBAR_QUERY, @@ -288,4 +305,6 @@ export { DOCUMENT_BY_ID_FOR_SLACK_QUERY, SIDE_ARTICLE_BY_SLUG_QUERY, PRINSIPPER_BY_SLUG_QUERY, + SITEMAP_LANDINGPAGES_QUERY, + SITEMAP_ARTICLES_BY_TYPE_QUERY, }; diff --git a/aksel.nav.no/website/app/_sanity/query-types.ts b/aksel.nav.no/website/app/_sanity/query-types.ts index 06751e89d8..6ab5fcf3f5 100644 --- a/aksel.nav.no/website/app/_sanity/query-types.ts +++ b/aksel.nav.no/website/app/_sanity/query-types.ts @@ -8283,9 +8283,10 @@ export type METADATA_BY_SLUG_QUERYResult = // Query: *[_type == $type && defined(slug.current)].slug.current export type SLUG_BY_TYPE_QUERYResult = Array; // Variable: GOD_PRAKSIS_ALL_TEMA_QUERY -// Query: *[_type == "gp.tema"] | order(lower(title)){ title, description, pictogram, "slug": slug.current, "articles": *[_type=="aksel_artikkel" && (^._id in undertema[]->tema._ref)] { heading, "slug": slug.current, "undertema": undertema[]->{title, "temaTitle": tema->title}, "innholdstype": innholdstype->title, "views": *[_type == "article_views" && article_ref._ref == ^._id][0].views_month } | order(coalesce(views, -1) desc)[0...4]{ heading, slug, undertema, innholdstype },} +// Query: *[_type == "gp.tema"] | order(lower(title)){ title, _updatedAt, description, pictogram, "slug": slug.current, "articles": *[_type=="aksel_artikkel" && (^._id in undertema[]->tema._ref)] { heading, "slug": slug.current, "undertema": undertema[]->{title, "temaTitle": tema->title}, "innholdstype": innholdstype->title, "views": *[_type == "article_views" && article_ref._ref == ^._id][0].views_month } | order(coalesce(views, -1) desc)[0...4]{ heading, slug, undertema, innholdstype },} export type GOD_PRAKSIS_ALL_TEMA_QUERYResult = Array<{ title: string | null; + _updatedAt: string; description: string | null; pictogram: { asset?: { @@ -11725,6 +11726,25 @@ export type DOCUMENT_BY_ID_FOR_SLACK_QUERYResult = contacts: Array | null; } | null; +// Variable: SITEMAP_LANDINGPAGES_QUERY +// Query: { "frontpage": *[_type == "aksel_forside"][0]._updatedAt, "godpraksis": *[_type == "godpraksis_landingsside"][0]._updatedAt, "blogg": *[_type == "blogg_landingsside"][0]._updatedAt,} +export type SITEMAP_LANDINGPAGES_QUERYResult = { + frontpage: string | null; + godpraksis: string | null; + blogg: string | null; +}; +// Variable: SITEMAP_ARTICLES_BY_TYPE_QUERY +// Query: *[_type in $doctypes]{ "slug": slug.current, _updatedAt } +export type SITEMAP_ARTICLES_BY_TYPE_QUERYResult = Array< + | { + slug: null; + _updatedAt: string; + } + | { + slug: string | null; + _updatedAt: string; + } +>; declare module "@sanity/client" { interface SanityQueries { @@ -11745,7 +11765,7 @@ declare module "@sanity/client" { '*[slug.current == $slug][0].content[style match \'h2\'][]{\n "id": _key,\n "title": pt::text(@)\n}': TOC_BY_SLUG_QUERYResult; "*[slug.current == $slug][0]{\n heading,\n ingress,\n publishedAt,\n seo\n}": METADATA_BY_SLUG_QUERYResult; "\n *[_type == $type && defined(slug.current)].slug.current\n": SLUG_BY_TYPE_QUERYResult; - '*[_type == "gp.tema"] | order(lower(title)){\n title,\n description,\n pictogram,\n "slug": slug.current,\n "articles": *[_type=="aksel_artikkel"\n && (^._id in undertema[]->tema._ref)] {\n heading,\n "slug": slug.current,\n "undertema": undertema[]->{title, "temaTitle": tema->title},\n "innholdstype": innholdstype->title,\n "views": *[_type == "article_views" && article_ref._ref == ^._id][0].views_month\n } | order(coalesce(views, -1) desc)[0...4]{\n heading,\n slug,\n undertema,\n innholdstype\n },\n}': GOD_PRAKSIS_ALL_TEMA_QUERYResult; + '*[_type == "gp.tema"] | order(lower(title)){\n title,\n _updatedAt,\n description,\n pictogram,\n "slug": slug.current,\n "articles": *[_type=="aksel_artikkel"\n && (^._id in undertema[]->tema._ref)] {\n heading,\n "slug": slug.current,\n "undertema": undertema[]->{title, "temaTitle": tema->title},\n "innholdstype": innholdstype->title,\n "views": *[_type == "article_views" && article_ref._ref == ^._id][0].views_month\n } | order(coalesce(views, -1) desc)[0...4]{\n heading,\n slug,\n undertema,\n innholdstype\n },\n}': GOD_PRAKSIS_ALL_TEMA_QUERYResult; '*[_type == "godpraksis_landingsside"][0].seo': GOD_PRAKSIS_LANDING_PAGE_SEO_QUERYResult; '*[_type == "gp.tema" && slug.current == $slug][0]{\n ...,\n "undertema": *[_type == "gp.tema.undertema" && tema->slug.current == $slug]{\n title,\n description\n },\n }': GOD_PRAKSIS_TEMA_BY_SLUG_QUERYResult; '*[_type == "aksel_artikkel" && defined(undertema) && $slug in undertema[]->tema->slug.current] | order(updateInfo.lastVerified desc) {\n _id,\n heading,\n "displayDate": updateInfo.lastVerified,\n "description": ingress,\n "undertema": undertema[]->{title, "temaTitle": tema->title},\n "innholdstype": innholdstype->title,\n "slug": slug.current,\n }': GOD_PRAKSIS_ARTICLES_BY_TEMA_QUERYResult; @@ -11753,5 +11773,7 @@ declare module "@sanity/client" { '\n*[slug.current == $slug && _type == "aksel_standalone"][0]\n {\n ...,\n content[]{\n ...,\n \n_type == "language" =>{\n ...,\n body[]{\n ...,\n \nmarkDefs[]{\n ...,\n _type == \'internalLink\' => {\n "slug": @.reference->slug,\n },\n}\n }\n},\n_type == "alert" =>{\n ...,\n body[]{\n ...,\n \nmarkDefs[]{\n ...,\n _type == \'internalLink\' => {\n "slug": @.reference->slug,\n },\n}\n }\n},\n_type == "attachment" =>{\n ...,\n "downloadLink": asset->url,\n "size": asset->size,\n body[]{\n ...,\n \nmarkDefs[]{\n ...,\n _type == \'internalLink\' => {\n "slug": @.reference->slug,\n },\n}\n }\n},\n_type == "tips" =>{\n ...,\n body[]{\n ...,\n \nmarkDefs[]{\n ...,\n _type == \'internalLink\' => {\n "slug": @.reference->slug,\n },\n}\n }\n},\n_type == "token_ref"=>@->,\n\nmarkDefs[]{\n ...,\n _type == \'internalLink\' => {\n "slug": @.reference->slug,\n },\n},\n_type == "intro_komponent" =>{\n ...,\n body[]{\n ...,\n \nmarkDefs[]{\n ...,\n _type == \'internalLink\' => {\n "slug": @.reference->slug,\n },\n}\n }\n},\n_type == "relatert_innhold" =>{\n title,\n lenker[]{\n ...,\n "intern_lenke": intern_lenke->slug.current,\n }\n},\n_type == "live_demo" =>{\n ...,\n "sandbox_ref": sandbox_ref->{...},\n},\n_type == "props_seksjon" =>{\n ...,\n komponenter[]{\n ...,\n "propref": propref->{...}\n },\n},\n_type == "installasjon_seksjon" =>{\n ...,\n "code_ref": code_ref->{...},\n},\n_type == "spesial_seksjon" =>{\n ...,\n "token": token_ref->{...}\n},\n_type == "accordion"=>{\n ...,\n list[]{\n ...,\n content[]{\n ...,\n \nmarkDefs[]{\n ...,\n _type == \'internalLink\' => {\n "slug": @.reference->slug,\n },\n},\n \n _type == "bilde" =>{\n ...,\n floating_text[]{\n ...,\n \nmarkDefs[]{\n ...,\n _type == \'internalLink\' => {\n "slug": @.reference->slug,\n },\n}\n }\n },\n _type == "video" =>{\n ...,\n "webm": {\n "url": webm.asset->url,\n "extension": webm.asset->extension\n },\n "fallback": {\n "url": fallback.asset->url,\n "extension": fallback.asset->extension\n },\n "track": track.asset->url\n },\n _type == "alert" =>{\n ...,\n body[]{\n ...,\n \nmarkDefs[]{\n ...,\n _type == \'internalLink\' => {\n "slug": @.reference->slug,\n },\n}\n }\n },\n _type == "kode" =>{\n ...,\n "ref": ref->{...},\n },\n _type == "kode_eksempler" =>{\n ...,\n dir->,\n },\n _type == "kode_ref" => @->,\n _type == "tips" =>{\n ...,\n body[]{\n ...,\n \nmarkDefs[]{\n ...,\n _type == \'internalLink\' => {\n "slug": @.reference->slug,\n },\n}\n }\n},\n _type == "relatert_innhold" =>{\n title,\n lenker[]{\n ...,\n "intern_lenke": intern_lenke->slug.current,\n }\n}\n\n }\n }\n}\n,\n_type == "expansioncard"=>{\n ...,\n body[]{\n ...,\n \nmarkDefs[]{\n ...,\n _type == \'internalLink\' => {\n "slug": @.reference->slug,\n },\n},\n \n _type == "bilde" =>{\n ...,\n floating_text[]{\n ...,\n \nmarkDefs[]{\n ...,\n _type == \'internalLink\' => {\n "slug": @.reference->slug,\n },\n}\n }\n },\n _type == "video" =>{\n ...,\n "webm": {\n "url": webm.asset->url,\n "extension": webm.asset->extension\n },\n "fallback": {\n "url": fallback.asset->url,\n "extension": fallback.asset->extension\n },\n "track": track.asset->url\n },\n _type == "alert" =>{\n ...,\n body[]{\n ...,\n \nmarkDefs[]{\n ...,\n _type == \'internalLink\' => {\n "slug": @.reference->slug,\n },\n}\n }\n },\n _type == "kode" =>{\n ...,\n "ref": ref->{...},\n },\n _type == "kode_eksempler" =>{\n ...,\n dir->,\n },\n _type == "kode_ref" => @->,\n _type == "tips" =>{\n ...,\n body[]{\n ...,\n \nmarkDefs[]{\n ...,\n _type == \'internalLink\' => {\n "slug": @.reference->slug,\n },\n}\n }\n},\n _type == "relatert_innhold" =>{\n title,\n lenker[]{\n ...,\n "intern_lenke": intern_lenke->slug.current,\n }\n}\n\n }\n},\n\n _type == "bilde" =>{\n ...,\n floating_text[]{\n ...,\n \nmarkDefs[]{\n ...,\n _type == \'internalLink\' => {\n "slug": @.reference->slug,\n },\n}\n }\n },\n _type == "video" =>{\n ...,\n "webm": {\n "url": webm.asset->url,\n "extension": webm.asset->extension\n },\n "fallback": {\n "url": fallback.asset->url,\n "extension": fallback.asset->extension\n },\n "track": track.asset->url\n },\n _type == "alert" =>{\n ...,\n body[]{\n ...,\n \nmarkDefs[]{\n ...,\n _type == \'internalLink\' => {\n "slug": @.reference->slug,\n },\n}\n }\n },\n _type == "kode" =>{\n ...,\n "ref": ref->{...},\n },\n _type == "kode_eksempler" =>{\n ...,\n dir->,\n },\n _type == "kode_ref" => @->,\n _type == "tips" =>{\n ...,\n body[]{\n ...,\n \nmarkDefs[]{\n ...,\n _type == \'internalLink\' => {\n "slug": @.reference->slug,\n },\n}\n }\n},\n _type == "relatert_innhold" =>{\n title,\n lenker[]{\n ...,\n "intern_lenke": intern_lenke->slug.current,\n }\n}\n,\n\n }\n }\n': SIDE_ARTICLE_BY_SLUG_QUERYResult; '\n *[slug.current == $slug && _type == "aksel_prinsipp"][0]{\n ...,\n content[]{\n ...,\n \n_type == "language" =>{\n ...,\n body[]{\n ...,\n \nmarkDefs[]{\n ...,\n _type == \'internalLink\' => {\n "slug": @.reference->slug,\n },\n}\n }\n},\n_type == "alert" =>{\n ...,\n body[]{\n ...,\n \nmarkDefs[]{\n ...,\n _type == \'internalLink\' => {\n "slug": @.reference->slug,\n },\n}\n }\n},\n_type == "attachment" =>{\n ...,\n "downloadLink": asset->url,\n "size": asset->size,\n body[]{\n ...,\n \nmarkDefs[]{\n ...,\n _type == \'internalLink\' => {\n "slug": @.reference->slug,\n },\n}\n }\n},\n_type == "tips" =>{\n ...,\n body[]{\n ...,\n \nmarkDefs[]{\n ...,\n _type == \'internalLink\' => {\n "slug": @.reference->slug,\n },\n}\n }\n},\n_type == "token_ref"=>@->,\n\nmarkDefs[]{\n ...,\n _type == \'internalLink\' => {\n "slug": @.reference->slug,\n },\n},\n_type == "intro_komponent" =>{\n ...,\n body[]{\n ...,\n \nmarkDefs[]{\n ...,\n _type == \'internalLink\' => {\n "slug": @.reference->slug,\n },\n}\n }\n},\n_type == "relatert_innhold" =>{\n title,\n lenker[]{\n ...,\n "intern_lenke": intern_lenke->slug.current,\n }\n},\n_type == "live_demo" =>{\n ...,\n "sandbox_ref": sandbox_ref->{...},\n},\n_type == "props_seksjon" =>{\n ...,\n komponenter[]{\n ...,\n "propref": propref->{...}\n },\n},\n_type == "installasjon_seksjon" =>{\n ...,\n "code_ref": code_ref->{...},\n},\n_type == "spesial_seksjon" =>{\n ...,\n "token": token_ref->{...}\n},\n_type == "accordion"=>{\n ...,\n list[]{\n ...,\n content[]{\n ...,\n \nmarkDefs[]{\n ...,\n _type == \'internalLink\' => {\n "slug": @.reference->slug,\n },\n},\n \n _type == "bilde" =>{\n ...,\n floating_text[]{\n ...,\n \nmarkDefs[]{\n ...,\n _type == \'internalLink\' => {\n "slug": @.reference->slug,\n },\n}\n }\n },\n _type == "video" =>{\n ...,\n "webm": {\n "url": webm.asset->url,\n "extension": webm.asset->extension\n },\n "fallback": {\n "url": fallback.asset->url,\n "extension": fallback.asset->extension\n },\n "track": track.asset->url\n },\n _type == "alert" =>{\n ...,\n body[]{\n ...,\n \nmarkDefs[]{\n ...,\n _type == \'internalLink\' => {\n "slug": @.reference->slug,\n },\n}\n }\n },\n _type == "kode" =>{\n ...,\n "ref": ref->{...},\n },\n _type == "kode_eksempler" =>{\n ...,\n dir->,\n },\n _type == "kode_ref" => @->,\n _type == "tips" =>{\n ...,\n body[]{\n ...,\n \nmarkDefs[]{\n ...,\n _type == \'internalLink\' => {\n "slug": @.reference->slug,\n },\n}\n }\n},\n _type == "relatert_innhold" =>{\n title,\n lenker[]{\n ...,\n "intern_lenke": intern_lenke->slug.current,\n }\n}\n\n }\n }\n}\n,\n_type == "expansioncard"=>{\n ...,\n body[]{\n ...,\n \nmarkDefs[]{\n ...,\n _type == \'internalLink\' => {\n "slug": @.reference->slug,\n },\n},\n \n _type == "bilde" =>{\n ...,\n floating_text[]{\n ...,\n \nmarkDefs[]{\n ...,\n _type == \'internalLink\' => {\n "slug": @.reference->slug,\n },\n}\n }\n },\n _type == "video" =>{\n ...,\n "webm": {\n "url": webm.asset->url,\n "extension": webm.asset->extension\n },\n "fallback": {\n "url": fallback.asset->url,\n "extension": fallback.asset->extension\n },\n "track": track.asset->url\n },\n _type == "alert" =>{\n ...,\n body[]{\n ...,\n \nmarkDefs[]{\n ...,\n _type == \'internalLink\' => {\n "slug": @.reference->slug,\n },\n}\n }\n },\n _type == "kode" =>{\n ...,\n "ref": ref->{...},\n },\n _type == "kode_eksempler" =>{\n ...,\n dir->,\n },\n _type == "kode_ref" => @->,\n _type == "tips" =>{\n ...,\n body[]{\n ...,\n \nmarkDefs[]{\n ...,\n _type == \'internalLink\' => {\n "slug": @.reference->slug,\n },\n}\n }\n},\n _type == "relatert_innhold" =>{\n title,\n lenker[]{\n ...,\n "intern_lenke": intern_lenke->slug.current,\n }\n}\n\n }\n},\n\n _type == "bilde" =>{\n ...,\n floating_text[]{\n ...,\n \nmarkDefs[]{\n ...,\n _type == \'internalLink\' => {\n "slug": @.reference->slug,\n },\n}\n }\n },\n _type == "video" =>{\n ...,\n "webm": {\n "url": webm.asset->url,\n "extension": webm.asset->extension\n },\n "fallback": {\n "url": fallback.asset->url,\n "extension": fallback.asset->extension\n },\n "track": track.asset->url\n },\n _type == "alert" =>{\n ...,\n body[]{\n ...,\n \nmarkDefs[]{\n ...,\n _type == \'internalLink\' => {\n "slug": @.reference->slug,\n },\n}\n }\n },\n _type == "kode" =>{\n ...,\n "ref": ref->{...},\n },\n _type == "kode_eksempler" =>{\n ...,\n dir->,\n },\n _type == "kode_ref" => @->,\n _type == "tips" =>{\n ...,\n body[]{\n ...,\n \nmarkDefs[]{\n ...,\n _type == \'internalLink\' => {\n "slug": @.reference->slug,\n },\n}\n }\n},\n _type == "relatert_innhold" =>{\n title,\n lenker[]{\n ...,\n "intern_lenke": intern_lenke->slug.current,\n }\n}\n,\n\n },\n contributors[]->{title}\n}': PRINSIPPER_BY_SLUG_QUERYResult; '*[_id == $id][0]{\n "id": _id,\n "title": heading,\n "editors": contributors[]->email,\n "slug": slug.current,\n "contacts": undertema[]->tema->contacts[]->email\n }': DOCUMENT_BY_ID_FOR_SLACK_QUERYResult; + '\n{\n "frontpage": *[_type == "aksel_forside"][0]._updatedAt,\n "godpraksis": *[_type == "godpraksis_landingsside"][0]._updatedAt,\n "blogg": *[_type == "blogg_landingsside"][0]._updatedAt,\n}\n ': SITEMAP_LANDINGPAGES_QUERYResult; + '\n *[_type in $doctypes]{\n "slug": slug.current,\n _updatedAt\n }\n ': SITEMAP_ARTICLES_BY_TYPE_QUERYResult; } } diff --git a/aksel.nav.no/website/app/_sanity/utils.ts b/aksel.nav.no/website/app/_sanity/utils.ts index 623236f2a9..b15e0d4f19 100644 --- a/aksel.nav.no/website/app/_sanity/utils.ts +++ b/aksel.nav.no/website/app/_sanity/utils.ts @@ -1,6 +1,16 @@ import createImageUrlBuilder from "@sanity/image-url"; import type { Image } from "sanity"; -import { SANITY_DATASET, SANITY_PROJECT_ID } from "@/sanity/config"; +import { sanityFetch } from "@/app/_sanity/live"; +import { + GOD_PRAKSIS_ALL_TEMA_QUERY, + SITEMAP_ARTICLES_BY_TYPE_QUERY, + SITEMAP_LANDINGPAGES_QUERY, +} from "@/app/_sanity/queries"; +import { + SANITY_DATASET, + SANITY_PROJECT_ID, + allArticleDocuments, +} from "@/sanity/config"; const imageBuilder = createImageUrlBuilder({ projectId: SANITY_PROJECT_ID || "", @@ -20,4 +30,59 @@ function urlForOpenGraphImage(image: Image | null | undefined) { return urlForImage(image)?.width(1200).height(627).fit("crop").url(); } -export { urlForImage, urlForOpenGraphImage }; +async function fetchAllSanityPages(): Promise< + { + slug: string; + lastMod: string; + }[] +> { + const [ + { data: landingPageData }, + { data: articleListData }, + { data: temaListData }, + ] = await Promise.all([ + sanityFetch({ + query: SITEMAP_LANDINGPAGES_QUERY, + stega: false, + perspective: "published", + }), + sanityFetch({ + query: SITEMAP_ARTICLES_BY_TYPE_QUERY, + params: { + doctypes: allArticleDocuments, + }, + stega: false, + perspective: "published", + }), + sanityFetch({ + query: GOD_PRAKSIS_ALL_TEMA_QUERY, + stega: false, + perspective: "published", + }), + ]); + + /* { path: "ikoner", lastmod: undefined }, */ + const paths = [ + { slug: "", lastMod: landingPageData.frontpage }, + { slug: "/god-praksis", lastmod: landingPageData.godpraksis }, + { slug: "/produktbloggen", lastmod: landingPageData.blogg }, + { slug: "/designsystemet", lastmod: new Date().toISOString() }, + ...articleListData.map((page) => { + return { slug: `/${page.slug}`, lastMod: page._updatedAt }; + }), + ...temaListData + .filter((tema) => tema.articles.length > 0) + .map((page) => { + return { slug: `/god-praksis/${page.slug}`, lastMod: page._updatedAt }; + }), + ]; + + return paths + .filter((path) => !!path.lastMod && !!path.slug) + .map((path) => ({ + slug: path.slug as string, + lastMod: path.lastMod as string, + })); +} + +export { urlForImage, urlForOpenGraphImage, fetchAllSanityPages }; diff --git a/aksel.nav.no/website/app/dev/sitemap.ts b/aksel.nav.no/website/app/dev/sitemap.ts new file mode 100644 index 0000000000..e3bd3e71cd --- /dev/null +++ b/aksel.nav.no/website/app/dev/sitemap.ts @@ -0,0 +1,21 @@ +import type { MetadataRoute } from "next"; +import { fetchAllSanityPages } from "@/app/_sanity/utils"; + +export default async function sitemap(): Promise { + const pages = await fetchAllSanityPages(); + + return pages.map((page) => ({ + url: `https://www.aksel.nav.no${page.slug}`, + lastModified: page.lastMod.split("T")[0], + })); + + return [ + { + url: "https://example.com", + lastModified: "2021-01-01", + changeFrequency: "weekly", + priority: 0.5, + images: ["https://example.com/image.jpg"], + }, + ]; +} diff --git a/package.json b/package.json index 73c14256ca..d106e480f4 100644 --- a/package.json +++ b/package.json @@ -192,7 +192,7 @@ "stylelint-declaration-block-no-ignored-properties": "^2.8.0", "stylelint-value-no-unknown-custom-properties": "^6.0.1", "tsx": "^4.19.1", - "typescript": "5.5.4", + "typescript": "5.8.3", "vite": "^5.4.18", "vite-plugin-turbosnap": "^1.0.3", "vite-tsconfig-paths": "^5.1.4" diff --git a/yarn.lock b/yarn.lock index 3da2383b57..d9234cd846 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10734,7 +10734,7 @@ __metadata: stylelint-declaration-block-no-ignored-properties: "npm:^2.8.0" stylelint-value-no-unknown-custom-properties: "npm:^6.0.1" tsx: "npm:^4.19.1" - typescript: "npm:5.5.4" + typescript: "npm:5.8.3" vite: "npm:^5.4.18" vite-plugin-turbosnap: "npm:^1.0.3" vite-tsconfig-paths: "npm:^5.1.4" @@ -28652,6 +28652,16 @@ __metadata: languageName: node linkType: hard +"typescript@npm:5.8.3": + version: 5.8.3 + resolution: "typescript@npm:5.8.3" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10/65c40944c51b513b0172c6710ee62e951b70af6f75d5a5da745cb7fab132c09ae27ffdf7838996e3ed603bb015dadd099006658046941bd0ba30340cc563ae92 + languageName: node + linkType: hard + "typescript@npm:>=5.0.0": version: 5.6.3 resolution: "typescript@npm:5.6.3" @@ -28692,6 +28702,16 @@ __metadata: languageName: node linkType: hard +"typescript@patch:typescript@npm%3A5.8.3#optional!builtin": + version: 5.8.3 + resolution: "typescript@patch:typescript@npm%3A5.8.3#optional!builtin::version=5.8.3&hash=74658d" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10/98470634034ec37fd9ea61cc82dcf9a27950d0117a4646146b767d085a2ec14b137aae9642a83d1c62732d7fdcdac19bb6288b0bb468a72f7a06ae4e1d2c72c9 + languageName: node + linkType: hard + "typescript@patch:typescript@npm%3A>=5.0.0#optional!builtin": version: 5.6.3 resolution: "typescript@patch:typescript@npm%3A5.6.3#optional!builtin::version=5.6.3&hash=74658d" From 5b52162e48e8f22cf93cd9c0ee6fd73e85fc50d2 Mon Sep 17 00:00:00 2001 From: Ken Date: Thu, 8 May 2025 14:15:18 +0200 Subject: [PATCH 2/7] Move manual page mapping --- .../website/app/_sanity/live-fetch.ts | 92 +++++++++++++++++++ aksel.nav.no/website/app/_sanity/utils.ts | 69 +------------- .../_ui/sidebar/Sidebar.util.ts | 46 +++------- aksel.nav.no/website/app/dev/sitemap.ts | 14 +-- aksel.nav.no/website/app/routing-config.ts | 78 ++++++++++++++++ aksel.nav.no/website/sanity/config.ts | 6 +- 6 files changed, 189 insertions(+), 116 deletions(-) create mode 100644 aksel.nav.no/website/app/_sanity/live-fetch.ts create mode 100644 aksel.nav.no/website/app/routing-config.ts diff --git a/aksel.nav.no/website/app/_sanity/live-fetch.ts b/aksel.nav.no/website/app/_sanity/live-fetch.ts new file mode 100644 index 0000000000..eeff213234 --- /dev/null +++ b/aksel.nav.no/website/app/_sanity/live-fetch.ts @@ -0,0 +1,92 @@ +import { sanityFetch } from "@/app/_sanity/live"; +import { + DESIGNSYSTEM_GRUNNLEGGENDE_LANDINGPAGE_QUERY, + DESIGNSYSTEM_KOMPONENTER_LANDINGPAGE_QUERY, + DESIGNSYSTEM_TEMPLATES_LANDINGPAGE_QUERY, + GOD_PRAKSIS_ALL_TEMA_QUERY, + SITEMAP_ARTICLES_BY_TYPE_QUERY, + SITEMAP_LANDINGPAGES_QUERY, +} from "@/app/_sanity/queries"; +import { allArticleDocuments } from "@/sanity/config"; + +async function fetchAllSanityPages(): Promise< + { + slug: string; + lastMod: string | null; + }[] +> { + const [ + { data: landingPageData }, + { data: articleListData }, + { data: temaListData }, + { data: dsKomponenterData }, + { data: dsGrunnleggendeData }, + { data: dsTemplatesData }, + ] = await Promise.all([ + sanityFetch({ + query: SITEMAP_LANDINGPAGES_QUERY, + stega: false, + perspective: "published", + }), + sanityFetch({ + query: SITEMAP_ARTICLES_BY_TYPE_QUERY, + params: { + doctypes: allArticleDocuments, + }, + stega: false, + perspective: "published", + }), + sanityFetch({ + query: GOD_PRAKSIS_ALL_TEMA_QUERY, + stega: false, + perspective: "published", + }), + sanityFetch({ + query: DESIGNSYSTEM_KOMPONENTER_LANDINGPAGE_QUERY, + stega: false, + perspective: "published", + }), + sanityFetch({ + query: DESIGNSYSTEM_GRUNNLEGGENDE_LANDINGPAGE_QUERY, + stega: false, + perspective: "published", + }), + sanityFetch({ + query: DESIGNSYSTEM_TEMPLATES_LANDINGPAGE_QUERY, + stega: false, + perspective: "published", + }), + ]); + + /* { path: "ikoner", lastMod: undefined }, */ + const paths = [ + { slug: "", lastMod: landingPageData.frontpage }, + { slug: "/god-praksis", lastMod: landingPageData.godpraksis }, + { slug: "/produktbloggen", lastMod: landingPageData.blogg }, + { slug: "/designsystemet", lastMod: new Date().toISOString() }, + ...articleListData.map((page) => { + return { slug: `/${page.slug}`, lastMod: page._updatedAt }; + }), + ...temaListData + .filter((tema) => tema.articles.length > 0) + .map((page) => { + return { slug: `/god-praksis/${page.slug}`, lastMod: page._updatedAt }; + }), + ]; + + dsKomponenterData?.overview_pages?.forEach((overviewPage) => { + paths.push({ slug: `/komponenter/${overviewPage}`, lastMod: null }); + }); + + dsGrunnleggendeData?.overview_pages?.forEach((overviewPage) => { + paths.push({ slug: `/grunnleggende/${overviewPage}`, lastMod: null }); + }); + + dsTemplatesData?.overview_pages?.forEach((overviewPage) => { + paths.push({ slug: `/monster-maler/${overviewPage}`, lastMod: null }); + }); + + return paths; +} + +export { fetchAllSanityPages }; diff --git a/aksel.nav.no/website/app/_sanity/utils.ts b/aksel.nav.no/website/app/_sanity/utils.ts index b15e0d4f19..623236f2a9 100644 --- a/aksel.nav.no/website/app/_sanity/utils.ts +++ b/aksel.nav.no/website/app/_sanity/utils.ts @@ -1,16 +1,6 @@ import createImageUrlBuilder from "@sanity/image-url"; import type { Image } from "sanity"; -import { sanityFetch } from "@/app/_sanity/live"; -import { - GOD_PRAKSIS_ALL_TEMA_QUERY, - SITEMAP_ARTICLES_BY_TYPE_QUERY, - SITEMAP_LANDINGPAGES_QUERY, -} from "@/app/_sanity/queries"; -import { - SANITY_DATASET, - SANITY_PROJECT_ID, - allArticleDocuments, -} from "@/sanity/config"; +import { SANITY_DATASET, SANITY_PROJECT_ID } from "@/sanity/config"; const imageBuilder = createImageUrlBuilder({ projectId: SANITY_PROJECT_ID || "", @@ -30,59 +20,4 @@ function urlForOpenGraphImage(image: Image | null | undefined) { return urlForImage(image)?.width(1200).height(627).fit("crop").url(); } -async function fetchAllSanityPages(): Promise< - { - slug: string; - lastMod: string; - }[] -> { - const [ - { data: landingPageData }, - { data: articleListData }, - { data: temaListData }, - ] = await Promise.all([ - sanityFetch({ - query: SITEMAP_LANDINGPAGES_QUERY, - stega: false, - perspective: "published", - }), - sanityFetch({ - query: SITEMAP_ARTICLES_BY_TYPE_QUERY, - params: { - doctypes: allArticleDocuments, - }, - stega: false, - perspective: "published", - }), - sanityFetch({ - query: GOD_PRAKSIS_ALL_TEMA_QUERY, - stega: false, - perspective: "published", - }), - ]); - - /* { path: "ikoner", lastmod: undefined }, */ - const paths = [ - { slug: "", lastMod: landingPageData.frontpage }, - { slug: "/god-praksis", lastmod: landingPageData.godpraksis }, - { slug: "/produktbloggen", lastmod: landingPageData.blogg }, - { slug: "/designsystemet", lastmod: new Date().toISOString() }, - ...articleListData.map((page) => { - return { slug: `/${page.slug}`, lastMod: page._updatedAt }; - }), - ...temaListData - .filter((tema) => tema.articles.length > 0) - .map((page) => { - return { slug: `/god-praksis/${page.slug}`, lastMod: page._updatedAt }; - }), - ]; - - return paths - .filter((path) => !!path.lastMod && !!path.slug) - .map((path) => ({ - slug: path.slug as string, - lastMod: path.lastMod as string, - })); -} - -export { urlForImage, urlForOpenGraphImage, fetchAllSanityPages }; +export { urlForImage, urlForOpenGraphImage }; diff --git a/aksel.nav.no/website/app/dev/(designsystemet)/_ui/sidebar/Sidebar.util.ts b/aksel.nav.no/website/app/dev/(designsystemet)/_ui/sidebar/Sidebar.util.ts index 1f6886d9d0..7a6c4e69b8 100644 --- a/aksel.nav.no/website/app/dev/(designsystemet)/_ui/sidebar/Sidebar.util.ts +++ b/aksel.nav.no/website/app/dev/(designsystemet)/_ui/sidebar/Sidebar.util.ts @@ -3,53 +3,31 @@ import { DESIGNSYSTEM_OVERVIEW_PAGES_QUERYResult, DESIGNSYSTEM_SIDEBAR_QUERYResult, } from "@/app/_sanity/query-types"; +import { PAGE_ROUTES } from "@/app/routing-config"; import { sanityCategoryLookup } from "@/sanity/config"; import { DesignsystemSidebarSectionT, SidebarPageT } from "@/types"; -/** - * TODO: We currently do not support sorting "unique" pages - */ -const pageTypes = { - grunnleggende: { - name: "Grunnleggende", - _type: "ds_artikkel", - uniquePages: [], - }, - komponenter: { - name: "Komponenter", - _type: "komponent_artikkel", - uniquePages: [ - { - heading: "Ikoner", - slug: "komponenter/ikoner", - tag: "ready", - }, - ], - }, - templates: { - name: "Mønster og Maler", - _type: "templates_artikkel", - uniquePages: [], - }, -} as const; - type DesignsystemSidebarDataT = { label: string; links: DesignsystemSidebarSectionT; }[]; +function typedKeys(obj: T): (keyof T)[] { + return Object.keys(obj) as (keyof T)[]; +} + function generateSidebar( input: DESIGNSYSTEM_SIDEBAR_QUERYResult, overviewPages: DESIGNSYSTEM_OVERVIEW_PAGES_QUERYResult, ): DesignsystemSidebarDataT { - return Object.keys(pageTypes).map((type) => { + return typedKeys(PAGE_ROUTES).map((type) => { const overviewPageList = overviewPages.find((page) => page._type.includes(type), )?.overview_pages; - const categories = sanityCategoryLookup(type as keyof typeof pageTypes); + const categories = sanityCategoryLookup(type); const filteredInput = input.filter( - (doc) => doc._type === pageTypes[type]._type, + (doc) => doc._type === PAGE_ROUTES[type]._type, ); const standalonePages: SidebarPageT[] = filteredInput @@ -113,7 +91,7 @@ function generateSidebar( }); // TODO: Remove this when we have published any darkside pages from Sanity, so the entry above is activated - if ( + /* if ( type === "grunnleggende" && !groupedPages.some((page) => page.value === "darkside") ) { @@ -128,12 +106,12 @@ function generateSidebar( }, ], }); - } + } */ return { - label: pageTypes[type].name, + label: PAGE_ROUTES[type].title, links: [ - ...pageTypes[type].uniquePages, + /* ...PAGE_ROUTES[type]., */ ...standalonePages, ...groupedPages, ], diff --git a/aksel.nav.no/website/app/dev/sitemap.ts b/aksel.nav.no/website/app/dev/sitemap.ts index e3bd3e71cd..5c7c5b07a2 100644 --- a/aksel.nav.no/website/app/dev/sitemap.ts +++ b/aksel.nav.no/website/app/dev/sitemap.ts @@ -1,21 +1,11 @@ import type { MetadataRoute } from "next"; -import { fetchAllSanityPages } from "@/app/_sanity/utils"; +import { fetchAllSanityPages } from "@/app/_sanity/live-fetch"; export default async function sitemap(): Promise { const pages = await fetchAllSanityPages(); return pages.map((page) => ({ url: `https://www.aksel.nav.no${page.slug}`, - lastModified: page.lastMod.split("T")[0], + lastModified: page?.lastMod?.split("T")[0] ?? undefined, })); - - return [ - { - url: "https://example.com", - lastModified: "2021-01-01", - changeFrequency: "weekly", - priority: 0.5, - images: ["https://example.com/image.jpg"], - }, - ]; } diff --git a/aksel.nav.no/website/app/routing-config.ts b/aksel.nav.no/website/app/routing-config.ts new file mode 100644 index 0000000000..ee5b87f6dc --- /dev/null +++ b/aksel.nav.no/website/app/routing-config.ts @@ -0,0 +1,78 @@ +/** + * Some pages are not part of the sanity CMS environment, but rather + * hardcoded in the codebase. We still need a way to reference them in the sidebar and sitemap. + * This file contains the routes for those pages. + */ +import { + grunnleggendeKategorier, + komponentKategorier, + templatesKategorier, +} from "@/sanity/config"; + +type PageRoute = { + slug: string; + tag: string; + heading: string; +}; + +type Routes = { + komponenter: { + title: "Komponenter"; + _type: "komponent_artikkel"; + root: PageRoute[]; + nested?: Partial< + Record<(typeof komponentKategorier)[number]["value"], PageRoute[]> + >; + }; + grunnleggende: { + title: "Grunnleggende"; + _type: "ds_artikkel"; + root: PageRoute[]; + nested?: Partial< + Record<(typeof grunnleggendeKategorier)[number]["value"], PageRoute[]> + >; + }; + templates: { + title: "Mønster og Maler"; + _type: "templates_artikkel"; + root: PageRoute[]; + nested?: Partial< + Record<(typeof templatesKategorier)[number]["value"], PageRoute[]> + >; + }; +}; + +const PAGE_ROUTES: Routes = { + komponenter: { + title: "Komponenter", + _type: "komponent_artikkel", + root: [ + { + heading: "Ikoner", + slug: "komponenter/ikoner", + tag: "ready", + }, + ], + }, + grunnleggende: { + title: "Grunnleggende", + _type: "ds_artikkel", + root: [], + nested: { + darkside: [ + { + heading: "Tokens darkside", + slug: `grunnleggende/darkside/design-tokens`, + tag: "ready", + }, + ], + }, + }, + templates: { + title: "Mønster og Maler", + _type: "templates_artikkel", + root: [], + }, +} as const; + +export { PAGE_ROUTES }; diff --git a/aksel.nav.no/website/sanity/config.ts b/aksel.nav.no/website/sanity/config.ts index a45ec7d25a..53d6cfe605 100644 --- a/aksel.nav.no/website/sanity/config.ts +++ b/aksel.nav.no/website/sanity/config.ts @@ -53,7 +53,7 @@ export const komponentKategorier = [ { title: "Core", value: "core" }, { title: "Alpha", value: "alpha" }, { title: "Avviklet", value: "legacy" }, -]; +] as const; export const grunnleggendeKategorier = [ { title: "Introduksjon", value: "introduksjon" }, @@ -62,13 +62,13 @@ export const grunnleggendeKategorier = [ { title: "Darkside", value: "darkside" }, { title: "Guider", value: "guider" }, { title: "Kode", value: "kode" }, -]; +] as const; export const templatesKategorier = [ { title: "Brev", value: "brev" }, { title: "Støtte", value: "stotte" }, { title: "Søknadsdialog", value: "soknadsdialog" }, -]; +] as const; export const bloggKategorier = [ { title: "Nytt fra teamene", value: "nytt-fra-teamene" }, From 718f66e09e8221a3602eb0909c78c99a2b21ac5a Mon Sep 17 00:00:00 2001 From: Ken Date: Thu, 8 May 2025 15:11:56 +0200 Subject: [PATCH 3/7] :tada: use global routing config for sidebar generation --- .../_ui/sidebar/Sidebar.util.ts | 41 +++++-------------- aksel.nav.no/website/app/routing-config.ts | 22 +++++----- 2 files changed, 21 insertions(+), 42 deletions(-) diff --git a/aksel.nav.no/website/app/dev/(designsystemet)/_ui/sidebar/Sidebar.util.ts b/aksel.nav.no/website/app/dev/(designsystemet)/_ui/sidebar/Sidebar.util.ts index 7a6c4e69b8..3a6c6cf391 100644 --- a/aksel.nav.no/website/app/dev/(designsystemet)/_ui/sidebar/Sidebar.util.ts +++ b/aksel.nav.no/website/app/dev/(designsystemet)/_ui/sidebar/Sidebar.util.ts @@ -26,6 +26,7 @@ function generateSidebar( )?.overview_pages; const categories = sanityCategoryLookup(type); + const filteredInput = input.filter( (doc) => doc._type === PAGE_ROUTES[type]._type, ); @@ -56,7 +57,7 @@ function generateSidebar( .sort(sortIndex) .sort(sortDeprecated), })) - .filter((category) => !(!category.pages || category.pages.length === 0)) + /* .filter((category) => !(!category.pages || category.pages.length === 0)) */ .map((category) => { const hasOverviewPage = overviewPageList?.some( (page) => category.value === page, @@ -76,45 +77,23 @@ function generateSidebar( }); } - if (type === "grunnleggende" && category.value === "darkside") { - pages.push({ - heading: "Tokens darkside", - slug: `${type}/${category.value}/design-tokens`, - tag: "ready", - }); + if ( + PAGE_ROUTES[type].nested && + category.value in PAGE_ROUTES[type].nested + ) { + pages.push(...PAGE_ROUTES[type].nested[category.value]); } return { ...category, pages, }; - }); - - // TODO: Remove this when we have published any darkside pages from Sanity, so the entry above is activated - /* if ( - type === "grunnleggende" && - !groupedPages.some((page) => page.value === "darkside") - ) { - groupedPages.push({ - title: "Darkside", - value: "darkside", - pages: [ - { - heading: "Design tokens", - slug: `${type}/darkside/design-tokens`, - tag: "ready", - }, - ], - }); - } */ + }) + .filter((category) => category.pages.length > 0); return { label: PAGE_ROUTES[type].title, - links: [ - /* ...PAGE_ROUTES[type]., */ - ...standalonePages, - ...groupedPages, - ], + links: [...PAGE_ROUTES[type].root, ...standalonePages, ...groupedPages], }; }); } diff --git a/aksel.nav.no/website/app/routing-config.ts b/aksel.nav.no/website/app/routing-config.ts index ee5b87f6dc..fa06eec403 100644 --- a/aksel.nav.no/website/app/routing-config.ts +++ b/aksel.nav.no/website/app/routing-config.ts @@ -43,17 +43,6 @@ type Routes = { }; const PAGE_ROUTES: Routes = { - komponenter: { - title: "Komponenter", - _type: "komponent_artikkel", - root: [ - { - heading: "Ikoner", - slug: "komponenter/ikoner", - tag: "ready", - }, - ], - }, grunnleggende: { title: "Grunnleggende", _type: "ds_artikkel", @@ -68,6 +57,17 @@ const PAGE_ROUTES: Routes = { ], }, }, + komponenter: { + title: "Komponenter", + _type: "komponent_artikkel", + root: [ + { + heading: "Ikoner", + slug: "komponenter/ikoner", + tag: "ready", + }, + ], + }, templates: { title: "Mønster og Maler", _type: "templates_artikkel", From af660e41bd05f2f022e7a898153fefbe63a9015e Mon Sep 17 00:00:00 2001 From: Ken Date: Thu, 8 May 2025 15:21:52 +0200 Subject: [PATCH 4/7] =?UTF-8?q?=F0=9F=94=A5=20Remove=20darkside=20styling?= =?UTF-8?q?=20from=20`Sidebar.group.tsx`=20and=20`Sidebar.module.css`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dev/(designsystemet)/_ui/sidebar/Sidebar.group.tsx | 8 +------- .../dev/(designsystemet)/_ui/sidebar/Sidebar.module.css | 4 ---- .../dev/(designsystemet)/_ui/sidebar/Sidebar.subnav.tsx | 4 +++- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/aksel.nav.no/website/app/dev/(designsystemet)/_ui/sidebar/Sidebar.group.tsx b/aksel.nav.no/website/app/dev/(designsystemet)/_ui/sidebar/Sidebar.group.tsx index 244076f3f3..e432298a37 100644 --- a/aksel.nav.no/website/app/dev/(designsystemet)/_ui/sidebar/Sidebar.group.tsx +++ b/aksel.nav.no/website/app/dev/(designsystemet)/_ui/sidebar/Sidebar.group.tsx @@ -15,16 +15,10 @@ function DesignsystemSidebarGroup(props: DesignsystemSidebarGroupT) { const { label, links, layout } = props; const id = useId(); - const isDarkside = label.toLowerCase() === "darkside"; - const LabelComponent = layout === "sidebar" ? Detail : BodyShort; return ( -
  • +
  • { return pathName?.split("#")[0] === `/${page.slug}`; }); @@ -22,7 +24,7 @@ function DesignsystemSidebarSubNav( const [open, setOpen] = useState(isSectionActive); return ( -
  • +