From 08fa754ce933f61b0cca9aa34507b603b1042f2b Mon Sep 17 00:00:00 2001 From: Tobias Stikvoort Date: Fri, 12 Jul 2024 13:18:07 +0200 Subject: [PATCH 1/2] adds check for language --- src/client.ts | 4 ++-- src/hooks/usePageTree.ts | 4 ++-- src/hooks/usePageTreeItem.ts | 4 ++-- src/next.ts | 4 ++-- src/queries/index.ts | 19 ++++++++++++------- src/types.ts | 2 ++ src/validators/parent-validator.ts | 28 +++++++++++++++++----------- src/validators/slug-validator.ts | 4 ++-- 8 files changed, 41 insertions(+), 28 deletions(-) diff --git a/src/client.ts b/src/client.ts index 2358900..8355537 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1,7 +1,7 @@ import { SanityClient } from 'sanity'; import { getAllPageMetadata } from './helpers/page-tree'; -import { getRawPageMetadataQuery } from './queries'; +import { getAllRawPageMetadataQuery } from './queries'; import { PageMetadata, PageTreeConfig } from './types'; export type { PageMetadata } from './types'; @@ -25,7 +25,7 @@ class PageTreeClient { } public async getAllPageMetadata(): Promise { - const rawPageMetadata = await this.client.fetch(getRawPageMetadataQuery(this.config)); + const rawPageMetadata = await this.client.fetch(getAllRawPageMetadataQuery(this.config)); return getAllPageMetadata(this.config, rawPageMetadata); } } diff --git a/src/hooks/usePageTree.ts b/src/hooks/usePageTree.ts index 0e156c1..3788409 100644 --- a/src/hooks/usePageTree.ts +++ b/src/hooks/usePageTree.ts @@ -2,11 +2,11 @@ import { useMemo } from 'react'; import { useListeningQuery } from 'sanity-plugin-utils'; import { mapRawPageMetadatasToPageTree } from '../helpers/page-tree'; -import { getRawPageMetadataQuery } from '../queries'; +import { getAllRawPageMetadataQuery } from '../queries'; import { PageTreeConfig, RawPageMetadata } from '../types'; export const usePageTree = (config: PageTreeConfig) => { - const { data, loading } = useListeningQuery(getRawPageMetadataQuery(config), { + const { data, loading } = useListeningQuery(getAllRawPageMetadataQuery(config), { options: { apiVersion: config.apiVersion }, }); diff --git a/src/hooks/usePageTreeItem.ts b/src/hooks/usePageTreeItem.ts index 4a4de90..43a89e0 100644 --- a/src/hooks/usePageTreeItem.ts +++ b/src/hooks/usePageTreeItem.ts @@ -3,11 +3,11 @@ import { useMemo } from 'react'; import { useListeningQuery } from 'sanity-plugin-utils'; import { getAllPageMetadata } from '../helpers/page-tree'; -import { getRawPageMetadataQuery } from '../queries'; +import { getAllRawPageMetadataQuery } from '../queries'; import { PageTreeConfig, RawPageMetadata } from '../types'; export const usePageTreeItem = (documentId: string, config: PageTreeConfig, perspective?: ClientPerspective) => { - const { data, loading } = useListeningQuery(getRawPageMetadataQuery(config), { + const { data, loading } = useListeningQuery(getAllRawPageMetadataQuery(config), { options: { apiVersion: config.apiVersion, perspective }, }); diff --git a/src/next.ts b/src/next.ts index 37d1c07..da864bc 100644 --- a/src/next.ts +++ b/src/next.ts @@ -1,7 +1,7 @@ import { FilteredResponseQueryOptions, SanityClient } from 'next-sanity'; import { getAllPageMetadata } from './helpers/page-tree'; -import { getRawPageMetadataQuery } from './queries'; +import { getAllRawPageMetadataQuery } from './queries'; import { PageMetadata, PageTreeConfig } from './types'; export type { PageMetadata } from './types'; @@ -29,7 +29,7 @@ class NextPageTreeClient { public async getAllPageMetadata(): Promise { const rawPageMetadata = await this.client.fetch( - getRawPageMetadataQuery(this.config), + getAllRawPageMetadataQuery(this.config), undefined, this.defaultSanityFetchOptions ?? {}, ); diff --git a/src/queries/index.ts b/src/queries/index.ts index 7b74a6d..fbd2185 100644 --- a/src/queries/index.ts +++ b/src/queries/index.ts @@ -1,18 +1,23 @@ import { getLanguageFieldName } from '../helpers/config'; import { PageTreeConfig } from '../types'; -export const getRawPageMetadataQuery = (config: PageTreeConfig) => `*[_type in [${Object.values(config.pageSchemaTypes) +export const getAllRawPageMetadataQuery = (config: PageTreeConfig) => `*[_type in [${Object.values( + config.pageSchemaTypes, +) .map(key => `"${key}"`) .join(', ')}]]{ + ${rawPageMetadataFragment(config)} + }`; + +export const getRawPageMetadataQuery = (documentId: string, config: PageTreeConfig) => `*[_id == "${documentId}"]{ + ${rawPageMetadataFragment(config)} +}`; + +export const rawPageMetadataFragment = (config: PageTreeConfig) => ` _id, _type, _updatedAt, parent, slug, title, - ${getLanguageFieldName(config) ?? ''} - }`; - -export const getDocumentTypeQuery = (documentId: string) => `*[_id == "${documentId}"]{ - _type -}`; + ${getLanguageFieldName(config) ?? ''}`; diff --git a/src/types.ts b/src/types.ts index d9897bc..42b8eda 100644 --- a/src/types.ts +++ b/src/types.ts @@ -60,6 +60,8 @@ export type PageTreeConfig = { supportedLanguages: string[]; /* Optional field name of the language field, defaults to "language" */ languageFieldName?: string; + /* Adds validation check to ensure that the language of the document matches the language of the parent document. Default: false */ + documentLanguageShouldMatchParent?: boolean; }; }; diff --git a/src/validators/parent-validator.ts b/src/validators/parent-validator.ts index 8ec849e..b273dd2 100644 --- a/src/validators/parent-validator.ts +++ b/src/validators/parent-validator.ts @@ -1,6 +1,7 @@ import { Reference, ValidationContext } from 'sanity'; -import { getDocumentTypeQuery } from '../queries'; +import { getLanguageFieldName } from '../helpers/config'; +import { getRawPageMetadataQuery } from '../queries'; import { PageTreeConfig, RawPageMetadata, SanityRef } from '../types'; /** @@ -8,13 +9,9 @@ import { PageTreeConfig, RawPageMetadata, SanityRef } from '../types'; */ export const allowedParentValidator = (config: PageTreeConfig, ownType: string) => - async (selectedParent: Reference | undefined, context: ValidationContext) => { + async (selectedParentRef: Reference | undefined, context: ValidationContext) => { const allowedParents = config.allowedParents?.[ownType]; - if (allowedParents === undefined) { - return true; - } - const parentRef = context.document?.parent as SanityRef | undefined; if (!parentRef) { return true; @@ -27,15 +24,24 @@ export const allowedParentValidator = } const client = context.getClient({ apiVersion: config.apiVersion }); - const selectedParentType = (await client.fetch[]>(getDocumentTypeQuery(parentId)))[0] - ?._type; + const selectedParent = (await client.fetch(getRawPageMetadataQuery(parentId, config)))?.[0]; - if (!selectedParentType) { + if (!selectedParent._type) { return 'Unable to check the type of the selected parent.'; } - if (!allowedParents.includes(selectedParentType)) { - return `The parent of type "${selectedParentType}" is not allowed for this type of document.`; + if (allowedParents && !allowedParents.includes(selectedParent._type)) { + return `The parent of type "${selectedParent._type}" is not allowed for this type of document.`; + } + + if (config.documentInternationalization?.documentLanguageShouldMatchParent) { + const languageFieldName = getLanguageFieldName(config); + const language = context.document?.[languageFieldName]; + const parentLanguage = selectedParent?.[languageFieldName]; + + if (language !== parentLanguage) { + return 'The language of the parent must match the language of the document.'; + } } return true; diff --git a/src/validators/slug-validator.ts b/src/validators/slug-validator.ts index a1dc818..350f749 100644 --- a/src/validators/slug-validator.ts +++ b/src/validators/slug-validator.ts @@ -1,7 +1,7 @@ import { SlugValue, ValidationContext } from 'sanity'; import { DRAFTS_PREFIX } from '../helpers/page-tree'; -import { getRawPageMetadataQuery } from '../queries'; +import { getAllRawPageMetadataQuery } from '../queries'; import { PageTreeConfig, RawPageMetadata, SanityRef } from '../types'; import { getSanityDocumentId } from '../utils/sanity'; @@ -17,7 +17,7 @@ export const slugValidator = return true; } - const allPages = await client.fetch(getRawPageMetadataQuery(config)); + const allPages = await client.fetch(getAllRawPageMetadataQuery(config)); const siblingPages = allPages.filter(page => page.parent?._ref === parentRef._ref); const siblingPagesWithSameSlug = siblingPages From 3fc5681ed13815a6698765eb282e579ded4ae70f Mon Sep 17 00:00:00 2001 From: Tobias Stikvoort Date: Fri, 12 Jul 2024 16:25:12 +0200 Subject: [PATCH 2/2] seperate validator logic --- examples/studio-i18n/page-tree-config.ts | 1 + src/schema/definePageType.ts | 4 +- src/validators/parent-validator.ts | 66 ++++++++++++++---------- 3 files changed, 42 insertions(+), 29 deletions(-) diff --git a/examples/studio-i18n/page-tree-config.ts b/examples/studio-i18n/page-tree-config.ts index 7050a27..f6a6803 100644 --- a/examples/studio-i18n/page-tree-config.ts +++ b/examples/studio-i18n/page-tree-config.ts @@ -7,5 +7,6 @@ export const pageTreeConfig: PageTreeConfig = { titleFieldName: 'title', documentInternationalization: { supportedLanguages: ['nl', 'en', 'fr'], + documentLanguageShouldMatchParent: true, } }; diff --git a/src/schema/definePageType.ts b/src/schema/definePageType.ts index a0e2b9d..fa4ba28 100644 --- a/src/schema/definePageType.ts +++ b/src/schema/definePageType.ts @@ -4,7 +4,7 @@ import { defineField, defineType, DocumentDefinition, SlugOptions } from 'sanity import { PageTreeField } from '../components/PageTreeField'; import { SlugField } from '../components/SlugField'; import { PageTreeConfig } from '../types'; -import { allowedParentValidator } from '../validators/parent-validator'; +import { parentValidator } from '../validators/parent-validator'; import { slugValidator } from '../validators/slug-validator'; type Options = { @@ -67,7 +67,7 @@ const basePageFields = (config: PageTreeConfig, options: Options, ownType: Docum title: 'Parent page', type: 'reference', to: getPossibleParentsFromConfig(config, ownType).map(type => ({ type })), - validation: Rule => Rule.required().custom(allowedParentValidator(config, ownType.name)), + validation: Rule => Rule.required().custom(parentValidator(config, ownType.name)), group: options.fieldsGroupName, components: { field: props => PageTreeField({ ...props, config, mode: 'select-parent' }), diff --git a/src/validators/parent-validator.ts b/src/validators/parent-validator.ts index b273dd2..3df8174 100644 --- a/src/validators/parent-validator.ts +++ b/src/validators/parent-validator.ts @@ -2,47 +2,59 @@ import { Reference, ValidationContext } from 'sanity'; import { getLanguageFieldName } from '../helpers/config'; import { getRawPageMetadataQuery } from '../queries'; -import { PageTreeConfig, RawPageMetadata, SanityRef } from '../types'; +import { PageTreeConfig, RawPageMetadata } from '../types'; /** * Validates that the slug is unique within the parent page and therefore that entire the path is unique. */ -export const allowedParentValidator = +export const parentValidator = (config: PageTreeConfig, ownType: string) => async (selectedParentRef: Reference | undefined, context: ValidationContext) => { - const allowedParents = config.allowedParents?.[ownType]; - - const parentRef = context.document?.parent as SanityRef | undefined; - if (!parentRef) { - return true; - } - - const parentId = parentRef._ref; + const client = context.getClient({ apiVersion: config.apiVersion }); - if (parentId === undefined) { + if (!selectedParentRef) { return true; } - const client = context.getClient({ apiVersion: config.apiVersion }); - const selectedParent = (await client.fetch(getRawPageMetadataQuery(parentId, config)))?.[0]; + const parentId = selectedParentRef._ref; + const selectedParent = (await client.fetch(getRawPageMetadataQuery(parentId, config)))[0]; - if (!selectedParent._type) { - return 'Unable to check the type of the selected parent.'; + const allowedParentValidation = allowedParentValidator(selectedParent, config, ownType); + if (allowedParentValidation !== true) { + return allowedParentValidation; } - if (allowedParents && !allowedParents.includes(selectedParent._type)) { - return `The parent of type "${selectedParent._type}" is not allowed for this type of document.`; - } + return parentLanguageValidator(selectedParent, config, context); + }; - if (config.documentInternationalization?.documentLanguageShouldMatchParent) { - const languageFieldName = getLanguageFieldName(config); - const language = context.document?.[languageFieldName]; - const parentLanguage = selectedParent?.[languageFieldName]; +const allowedParentValidator = (selectedParent: RawPageMetadata, config: PageTreeConfig, ownType: string) => { + const allowedParents = config.allowedParents?.[ownType]; - if (language !== parentLanguage) { - return 'The language of the parent must match the language of the document.'; - } + if (allowedParents === undefined) { + return true; + } + + if (!allowedParents.includes(selectedParent._type)) { + return `The parent of type "${selectedParent._type}" is not allowed for this type of document.`; + } + + return true; +}; + +const parentLanguageValidator = ( + selectedParent: RawPageMetadata, + config: PageTreeConfig, + context: ValidationContext, +) => { + if (config.documentInternationalization?.documentLanguageShouldMatchParent) { + const languageFieldName = getLanguageFieldName(config); + const language = context.document?.[languageFieldName]; + const parentLanguage = selectedParent?.[languageFieldName]; + + if (language !== parentLanguage) { + return 'The language of the parent must match the language of the document.'; } + } - return true; - }; + return true; +};