RICH_TEXT_V2 frontend (#10083)
Adds task and note support for the new `bodyV2` field. (Field metadata type of `bodyV2` is `RICH_TEXT_V2`.) Related to issue https://github.com/twentyhq/twenty/issues/7613 Upgrade commands will be in separate PRs. Fixes https://github.com/twentyhq/twenty/issues/10084 --------- Co-authored-by: ad-elias <elias@autodiligence.com> Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
@ -128,6 +128,15 @@ export type RawJsonFilter = {
|
||||
is?: IsFilter;
|
||||
};
|
||||
|
||||
export type RichTextV2LeafFilter = {
|
||||
ilike?: string;
|
||||
};
|
||||
|
||||
export type RichTextV2Filter = {
|
||||
blocknote?: RichTextV2LeafFilter;
|
||||
markdown?: RichTextV2LeafFilter;
|
||||
};
|
||||
|
||||
export type LeafFilter =
|
||||
| UUIDFilter
|
||||
| StringFilter
|
||||
@ -143,6 +152,7 @@ export type LeafFilter =
|
||||
| PhonesFilter
|
||||
| ArrayFilter
|
||||
| RawJsonFilter
|
||||
| RichTextV2Filter
|
||||
| undefined;
|
||||
|
||||
export type AndObjectRecordFilter = {
|
||||
|
||||
@ -15,6 +15,7 @@ import {
|
||||
import { useObjectNamePluralFromSingular } from '@/object-metadata/hooks/useObjectNamePluralFromSingular';
|
||||
import { useHandleToggleTrashColumnFilter } from '@/object-record/record-index/hooks/useHandleToggleTrashColumnFilter';
|
||||
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useObjectOptionsForBoard } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForBoard';
|
||||
import { useOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useOptionsDropdown';
|
||||
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
|
||||
@ -93,6 +94,11 @@ export const ObjectOptionsDropdownMenuContent = () => {
|
||||
viewType,
|
||||
});
|
||||
|
||||
// TODO: Remove this once we have implemented Rich Text v2 and removed the old rich text
|
||||
const canImportOrExport =
|
||||
objectMetadataItem.nameSingular !== CoreObjectNameSingular.Note &&
|
||||
objectMetadataItem.nameSingular !== CoreObjectNameSingular.Task;
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuHeader StartIcon={CurrentViewIcon ?? IconList}>
|
||||
@ -151,16 +157,20 @@ export const ObjectOptionsDropdownMenuContent = () => {
|
||||
</DropdownMenuItemsContainer>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
onClick={download}
|
||||
LeftIcon={IconFileExport}
|
||||
text={displayedExportProgress(progress)}
|
||||
/>
|
||||
<MenuItem
|
||||
onClick={() => openObjectRecordsSpreasheetImportDialog()}
|
||||
LeftIcon={IconFileImport}
|
||||
text="Import"
|
||||
/>
|
||||
{canImportOrExport && (
|
||||
<>
|
||||
<MenuItem
|
||||
onClick={download}
|
||||
LeftIcon={IconFileExport}
|
||||
text={displayedExportProgress(progress)}
|
||||
/>
|
||||
<MenuItem
|
||||
onClick={() => openObjectRecordsSpreasheetImportDialog()}
|
||||
LeftIcon={IconFileImport}
|
||||
text="Import"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
handleToggleTrashColumnFilter();
|
||||
|
||||
@ -9,6 +9,7 @@ import { PhonesFieldDisplay } from '@/object-record/record-field/meta-types/disp
|
||||
import { RatingFieldDisplay } from '@/object-record/record-field/meta-types/display/components/RatingFieldDisplay';
|
||||
import { RelationFromManyFieldDisplay } from '@/object-record/record-field/meta-types/display/components/RelationFromManyFieldDisplay';
|
||||
import { RichTextFieldDisplay } from '@/object-record/record-field/meta-types/display/components/RichTextFieldDisplay';
|
||||
import { RichTextV2FieldDisplay } from '@/object-record/record-field/meta-types/display/components/RichTextV2FieldDisplay';
|
||||
import { isFieldIdentifierDisplay } from '@/object-record/record-field/meta-types/display/utils/isFieldIdentifierDisplay';
|
||||
import { isFieldActor } from '@/object-record/record-field/types/guards/isFieldActor';
|
||||
import { isFieldArray } from '@/object-record/record-field/types/guards/isFieldArray';
|
||||
@ -20,6 +21,7 @@ import { isFieldRating } from '@/object-record/record-field/types/guards/isField
|
||||
import { isFieldRelationFromManyObjects } from '@/object-record/record-field/types/guards/isFieldRelationFromManyObjects';
|
||||
import { isFieldRelationToOneObject } from '@/object-record/record-field/types/guards/isFieldRelationToOneObject';
|
||||
import { isFieldRichText } from '@/object-record/record-field/types/guards/isFieldRichText';
|
||||
import { isFieldRichTextV2 } from '@/object-record/record-field/types/guards/isFieldRichTextV2';
|
||||
import { FieldContext } from '../contexts/FieldContext';
|
||||
import { AddressFieldDisplay } from '../meta-types/display/components/AddressFieldDisplay';
|
||||
import { ChipFieldDisplay } from '../meta-types/display/components/ChipFieldDisplay';
|
||||
@ -90,6 +92,8 @@ export const FieldDisplay = () => {
|
||||
<RatingFieldDisplay />
|
||||
) : isFieldRichText(fieldDefinition) ? (
|
||||
<RichTextFieldDisplay />
|
||||
) : isFieldRichTextV2(fieldDefinition) ? (
|
||||
<RichTextV2FieldDisplay />
|
||||
) : isFieldActor(fieldDefinition) ? (
|
||||
<ActorFieldDisplay />
|
||||
) : isFieldArray(fieldDefinition) ? (
|
||||
|
||||
@ -29,7 +29,9 @@ import { RecordForSelect } from '@/object-record/relation-picker/types/RecordFor
|
||||
import { isFieldArray } from '@/object-record/record-field/types/guards/isFieldArray';
|
||||
import { isFieldArrayValue } from '@/object-record/record-field/types/guards/isFieldArrayValue';
|
||||
import { isFieldRichText } from '@/object-record/record-field/types/guards/isFieldRichText';
|
||||
import { isFieldRichTextV2 } from '@/object-record/record-field/types/guards/isFieldRichTextV2';
|
||||
import { isFieldRichTextValue } from '@/object-record/record-field/types/guards/isFieldRichTextValue';
|
||||
import { isFieldRichTextV2Value } from '@/object-record/record-field/types/guards/isFieldRichTextValueV2';
|
||||
import { getForeignKeyNameFromRelationFieldName } from '@/object-record/utils/getForeignKeyNameFromRelationFieldName';
|
||||
import { FieldContext } from '../contexts/FieldContext';
|
||||
import { isFieldBoolean } from '../types/guards/isFieldBoolean';
|
||||
@ -118,6 +120,10 @@ export const usePersistField = () => {
|
||||
isFieldRichText(fieldDefinition) &&
|
||||
isFieldRichTextValue(valueToPersist);
|
||||
|
||||
const fieldIsRichTextV2 =
|
||||
isFieldRichTextV2(fieldDefinition) &&
|
||||
isFieldRichTextV2Value(valueToPersist);
|
||||
|
||||
const fieldIsArray =
|
||||
isFieldArray(fieldDefinition) && isFieldArrayValue(valueToPersist);
|
||||
|
||||
@ -139,7 +145,8 @@ export const usePersistField = () => {
|
||||
fieldIsAddress ||
|
||||
fieldIsRawJson ||
|
||||
fieldIsArray ||
|
||||
fieldIsRichText;
|
||||
fieldIsRichText ||
|
||||
fieldIsRichTextV2;
|
||||
|
||||
if (isValuePersistable) {
|
||||
const fieldName = fieldDefinition.metadata.fieldName;
|
||||
|
||||
@ -0,0 +1,16 @@
|
||||
import { useRichTextV2FieldDisplay } from '@/object-record/record-field/meta-types/hooks/useRichTextV2FieldDisplay';
|
||||
import { getFirstNonEmptyLineOfRichText } from '@/ui/input/editor/utils/getFirstNonEmptyLineOfRichText';
|
||||
import { PartialBlock } from '@blocknote/core';
|
||||
import { parseJson } from '~/utils/parseJson';
|
||||
|
||||
export const RichTextV2FieldDisplay = () => {
|
||||
const { fieldValue } = useRichTextV2FieldDisplay();
|
||||
|
||||
const blocks = parseJson<PartialBlock[]>(fieldValue?.blocknote);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<span>{getFirstNonEmptyLineOfRichText(blocks)}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,73 @@
|
||||
import { useContext } from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
|
||||
import { useRecordFieldInput } from '@/object-record/record-field/hooks/useRecordFieldInput';
|
||||
import {
|
||||
FieldRichTextV2Value,
|
||||
FieldRichTextValue,
|
||||
} from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
import { usePersistField } from '@/object-record/record-field/hooks/usePersistField';
|
||||
import { isFieldRichTextV2 } from '@/object-record/record-field/types/guards/isFieldRichTextV2';
|
||||
import { isFieldRichTextV2Value } from '@/object-record/record-field/types/guards/isFieldRichTextValueV2';
|
||||
import { PartialBlock } from '@blocknote/core';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { FieldContext } from '../../contexts/FieldContext';
|
||||
import { assertFieldMetadata } from '../../types/guards/assertFieldMetadata';
|
||||
|
||||
export const useRichTextV2Field = () => {
|
||||
const { recordId, fieldDefinition, hotkeyScope, maxWidth } =
|
||||
useContext(FieldContext);
|
||||
|
||||
assertFieldMetadata(
|
||||
FieldMetadataType.RICH_TEXT_V2,
|
||||
isFieldRichTextV2,
|
||||
fieldDefinition,
|
||||
);
|
||||
|
||||
const fieldName = fieldDefinition.metadata.fieldName;
|
||||
|
||||
const [fieldValue, setFieldValue] = useRecoilState<FieldRichTextValue>(
|
||||
recordStoreFamilySelector({
|
||||
recordId,
|
||||
fieldName: fieldName,
|
||||
}),
|
||||
);
|
||||
const fieldRichTextV2Value = isFieldRichTextV2Value(fieldValue)
|
||||
? fieldValue
|
||||
: ({ blocknote: null, markdown: null } as FieldRichTextV2Value);
|
||||
|
||||
const { setDraftValue, getDraftValueSelector } =
|
||||
useRecordFieldInput<FieldRichTextValue>(`${recordId}-${fieldName}`);
|
||||
|
||||
const draftValue = useRecoilValue(getDraftValueSelector());
|
||||
|
||||
const draftValueParsed: PartialBlock[] = isNonEmptyString(draftValue)
|
||||
? JSON.parse(draftValue)
|
||||
: draftValue;
|
||||
|
||||
const persistField = usePersistField();
|
||||
|
||||
const persistRichTextField = (nextValue: PartialBlock[]) => {
|
||||
if (!nextValue) {
|
||||
persistField(null);
|
||||
} else {
|
||||
const parsedValueToPersist = JSON.stringify(nextValue);
|
||||
|
||||
persistField(parsedValueToPersist);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
draftValue: draftValueParsed,
|
||||
setDraftValue,
|
||||
maxWidth,
|
||||
fieldDefinition,
|
||||
fieldValue: fieldRichTextV2Value,
|
||||
setFieldValue,
|
||||
hotkeyScope,
|
||||
persistRichTextField,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,32 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||
|
||||
import { FieldRichTextV2Value } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { assertFieldMetadata } from '@/object-record/record-field/types/guards/assertFieldMetadata';
|
||||
import { isFieldRichTextV2 } from '@/object-record/record-field/types/guards/isFieldRichTextV2';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import { FieldContext } from '../../contexts/FieldContext';
|
||||
|
||||
export const useRichTextV2FieldDisplay = () => {
|
||||
const { recordId, fieldDefinition, hotkeyScope } = useContext(FieldContext);
|
||||
|
||||
assertFieldMetadata(
|
||||
FieldMetadataType.RICH_TEXT_V2,
|
||||
isFieldRichTextV2,
|
||||
fieldDefinition,
|
||||
);
|
||||
|
||||
const fieldName = fieldDefinition.metadata.fieldName;
|
||||
|
||||
const fieldValue = useRecordFieldValue<FieldRichTextV2Value | undefined>(
|
||||
recordId,
|
||||
fieldName,
|
||||
);
|
||||
|
||||
return {
|
||||
fieldDefinition,
|
||||
fieldValue,
|
||||
hotkeyScope,
|
||||
};
|
||||
};
|
||||
@ -128,6 +128,12 @@ export type FieldRawJsonMetadata = {
|
||||
settings?: null;
|
||||
};
|
||||
|
||||
export type FieldRichTextV2Metadata = {
|
||||
objectMetadataNameSingular?: string;
|
||||
fieldName: string;
|
||||
settings?: null;
|
||||
};
|
||||
|
||||
export type FieldRichTextMetadata = {
|
||||
objectMetadataNameSingular?: string;
|
||||
fieldName: string;
|
||||
@ -212,7 +218,9 @@ export type FieldMetadata =
|
||||
| FieldAddressMetadata
|
||||
| FieldActorMetadata
|
||||
| FieldArrayMetadata
|
||||
| FieldTsVectorMetadata;
|
||||
| FieldTsVectorMetadata
|
||||
| FieldRichTextV2Metadata
|
||||
| FieldRichTextMetadata;
|
||||
|
||||
export type FieldTextValue = string;
|
||||
export type FieldUUidValue = string; // TODO: can we replace with a template literal type, or maybe overkill ?
|
||||
@ -264,6 +272,11 @@ export type FieldRelationValue<
|
||||
export type Json = ZodHelperLiteral | { [key: string]: Json } | Json[];
|
||||
export type FieldJsonValue = Record<string, Json> | Json[] | null;
|
||||
|
||||
export type FieldRichTextV2Value = {
|
||||
blocknote: string | null;
|
||||
markdown: string | null;
|
||||
};
|
||||
|
||||
export type FieldRichTextValue = null | string;
|
||||
|
||||
export type FieldActorValue = {
|
||||
|
||||
@ -23,6 +23,7 @@ import {
|
||||
FieldRawJsonMetadata,
|
||||
FieldRelationMetadata,
|
||||
FieldRichTextMetadata,
|
||||
FieldRichTextV2Metadata,
|
||||
FieldSelectMetadata,
|
||||
FieldTextMetadata,
|
||||
FieldUuidMetadata,
|
||||
@ -68,15 +69,17 @@ type AssertFieldMetadataFunction = <
|
||||
? FieldAddressMetadata
|
||||
: E extends 'RAW_JSON'
|
||||
? FieldRawJsonMetadata
|
||||
: E extends 'RICH_TEXT'
|
||||
? FieldRichTextMetadata
|
||||
: E extends 'ACTOR'
|
||||
? FieldActorMetadata
|
||||
: E extends 'ARRAY'
|
||||
? FieldArrayMetadata
|
||||
: E extends 'PHONES'
|
||||
? FieldPhonesMetadata
|
||||
: never,
|
||||
: E extends 'RICH_TEXT_V2'
|
||||
? FieldRichTextV2Metadata
|
||||
: E extends 'RICH_TEXT'
|
||||
? FieldRichTextMetadata
|
||||
: E extends 'ACTOR'
|
||||
? FieldActorMetadata
|
||||
: E extends 'ARRAY'
|
||||
? FieldArrayMetadata
|
||||
: E extends 'PHONES'
|
||||
? FieldPhonesMetadata
|
||||
: never,
|
||||
>(
|
||||
fieldType: E,
|
||||
fieldTypeGuard: (
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
import { FieldDefinition } from '../FieldDefinition';
|
||||
import { FieldMetadata, FieldRichTextV2Metadata } from '../FieldMetadata';
|
||||
|
||||
export const isFieldRichTextV2 = (
|
||||
field: Pick<FieldDefinition<FieldMetadata>, 'type'>,
|
||||
): field is FieldDefinition<FieldRichTextV2Metadata> =>
|
||||
field.type === FieldMetadataType.RICH_TEXT_V2;
|
||||
@ -2,7 +2,7 @@ import { z } from 'zod';
|
||||
import { FieldRichTextValue } from '../FieldMetadata';
|
||||
|
||||
export const richTextSchema: z.ZodType<FieldRichTextValue> = z.union([
|
||||
z.null(), // Exclude literal values other than null
|
||||
z.null(),
|
||||
z.string(),
|
||||
]);
|
||||
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
import { FieldRichTextV2Value } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const richTextV2Schema: z.ZodType<FieldRichTextV2Value> = z.object({
|
||||
blocknote: z.string().nullable(),
|
||||
markdown: z.string().nullable(),
|
||||
});
|
||||
|
||||
export const isFieldRichTextV2Value = (
|
||||
fieldValue: unknown,
|
||||
): fieldValue is FieldRichTextV2Value =>
|
||||
richTextV2Schema.safeParse(fieldValue).success;
|
||||
@ -29,6 +29,8 @@ import { isFieldRating } from '@/object-record/record-field/types/guards/isField
|
||||
import { isFieldRawJson } from '@/object-record/record-field/types/guards/isFieldRawJson';
|
||||
import { isFieldRelation } from '@/object-record/record-field/types/guards/isFieldRelation';
|
||||
import { isFieldRichText } from '@/object-record/record-field/types/guards/isFieldRichText';
|
||||
import { isFieldRichTextV2 } from '@/object-record/record-field/types/guards/isFieldRichTextV2';
|
||||
import { isFieldRichTextV2Value } from '@/object-record/record-field/types/guards/isFieldRichTextValueV2';
|
||||
import { isFieldSelect } from '@/object-record/record-field/types/guards/isFieldSelect';
|
||||
import { isFieldSelectValue } from '@/object-record/record-field/types/guards/isFieldSelectValue';
|
||||
import { isFieldText } from '@/object-record/record-field/types/guards/isFieldText';
|
||||
@ -142,6 +144,14 @@ export const isFieldValueEmpty = ({
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isFieldRichTextV2(fieldDefinition)) {
|
||||
return (
|
||||
!isFieldRichTextV2Value(fieldValue) ||
|
||||
(isValueEmpty(fieldValue?.blocknote) &&
|
||||
isValueEmpty(fieldValue?.markdown))
|
||||
);
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Entity field type not supported in isFieldValueEmpty : ${fieldDefinition.type}}`,
|
||||
);
|
||||
|
||||
@ -2,6 +2,7 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
|
||||
import { isWorkflowSubObjectMetadata } from '@/object-metadata/utils/isWorkflowSubObjectMetadata';
|
||||
import { isFieldActor } from '@/object-record/record-field/types/guards/isFieldActor';
|
||||
import { isFieldRichText } from '@/object-record/record-field/types/guards/isFieldRichText';
|
||||
import { isFieldRichTextV2 } from '@/object-record/record-field/types/guards/isFieldRichTextV2';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
@ -49,7 +50,9 @@ export const isFieldValueReadOnly = ({
|
||||
|
||||
if (
|
||||
isDefined(fieldType) &&
|
||||
(isFieldActor({ type: fieldType }) || isFieldRichText({ type: fieldType }))
|
||||
(isFieldActor({ type: fieldType }) ||
|
||||
isFieldRichText({ type: fieldType }) ||
|
||||
isFieldRichTextV2({ type: fieldType }))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -0,0 +1,25 @@
|
||||
import { RichTextV2Filter } from '@/object-record/graphql/types/RecordGqlOperationFilter';
|
||||
import escapeRegExp from 'lodash.escaperegexp';
|
||||
|
||||
export const isMatchingRichTextV2Filter = ({
|
||||
richTextV2Filter,
|
||||
value,
|
||||
}: {
|
||||
richTextV2Filter: RichTextV2Filter;
|
||||
value: string;
|
||||
}) => {
|
||||
switch (true) {
|
||||
case richTextV2Filter.markdown !== undefined: {
|
||||
const escapedPattern = escapeRegExp(richTextV2Filter.markdown.ilike);
|
||||
const regexPattern = escapedPattern.replace(/%/g, '.*');
|
||||
const regexCaseInsensitive = new RegExp(`^${regexPattern}$`, 'i');
|
||||
|
||||
return regexCaseInsensitive.test(value);
|
||||
}
|
||||
default: {
|
||||
throw new Error(
|
||||
`Unexpected value for RICH_TEXT_V2 filter : ${JSON.stringify(richTextV2Filter)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -21,6 +21,7 @@ import {
|
||||
RatingFilter,
|
||||
RawJsonFilter,
|
||||
RecordGqlOperationFilter,
|
||||
RichTextV2Filter,
|
||||
SelectFilter,
|
||||
StringFilter,
|
||||
UUIDFilter,
|
||||
@ -33,6 +34,7 @@ import { isMatchingFloatFilter } from '@/object-record/record-filter/utils/isMat
|
||||
import { isMatchingMultiSelectFilter } from '@/object-record/record-filter/utils/isMatchingMultiSelectFilter';
|
||||
import { isMatchingRatingFilter } from '@/object-record/record-filter/utils/isMatchingRatingFilter';
|
||||
import { isMatchingRawJsonFilter } from '@/object-record/record-filter/utils/isMatchingRawJsonFilter';
|
||||
import { isMatchingRichTextV2Filter } from '@/object-record/record-filter/utils/isMatchingRichTextV2Filter';
|
||||
import { isMatchingSelectFilter } from '@/object-record/record-filter/utils/isMatchingSelectFilter';
|
||||
import { isMatchingStringFilter } from '@/object-record/record-filter/utils/isMatchingStringFilter';
|
||||
import { isMatchingUUIDFilter } from '@/object-record/record-filter/utils/isMatchingUUIDFilter';
|
||||
@ -199,6 +201,12 @@ export const isRecordMatchingFilter = ({
|
||||
value: record[filterKey],
|
||||
});
|
||||
}
|
||||
case FieldMetadataType.RICH_TEXT_V2: {
|
||||
return isMatchingRichTextV2Filter({
|
||||
richTextV2Filter: filterValue as RichTextV2Filter,
|
||||
value: record[filterKey],
|
||||
});
|
||||
}
|
||||
case FieldMetadataType.SELECT:
|
||||
return isMatchingSelectFilter({
|
||||
selectFilter: filterValue as SelectFilter,
|
||||
|
||||
@ -5,6 +5,7 @@ import {
|
||||
FieldFullNameValue,
|
||||
FieldLinksValue,
|
||||
FieldPhonesValue,
|
||||
FieldRichTextV2Value,
|
||||
} from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { CompositeFieldLabels } from '@/object-record/spreadsheet-import/types/CompositeFieldLabels';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
@ -39,6 +40,10 @@ export const COMPOSITE_FIELD_IMPORT_LABELS = {
|
||||
primaryPhoneCountryCodeLabel: 'Phone country code',
|
||||
primaryPhoneNumberLabel: 'Phone number',
|
||||
} satisfies Partial<CompositeFieldLabels<FieldPhonesValue>>,
|
||||
[FieldMetadataType.RICH_TEXT_V2]: {
|
||||
blocknoteLabel: 'BlockNote',
|
||||
markdownLabel: 'Markdown',
|
||||
} satisfies Partial<CompositeFieldLabels<FieldRichTextV2Value>>,
|
||||
[FieldMetadataType.ACTOR]: {
|
||||
sourceLabel: 'Source',
|
||||
},
|
||||
|
||||
@ -28,6 +28,7 @@ export const useBuildAvailableFieldsForImport = () => {
|
||||
fieldType: {
|
||||
type: 'input',
|
||||
},
|
||||
fieldMetadataType: fieldMetadataItem.type,
|
||||
fieldValidationDefinitions: getSpreadSheetFieldValidationDefinitions(
|
||||
fieldMetadataItem.type,
|
||||
`${firstNameLabel} (${fieldMetadataItem.label})`,
|
||||
@ -41,6 +42,7 @@ export const useBuildAvailableFieldsForImport = () => {
|
||||
fieldType: {
|
||||
type: 'input',
|
||||
},
|
||||
fieldMetadataType: fieldMetadataItem.type,
|
||||
fieldValidationDefinitions: getSpreadSheetFieldValidationDefinitions(
|
||||
fieldMetadataItem.type,
|
||||
`${lastNameLabel} (${fieldMetadataItem.label})`,
|
||||
@ -54,6 +56,7 @@ export const useBuildAvailableFieldsForImport = () => {
|
||||
fieldType: {
|
||||
type: 'input',
|
||||
},
|
||||
fieldMetadataType: fieldMetadataItem.type,
|
||||
fieldValidationDefinitions: getSpreadSheetFieldValidationDefinitions(
|
||||
fieldMetadataItem.type,
|
||||
fieldMetadataItem.label + ' (ID)',
|
||||
@ -70,6 +73,7 @@ export const useBuildAvailableFieldsForImport = () => {
|
||||
fieldType: {
|
||||
type: 'input',
|
||||
},
|
||||
fieldMetadataType: fieldMetadataItem.type,
|
||||
fieldValidationDefinitions: getSpreadSheetFieldValidationDefinitions(
|
||||
fieldMetadataItem.type,
|
||||
`${currencyCodeLabel} (${fieldMetadataItem.label})`,
|
||||
@ -83,6 +87,7 @@ export const useBuildAvailableFieldsForImport = () => {
|
||||
fieldType: {
|
||||
type: 'input',
|
||||
},
|
||||
fieldMetadataType: fieldMetadataItem.type,
|
||||
fieldValidationDefinitions: getSpreadSheetFieldValidationDefinitions(
|
||||
FieldMetadataType.NUMBER,
|
||||
`${amountMicrosLabel} (${fieldMetadataItem.label})`,
|
||||
@ -99,6 +104,7 @@ export const useBuildAvailableFieldsForImport = () => {
|
||||
fieldType: {
|
||||
type: 'input',
|
||||
},
|
||||
fieldMetadataType: fieldMetadataItem.type,
|
||||
fieldValidationDefinitions:
|
||||
getSpreadSheetFieldValidationDefinitions(
|
||||
fieldMetadataItem.type,
|
||||
@ -117,6 +123,7 @@ export const useBuildAvailableFieldsForImport = () => {
|
||||
fieldType: {
|
||||
type: 'input',
|
||||
},
|
||||
fieldMetadataType: fieldMetadataItem.type,
|
||||
fieldValidationDefinitions:
|
||||
getSpreadSheetFieldValidationDefinitions(
|
||||
fieldMetadataItem.type,
|
||||
@ -138,6 +145,7 @@ export const useBuildAvailableFieldsForImport = () => {
|
||||
color: option.color,
|
||||
})) || [],
|
||||
},
|
||||
fieldMetadataType: fieldMetadataItem.type,
|
||||
fieldValidationDefinitions: getSpreadSheetFieldValidationDefinitions(
|
||||
fieldMetadataItem.type,
|
||||
fieldMetadataItem.label + ' (ID)',
|
||||
@ -157,6 +165,7 @@ export const useBuildAvailableFieldsForImport = () => {
|
||||
color: option.color,
|
||||
})) || [],
|
||||
},
|
||||
fieldMetadataType: fieldMetadataItem.type,
|
||||
fieldValidationDefinitions: getSpreadSheetFieldValidationDefinitions(
|
||||
fieldMetadataItem.type,
|
||||
fieldMetadataItem.label + ' (ID)',
|
||||
@ -170,6 +179,7 @@ export const useBuildAvailableFieldsForImport = () => {
|
||||
fieldType: {
|
||||
type: 'checkbox',
|
||||
},
|
||||
fieldMetadataType: fieldMetadataItem.type,
|
||||
fieldValidationDefinitions: getSpreadSheetFieldValidationDefinitions(
|
||||
fieldMetadataItem.type,
|
||||
fieldMetadataItem.label,
|
||||
@ -186,6 +196,7 @@ export const useBuildAvailableFieldsForImport = () => {
|
||||
fieldType: {
|
||||
type: 'input',
|
||||
},
|
||||
fieldMetadataType: fieldMetadataItem.type,
|
||||
fieldValidationDefinitions:
|
||||
getSpreadSheetFieldValidationDefinitions(
|
||||
fieldMetadataItem.type,
|
||||
@ -204,6 +215,7 @@ export const useBuildAvailableFieldsForImport = () => {
|
||||
fieldType: {
|
||||
type: 'input',
|
||||
},
|
||||
fieldMetadataType: fieldMetadataItem.type,
|
||||
fieldValidationDefinitions:
|
||||
getSpreadSheetFieldValidationDefinitions(
|
||||
fieldMetadataItem.type,
|
||||
@ -211,6 +223,20 @@ export const useBuildAvailableFieldsForImport = () => {
|
||||
),
|
||||
});
|
||||
});
|
||||
} else if (fieldMetadataItem.type === FieldMetadataType.RICH_TEXT_V2) {
|
||||
Object.entries(
|
||||
COMPOSITE_FIELD_IMPORT_LABELS[FieldMetadataType.RICH_TEXT_V2],
|
||||
).forEach(([_, fieldLabel]) => {
|
||||
availableFieldsForImport.push({
|
||||
icon: getIcon(fieldMetadataItem.icon),
|
||||
label: `${fieldLabel} (${fieldMetadataItem.label})`,
|
||||
key: `${fieldLabel} (${fieldMetadataItem.name})`,
|
||||
fieldType: {
|
||||
type: 'input',
|
||||
},
|
||||
fieldMetadataType: fieldMetadataItem.type,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
availableFieldsForImport.push({
|
||||
icon: getIcon(fieldMetadataItem.icon),
|
||||
@ -219,6 +245,7 @@ export const useBuildAvailableFieldsForImport = () => {
|
||||
fieldType: {
|
||||
type: 'input',
|
||||
},
|
||||
fieldMetadataType: fieldMetadataItem.type,
|
||||
fieldValidationDefinitions: getSpreadSheetFieldValidationDefinitions(
|
||||
fieldMetadataItem.type,
|
||||
fieldMetadataItem.label,
|
||||
|
||||
@ -3,11 +3,12 @@ import {
|
||||
SpreadsheetImportFieldType,
|
||||
} from '@/spreadsheet-import/types';
|
||||
import { IconComponent } from 'twenty-ui';
|
||||
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
export type AvailableFieldForImport = {
|
||||
icon: IconComponent;
|
||||
label: string;
|
||||
key: string;
|
||||
fieldType: SpreadsheetImportFieldType;
|
||||
fieldValidationDefinitions?: FieldValidationDefinition[];
|
||||
fieldMetadataType: FieldMetadataType;
|
||||
};
|
||||
|
||||
@ -4,6 +4,7 @@ import {
|
||||
FieldEmailsValue,
|
||||
FieldLinksValue,
|
||||
FieldPhonesValue,
|
||||
FieldRichTextV2Value,
|
||||
} from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { COMPOSITE_FIELD_IMPORT_LABELS } from '@/object-record/spreadsheet-import/constants/CompositeFieldImportLabels';
|
||||
import { ImportedStructuredRow } from '@/spreadsheet-import/types';
|
||||
@ -36,6 +37,7 @@ export const buildRecordFromImportedStructuredRow = (
|
||||
LINKS: { primaryLinkUrlLabel },
|
||||
EMAILS: { primaryEmailLabel },
|
||||
PHONES: { primaryPhoneNumberLabel, primaryPhoneCountryCodeLabel },
|
||||
RICH_TEXT_V2: { blocknoteLabel, markdownLabel },
|
||||
} = COMPOSITE_FIELD_IMPORT_LABELS;
|
||||
|
||||
for (const field of fields) {
|
||||
@ -158,6 +160,24 @@ export const buildRecordFromImportedStructuredRow = (
|
||||
}
|
||||
break;
|
||||
}
|
||||
case FieldMetadataType.RICH_TEXT_V2: {
|
||||
if (
|
||||
isDefined(
|
||||
importedStructuredRow[`${blocknoteLabel} (${field.name})`] ||
|
||||
importedStructuredRow[`${markdownLabel} (${field.name})`],
|
||||
)
|
||||
) {
|
||||
recordToBuild[field.name] = {
|
||||
blocknote: castToString(
|
||||
importedStructuredRow[`${blocknoteLabel} (${field.name})`],
|
||||
),
|
||||
markdown: castToString(
|
||||
importedStructuredRow[`${markdownLabel} (${field.name})`],
|
||||
),
|
||||
} satisfies FieldRichTextV2Value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case FieldMetadataType.EMAILS: {
|
||||
if (
|
||||
isDefined(
|
||||
|
||||
@ -84,6 +84,12 @@ export const generateEmptyFieldValue = (
|
||||
case FieldMetadataType.RICH_TEXT: {
|
||||
return null;
|
||||
}
|
||||
case FieldMetadataType.RICH_TEXT_V2: {
|
||||
return {
|
||||
blocknote: null,
|
||||
markdown: null,
|
||||
};
|
||||
}
|
||||
case FieldMetadataType.ACTOR: {
|
||||
return {
|
||||
source: 'MANUAL',
|
||||
|
||||
Reference in New Issue
Block a user