⚠ This page is served via a proxy. Original site: https://github.com
This service does not collect credentials or authentication data.
Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
5 changes: 4 additions & 1 deletion plugins/csv-import/src/components/FieldMapperRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export interface FieldMappingItem {
interface FieldMapperRowProps {
item: FieldMappingItem
existingFields: Field[]
slugFieldName: string | null
onToggleIgnored: () => void
onSetIgnored: (ignored: boolean) => void
onTargetChange: (targetFieldId: string | null) => void
Expand All @@ -29,13 +30,15 @@ interface FieldMapperRowProps {
export function FieldMapperRow({
item,
existingFields,
slugFieldName,
onToggleIgnored,
onSetIgnored,
onTargetChange,
onTypeChange,
}: FieldMapperRowProps) {
const { inferredField, action, targetFieldId, hasTypeMismatch, overrideType } = item
const isIgnored = action === "ignore"
const isSlugField = slugFieldName && inferredField.columnName === slugFieldName

// Find the target field when mapping to an existing field
const targetField = targetFieldId ? existingFields.find(f => f.id === targetFieldId) : null
Expand Down Expand Up @@ -98,7 +101,7 @@ export function FieldMapperRow({
}}
>
<option value="__create__">New Field...</option>
{isIgnored && <option value="__ignore__"></option>}
{isIgnored && <option value="__ignore__">{isSlugField ? "Slug Field" : ""}</option>}

{existingFields.length > 0 && <hr />}
{existingFields.map(field => (
Expand Down
125 changes: 56 additions & 69 deletions plugins/csv-import/src/routes/FieldMapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@ interface FieldMapperProps {
onSubmit: (opts: FieldMapperSubmitOpts) => Promise<void>
}

function isValidSlugColumn(columnName: string, csvRecords: Record<string, string>[]) {
return csvRecords.every(record => record[columnName])
}

function calculatePossibleSlugFields(mappings: FieldMappingItem[], csvRecords: Record<string, string>[]) {
return mappings
.filter(m => csvRecords.every(record => record[m.inferredField.columnName]) && m.action !== "ignore")
.map(m => m.inferredField)
return mappings.filter(m => isValidSlugColumn(m.inferredField.columnName, csvRecords)).map(m => m.inferredField)
}

export function FieldMapper({ collection, csvRecords, onSubmit }: FieldMapperProps) {
Expand All @@ -54,34 +56,48 @@ export function FieldMapper({ collection, csvRecords, onSubmit }: FieldMapperPro

const inferredFields = inferFieldsFromCSV(csvRecords)

// Determine which column should be the slug field
// Ensure the slug column is in inferredFields (exists in CSV) so it appears in possibleSlugFields
const slugColumnNameFromCollection =
collection.slugFieldName &&
inferredFields.find(field => field.columnName === collection.slugFieldName)?.columnName
const slugColumnName =
slugColumnNameFromCollection ??
inferredFields.find(field => isValidSlugColumn(field.columnName, csvRecords))?.columnName

// Create initial mappings based on name matching
const initialMappings: FieldMappingItem[] = inferredFields.map(inferredField => {
// Only ignore slug field if it matches the collection's slugFieldName
// If it was auto-detected, keep it enabled
const isSlugField =
slugColumnNameFromCollection && inferredField.columnName === slugColumnNameFromCollection

// Try to find an existing field with matching name
const matchingField = fields.find(f => f.name.toLowerCase() === inferredField.name.toLowerCase())

if (matchingField) {
const hasTypeMismatch = !isTypeCompatible(inferredField.inferredType, matchingField.type)
mappedFieldIds.add(matchingField.id)
if (!isSlugField) {
mappedFieldIds.add(matchingField.id)
}
return {
inferredField,
action: "map",
targetFieldId: matchingField.id,
hasTypeMismatch,
action: isSlugField ? "ignore" : "map",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm I just tried an import on a new collection and it didn't auto-ignore the slug column, maybe the change I proposed needs a bugfix

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was the slug column's name the same as the slug field name on the collection, usually "Slug"? It only auto ignores it if the slug column name matches, because we don't want to ignore it by default if it's using a different column for the slug like "Title" for example.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you copy and paste this table in with the Slug column, it should make the Slug field ignored by default. But if you copy without the first column then it should make Title the slug field and not make Title ignored.

https://docs.google.com/spreadsheets/d/1JuOKh_Wb0uIytKe4eE7uI6Js826i8V_uf2oFtyolDtA/edit?gid=1174080602#gid=1174080602

Copy link
Contributor

@Nick-Lucas Nick-Lucas Jan 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah good point.

Image

So I think this might be confusing to be honest, not sure most users will understand why that column is disabled. And also if we're only covering the scenario where the slug name is the same as a column name, then displaying the mapping isn't a bad thing or incorrect right?

Maybe we should bring @johannes-ger in on the discussion?

targetFieldId: isSlugField ? undefined : matchingField.id,
hasTypeMismatch: isSlugField ? false : hasTypeMismatch,
}
}

// No match - create new field
// No match - create new field or ignore if it's the slug field
return {
inferredField,
action: "create",
action: isSlugField ? "ignore" : "create",
hasTypeMismatch: false,
}
})

setMappings(initialMappings)

const possibleSlugFields = calculatePossibleSlugFields(initialMappings, csvRecords)
setSelectedSlugFieldName(possibleSlugFields[0]?.columnName ?? null)
setSelectedSlugFieldName(slugColumnName ?? null)

// Find fields that exist in collection but are not mapped from CSV
const initialMissingFields: MissingFieldItem[] = fields
Expand All @@ -103,64 +119,41 @@ export function FieldMapper({ collection, csvRecords, onSubmit }: FieldMapperPro
void loadFields()
}, [collection, csvRecords])

const toggleIgnored = useCallback(
(columnName: string) => {
setMappings(prev => {
const currentItem = prev.find(item => item.inferredField.columnName === columnName)
const willBeIgnored = currentItem?.action !== "ignore"

const newMappings = prev.map(item => {
if (item.inferredField.columnName !== columnName) return item

if (item.action === "ignore") {
// Un-ignore: restore to create mode
return { ...item, action: "create" as const, targetFieldId: undefined, hasTypeMismatch: false }
} else {
// Ignore
return { ...item, action: "ignore" as const, targetFieldId: undefined, hasTypeMismatch: false }
}
})
const toggleIgnored = useCallback((columnName: string) => {
setMappings(prev => {
const newMappings = prev.map(item => {
if (item.inferredField.columnName !== columnName) return item

// If ignoring the current slug field, switch to another available one
if (willBeIgnored && columnName === selectedSlugFieldName) {
setSelectedSlugFieldName(null)
if (item.action === "ignore") {
// Un-ignore: restore to create mode
return { ...item, action: "create" as const, targetFieldId: undefined, hasTypeMismatch: false }
} else {
// Ignore
return { ...item, action: "ignore" as const, targetFieldId: undefined, hasTypeMismatch: false }
}

return newMappings
})
},
[selectedSlugFieldName]
)

const setIgnored = useCallback(
(columnName: string, ignored: boolean) => {
setMappings(prev => {
const newMappings = prev.map(item => {
if (item.inferredField.columnName !== columnName) return item

if (ignored) {
return { ...item, action: "ignore" as const, targetFieldId: undefined, hasTypeMismatch: false }
} else if (item.action === "ignore") {
// Un-ignore: restore to create mode
return { ...item, action: "create" as const, targetFieldId: undefined, hasTypeMismatch: false }
}
return item
})
return newMappings
})
}, [])

// If ignoring the current slug field, switch to another available one
if (ignored && columnName === selectedSlugFieldName) {
const newSlugField = newMappings
.filter(m => m.action !== "ignore")
.find(m => csvRecords.every(record => record[m.inferredField.columnName]))
const setIgnored = useCallback((columnName: string, ignored: boolean) => {
setMappings(prev => {
const newMappings = prev.map(item => {
if (item.inferredField.columnName !== columnName) return item

setSelectedSlugFieldName(newSlugField?.inferredField.columnName ?? null)
if (ignored) {
return { ...item, action: "ignore" as const, targetFieldId: undefined, hasTypeMismatch: false }
} else if (item.action === "ignore") {
// Un-ignore: restore to create mode
return { ...item, action: "create" as const, targetFieldId: undefined, hasTypeMismatch: false }
}

return newMappings
return item
})
},
[selectedSlugFieldName, csvRecords]
)

return newMappings
})
}, [])

const updateTarget = useCallback(
(columnName: string, targetFieldId: string | null) => {
Expand Down Expand Up @@ -237,13 +230,6 @@ export function FieldMapper({ collection, csvRecords, onSubmit }: FieldMapperPro
return
}

// Check if the slug field is being ignored
const slugMapping = mappings.find(m => m.inferredField.columnName === selectedSlugFieldName)
if (slugMapping?.action === "ignore") {
framer.notify("The slug field cannot be ignored.", { variant: "warning" })
return
}

// Check if all required fields are mapped
if (unmappedRequiredFields.length > 0) {
framer.notify("All required fields must be mapped before importing.", { variant: "warning" })
Expand Down Expand Up @@ -321,6 +307,7 @@ export function FieldMapper({ collection, csvRecords, onSubmit }: FieldMapperPro
key={item.inferredField.columnName}
item={item}
existingFields={existingFields}
slugFieldName={selectedSlugFieldName}
onToggleIgnored={() => {
toggleIgnored(item.inferredField.columnName)
}}
Expand Down
Loading