diff --git a/studio/schemas/documents/legalDocuments.ts b/studio/schemas/documents/legalDocuments.ts index fc9b9bf02..d7d187379 100644 --- a/studio/schemas/documents/legalDocuments.ts +++ b/studio/schemas/documents/legalDocuments.ts @@ -1,6 +1,6 @@ import { defineField } from "sanity"; import { richText, title } from "../fields/text"; -import { legalSlug } from "../schemaTypes/slug"; +import { titleSlug } from "../schemaTypes/slug"; export const legalDocumentID = "legalDocuments"; @@ -8,7 +8,7 @@ const legalDocument = defineField({ name: legalDocumentID, type: "document", title: "Legal Document", - fields: [title, legalSlug, richText], + fields: [title, titleSlug, richText], preview: { select: { title: "basicTitle", diff --git a/studio/schemas/documents/post.ts b/studio/schemas/documents/post.ts index 3cd85385d..78ddf0854 100644 --- a/studio/schemas/documents/post.ts +++ b/studio/schemas/documents/post.ts @@ -1,6 +1,6 @@ import { defineField, defineType } from "sanity"; import { format, parseISO } from "date-fns"; -import { blogSlug } from "../schemaTypes/slug"; +import { titleSlug } from "../schemaTypes/slug"; import { richText, title } from "../fields/text"; import CategorySelector from "studio/components/CategorySelect"; import image from "../fields/media"; @@ -13,7 +13,7 @@ const posts = defineType({ title: "Posts", fields: [ title, - blogSlug, + titleSlug, defineField({ name: "date", title: "Publish Date", diff --git a/studio/schemas/documents/salaryAndBenefits.ts b/studio/schemas/documents/salaryAndBenefits.ts index 483d3d761..6268463af 100644 --- a/studio/schemas/documents/salaryAndBenefits.ts +++ b/studio/schemas/documents/salaryAndBenefits.ts @@ -1,5 +1,5 @@ import { defineField, defineType } from "sanity"; -import { pageSlug } from "../schemaTypes/slug"; +import { titleSlug } from "../schemaTypes/slug"; import seo from "../objects/seo"; import { title } from "../fields/text"; import { benefitId } from "./benefit"; @@ -12,7 +12,7 @@ const salaryAndBenefits = defineType({ title: "Salary and Benefits", fields: [ title, - pageSlug, + titleSlug, seo, defineField({ name: "showSalaryCalculator", diff --git a/studio/schemas/schemaTypes/slug.ts b/studio/schemas/schemaTypes/slug.ts index 31d14d2ca..62c105c72 100644 --- a/studio/schemas/schemaTypes/slug.ts +++ b/studio/schemas/schemaTypes/slug.ts @@ -2,7 +2,7 @@ import { defineField, SlugValidationContext } from "sanity"; async function isSlugUniqueAcrossAllDocuments( slug: string, - context: SlugValidationContext + context: SlugValidationContext, ) { const { document, getClient } = context; const client = getClient({ apiVersion: "2022-12-07" }); @@ -19,26 +19,42 @@ async function isSlugUniqueAcrossAllDocuments( return result; } +const SLUG_MAX_LENGTH = 200; + function createSlugField(source: string) { return defineField({ type: "slug", name: "slug", title: "URL Path (slug)", description: - "Enter a unique URL path for the page. This path will be used in the website's address bar. A URL path, also known as a slug, is a URL-friendly version of the page title, used to create a human-readable and search engine optimized URL for the content.", + "Enter a unique URL path for the page. This path will be used in the website's address bar. A URL path, also known as a slug, is a URL-friendly version of the page title, used to create a human-readable and search engine optimized URL for the content. Legal characters include latin letters, digits, hyphen (-), underscore (_), full stop (.) and tilde (~)", options: { source, - maxLength: 200, + maxLength: SLUG_MAX_LENGTH, slugify: (input) => - input.toLowerCase().replace(/\s+/g, "-").slice(0, 200), + input + .toLowerCase() + // replace æøå according to https://sprakradet.no/spraksporsmal-og-svar/ae-o-og-a-i-internasjonal-sammenheng/ + .replace(/[æ,å]/g, "a") + .replace(/ø/g, "o") + .replace(/[^a-zA-Z0-9-_.~\s]/g, "") // remove non-whitespace URL-unsafe chars (section 2.3 in https://www.ietf.org/rfc/rfc3986.txt) + .trim() + .replace(/\s+/g, "-") + .slice(0, SLUG_MAX_LENGTH), isUnique: isSlugUniqueAcrossAllDocuments, }, - validation: (Rule) => Rule.required(), + validation: (Rule) => + Rule.required().custom((value) => { + if (value?.current === undefined) return true; + return ( + encodeURIComponent(value.current) === value.current || + "Slug can only consist of latin letters, digits, hyphen (-), underscore (_), full stop (.) and tilde (~)" + ); + }), }); } const pageSlug = createSlugField("page"); -const blogSlug = createSlugField("basicTitle"); -const legalSlug = createSlugField("basicTitle"); +const titleSlug = createSlugField("basicTitle"); -export { pageSlug, blogSlug, legalSlug }; +export { pageSlug, titleSlug };