/** * Sanity document scheme for SKOS Taxonomy Concepts * @todo Improve typings * @todo Hierarchy, Broader, & Associated: enforce disjointedness between Associated and BroaderTransitive (integrity constraint); prohibit cycles in hierarchical relations (best practice). * 2022-03-31: Filtering added to Related to five levels of hierarchy, document filtering present for Broader. Consider more robust filtering and validation for future releases. * @todo Document level validation for the disjunction between Preferred, Alternate, and Hidden Labels * @todo Lexical labels: add child level validation so that offending labels are shown directly when a duplicate is entered. Then consider removing document level validation. cf. https://www.sanity.io/docs/validation#9e69d5db6f72 * @todo Scheme initial value: Configure "default" option in Concept Scheme, for cases when there are multiple schemes; configure initialValue to default to that selection (It's currently configure to take the scheme ordered first. This isn't transparent.) * @todo Abstract broader and related concept filter into reusable function, and/or add in validation to cover wider scenarios. */ import {defineField, defineType} from 'sanity' import {WarningOutlineIcon} from '@sanity/icons' import {randomKey} from '@sanity/util/content' import {AiOutlineTag, AiOutlineTags} from 'react-icons/ai' import {StyledDescription} from './styles' import baseIriField from './modules/baseIriField' import {baseLanguage} from './locale/languages' export default function skosConcept(baseUri?: string) { return defineType({ name: 'skosConcept', title: 'Category', type: 'document', icon: AiOutlineTags, initialValue: async (props, context) => { if (baseUri) return { baseIri: baseUri, broader: [], // an empty array is needed here in order to return concepts with no "broader" for "related" related: [], // an empty array is needed here in order to return concepts with no "broader" for "related" } const {getClient} = context const client = getClient({apiVersion: '2021-03-25'}) const baseIri = (await client.fetch(` *[(_type == 'skosConcept' || _type == 'skosConceptScheme') && defined(baseIri)]| order(_createdAt desc)[0].baseIri `)) ?? undefined return { baseIri: baseIri, broader: [], // an empty array is needed here in order to return concepts with no "broader" for "related" related: [], // an empty array is needed here in order to return concepts with no "broader" for "related" } }, fields: [ defineField({ name: 'prefLabel', title: 'Preferred Label', type: 'localeString', description: 'The preferred lexical label for this concept.', validation: (Rule) => Rule.required().custom((prefLabel, context) => { const {getClient} = context const client = getClient({apiVersion: '2022-12-14'}) return client .fetch( `*[_type == "skosConcept" && prefLabel == "${prefLabel}" && !(_id in path("drafts.**"))][0]._id` ) .then((conceptId) => { if (conceptId && conceptId !== context.document?._id.replace('drafts.', '')) { return 'Preferred Label must be unique.' } return true }) }), }), defineField({ name: 'definition', title: 'Definition', type: 'localeText', description: ( A complete explanation of the intended meaning of the concept.
Example: documentation
Definition: "The process of storing and retrieving information in all fields of knowledge."

For more information on the recommended usage of the SKOS documentation properties, see {' '} W3C SKOS Primer: 2.4 Documentary Notes

), }), defineField({ name: 'example', title: 'Examples', type: 'text', description: ( An example of the use of the concept.
Example: organizations of science and culture
Example: "academies of science, general museums, world fairs"

For more information on the recommended usage of the SKOS documentation properties, see {' '} W3C SKOS Primer: 2.4 Documentary Notes

), rows: 3, }), defineField({ name: 'scopeNote', title: 'Scope Note', type: 'text', description: ( A brief statement on the intended meaning of this concept, especially as an indication of how the use of the concept is limited in indexing practice.
Example: microwave frequencies
Scope Note: "Used for frequencies between 1Ghz and 300Ghz"

For more information on the recommended usage of the SKOS documentation properties, see {' '} W3C SKOS Primer: 2.4 Documentary Notes

), rows: 3, }), defineField({ name: 'altLabel', title: 'Alternate Label(s)', type: 'array', description: ( Synonyms, near-synonyms, abbreviations, and acronyms to a concept.

Preferred, alternative, and hidden label sets must not overlap.

), of: [{type: 'string'}], validation: (Rule) => Rule.unique(), }), defineField({ name: 'hiddenLabel', title: 'Hidden Label(s)', type: 'array', description: ( Character strings that need to be accessible to applications performing text-based indexing and search operations, but which should not be displayed to end users.

Hidden labels may for instance be used to include misspelled variants of other lexical labels.

Preferred, alternative, and hidden label sets must not overlap.

), of: [{type: 'string'}], validation: (Rule) => Rule.unique(), }), ...baseIriField, defineField({ name: 'conceptId', title: 'Identifier', description: 'Concept unique identifier.', type: 'string', initialValue: () => `${randomKey(6)}`, }), defineField({ name: 'highlight', title: 'Highlight', description: 'Highlight this concept in the hierarchy view.', type: 'boolean', }), defineField({ name: 'icon', title: 'Icon', type: 'string', description: 'Icon to display.', }), defineField({ name: 'target', title: 'Target', type: 'string', description: 'Browser target for links.', }), defineField({ name: 'broader', title: 'Broader Concept(s)', description: ( Create hierarchy between concepts, for example to create category/subcategory, part/whole, or class/instance relationships.

Broader and Associated relationships are mutually exclusive.

), type: 'array', of: [ { type: 'reference', to: {type: 'skosConcept'}, options: { filter: ({document}: {document: any}) => { return { // Broader filter only performs document-level validation for broader-transitive/related disjunction. // Consider adding custom validation to prevent broader taxonomy inconsistencies. filter: '!(_id in $broader || _id in $related || _id in path("drafts.**") || _id == $self)', params: { self: document._id.replace('drafts.', ''), broader: document.broader.map(({_ref}: {_ref: any}) => _ref), related: document.related.map(({_ref}: {_ref: any}) => _ref), }, } }, }, }, ], }), defineField({ name: 'related', title: 'Related Concept(s)', description: ( Indicate that two concepts are inherently "related", but that one is not in any way more general than the other.

Broader and Associated relationships are mutually exclusive.

), type: 'array', of: [ { type: 'reference', to: [{type: 'skosConcept'}], }, ], }), defineField({ name: 'historyNote', title: 'History Notes', type: 'text', description: ( Significant changes to the meaning of the form of this concept.
Example: person with disabilities
History Note: "Estab. 1992; heading was: handicapped [1884 - 1992]."

For more information on the recommended usage of the SKOS documentation properties, see {' '} W3C SKOS Primer: 2.4 Documentary Notes

), rows: 3, }), defineField({ name: 'editorialNote', title: 'Editorial Notes', type: 'text', description: ( Information to aid in administrative housekeeping, such as reminders of editorial work still to be done, or warnings in the event that future editorial changes might be made.
Example: doubleclick
Editorial Note: "Review this term after the company merger is complete."

For more information on the recommended usage of the SKOS documentation properties, see {' '} W3C SKOS Primer: 2.4 Documentary Notes

), rows: 3, }), defineField({ name: 'image', title: 'Image', type: 'image', }), defineField({ name: 'priority', title: 'Priority', type: 'number', }), defineField({ name: 'changeNote', title: 'Change Notes', type: 'text', description: ( Fine-grained changes to a concept, for the purposes of administration and maintenance.
Example: tomato
Change Note: "Moved from under 'fruits' to under 'vegetables' by Horace Gray"

For more information on the recommended usage of the SKOS documentation properties, see {' '} W3C SKOS Primer: 2.4 Documentary Notes

), rows: 3, }), ], orderings: [ { title: 'Top Concepts', name: 'topConcept', by: [ {field: 'topConcept', direction: 'desc'}, {field: `prefLabel.${baseLanguage?.id}`, direction: 'asc'}, ], }, { title: 'Preferred Label', name: 'prefLabel', by: [{field: `prefLabel.${baseLanguage?.id}`, direction: 'asc'}], }, ], preview: { select: { title: `prefLabel.${baseLanguage?.id}`, }, prepare({title}) { return { title: title, media: AiOutlineTag, } }, }, }) }