Created
July 15, 2021 14:25
-
-
Save SimeonGriggs/44929f1ba5fbde86ef38fe3f64659b38 to your computer and use it in GitHub Desktop.
Sanity.io Document Action to automate Annotations from a 'Vocabulary' schema
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {useState} from 'react' | |
import {randomKey} from '@sanity/util/content' | |
import sanityClient from 'part:@sanity/base/client' | |
const apiVersion = `2021-05-19` | |
const client = sanityClient.withConfig({apiVersion}) | |
export default function AnnotateAction({draft, published}) { | |
const [isAnnotating, setIsAnnotating] = useState(false) | |
const doc = draft || published | |
async function performAnnotation() { | |
setIsAnnotating(true) | |
const {content} = doc | |
// 1. Map over every paragraph (aka "block") | |
// 2. Extract every piece of text | |
// 3. Filter that text's words down to any word OVER three characters | |
// 4. Query the `vocabulary` schema for matching words | |
// 5. Then create a markDef (annotation) to create any matching references | |
const contentWithAnnotations = await Promise.all( | |
content.map(async (block) => { | |
if (!block?.children?.length) { | |
return block | |
} | |
const newMarkDefs = block?.markDefs?.length ? [...block.markDefs] : [] | |
const newChildren = await block.children.reduce(async (allChildren, child) => { | |
// Only query `span` text with no existing marks | |
if (child._type !== 'span' || !child?.text || child.marks.length) { | |
return [...(await allChildren), child] | |
} | |
// Create the query params | |
const words = child.text | |
.split(' ') | |
.filter((word) => word.length > 3) | |
.map((word) => word.toLowerCase()) | |
if (!words?.length) { | |
return [...(await allChildren), child] | |
} | |
// Query the words in this span `text` against the vocabulary schema | |
const query = `*[_type == "vocabulary" && lower(word) in $words]{ _id, word }` | |
const matches = await client.fetch(query, {words}) | |
// No matches, just return the child | |
if (!matches?.length) { | |
return [...(await allChildren), child] | |
} | |
// Separate multiple unmatched words from matches ones, | |
// and turn them into reference annotations | |
const separateTextFromMatches = child.text.split(' ').reduce((acc, word, index) => { | |
const lastIdx = acc.length - 1 | |
const spanKey = randomKey(12) | |
const markKey = randomKey(12) | |
// Word matches the vocabulary query, setup a marksDef | |
const match = matches.find( | |
// eslint-disable-next-line max-nested-callbacks | |
(refMatch) => refMatch.word.toLowerCase() === word.toLowerCase() | |
) | |
if (match) { | |
// console.log(`word match: `, word) | |
// Add a space before, if it's not the first word | |
if (index) { | |
acc.push({ | |
_type: `span`, | |
_key: randomKey(12), | |
text: ` `, | |
marks: [], | |
}) | |
} | |
// Add our new annotation... | |
acc.push({ | |
_type: `span`, | |
_key: spanKey, | |
text: word, | |
marks: [markKey], | |
}) | |
// ...and its markDef to the outer block | |
newMarkDefs.push({ | |
_key: markKey, | |
_type: `vocabulary`, | |
vocabularyReference: { | |
_ref: match._id, | |
_type: `reference`, | |
}, | |
}) | |
} else if (acc[lastIdx]?.text && !acc[lastIdx]?.marks?.length) { | |
// console.log(`word add: `, word) | |
// Last item was a word, so add this word onto it | |
// Re-split the text and then join again (to prevent double spaces) | |
acc[lastIdx].text = [...acc[lastIdx].text.split(' '), word].join(' ') | |
} else { | |
// console.log(`word new: `, word) | |
// Word is just a word, add it to the last one or start a new one | |
acc.push({ | |
_type: `span`, | |
_key: spanKey, | |
text: index ? ` ${word}` : word, | |
marks: [], | |
}) | |
} | |
return acc | |
}, []) | |
return [...(await allChildren), ...separateTextFromMatches] | |
}, []) | |
// Compile the new content | |
return {...block, children: newChildren, markDefs: newMarkDefs} | |
}) | |
) | |
// In this example we send the new annotated text to a different field | |
client | |
.patch(doc._id) | |
.set({contentProcessed: contentWithAnnotations}) | |
.commit() | |
.then(() => setIsAnnotating(false)) | |
.catch((error) => { | |
console.error(error) | |
setIsAnnotating(false) | |
}) | |
} | |
return { | |
label: isAnnotating ? 'Annotating...' : 'Annotate', | |
onHandle: () => performAnnotation(), | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment