Skip to content
This repository was archived by the owner on Feb 3, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 51 additions & 2 deletions src/components/DocumentAddButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
isSanityDocument,
PatchEvent,
setIfMissing,
useClient,

Check failure on line 10 in src/components/DocumentAddButtons.tsx

View workflow job for this annotation

GitHub Actions / Lint & Build

'useClient' is defined but never used

Check failure on line 10 in src/components/DocumentAddButtons.tsx

View workflow job for this annotation

GitHub Actions / Lint & Build

'useClient' is defined but never used
useSchema,
} from 'sanity'
import {useDocumentPane} from 'sanity/structure'

Expand All @@ -21,15 +23,59 @@
// eslint-disable-next-line @typescript-eslint/no-explicit-any
value: Record<string, any> | undefined
}
export default function DocumentAddButtons(props: DocumentAddButtonsProps) {

Check warning on line 26 in src/components/DocumentAddButtons.tsx

View workflow job for this annotation

GitHub Actions / Lint & Build

Missing return type on function
const {filteredLanguages} = useInternationalizedArrayContext()
const value = isSanityDocument(props.value) ? props.value : undefined

const toast = useToast()
const {onChange} = useDocumentPane()
const schema = useSchema()

const documentsToTranslation = getDocumentsToTranslate(value, [])

// Helper function to determine if a field should be initialized as an array
const getInitialValueForType = useCallback((typeName: string): any => {

Check failure on line 37 in src/components/DocumentAddButtons.tsx

View workflow job for this annotation

GitHub Actions / Lint & Build

Unexpected any. Specify a different type

Check failure on line 37 in src/components/DocumentAddButtons.tsx

View workflow job for this annotation

GitHub Actions / Lint & Build

Insert `⏎····`
if (!typeName) return undefined

Check failure on line 38 in src/components/DocumentAddButtons.tsx

View workflow job for this annotation

GitHub Actions / Lint & Build

Insert `··`

// Extract the base type name from internationalized array type

Check failure on line 40 in src/components/DocumentAddButtons.tsx

View workflow job for this annotation

GitHub Actions / Lint & Build

Insert `··`
// e.g., "internationalizedArrayBodyValue" -> "body"

Check failure on line 41 in src/components/DocumentAddButtons.tsx

View workflow job for this annotation

GitHub Actions / Lint & Build

Insert `··`
const match = typeName.match(/^internationalizedArray(.+)Value$/)

Check failure on line 42 in src/components/DocumentAddButtons.tsx

View workflow job for this annotation

GitHub Actions / Lint & Build

Insert `··`
if (!match) return undefined

Check failure on line 43 in src/components/DocumentAddButtons.tsx

View workflow job for this annotation

GitHub Actions / Lint & Build

Insert `··`

const baseTypeName = match[1].charAt(0).toLowerCase() + match[1].slice(1)

Check failure on line 45 in src/components/DocumentAddButtons.tsx

View workflow job for this annotation

GitHub Actions / Lint & Build

Insert `··`

// Check if it's a known array-based type (Portable Text fields)
const arrayBasedTypes = ['body', 'htmlContent', 'blockContent', 'portableText']
if (arrayBasedTypes.includes(baseTypeName)) {
return []
}

// Try to look up the schema type to determine if it's an array
try {
const schemaType = schema.get(typeName)
if (schemaType) {
// Check if this is an object type with a 'value' field
const valueField = (schemaType as any)?.fields?.find((f: any) => f.name === 'value')
if (valueField) {
const fieldType = valueField.type
// Check if the value field is an array type
if (fieldType?.jsonType === 'array' ||
fieldType?.name === 'array' ||
fieldType?.type === 'array' ||
fieldType?.of !== undefined ||
arrayBasedTypes.includes(fieldType?.name)) {
return []
}
}
}
} catch (error) {
// If we can't determine from schema, fall back to undefined
console.warn('Could not determine field type from schema:', typeName, error)
}

return undefined
}, [schema])

const handleDocumentButtonClick = useCallback(
async (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
const languageId = event.currentTarget.value
Expand Down Expand Up @@ -77,13 +123,16 @@
for (const toTranslate of removeDuplicates) {
const path = toTranslate.path

// Get the appropriate initial value for this field type
const initialValue = getInitialValueForType(toTranslate._type)

const ifMissing = setIfMissing([], path)
const insertValue = insert(
[
{
_key: languageId,
_type: toTranslate._type,
value: undefined,
value: initialValue, // Use the determined initial value instead of undefined
},
],
'after',
Expand All @@ -95,7 +144,7 @@

onChange(PatchEvent.from(patches.flat()))
},
[documentsToTranslation, onChange, toast]
[documentsToTranslation, getInitialValueForType, onChange, toast]
)
return (
<Stack space={3}>
Expand Down
91 changes: 90 additions & 1 deletion src/components/InternationalizedInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,93 @@ export default function InternationalizedInput(
props.path.slice(0, -1)
) as InternationalizedValue[]

// Extract the original onChange to avoid dependency issues
const originalOnChange = props.inputProps.onChange

// Create a wrapped onChange handler to intercept patches for paste operations
const wrappedOnChange = useCallback(
(patches: any) => {
// Ensure patches is an array before proceeding with paste logic
// For single patch operations (like unset), pass through directly
if (!Array.isArray(patches)) {
return originalOnChange(patches)
}

// Check if this is a paste operation into an empty or uninitialized Portable Text field
const valueField = props.value?.value
const isEmptyOrUndefined =
valueField === undefined ||
valueField === null ||
(Array.isArray(valueField) && valueField.length === 0)

if (isEmptyOrUndefined) {
// Check for insert patches that are trying to operate on a non-existent structure
const hasProblematicInsert = patches.some((patch: any) => {
// Ensure patch exists and has required properties
if (!patch || typeof patch !== 'object') {
return false
}

// Look for insert patches targeting the value field or direct array index
if (
patch.type === 'insert' &&
patch.path &&
Array.isArray(patch.path) &&
patch.path.length > 0
) {
// The path might be ['value', index] or just [index] depending on context
const isTargetingValue =
patch.path[0] === 'value' || typeof patch.path[0] === 'number'
return isTargetingValue
}
return false
})

if (hasProblematicInsert) {
// First, ensure the value field exists as an empty array if it doesn't
const initPatch =
valueField === undefined
? {type: 'setIfMissing', path: ['value'], value: []}
: null

// Transform the patches to ensure they work with the nested structure
const fixedPatches = patches.map((patch: any) => {
// Ensure patch exists and has required properties
if (!patch || typeof patch !== 'object') {
return patch
}

if (
patch.type === 'insert' &&
patch.path &&
Array.isArray(patch.path)
) {
// Ensure the path is correct for the nested structure
const fixedPath =
patch.path[0] === 'value'
? patch.path
: ['value', ...patch.path]
const fixedPatch = {...patch, path: fixedPath}
return fixedPatch
}
return patch
})

// If we need to initialize the field, include that patch first
const allPatches = initPatch
? [initPatch, ...fixedPatches]
: fixedPatches

return originalOnChange(allPatches)
}
}

// For all other cases, pass through unchanged
return originalOnChange(patches)
},
[props.value, originalOnChange]
)

const inlineProps = {
...props.inputProps,
// This is the magic that makes inline editing work?
Expand All @@ -43,6 +130,8 @@ export default function InternationalizedInput(
// This just overrides the type
// Remove this as it shouldn't be necessary?
value: props.value as InternationalizedValue,
// Use our wrapped onChange handler
onChange: wrappedOnChange,
}

const {validation, value, onChange, readOnly} = inlineProps
Expand Down Expand Up @@ -137,7 +226,7 @@ export default function InternationalizedInput(
</Card>
<Flex align="center" gap={2}>
<Card flex={1} tone="inherit">
{props.inputProps.renderInput(props.inputProps)}
{props.inputProps.renderInput(inlineProps)}
</Card>

<Card tone="inherit">
Expand Down
Loading