@ -1,5 +1,6 @@
import { UITypes } from 'nocodb-sdk'
import { type ColumnType , FieldNameFromUITypes , UITypes } from 'nocodb-sdk'
import isURL from 'validator/lib/isURL'
import { pluralize } from 'inflection'
// This regex pattern matches email addresses by looking for sequences that start with characters before the "@" symbol, followed by the domain.
// It's designed to capture most email formats, including those with periods and "+" symbols in the local part.
@ -279,3 +280,208 @@ export const getFormattedViewTabTitle = ({
return title
}
export const generateUniqueColumnSuffix = ( {
tableExplorerColumns ,
metaColumns ,
} : {
tableExplorerColumns? : ColumnType [ ]
metaColumns : ColumnType [ ]
} ) = > {
let suffix = ( metaColumns ? . length || 0 ) + 1
let columnName = ` title ${ suffix } `
while (
( tableExplorerColumns || metaColumns ) ? . some (
( c ) = >
( c . column_name || '' ) . toLowerCase ( ) === columnName . toLowerCase ( ) ||
( c . title || '' ) . toLowerCase ( ) === columnName . toLowerCase ( ) ,
)
) {
suffix ++
columnName = ` title ${ suffix } `
}
return suffix
}
const extractNextDefaultColumnName = ( {
tableExplorerColumns ,
metaColumns ,
defaultColumnName ,
newFieldTitles ,
formState ,
} : {
tableExplorerColumns? : ColumnType [ ]
metaColumns : ColumnType [ ]
defaultColumnName : string
newFieldTitles : string [ ]
formState : Record < string , any >
} ) : string = > {
// Extract and sort numbers associated with the provided defaultName
const namesData = ( ( tableExplorerColumns || metaColumns )
? . flatMap ( ( c ) = > {
if ( formState ? . temp_id && c ? . temp_id && formState ? . temp_id === c ? . temp_id ) {
return [ ]
}
if ( c . title !== c . column_name ) {
return [ c . title ? . toLowerCase ( ) , c . column_name ? . toLowerCase ( ) ]
}
return [ c . title ? . toLowerCase ( ) ]
} )
. filter ( ( t ) = > t && t . startsWith ( defaultColumnName . toLowerCase ( ) ) ) || [ ] ) as string [ ]
if ( ! [ . . . namesData , . . . newFieldTitles ] . includes ( defaultColumnName . toLowerCase ( ) ) ) {
return defaultColumnName
}
const extractedSortedNumbers =
( namesData
. map ( ( name ) = > {
const [ _defaultName , number ] = name . split ( / (?!.* )/ )
if ( _defaultName === defaultColumnName . toLowerCase ( ) && ! isNaN ( Number ( number ? . trim ( ) ) ) ) {
return Number ( number ? . trim ( ) )
}
return undefined
} )
. filter ( ( e ) = > e )
. sort ( ( a , b ) = > {
if ( a !== undefined && b !== undefined ) {
return a - b
}
return 0
} ) as number [ ] ) || [ ]
return extractedSortedNumbers . length
? ` ${ defaultColumnName } ${ extractedSortedNumbers [ extractedSortedNumbers . length - 1 ] + 1 } `
: ` ${ defaultColumnName } 1 `
}
export const generateUniqueColumnName = ( {
tableExplorerColumns ,
metaColumns ,
formState ,
newFieldTitles ,
} : {
tableExplorerColumns? : ColumnType [ ]
metaColumns : ColumnType [ ]
formState : Record < string , any >
newFieldTitles? : string [ ]
} ) = > {
let defaultColumnName = FieldNameFromUITypes [ formState . uidt as UITypes ]
if ( ! defaultColumnName ) {
return ` title ${ generateUniqueColumnSuffix ( { tableExplorerColumns , metaColumns } )} `
}
switch ( formState . uidt ) {
case UITypes . User : {
if ( formState . meta . is_multi ) {
defaultColumnName = ` ${ defaultColumnName } s `
}
break
}
case UITypes . Links :
case UITypes . LinkToAnotherRecord : {
if ( ! formState . childTableTitle ) {
return ` title ${ generateUniqueColumnSuffix ( { tableExplorerColumns , metaColumns } )} `
}
let childTableTitle = formState . childTableTitle
// Use plural for links except oo relation type
if ( formState . uidt === UITypes . Links && formState ? . type !== 'oo' ) {
childTableTitle = pluralize ( childTableTitle )
}
// Calculate the remaining length available for childTableTitle
const maxLength = 255 - ( defaultColumnName . length - 11 + '{TableName}' . length )
// Truncate childTableTitle if it exceeds the maxLength
if ( childTableTitle . length > maxLength ) {
childTableTitle = ` ${ childTableTitle . slice ( 0 , maxLength - 3 ) } ... `
}
// Replace {TableName} with the potentially truncated childTableTitle
defaultColumnName = defaultColumnName . replace ( '{TableName}' , childTableTitle )
// Ensure the final defaultColumnName is less than 255 characters
if ( defaultColumnName . length >= 255 ) {
defaultColumnName = ` ${ defaultColumnName . slice ( 0 , 252 ) } ... `
}
break
}
case UITypes . Lookup : {
if ( ! formState . lookupTableTitle || ! formState . lookupColumnTitle ) {
return ` title ${ generateUniqueColumnSuffix ( { tableExplorerColumns , metaColumns } )} `
}
let lookupTableTitle = formState . lookupTableTitle
let lookupColumnTitle = formState . lookupColumnTitle
// Calculate the lengths of the placeholders
const placeholderLength = '{TableName}' . length + '{FieldName}' . length
const baseLength = defaultColumnName . length - placeholderLength
// Calculate the maximum length allowed for both titles combined
const maxTotalLength = 255 - baseLength
const maxLengthPerTitle = Math . floor ( maxTotalLength / 2 )
// Truncate the titles if necessary
if ( lookupTableTitle . length > maxLengthPerTitle ) {
lookupTableTitle = ` ${ lookupTableTitle . slice ( 0 , maxLengthPerTitle - 3 ) } ... `
}
if ( lookupColumnTitle . length > maxLengthPerTitle ) {
lookupColumnTitle = ` ${ lookupColumnTitle . slice ( 0 , maxLengthPerTitle - 3 ) } ... `
}
// Replace placeholders
defaultColumnName = defaultColumnName . replace ( '{TableName}' , lookupTableTitle ) . replace ( '{FieldName}' , lookupColumnTitle )
break
}
case UITypes . Rollup : {
if ( ! formState . rollupTableTitle || ! formState . rollupColumnTitle || ! formState ? . rollup_function_name ) {
return ` title ${ generateUniqueColumnSuffix ( { tableExplorerColumns , metaColumns } )} `
}
let rollupTableTitle = formState . rollupTableTitle
let rollupColumnTitle = formState . rollupColumnTitle
// Update rollup function name
defaultColumnName = defaultColumnName . replace ( '{RollupFunction}' , formState . rollup_function_name )
// Calculate the lengths of the placeholders
const placeholderLength = '{TableName}' . length + '{FieldName}' . length
const baseLength = defaultColumnName . length - placeholderLength
// Calculate the maximum length allowed for both titles combined
const maxTotalLength = 255 - baseLength
const maxLengthPerTitle = Math . floor ( maxTotalLength / 2 )
// Truncate the titles if necessary
if ( rollupTableTitle . length > maxLengthPerTitle ) {
rollupTableTitle = ` ${ rollupTableTitle . slice ( 0 , maxLengthPerTitle - 3 ) } ... `
}
if ( rollupColumnTitle . length > maxLengthPerTitle ) {
rollupColumnTitle = ` ${ rollupColumnTitle . slice ( 0 , maxLengthPerTitle - 3 ) } ... `
}
// Replace placeholders
defaultColumnName = defaultColumnName . replace ( '{TableName}' , rollupTableTitle ) . replace ( '{FieldName}' , rollupColumnTitle )
break
}
}
return extractNextDefaultColumnName ( {
tableExplorerColumns ,
metaColumns ,
defaultColumnName ,
newFieldTitles : newFieldTitles || [ ] ,
formState ,
} )
}