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:
@ -1,5 +1,5 @@
|
|||||||
import { gql } from '@apollo/client';
|
|
||||||
import * as Apollo from '@apollo/client';
|
import * as Apollo from '@apollo/client';
|
||||||
|
import { gql } from '@apollo/client';
|
||||||
export type Maybe<T> = T | null;
|
export type Maybe<T> = T | null;
|
||||||
export type InputMaybe<T> = Maybe<T>;
|
export type InputMaybe<T> = Maybe<T>;
|
||||||
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
|
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
|
||||||
|
|||||||
@ -49,6 +49,10 @@ export const ActivityRichTextEditor = ({
|
|||||||
const cache = useApolloClient().cache;
|
const cache = useApolloClient().cache;
|
||||||
const activity = activityInStore as Task | Note | null;
|
const activity = activityInStore as Task | Note | null;
|
||||||
|
|
||||||
|
const isRichTextV2Enabled = useIsFeatureEnabled(
|
||||||
|
FeatureFlagKey.IsRichTextV2Enabled,
|
||||||
|
);
|
||||||
|
|
||||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||||
);
|
);
|
||||||
@ -67,13 +71,20 @@ export const ActivityRichTextEditor = ({
|
|||||||
activityObjectNameSingular: activityObjectNameSingular,
|
activityObjectNameSingular: activityObjectNameSingular,
|
||||||
});
|
});
|
||||||
|
|
||||||
const persistBodyDebounced = useDebouncedCallback((newBody: string) => {
|
const persistBodyDebounced = useDebouncedCallback((blocknote: string) => {
|
||||||
|
const input = isRichTextV2Enabled
|
||||||
|
? {
|
||||||
|
bodyV2: {
|
||||||
|
blocknote,
|
||||||
|
markdown: null,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: { body: blocknote };
|
||||||
|
|
||||||
if (isDefined(activity)) {
|
if (isDefined(activity)) {
|
||||||
upsertActivity({
|
upsertActivity({
|
||||||
activity,
|
activity,
|
||||||
input: {
|
input,
|
||||||
body: newBody,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, 300);
|
}, 300);
|
||||||
@ -163,14 +174,18 @@ export const ActivityRichTextEditor = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const initialBody = useMemo(() => {
|
const initialBody = useMemo(() => {
|
||||||
|
const blocknote = isRichTextV2Enabled
|
||||||
|
? activity?.bodyV2?.blocknote
|
||||||
|
: activity?.body;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
isDefined(activity) &&
|
isDefined(activity) &&
|
||||||
isNonEmptyString(activity.body) &&
|
isNonEmptyString(blocknote) &&
|
||||||
activity?.body !== '{}'
|
blocknote !== '{}'
|
||||||
) {
|
) {
|
||||||
return JSON.parse(activity.body);
|
return JSON.parse(blocknote);
|
||||||
}
|
}
|
||||||
}, [activity]);
|
}, [activity, isRichTextV2Enabled]);
|
||||||
|
|
||||||
const handleEditorBuiltInUploadFile = async (file: File) => {
|
const handleEditorBuiltInUploadFile = async (file: File) => {
|
||||||
const { attachmentAbsoluteURL } = await handleUploadAttachment(file);
|
const { attachmentAbsoluteURL } = await handleUploadAttachment(file);
|
||||||
|
|||||||
@ -94,7 +94,11 @@ const task = {
|
|||||||
createdAt: '2023-04-26T10:12:42.33625+00:00',
|
createdAt: '2023-04-26T10:12:42.33625+00:00',
|
||||||
updatedAt: '2023-04-26T10:23:42.33625+00:00',
|
updatedAt: '2023-04-26T10:23:42.33625+00:00',
|
||||||
title: 'Task title',
|
title: 'Task title',
|
||||||
body: null,
|
body: '',
|
||||||
|
bodyV2: {
|
||||||
|
blocknote: null,
|
||||||
|
markdown: null,
|
||||||
|
},
|
||||||
assigneeId: null,
|
assigneeId: null,
|
||||||
status: null,
|
status: null,
|
||||||
dueAt: '2023-04-26T10:12:42.33625+00:00',
|
dueAt: '2023-04-26T10:12:42.33625+00:00',
|
||||||
|
|||||||
@ -6,6 +6,8 @@ import { Note } from '@/activities/types/Note';
|
|||||||
import { getActivityPreview } from '@/activities/utils/getActivityPreview';
|
import { getActivityPreview } from '@/activities/utils/getActivityPreview';
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
import { useFieldContext } from '@/object-record/hooks/useFieldContext';
|
import { useFieldContext } from '@/object-record/hooks/useFieldContext';
|
||||||
|
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||||
|
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
const StyledCard = styled.div<{ isSingleNote: boolean }>`
|
const StyledCard = styled.div<{ isSingleNote: boolean }>`
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
@ -71,7 +73,12 @@ export const NoteCard = ({
|
|||||||
const openActivityRightDrawer = useOpenActivityRightDrawer({
|
const openActivityRightDrawer = useOpenActivityRightDrawer({
|
||||||
objectNameSingular: CoreObjectNameSingular.Note,
|
objectNameSingular: CoreObjectNameSingular.Note,
|
||||||
});
|
});
|
||||||
const body = getActivityPreview(note.body);
|
const isRichTextV2Enabled = useIsFeatureEnabled(
|
||||||
|
FeatureFlagKey.IsRichTextV2Enabled,
|
||||||
|
);
|
||||||
|
const body = getActivityPreview(
|
||||||
|
isRichTextV2Enabled ? (note?.bodyV2?.blocknote ?? null) : note?.body,
|
||||||
|
);
|
||||||
|
|
||||||
const { FieldContextProvider: NoteTargetsContextProvider } = useFieldContext({
|
const { FieldContextProvider: NoteTargetsContextProvider } = useFieldContext({
|
||||||
objectNameSingular: CoreObjectNameSingular.Note,
|
objectNameSingular: CoreObjectNameSingular.Note,
|
||||||
|
|||||||
@ -16,6 +16,8 @@ import { ActivityRow } from '@/activities/components/ActivityRow';
|
|||||||
import { Task } from '@/activities/types/Task';
|
import { Task } from '@/activities/types/Task';
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
import { useFieldContext } from '@/object-record/hooks/useFieldContext';
|
import { useFieldContext } from '@/object-record/hooks/useFieldContext';
|
||||||
|
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||||
|
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||||
import { useCompleteTask } from '../hooks/useCompleteTask';
|
import { useCompleteTask } from '../hooks/useCompleteTask';
|
||||||
|
|
||||||
const StyledTaskBody = styled.div`
|
const StyledTaskBody = styled.div`
|
||||||
@ -82,7 +84,14 @@ export const TaskRow = ({ task }: { task: Task }) => {
|
|||||||
objectNameSingular: CoreObjectNameSingular.Task,
|
objectNameSingular: CoreObjectNameSingular.Task,
|
||||||
});
|
});
|
||||||
|
|
||||||
const body = getActivitySummary(task.body);
|
const isRichTextV2Enabled = useIsFeatureEnabled(
|
||||||
|
FeatureFlagKey.IsRichTextV2Enabled,
|
||||||
|
);
|
||||||
|
|
||||||
|
const body = getActivitySummary(
|
||||||
|
isRichTextV2Enabled ? (task?.bodyV2?.blocknote ?? null) : task?.body,
|
||||||
|
);
|
||||||
|
|
||||||
const { completeTask } = useCompleteTask(task);
|
const { completeTask } = useCompleteTask(task);
|
||||||
|
|
||||||
const { FieldContextProvider: TaskTargetsContextProvider } = useFieldContext({
|
const { FieldContextProvider: TaskTargetsContextProvider } = useFieldContext({
|
||||||
|
|||||||
@ -11,6 +11,10 @@ const task: Task = {
|
|||||||
status: 'DONE',
|
status: 'DONE',
|
||||||
title: 'Test',
|
title: 'Test',
|
||||||
body: 'Test',
|
body: 'Test',
|
||||||
|
bodyV2: {
|
||||||
|
blocknote: 'Test',
|
||||||
|
markdown: 'Test',
|
||||||
|
},
|
||||||
dueAt: '2024-03-15T07:33:14.212Z',
|
dueAt: '2024-03-15T07:33:14.212Z',
|
||||||
createdAt: '2024-03-15T07:33:14.212Z',
|
createdAt: '2024-03-15T07:33:14.212Z',
|
||||||
updatedAt: '2024-03-15T07:33:14.212Z',
|
updatedAt: '2024-03-15T07:33:14.212Z',
|
||||||
|
|||||||
@ -4,4 +4,8 @@ export type Activity = {
|
|||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
title: string;
|
title: string;
|
||||||
body: string | null;
|
body: string | null;
|
||||||
|
bodyV2?: {
|
||||||
|
blocknote: string | null;
|
||||||
|
markdown: string | null;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -10,12 +10,14 @@ import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
|||||||
import { useMultiObjectSearch } from '@/object-record/relation-picker/hooks/useMultiObjectSearch';
|
import { useMultiObjectSearch } from '@/object-record/relation-picker/hooks/useMultiObjectSearch';
|
||||||
import { useMultiObjectSearchQueryResultFormattedAsObjectRecordsMap } from '@/object-record/relation-picker/hooks/useMultiObjectSearchQueryResultFormattedAsObjectRecordsMap';
|
import { useMultiObjectSearchQueryResultFormattedAsObjectRecordsMap } from '@/object-record/relation-picker/hooks/useMultiObjectSearchQueryResultFormattedAsObjectRecordsMap';
|
||||||
import { makeOrFilterVariables } from '@/object-record/utils/makeOrFilterVariables';
|
import { makeOrFilterVariables } from '@/object-record/utils/makeOrFilterVariables';
|
||||||
|
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||||
import { t } from '@lingui/core/macro';
|
import { t } from '@lingui/core/macro';
|
||||||
import isEmpty from 'lodash.isempty';
|
import isEmpty from 'lodash.isempty';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { Avatar, IconCheckbox, IconNotes } from 'twenty-ui';
|
import { Avatar, IconCheckbox, IconNotes } from 'twenty-ui';
|
||||||
import { useDebounce } from 'use-debounce';
|
import { useDebounce } from 'use-debounce';
|
||||||
|
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||||
import { getLogoUrlFromDomainName } from '~/utils';
|
import { getLogoUrlFromDomainName } from '~/utils';
|
||||||
|
|
||||||
const MAX_SEARCH_RESULTS_PER_OBJECT = 8;
|
const MAX_SEARCH_RESULTS_PER_OBJECT = 8;
|
||||||
@ -23,6 +25,10 @@ const MAX_SEARCH_RESULTS_PER_OBJECT = 8;
|
|||||||
export const useSearchRecords = () => {
|
export const useSearchRecords = () => {
|
||||||
const commandMenuSearch = useRecoilValue(commandMenuSearchState);
|
const commandMenuSearch = useRecoilValue(commandMenuSearchState);
|
||||||
|
|
||||||
|
const isRichTextV2Enabled = useIsFeatureEnabled(
|
||||||
|
FeatureFlagKey.IsRichTextV2Enabled,
|
||||||
|
);
|
||||||
|
|
||||||
const [deferredCommandMenuSearch] = useDebounce(commandMenuSearch, 300);
|
const [deferredCommandMenuSearch] = useDebounce(commandMenuSearch, 300);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -45,7 +51,13 @@ export const useSearchRecords = () => {
|
|||||||
filter: deferredCommandMenuSearch
|
filter: deferredCommandMenuSearch
|
||||||
? makeOrFilterVariables([
|
? makeOrFilterVariables([
|
||||||
{ title: { ilike: `%${deferredCommandMenuSearch}%` } },
|
{ title: { ilike: `%${deferredCommandMenuSearch}%` } },
|
||||||
{ body: { ilike: `%${deferredCommandMenuSearch}%` } },
|
isRichTextV2Enabled
|
||||||
|
? {
|
||||||
|
bodyV2: {
|
||||||
|
markdown: { ilike: `%${deferredCommandMenuSearch}%` },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: { body: { ilike: `%${deferredCommandMenuSearch}%` } },
|
||||||
])
|
])
|
||||||
: undefined,
|
: undefined,
|
||||||
limit: MAX_SEARCH_RESULTS_PER_OBJECT,
|
limit: MAX_SEARCH_RESULTS_PER_OBJECT,
|
||||||
@ -56,7 +68,13 @@ export const useSearchRecords = () => {
|
|||||||
filter: deferredCommandMenuSearch
|
filter: deferredCommandMenuSearch
|
||||||
? makeOrFilterVariables([
|
? makeOrFilterVariables([
|
||||||
{ title: { ilike: `%${deferredCommandMenuSearch}%` } },
|
{ title: { ilike: `%${deferredCommandMenuSearch}%` } },
|
||||||
{ body: { ilike: `%${deferredCommandMenuSearch}%` } },
|
isRichTextV2Enabled
|
||||||
|
? {
|
||||||
|
bodyV2: {
|
||||||
|
markdown: { ilike: `%${deferredCommandMenuSearch}%` },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: { body: { ilike: `%${deferredCommandMenuSearch}%` } },
|
||||||
])
|
])
|
||||||
: undefined,
|
: undefined,
|
||||||
limit: MAX_SEARCH_RESULTS_PER_OBJECT,
|
limit: MAX_SEARCH_RESULTS_PER_OBJECT,
|
||||||
|
|||||||
@ -163,5 +163,13 @@ ${mapObjectMetadataToGraphQLQuery({
|
|||||||
}`;
|
}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fieldType === FieldMetadataType.RICH_TEXT_V2) {
|
||||||
|
return `${field.name}
|
||||||
|
{
|
||||||
|
blocknote
|
||||||
|
markdown
|
||||||
|
}`;
|
||||||
|
}
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
};
|
};
|
||||||
|
|||||||
@ -128,6 +128,15 @@ export type RawJsonFilter = {
|
|||||||
is?: IsFilter;
|
is?: IsFilter;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type RichTextV2LeafFilter = {
|
||||||
|
ilike?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RichTextV2Filter = {
|
||||||
|
blocknote?: RichTextV2LeafFilter;
|
||||||
|
markdown?: RichTextV2LeafFilter;
|
||||||
|
};
|
||||||
|
|
||||||
export type LeafFilter =
|
export type LeafFilter =
|
||||||
| UUIDFilter
|
| UUIDFilter
|
||||||
| StringFilter
|
| StringFilter
|
||||||
@ -143,6 +152,7 @@ export type LeafFilter =
|
|||||||
| PhonesFilter
|
| PhonesFilter
|
||||||
| ArrayFilter
|
| ArrayFilter
|
||||||
| RawJsonFilter
|
| RawJsonFilter
|
||||||
|
| RichTextV2Filter
|
||||||
| undefined;
|
| undefined;
|
||||||
|
|
||||||
export type AndObjectRecordFilter = {
|
export type AndObjectRecordFilter = {
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import {
|
|||||||
import { useObjectNamePluralFromSingular } from '@/object-metadata/hooks/useObjectNamePluralFromSingular';
|
import { useObjectNamePluralFromSingular } from '@/object-metadata/hooks/useObjectNamePluralFromSingular';
|
||||||
import { useHandleToggleTrashColumnFilter } from '@/object-record/record-index/hooks/useHandleToggleTrashColumnFilter';
|
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 { useObjectOptionsForBoard } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForBoard';
|
||||||
import { useOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useOptionsDropdown';
|
import { useOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useOptionsDropdown';
|
||||||
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
|
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
|
||||||
@ -93,6 +94,11 @@ export const ObjectOptionsDropdownMenuContent = () => {
|
|||||||
viewType,
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<DropdownMenuHeader StartIcon={CurrentViewIcon ?? IconList}>
|
<DropdownMenuHeader StartIcon={CurrentViewIcon ?? IconList}>
|
||||||
@ -151,16 +157,20 @@ export const ObjectOptionsDropdownMenuContent = () => {
|
|||||||
</DropdownMenuItemsContainer>
|
</DropdownMenuItemsContainer>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuItemsContainer>
|
<DropdownMenuItemsContainer>
|
||||||
<MenuItem
|
{canImportOrExport && (
|
||||||
onClick={download}
|
<>
|
||||||
LeftIcon={IconFileExport}
|
<MenuItem
|
||||||
text={displayedExportProgress(progress)}
|
onClick={download}
|
||||||
/>
|
LeftIcon={IconFileExport}
|
||||||
<MenuItem
|
text={displayedExportProgress(progress)}
|
||||||
onClick={() => openObjectRecordsSpreasheetImportDialog()}
|
/>
|
||||||
LeftIcon={IconFileImport}
|
<MenuItem
|
||||||
text="Import"
|
onClick={() => openObjectRecordsSpreasheetImportDialog()}
|
||||||
/>
|
LeftIcon={IconFileImport}
|
||||||
|
text="Import"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleToggleTrashColumnFilter();
|
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 { RatingFieldDisplay } from '@/object-record/record-field/meta-types/display/components/RatingFieldDisplay';
|
||||||
import { RelationFromManyFieldDisplay } from '@/object-record/record-field/meta-types/display/components/RelationFromManyFieldDisplay';
|
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 { 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 { isFieldIdentifierDisplay } from '@/object-record/record-field/meta-types/display/utils/isFieldIdentifierDisplay';
|
||||||
import { isFieldActor } from '@/object-record/record-field/types/guards/isFieldActor';
|
import { isFieldActor } from '@/object-record/record-field/types/guards/isFieldActor';
|
||||||
import { isFieldArray } from '@/object-record/record-field/types/guards/isFieldArray';
|
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 { isFieldRelationFromManyObjects } from '@/object-record/record-field/types/guards/isFieldRelationFromManyObjects';
|
||||||
import { isFieldRelationToOneObject } from '@/object-record/record-field/types/guards/isFieldRelationToOneObject';
|
import { isFieldRelationToOneObject } from '@/object-record/record-field/types/guards/isFieldRelationToOneObject';
|
||||||
import { isFieldRichText } from '@/object-record/record-field/types/guards/isFieldRichText';
|
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 { FieldContext } from '../contexts/FieldContext';
|
||||||
import { AddressFieldDisplay } from '../meta-types/display/components/AddressFieldDisplay';
|
import { AddressFieldDisplay } from '../meta-types/display/components/AddressFieldDisplay';
|
||||||
import { ChipFieldDisplay } from '../meta-types/display/components/ChipFieldDisplay';
|
import { ChipFieldDisplay } from '../meta-types/display/components/ChipFieldDisplay';
|
||||||
@ -90,6 +92,8 @@ export const FieldDisplay = () => {
|
|||||||
<RatingFieldDisplay />
|
<RatingFieldDisplay />
|
||||||
) : isFieldRichText(fieldDefinition) ? (
|
) : isFieldRichText(fieldDefinition) ? (
|
||||||
<RichTextFieldDisplay />
|
<RichTextFieldDisplay />
|
||||||
|
) : isFieldRichTextV2(fieldDefinition) ? (
|
||||||
|
<RichTextV2FieldDisplay />
|
||||||
) : isFieldActor(fieldDefinition) ? (
|
) : isFieldActor(fieldDefinition) ? (
|
||||||
<ActorFieldDisplay />
|
<ActorFieldDisplay />
|
||||||
) : isFieldArray(fieldDefinition) ? (
|
) : 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 { isFieldArray } from '@/object-record/record-field/types/guards/isFieldArray';
|
||||||
import { isFieldArrayValue } from '@/object-record/record-field/types/guards/isFieldArrayValue';
|
import { isFieldArrayValue } from '@/object-record/record-field/types/guards/isFieldArrayValue';
|
||||||
import { isFieldRichText } from '@/object-record/record-field/types/guards/isFieldRichText';
|
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 { 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 { getForeignKeyNameFromRelationFieldName } from '@/object-record/utils/getForeignKeyNameFromRelationFieldName';
|
||||||
import { FieldContext } from '../contexts/FieldContext';
|
import { FieldContext } from '../contexts/FieldContext';
|
||||||
import { isFieldBoolean } from '../types/guards/isFieldBoolean';
|
import { isFieldBoolean } from '../types/guards/isFieldBoolean';
|
||||||
@ -118,6 +120,10 @@ export const usePersistField = () => {
|
|||||||
isFieldRichText(fieldDefinition) &&
|
isFieldRichText(fieldDefinition) &&
|
||||||
isFieldRichTextValue(valueToPersist);
|
isFieldRichTextValue(valueToPersist);
|
||||||
|
|
||||||
|
const fieldIsRichTextV2 =
|
||||||
|
isFieldRichTextV2(fieldDefinition) &&
|
||||||
|
isFieldRichTextV2Value(valueToPersist);
|
||||||
|
|
||||||
const fieldIsArray =
|
const fieldIsArray =
|
||||||
isFieldArray(fieldDefinition) && isFieldArrayValue(valueToPersist);
|
isFieldArray(fieldDefinition) && isFieldArrayValue(valueToPersist);
|
||||||
|
|
||||||
@ -139,7 +145,8 @@ export const usePersistField = () => {
|
|||||||
fieldIsAddress ||
|
fieldIsAddress ||
|
||||||
fieldIsRawJson ||
|
fieldIsRawJson ||
|
||||||
fieldIsArray ||
|
fieldIsArray ||
|
||||||
fieldIsRichText;
|
fieldIsRichText ||
|
||||||
|
fieldIsRichTextV2;
|
||||||
|
|
||||||
if (isValuePersistable) {
|
if (isValuePersistable) {
|
||||||
const fieldName = fieldDefinition.metadata.fieldName;
|
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;
|
settings?: null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type FieldRichTextV2Metadata = {
|
||||||
|
objectMetadataNameSingular?: string;
|
||||||
|
fieldName: string;
|
||||||
|
settings?: null;
|
||||||
|
};
|
||||||
|
|
||||||
export type FieldRichTextMetadata = {
|
export type FieldRichTextMetadata = {
|
||||||
objectMetadataNameSingular?: string;
|
objectMetadataNameSingular?: string;
|
||||||
fieldName: string;
|
fieldName: string;
|
||||||
@ -212,7 +218,9 @@ export type FieldMetadata =
|
|||||||
| FieldAddressMetadata
|
| FieldAddressMetadata
|
||||||
| FieldActorMetadata
|
| FieldActorMetadata
|
||||||
| FieldArrayMetadata
|
| FieldArrayMetadata
|
||||||
| FieldTsVectorMetadata;
|
| FieldTsVectorMetadata
|
||||||
|
| FieldRichTextV2Metadata
|
||||||
|
| FieldRichTextMetadata;
|
||||||
|
|
||||||
export type FieldTextValue = string;
|
export type FieldTextValue = string;
|
||||||
export type FieldUUidValue = string; // TODO: can we replace with a template literal type, or maybe overkill ?
|
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 Json = ZodHelperLiteral | { [key: string]: Json } | Json[];
|
||||||
export type FieldJsonValue = Record<string, Json> | Json[] | null;
|
export type FieldJsonValue = Record<string, Json> | Json[] | null;
|
||||||
|
|
||||||
|
export type FieldRichTextV2Value = {
|
||||||
|
blocknote: string | null;
|
||||||
|
markdown: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
export type FieldRichTextValue = null | string;
|
export type FieldRichTextValue = null | string;
|
||||||
|
|
||||||
export type FieldActorValue = {
|
export type FieldActorValue = {
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import {
|
|||||||
FieldRawJsonMetadata,
|
FieldRawJsonMetadata,
|
||||||
FieldRelationMetadata,
|
FieldRelationMetadata,
|
||||||
FieldRichTextMetadata,
|
FieldRichTextMetadata,
|
||||||
|
FieldRichTextV2Metadata,
|
||||||
FieldSelectMetadata,
|
FieldSelectMetadata,
|
||||||
FieldTextMetadata,
|
FieldTextMetadata,
|
||||||
FieldUuidMetadata,
|
FieldUuidMetadata,
|
||||||
@ -68,15 +69,17 @@ type AssertFieldMetadataFunction = <
|
|||||||
? FieldAddressMetadata
|
? FieldAddressMetadata
|
||||||
: E extends 'RAW_JSON'
|
: E extends 'RAW_JSON'
|
||||||
? FieldRawJsonMetadata
|
? FieldRawJsonMetadata
|
||||||
: E extends 'RICH_TEXT'
|
: E extends 'RICH_TEXT_V2'
|
||||||
? FieldRichTextMetadata
|
? FieldRichTextV2Metadata
|
||||||
: E extends 'ACTOR'
|
: E extends 'RICH_TEXT'
|
||||||
? FieldActorMetadata
|
? FieldRichTextMetadata
|
||||||
: E extends 'ARRAY'
|
: E extends 'ACTOR'
|
||||||
? FieldArrayMetadata
|
? FieldActorMetadata
|
||||||
: E extends 'PHONES'
|
: E extends 'ARRAY'
|
||||||
? FieldPhonesMetadata
|
? FieldArrayMetadata
|
||||||
: never,
|
: E extends 'PHONES'
|
||||||
|
? FieldPhonesMetadata
|
||||||
|
: never,
|
||||||
>(
|
>(
|
||||||
fieldType: E,
|
fieldType: E,
|
||||||
fieldTypeGuard: (
|
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';
|
import { FieldRichTextValue } from '../FieldMetadata';
|
||||||
|
|
||||||
export const richTextSchema: z.ZodType<FieldRichTextValue> = z.union([
|
export const richTextSchema: z.ZodType<FieldRichTextValue> = z.union([
|
||||||
z.null(), // Exclude literal values other than null
|
z.null(),
|
||||||
z.string(),
|
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 { isFieldRawJson } from '@/object-record/record-field/types/guards/isFieldRawJson';
|
||||||
import { isFieldRelation } from '@/object-record/record-field/types/guards/isFieldRelation';
|
import { isFieldRelation } from '@/object-record/record-field/types/guards/isFieldRelation';
|
||||||
import { isFieldRichText } from '@/object-record/record-field/types/guards/isFieldRichText';
|
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 { isFieldSelect } from '@/object-record/record-field/types/guards/isFieldSelect';
|
||||||
import { isFieldSelectValue } from '@/object-record/record-field/types/guards/isFieldSelectValue';
|
import { isFieldSelectValue } from '@/object-record/record-field/types/guards/isFieldSelectValue';
|
||||||
import { isFieldText } from '@/object-record/record-field/types/guards/isFieldText';
|
import { isFieldText } from '@/object-record/record-field/types/guards/isFieldText';
|
||||||
@ -142,6 +144,14 @@ export const isFieldValueEmpty = ({
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isFieldRichTextV2(fieldDefinition)) {
|
||||||
|
return (
|
||||||
|
!isFieldRichTextV2Value(fieldValue) ||
|
||||||
|
(isValueEmpty(fieldValue?.blocknote) &&
|
||||||
|
isValueEmpty(fieldValue?.markdown))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Entity field type not supported in isFieldValueEmpty : ${fieldDefinition.type}}`,
|
`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 { isWorkflowSubObjectMetadata } from '@/object-metadata/utils/isWorkflowSubObjectMetadata';
|
||||||
import { isFieldActor } from '@/object-record/record-field/types/guards/isFieldActor';
|
import { isFieldActor } from '@/object-record/record-field/types/guards/isFieldActor';
|
||||||
import { isFieldRichText } from '@/object-record/record-field/types/guards/isFieldRichText';
|
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 { isDefined } from 'twenty-shared';
|
||||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
@ -49,7 +50,9 @@ export const isFieldValueReadOnly = ({
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
isDefined(fieldType) &&
|
isDefined(fieldType) &&
|
||||||
(isFieldActor({ type: fieldType }) || isFieldRichText({ type: fieldType }))
|
(isFieldActor({ type: fieldType }) ||
|
||||||
|
isFieldRichText({ type: fieldType }) ||
|
||||||
|
isFieldRichTextV2({ type: fieldType }))
|
||||||
) {
|
) {
|
||||||
return true;
|
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,
|
RatingFilter,
|
||||||
RawJsonFilter,
|
RawJsonFilter,
|
||||||
RecordGqlOperationFilter,
|
RecordGqlOperationFilter,
|
||||||
|
RichTextV2Filter,
|
||||||
SelectFilter,
|
SelectFilter,
|
||||||
StringFilter,
|
StringFilter,
|
||||||
UUIDFilter,
|
UUIDFilter,
|
||||||
@ -33,6 +34,7 @@ import { isMatchingFloatFilter } from '@/object-record/record-filter/utils/isMat
|
|||||||
import { isMatchingMultiSelectFilter } from '@/object-record/record-filter/utils/isMatchingMultiSelectFilter';
|
import { isMatchingMultiSelectFilter } from '@/object-record/record-filter/utils/isMatchingMultiSelectFilter';
|
||||||
import { isMatchingRatingFilter } from '@/object-record/record-filter/utils/isMatchingRatingFilter';
|
import { isMatchingRatingFilter } from '@/object-record/record-filter/utils/isMatchingRatingFilter';
|
||||||
import { isMatchingRawJsonFilter } from '@/object-record/record-filter/utils/isMatchingRawJsonFilter';
|
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 { isMatchingSelectFilter } from '@/object-record/record-filter/utils/isMatchingSelectFilter';
|
||||||
import { isMatchingStringFilter } from '@/object-record/record-filter/utils/isMatchingStringFilter';
|
import { isMatchingStringFilter } from '@/object-record/record-filter/utils/isMatchingStringFilter';
|
||||||
import { isMatchingUUIDFilter } from '@/object-record/record-filter/utils/isMatchingUUIDFilter';
|
import { isMatchingUUIDFilter } from '@/object-record/record-filter/utils/isMatchingUUIDFilter';
|
||||||
@ -199,6 +201,12 @@ export const isRecordMatchingFilter = ({
|
|||||||
value: record[filterKey],
|
value: record[filterKey],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
case FieldMetadataType.RICH_TEXT_V2: {
|
||||||
|
return isMatchingRichTextV2Filter({
|
||||||
|
richTextV2Filter: filterValue as RichTextV2Filter,
|
||||||
|
value: record[filterKey],
|
||||||
|
});
|
||||||
|
}
|
||||||
case FieldMetadataType.SELECT:
|
case FieldMetadataType.SELECT:
|
||||||
return isMatchingSelectFilter({
|
return isMatchingSelectFilter({
|
||||||
selectFilter: filterValue as SelectFilter,
|
selectFilter: filterValue as SelectFilter,
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import {
|
|||||||
FieldFullNameValue,
|
FieldFullNameValue,
|
||||||
FieldLinksValue,
|
FieldLinksValue,
|
||||||
FieldPhonesValue,
|
FieldPhonesValue,
|
||||||
|
FieldRichTextV2Value,
|
||||||
} from '@/object-record/record-field/types/FieldMetadata';
|
} from '@/object-record/record-field/types/FieldMetadata';
|
||||||
import { CompositeFieldLabels } from '@/object-record/spreadsheet-import/types/CompositeFieldLabels';
|
import { CompositeFieldLabels } from '@/object-record/spreadsheet-import/types/CompositeFieldLabels';
|
||||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
@ -39,6 +40,10 @@ export const COMPOSITE_FIELD_IMPORT_LABELS = {
|
|||||||
primaryPhoneCountryCodeLabel: 'Phone country code',
|
primaryPhoneCountryCodeLabel: 'Phone country code',
|
||||||
primaryPhoneNumberLabel: 'Phone number',
|
primaryPhoneNumberLabel: 'Phone number',
|
||||||
} satisfies Partial<CompositeFieldLabels<FieldPhonesValue>>,
|
} satisfies Partial<CompositeFieldLabels<FieldPhonesValue>>,
|
||||||
|
[FieldMetadataType.RICH_TEXT_V2]: {
|
||||||
|
blocknoteLabel: 'BlockNote',
|
||||||
|
markdownLabel: 'Markdown',
|
||||||
|
} satisfies Partial<CompositeFieldLabels<FieldRichTextV2Value>>,
|
||||||
[FieldMetadataType.ACTOR]: {
|
[FieldMetadataType.ACTOR]: {
|
||||||
sourceLabel: 'Source',
|
sourceLabel: 'Source',
|
||||||
},
|
},
|
||||||
|
|||||||
@ -28,6 +28,7 @@ export const useBuildAvailableFieldsForImport = () => {
|
|||||||
fieldType: {
|
fieldType: {
|
||||||
type: 'input',
|
type: 'input',
|
||||||
},
|
},
|
||||||
|
fieldMetadataType: fieldMetadataItem.type,
|
||||||
fieldValidationDefinitions: getSpreadSheetFieldValidationDefinitions(
|
fieldValidationDefinitions: getSpreadSheetFieldValidationDefinitions(
|
||||||
fieldMetadataItem.type,
|
fieldMetadataItem.type,
|
||||||
`${firstNameLabel} (${fieldMetadataItem.label})`,
|
`${firstNameLabel} (${fieldMetadataItem.label})`,
|
||||||
@ -41,6 +42,7 @@ export const useBuildAvailableFieldsForImport = () => {
|
|||||||
fieldType: {
|
fieldType: {
|
||||||
type: 'input',
|
type: 'input',
|
||||||
},
|
},
|
||||||
|
fieldMetadataType: fieldMetadataItem.type,
|
||||||
fieldValidationDefinitions: getSpreadSheetFieldValidationDefinitions(
|
fieldValidationDefinitions: getSpreadSheetFieldValidationDefinitions(
|
||||||
fieldMetadataItem.type,
|
fieldMetadataItem.type,
|
||||||
`${lastNameLabel} (${fieldMetadataItem.label})`,
|
`${lastNameLabel} (${fieldMetadataItem.label})`,
|
||||||
@ -54,6 +56,7 @@ export const useBuildAvailableFieldsForImport = () => {
|
|||||||
fieldType: {
|
fieldType: {
|
||||||
type: 'input',
|
type: 'input',
|
||||||
},
|
},
|
||||||
|
fieldMetadataType: fieldMetadataItem.type,
|
||||||
fieldValidationDefinitions: getSpreadSheetFieldValidationDefinitions(
|
fieldValidationDefinitions: getSpreadSheetFieldValidationDefinitions(
|
||||||
fieldMetadataItem.type,
|
fieldMetadataItem.type,
|
||||||
fieldMetadataItem.label + ' (ID)',
|
fieldMetadataItem.label + ' (ID)',
|
||||||
@ -70,6 +73,7 @@ export const useBuildAvailableFieldsForImport = () => {
|
|||||||
fieldType: {
|
fieldType: {
|
||||||
type: 'input',
|
type: 'input',
|
||||||
},
|
},
|
||||||
|
fieldMetadataType: fieldMetadataItem.type,
|
||||||
fieldValidationDefinitions: getSpreadSheetFieldValidationDefinitions(
|
fieldValidationDefinitions: getSpreadSheetFieldValidationDefinitions(
|
||||||
fieldMetadataItem.type,
|
fieldMetadataItem.type,
|
||||||
`${currencyCodeLabel} (${fieldMetadataItem.label})`,
|
`${currencyCodeLabel} (${fieldMetadataItem.label})`,
|
||||||
@ -83,6 +87,7 @@ export const useBuildAvailableFieldsForImport = () => {
|
|||||||
fieldType: {
|
fieldType: {
|
||||||
type: 'input',
|
type: 'input',
|
||||||
},
|
},
|
||||||
|
fieldMetadataType: fieldMetadataItem.type,
|
||||||
fieldValidationDefinitions: getSpreadSheetFieldValidationDefinitions(
|
fieldValidationDefinitions: getSpreadSheetFieldValidationDefinitions(
|
||||||
FieldMetadataType.NUMBER,
|
FieldMetadataType.NUMBER,
|
||||||
`${amountMicrosLabel} (${fieldMetadataItem.label})`,
|
`${amountMicrosLabel} (${fieldMetadataItem.label})`,
|
||||||
@ -99,6 +104,7 @@ export const useBuildAvailableFieldsForImport = () => {
|
|||||||
fieldType: {
|
fieldType: {
|
||||||
type: 'input',
|
type: 'input',
|
||||||
},
|
},
|
||||||
|
fieldMetadataType: fieldMetadataItem.type,
|
||||||
fieldValidationDefinitions:
|
fieldValidationDefinitions:
|
||||||
getSpreadSheetFieldValidationDefinitions(
|
getSpreadSheetFieldValidationDefinitions(
|
||||||
fieldMetadataItem.type,
|
fieldMetadataItem.type,
|
||||||
@ -117,6 +123,7 @@ export const useBuildAvailableFieldsForImport = () => {
|
|||||||
fieldType: {
|
fieldType: {
|
||||||
type: 'input',
|
type: 'input',
|
||||||
},
|
},
|
||||||
|
fieldMetadataType: fieldMetadataItem.type,
|
||||||
fieldValidationDefinitions:
|
fieldValidationDefinitions:
|
||||||
getSpreadSheetFieldValidationDefinitions(
|
getSpreadSheetFieldValidationDefinitions(
|
||||||
fieldMetadataItem.type,
|
fieldMetadataItem.type,
|
||||||
@ -138,6 +145,7 @@ export const useBuildAvailableFieldsForImport = () => {
|
|||||||
color: option.color,
|
color: option.color,
|
||||||
})) || [],
|
})) || [],
|
||||||
},
|
},
|
||||||
|
fieldMetadataType: fieldMetadataItem.type,
|
||||||
fieldValidationDefinitions: getSpreadSheetFieldValidationDefinitions(
|
fieldValidationDefinitions: getSpreadSheetFieldValidationDefinitions(
|
||||||
fieldMetadataItem.type,
|
fieldMetadataItem.type,
|
||||||
fieldMetadataItem.label + ' (ID)',
|
fieldMetadataItem.label + ' (ID)',
|
||||||
@ -157,6 +165,7 @@ export const useBuildAvailableFieldsForImport = () => {
|
|||||||
color: option.color,
|
color: option.color,
|
||||||
})) || [],
|
})) || [],
|
||||||
},
|
},
|
||||||
|
fieldMetadataType: fieldMetadataItem.type,
|
||||||
fieldValidationDefinitions: getSpreadSheetFieldValidationDefinitions(
|
fieldValidationDefinitions: getSpreadSheetFieldValidationDefinitions(
|
||||||
fieldMetadataItem.type,
|
fieldMetadataItem.type,
|
||||||
fieldMetadataItem.label + ' (ID)',
|
fieldMetadataItem.label + ' (ID)',
|
||||||
@ -170,6 +179,7 @@ export const useBuildAvailableFieldsForImport = () => {
|
|||||||
fieldType: {
|
fieldType: {
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
},
|
},
|
||||||
|
fieldMetadataType: fieldMetadataItem.type,
|
||||||
fieldValidationDefinitions: getSpreadSheetFieldValidationDefinitions(
|
fieldValidationDefinitions: getSpreadSheetFieldValidationDefinitions(
|
||||||
fieldMetadataItem.type,
|
fieldMetadataItem.type,
|
||||||
fieldMetadataItem.label,
|
fieldMetadataItem.label,
|
||||||
@ -186,6 +196,7 @@ export const useBuildAvailableFieldsForImport = () => {
|
|||||||
fieldType: {
|
fieldType: {
|
||||||
type: 'input',
|
type: 'input',
|
||||||
},
|
},
|
||||||
|
fieldMetadataType: fieldMetadataItem.type,
|
||||||
fieldValidationDefinitions:
|
fieldValidationDefinitions:
|
||||||
getSpreadSheetFieldValidationDefinitions(
|
getSpreadSheetFieldValidationDefinitions(
|
||||||
fieldMetadataItem.type,
|
fieldMetadataItem.type,
|
||||||
@ -204,6 +215,7 @@ export const useBuildAvailableFieldsForImport = () => {
|
|||||||
fieldType: {
|
fieldType: {
|
||||||
type: 'input',
|
type: 'input',
|
||||||
},
|
},
|
||||||
|
fieldMetadataType: fieldMetadataItem.type,
|
||||||
fieldValidationDefinitions:
|
fieldValidationDefinitions:
|
||||||
getSpreadSheetFieldValidationDefinitions(
|
getSpreadSheetFieldValidationDefinitions(
|
||||||
fieldMetadataItem.type,
|
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 {
|
} else {
|
||||||
availableFieldsForImport.push({
|
availableFieldsForImport.push({
|
||||||
icon: getIcon(fieldMetadataItem.icon),
|
icon: getIcon(fieldMetadataItem.icon),
|
||||||
@ -219,6 +245,7 @@ export const useBuildAvailableFieldsForImport = () => {
|
|||||||
fieldType: {
|
fieldType: {
|
||||||
type: 'input',
|
type: 'input',
|
||||||
},
|
},
|
||||||
|
fieldMetadataType: fieldMetadataItem.type,
|
||||||
fieldValidationDefinitions: getSpreadSheetFieldValidationDefinitions(
|
fieldValidationDefinitions: getSpreadSheetFieldValidationDefinitions(
|
||||||
fieldMetadataItem.type,
|
fieldMetadataItem.type,
|
||||||
fieldMetadataItem.label,
|
fieldMetadataItem.label,
|
||||||
|
|||||||
@ -3,11 +3,12 @@ import {
|
|||||||
SpreadsheetImportFieldType,
|
SpreadsheetImportFieldType,
|
||||||
} from '@/spreadsheet-import/types';
|
} from '@/spreadsheet-import/types';
|
||||||
import { IconComponent } from 'twenty-ui';
|
import { IconComponent } from 'twenty-ui';
|
||||||
|
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
export type AvailableFieldForImport = {
|
export type AvailableFieldForImport = {
|
||||||
icon: IconComponent;
|
icon: IconComponent;
|
||||||
label: string;
|
label: string;
|
||||||
key: string;
|
key: string;
|
||||||
fieldType: SpreadsheetImportFieldType;
|
fieldType: SpreadsheetImportFieldType;
|
||||||
fieldValidationDefinitions?: FieldValidationDefinition[];
|
fieldValidationDefinitions?: FieldValidationDefinition[];
|
||||||
|
fieldMetadataType: FieldMetadataType;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import {
|
|||||||
FieldEmailsValue,
|
FieldEmailsValue,
|
||||||
FieldLinksValue,
|
FieldLinksValue,
|
||||||
FieldPhonesValue,
|
FieldPhonesValue,
|
||||||
|
FieldRichTextV2Value,
|
||||||
} from '@/object-record/record-field/types/FieldMetadata';
|
} from '@/object-record/record-field/types/FieldMetadata';
|
||||||
import { COMPOSITE_FIELD_IMPORT_LABELS } from '@/object-record/spreadsheet-import/constants/CompositeFieldImportLabels';
|
import { COMPOSITE_FIELD_IMPORT_LABELS } from '@/object-record/spreadsheet-import/constants/CompositeFieldImportLabels';
|
||||||
import { ImportedStructuredRow } from '@/spreadsheet-import/types';
|
import { ImportedStructuredRow } from '@/spreadsheet-import/types';
|
||||||
@ -36,6 +37,7 @@ export const buildRecordFromImportedStructuredRow = (
|
|||||||
LINKS: { primaryLinkUrlLabel },
|
LINKS: { primaryLinkUrlLabel },
|
||||||
EMAILS: { primaryEmailLabel },
|
EMAILS: { primaryEmailLabel },
|
||||||
PHONES: { primaryPhoneNumberLabel, primaryPhoneCountryCodeLabel },
|
PHONES: { primaryPhoneNumberLabel, primaryPhoneCountryCodeLabel },
|
||||||
|
RICH_TEXT_V2: { blocknoteLabel, markdownLabel },
|
||||||
} = COMPOSITE_FIELD_IMPORT_LABELS;
|
} = COMPOSITE_FIELD_IMPORT_LABELS;
|
||||||
|
|
||||||
for (const field of fields) {
|
for (const field of fields) {
|
||||||
@ -158,6 +160,24 @@ export const buildRecordFromImportedStructuredRow = (
|
|||||||
}
|
}
|
||||||
break;
|
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: {
|
case FieldMetadataType.EMAILS: {
|
||||||
if (
|
if (
|
||||||
isDefined(
|
isDefined(
|
||||||
|
|||||||
@ -84,6 +84,12 @@ export const generateEmptyFieldValue = (
|
|||||||
case FieldMetadataType.RICH_TEXT: {
|
case FieldMetadataType.RICH_TEXT: {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
case FieldMetadataType.RICH_TEXT_V2: {
|
||||||
|
return {
|
||||||
|
blocknote: null,
|
||||||
|
markdown: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
case FieldMetadataType.ACTOR: {
|
case FieldMetadataType.ACTOR: {
|
||||||
return {
|
return {
|
||||||
source: 'MANUAL',
|
source: 'MANUAL',
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import {
|
|||||||
FieldFullNameValue,
|
FieldFullNameValue,
|
||||||
FieldLinksValue,
|
FieldLinksValue,
|
||||||
FieldPhonesValue,
|
FieldPhonesValue,
|
||||||
|
FieldRichTextV2Value,
|
||||||
} from '@/object-record/record-field/types/FieldMetadata';
|
} from '@/object-record/record-field/types/FieldMetadata';
|
||||||
import { SettingsFieldTypeConfig } from '@/settings/data-model/constants/SettingsNonCompositeFieldTypeConfigs';
|
import { SettingsFieldTypeConfig } from '@/settings/data-model/constants/SettingsNonCompositeFieldTypeConfigs';
|
||||||
import { CompositeFieldType } from '@/settings/data-model/types/CompositeFieldType';
|
import { CompositeFieldType } from '@/settings/data-model/types/CompositeFieldType';
|
||||||
@ -18,6 +19,7 @@ import {
|
|||||||
IllustrationIconMap,
|
IllustrationIconMap,
|
||||||
IllustrationIconPhone,
|
IllustrationIconPhone,
|
||||||
IllustrationIconSetting,
|
IllustrationIconSetting,
|
||||||
|
IllustrationIconText,
|
||||||
IllustrationIconUser,
|
IllustrationIconUser,
|
||||||
} from 'twenty-ui';
|
} from 'twenty-ui';
|
||||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
@ -185,4 +187,19 @@ export const SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS = {
|
|||||||
context: { provider: ConnectedAccountProvider.GOOGLE },
|
context: { provider: ConnectedAccountProvider.GOOGLE },
|
||||||
},
|
},
|
||||||
} as const satisfies SettingsCompositeFieldTypeConfig<FieldActorValue>,
|
} as const satisfies SettingsCompositeFieldTypeConfig<FieldActorValue>,
|
||||||
|
[FieldMetadataType.RICH_TEXT_V2]: {
|
||||||
|
label: 'Rich Text',
|
||||||
|
Icon: IllustrationIconText,
|
||||||
|
subFields: ['blocknote', 'markdown'],
|
||||||
|
filterableSubFields: [],
|
||||||
|
labelBySubField: {
|
||||||
|
blocknote: 'BlockNote',
|
||||||
|
markdown: 'Markdown',
|
||||||
|
},
|
||||||
|
exampleValue: {
|
||||||
|
blocknote: '[{"type":"heading","content":"Hello"}]',
|
||||||
|
markdown: '# Hello',
|
||||||
|
},
|
||||||
|
category: 'Basic',
|
||||||
|
} as const satisfies SettingsCompositeFieldTypeConfig<FieldRichTextV2Value>,
|
||||||
} as const satisfies SettingsCompositeFieldTypeConfigArray;
|
} as const satisfies SettingsCompositeFieldTypeConfigArray;
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import {
|
|||||||
IllustrationIconJson,
|
IllustrationIconJson,
|
||||||
IllustrationIconNumbers,
|
IllustrationIconNumbers,
|
||||||
IllustrationIconOneToMany,
|
IllustrationIconOneToMany,
|
||||||
IllustrationIconSetting,
|
|
||||||
IllustrationIconStar,
|
IllustrationIconStar,
|
||||||
IllustrationIconTag,
|
IllustrationIconTag,
|
||||||
IllustrationIconTags,
|
IllustrationIconTags,
|
||||||
@ -25,7 +24,6 @@ import {
|
|||||||
FieldNumberValue,
|
FieldNumberValue,
|
||||||
FieldRatingValue,
|
FieldRatingValue,
|
||||||
FieldRelationValue,
|
FieldRelationValue,
|
||||||
FieldRichTextValue,
|
|
||||||
FieldSelectValue,
|
FieldSelectValue,
|
||||||
FieldTextValue,
|
FieldTextValue,
|
||||||
FieldUUidValue,
|
FieldUUidValue,
|
||||||
@ -122,12 +120,6 @@ export const SETTINGS_NON_COMPOSITE_FIELD_TYPE_CONFIGS: SettingsNonCompositeFiel
|
|||||||
exampleValue: { key: 'value' },
|
exampleValue: { key: 'value' },
|
||||||
category: 'Advanced',
|
category: 'Advanced',
|
||||||
} as const satisfies SettingsFieldTypeConfig<FieldJsonValue>,
|
} as const satisfies SettingsFieldTypeConfig<FieldJsonValue>,
|
||||||
[FieldMetadataType.RICH_TEXT]: {
|
|
||||||
label: 'Rich Text',
|
|
||||||
Icon: IllustrationIconSetting,
|
|
||||||
exampleValue: "{ key: 'value' }",
|
|
||||||
category: 'Basic',
|
|
||||||
} as const satisfies SettingsFieldTypeConfig<FieldRichTextValue>,
|
|
||||||
[FieldMetadataType.ARRAY]: {
|
[FieldMetadataType.ARRAY]: {
|
||||||
label: 'Array',
|
label: 'Array',
|
||||||
Icon: IllustrationIconArray,
|
Icon: IllustrationIconArray,
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { SettingsFieldTypeConfig } from '@/settings/data-model/constants/Setting
|
|||||||
import { useBooleanSettingsFormInitialValues } from '@/settings/data-model/fields/forms/boolean/hooks/useBooleanSettingsFormInitialValues';
|
import { useBooleanSettingsFormInitialValues } from '@/settings/data-model/fields/forms/boolean/hooks/useBooleanSettingsFormInitialValues';
|
||||||
import { useCurrencySettingsFormInitialValues } from '@/settings/data-model/fields/forms/currency/hooks/useCurrencySettingsFormInitialValues';
|
import { useCurrencySettingsFormInitialValues } from '@/settings/data-model/fields/forms/currency/hooks/useCurrencySettingsFormInitialValues';
|
||||||
import { useSelectSettingsFormInitialValues } from '@/settings/data-model/fields/forms/select/hooks/useSelectSettingsFormInitialValues';
|
import { useSelectSettingsFormInitialValues } from '@/settings/data-model/fields/forms/select/hooks/useSelectSettingsFormInitialValues';
|
||||||
|
import { FieldType } from '@/settings/data-model/types/FieldType';
|
||||||
import { SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType';
|
import { SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType';
|
||||||
import { SettingsPath } from '@/types/SettingsPath';
|
import { SettingsPath } from '@/types/SettingsPath';
|
||||||
import { TextInput } from '@/ui/input/components/TextInput';
|
import { TextInput } from '@/ui/input/components/TextInput';
|
||||||
@ -22,7 +23,7 @@ import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
|||||||
|
|
||||||
type SettingsObjectNewFieldSelectorProps = {
|
type SettingsObjectNewFieldSelectorProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
excludedFieldTypes?: SettingsFieldType[];
|
excludedFieldTypes?: FieldType[];
|
||||||
fieldMetadataItem?: Pick<
|
fieldMetadataItem?: Pick<
|
||||||
FieldMetadataItem,
|
FieldMetadataItem,
|
||||||
'defaultValue' | 'options' | 'type'
|
'defaultValue' | 'options' | 'type'
|
||||||
|
|||||||
@ -10,6 +10,7 @@ export const COMPOSITE_FIELD_TYPES = [
|
|||||||
'PHONES',
|
'PHONES',
|
||||||
'FULL_NAME',
|
'FULL_NAME',
|
||||||
'ACTOR',
|
'ACTOR',
|
||||||
|
'RICH_TEXT_V2',
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
type CompositeFieldTypeBaseLiteral = (typeof COMPOSITE_FIELD_TYPES)[number];
|
type CompositeFieldTypeBaseLiteral = (typeof COMPOSITE_FIELD_TYPES)[number];
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
import { CompositeFieldType } from '@/settings/data-model/types/CompositeFieldType';
|
import { CompositeFieldType } from '@/settings/data-model/types/CompositeFieldType';
|
||||||
import { SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType';
|
import { SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType';
|
||||||
import { PickLiteral } from '~/types/PickLiteral';
|
|
||||||
|
|
||||||
export type SettingsCompositeFieldType = PickLiteral<
|
export type SettingsCompositeFieldType = Extract<
|
||||||
SettingsFieldType,
|
SettingsFieldType,
|
||||||
CompositeFieldType
|
CompositeFieldType
|
||||||
>;
|
>;
|
||||||
|
|||||||
@ -3,5 +3,5 @@ import { PickLiteral } from '~/types/PickLiteral';
|
|||||||
|
|
||||||
export type SettingsExcludedFieldType = PickLiteral<
|
export type SettingsExcludedFieldType = PickLiteral<
|
||||||
FieldType,
|
FieldType,
|
||||||
'POSITION' | 'TS_VECTOR' | 'RICH_TEXT_V2'
|
'POSITION' | 'TS_VECTOR' | 'RICH_TEXT' | 'RICH_TEXT_V2'
|
||||||
>;
|
>;
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
import { NonCompositeFieldType } from '@/settings/data-model/types/NonCompositeFieldType';
|
import { NonCompositeFieldType } from '@/settings/data-model/types/NonCompositeFieldType';
|
||||||
import { SettingsExcludedFieldType } from '@/settings/data-model/types/SettingsExcludedFieldType';
|
import { SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType';
|
||||||
import { ExcludeLiteral } from '~/types/ExcludeLiteral';
|
|
||||||
|
|
||||||
export type SettingsNonCompositeFieldType = ExcludeLiteral<
|
export type SettingsNonCompositeFieldType = Extract<
|
||||||
NonCompositeFieldType,
|
NonCompositeFieldType,
|
||||||
SettingsExcludedFieldType
|
SettingsFieldType
|
||||||
>;
|
>;
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import {
|
|||||||
Fields,
|
Fields,
|
||||||
SpreadsheetImportDialogOptions,
|
SpreadsheetImportDialogOptions,
|
||||||
} from '@/spreadsheet-import/types';
|
} from '@/spreadsheet-import/types';
|
||||||
|
import { FieldMetadataType } from 'twenty-shared';
|
||||||
import { sleep } from '~/utils/sleep';
|
import { sleep } from '~/utils/sleep';
|
||||||
|
|
||||||
const fields = [
|
const fields = [
|
||||||
@ -22,6 +23,7 @@ const fields = [
|
|||||||
errorMessage: 'Name is required',
|
errorMessage: 'Name is required',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
fieldMetadataType: FieldMetadataType.TEXT,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: null,
|
icon: null,
|
||||||
|
|||||||
@ -4,6 +4,9 @@ import { IconForbid } from 'twenty-ui';
|
|||||||
import { MatchColumnSelect } from '@/spreadsheet-import/components/MatchColumnSelect';
|
import { MatchColumnSelect } from '@/spreadsheet-import/components/MatchColumnSelect';
|
||||||
import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal';
|
import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal';
|
||||||
|
|
||||||
|
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||||
|
import { FieldMetadataType } from 'twenty-shared';
|
||||||
|
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||||
import { Columns, ColumnType } from '../MatchColumnsStep';
|
import { Columns, ColumnType } from '../MatchColumnsStep';
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
const StyledContainer = styled.div`
|
||||||
@ -24,26 +27,35 @@ export const TemplateColumn = <T extends string>({
|
|||||||
columnIndex,
|
columnIndex,
|
||||||
onChange,
|
onChange,
|
||||||
}: TemplateColumnProps<T>) => {
|
}: TemplateColumnProps<T>) => {
|
||||||
|
const isRichTextV2Enabled = useIsFeatureEnabled(
|
||||||
|
FeatureFlagKey.IsRichTextV2Enabled,
|
||||||
|
);
|
||||||
const { fields } = useSpreadsheetImportInternal<T>();
|
const { fields } = useSpreadsheetImportInternal<T>();
|
||||||
const column = columns[columnIndex];
|
const column = columns[columnIndex];
|
||||||
const isIgnored = column.type === ColumnType.ignored;
|
const isIgnored = column.type === ColumnType.ignored;
|
||||||
|
|
||||||
const fieldOptions = fields.map(({ icon, label, key }) => {
|
const fieldOptions = fields
|
||||||
const isSelected =
|
.filter((field) =>
|
||||||
columns.findIndex((column) => {
|
isRichTextV2Enabled
|
||||||
if ('value' in column) {
|
? field.fieldMetadataType !== FieldMetadataType.RICH_TEXT
|
||||||
return column.value === key;
|
: true,
|
||||||
}
|
)
|
||||||
return false;
|
.map(({ icon, label, key }) => {
|
||||||
}) !== -1;
|
const isSelected =
|
||||||
|
columns.findIndex((column) => {
|
||||||
|
if ('value' in column) {
|
||||||
|
return column.value === key;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}) !== -1;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
icon: icon,
|
icon: icon,
|
||||||
value: key,
|
value: key,
|
||||||
label: label,
|
label: label,
|
||||||
disabled: isSelected,
|
disabled: isSelected,
|
||||||
} as const;
|
} as const;
|
||||||
});
|
});
|
||||||
|
|
||||||
const selectOptions = [
|
const selectOptions = [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { ReadonlyDeep } from 'type-fest';
|
|||||||
import { Columns } from '@/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep';
|
import { Columns } from '@/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep';
|
||||||
import { ImportedStructuredRowMetadata } from '@/spreadsheet-import/steps/components/ValidationStep/types';
|
import { ImportedStructuredRowMetadata } from '@/spreadsheet-import/steps/components/ValidationStep/types';
|
||||||
import { SpreadsheetImportStep } from '@/spreadsheet-import/steps/types/SpreadsheetImportStep';
|
import { SpreadsheetImportStep } from '@/spreadsheet-import/steps/types/SpreadsheetImportStep';
|
||||||
|
import { FieldMetadataType } from 'twenty-shared';
|
||||||
|
|
||||||
export type SpreadsheetImportDialogOptions<FieldNames extends string> = {
|
export type SpreadsheetImportDialogOptions<FieldNames extends string> = {
|
||||||
// Is modal visible.
|
// Is modal visible.
|
||||||
@ -122,6 +123,8 @@ export type Field<T extends string> = {
|
|||||||
fieldValidationDefinitions?: FieldValidationDefinition[];
|
fieldValidationDefinitions?: FieldValidationDefinition[];
|
||||||
// Field entry component, default: Input
|
// Field entry component, default: Input
|
||||||
fieldType: SpreadsheetImportFieldType;
|
fieldType: SpreadsheetImportFieldType;
|
||||||
|
// Field metadata type
|
||||||
|
fieldMetadataType: FieldMetadataType;
|
||||||
// UI-facing values shown to user as field examples pre-upload phase
|
// UI-facing values shown to user as field examples pre-upload phase
|
||||||
example?: string;
|
example?: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import {
|
|||||||
TableHook,
|
TableHook,
|
||||||
} from '@/spreadsheet-import/types';
|
} from '@/spreadsheet-import/types';
|
||||||
import { addErrorsAndRunHooks } from '@/spreadsheet-import/utils/dataMutations';
|
import { addErrorsAndRunHooks } from '@/spreadsheet-import/utils/dataMutations';
|
||||||
|
import { FieldMetadataType } from 'twenty-shared';
|
||||||
|
|
||||||
describe('addErrorsAndRunHooks', () => {
|
describe('addErrorsAndRunHooks', () => {
|
||||||
type FullData = ImportedStructuredRow<'name' | 'age' | 'country'>;
|
type FullData = ImportedStructuredRow<'name' | 'age' | 'country'>;
|
||||||
@ -15,6 +16,7 @@ describe('addErrorsAndRunHooks', () => {
|
|||||||
fieldValidationDefinitions: [{ rule: 'required' }],
|
fieldValidationDefinitions: [{ rule: 'required' }],
|
||||||
icon: null,
|
icon: null,
|
||||||
fieldType: { type: 'input' },
|
fieldType: { type: 'input' },
|
||||||
|
fieldMetadataType: FieldMetadataType.TEXT,
|
||||||
};
|
};
|
||||||
|
|
||||||
const regexField: Field<'age'> = {
|
const regexField: Field<'age'> = {
|
||||||
@ -25,6 +27,7 @@ describe('addErrorsAndRunHooks', () => {
|
|||||||
],
|
],
|
||||||
icon: null,
|
icon: null,
|
||||||
fieldType: { type: 'input' },
|
fieldType: { type: 'input' },
|
||||||
|
fieldMetadataType: FieldMetadataType.NUMBER,
|
||||||
};
|
};
|
||||||
|
|
||||||
const uniqueField: Field<'country'> = {
|
const uniqueField: Field<'country'> = {
|
||||||
@ -33,6 +36,7 @@ describe('addErrorsAndRunHooks', () => {
|
|||||||
fieldValidationDefinitions: [{ rule: 'unique' }],
|
fieldValidationDefinitions: [{ rule: 'unique' }],
|
||||||
icon: null,
|
icon: null,
|
||||||
fieldType: { type: 'input' },
|
fieldType: { type: 'input' },
|
||||||
|
fieldMetadataType: FieldMetadataType.SELECT,
|
||||||
};
|
};
|
||||||
|
|
||||||
const functionValidationFieldTrue: Field<'email'> = {
|
const functionValidationFieldTrue: Field<'email'> = {
|
||||||
@ -47,6 +51,7 @@ describe('addErrorsAndRunHooks', () => {
|
|||||||
],
|
],
|
||||||
icon: null,
|
icon: null,
|
||||||
fieldType: { type: 'input' },
|
fieldType: { type: 'input' },
|
||||||
|
fieldMetadataType: FieldMetadataType.EMAILS,
|
||||||
};
|
};
|
||||||
|
|
||||||
const functionValidationFieldFalse: Field<'email'> = {
|
const functionValidationFieldFalse: Field<'email'> = {
|
||||||
@ -61,6 +66,7 @@ describe('addErrorsAndRunHooks', () => {
|
|||||||
],
|
],
|
||||||
icon: null,
|
icon: null,
|
||||||
fieldType: { type: 'input' },
|
fieldType: { type: 'input' },
|
||||||
|
fieldMetadataType: FieldMetadataType.EMAILS,
|
||||||
};
|
};
|
||||||
|
|
||||||
const validData: ImportedStructuredRow<'name' | 'age'> = {
|
const validData: ImportedStructuredRow<'name' | 'age'> = {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Field } from '@/spreadsheet-import/types';
|
import { Field } from '@/spreadsheet-import/types';
|
||||||
import { findMatch } from '@/spreadsheet-import/utils/findMatch';
|
import { findMatch } from '@/spreadsheet-import/utils/findMatch';
|
||||||
|
import { FieldMetadataType } from 'twenty-shared';
|
||||||
describe('findMatch', () => {
|
describe('findMatch', () => {
|
||||||
const defaultField: Field<'defaultField'> = {
|
const defaultField: Field<'defaultField'> = {
|
||||||
key: 'defaultField',
|
key: 'defaultField',
|
||||||
@ -9,6 +9,7 @@ describe('findMatch', () => {
|
|||||||
fieldType: {
|
fieldType: {
|
||||||
type: 'input',
|
type: 'input',
|
||||||
},
|
},
|
||||||
|
fieldMetadataType: FieldMetadataType.TEXT,
|
||||||
alternateMatches: ['Full Name', 'First Name'],
|
alternateMatches: ['Full Name', 'First Name'],
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -19,6 +20,7 @@ describe('findMatch', () => {
|
|||||||
fieldType: {
|
fieldType: {
|
||||||
type: 'input',
|
type: 'input',
|
||||||
},
|
},
|
||||||
|
fieldMetadataType: FieldMetadataType.TEXT,
|
||||||
};
|
};
|
||||||
|
|
||||||
const fields = [defaultField, secondaryField];
|
const fields = [defaultField, secondaryField];
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import {
|
|||||||
} from '@/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep';
|
} from '@/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep';
|
||||||
import { Field, FieldValidationDefinition } from '@/spreadsheet-import/types';
|
import { Field, FieldValidationDefinition } from '@/spreadsheet-import/types';
|
||||||
import { findUnmatchedRequiredFields } from '@/spreadsheet-import/utils/findUnmatchedRequiredFields';
|
import { findUnmatchedRequiredFields } from '@/spreadsheet-import/utils/findUnmatchedRequiredFields';
|
||||||
|
import { FieldMetadataType } from 'twenty-shared';
|
||||||
|
|
||||||
const nameField: Field<'Name'> = {
|
const nameField: Field<'Name'> = {
|
||||||
key: 'Name',
|
key: 'Name',
|
||||||
@ -12,6 +13,7 @@ const nameField: Field<'Name'> = {
|
|||||||
fieldType: {
|
fieldType: {
|
||||||
type: 'input',
|
type: 'input',
|
||||||
},
|
},
|
||||||
|
fieldMetadataType: FieldMetadataType.TEXT,
|
||||||
};
|
};
|
||||||
|
|
||||||
const ageField: Field<'Age'> = {
|
const ageField: Field<'Age'> = {
|
||||||
@ -21,7 +23,9 @@ const ageField: Field<'Age'> = {
|
|||||||
fieldType: {
|
fieldType: {
|
||||||
type: 'input',
|
type: 'input',
|
||||||
},
|
},
|
||||||
|
fieldMetadataType: FieldMetadataType.NUMBER,
|
||||||
};
|
};
|
||||||
|
|
||||||
const validations: FieldValidationDefinition[] = [{ rule: 'required' }];
|
const validations: FieldValidationDefinition[] = [{ rule: 'required' }];
|
||||||
const nameFieldWithValidations: Field<'Name'> = {
|
const nameFieldWithValidations: Field<'Name'> = {
|
||||||
...nameField,
|
...nameField,
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { Field } from '@/spreadsheet-import/types';
|
import { Field } from '@/spreadsheet-import/types';
|
||||||
import { generateExampleRow } from '@/spreadsheet-import/utils/generateExampleRow';
|
import { generateExampleRow } from '@/spreadsheet-import/utils/generateExampleRow';
|
||||||
|
import { FieldMetadataType } from 'twenty-shared';
|
||||||
|
|
||||||
describe('generateExampleRow', () => {
|
describe('generateExampleRow', () => {
|
||||||
const defaultField: Field<'defaultField'> = {
|
const defaultField: Field<'defaultField'> = {
|
||||||
@ -9,6 +10,7 @@ describe('generateExampleRow', () => {
|
|||||||
fieldType: {
|
fieldType: {
|
||||||
type: 'input',
|
type: 'input',
|
||||||
},
|
},
|
||||||
|
fieldMetadataType: FieldMetadataType.TEXT,
|
||||||
};
|
};
|
||||||
|
|
||||||
it('should generate an example row from input field type', () => {
|
it('should generate an example row from input field type', () => {
|
||||||
@ -24,6 +26,7 @@ describe('generateExampleRow', () => {
|
|||||||
{
|
{
|
||||||
...defaultField,
|
...defaultField,
|
||||||
fieldType: { type: 'checkbox' },
|
fieldType: { type: 'checkbox' },
|
||||||
|
fieldMetadataType: FieldMetadataType.BOOLEAN,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -37,6 +40,7 @@ describe('generateExampleRow', () => {
|
|||||||
{
|
{
|
||||||
...defaultField,
|
...defaultField,
|
||||||
fieldType: { type: 'select', options: [] },
|
fieldType: { type: 'select', options: [] },
|
||||||
|
fieldMetadataType: FieldMetadataType.SELECT,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -50,6 +54,7 @@ describe('generateExampleRow', () => {
|
|||||||
{
|
{
|
||||||
...defaultField,
|
...defaultField,
|
||||||
example: 'Example',
|
example: 'Example',
|
||||||
|
fieldMetadataType: FieldMetadataType.TEXT,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { Field } from '@/spreadsheet-import/types';
|
import { Field } from '@/spreadsheet-import/types';
|
||||||
import { getFieldOptions } from '@/spreadsheet-import/utils/getFieldOptions';
|
import { getFieldOptions } from '@/spreadsheet-import/utils/getFieldOptions';
|
||||||
|
import { FieldMetadataType } from 'twenty-shared';
|
||||||
|
|
||||||
describe('getFieldOptions', () => {
|
describe('getFieldOptions', () => {
|
||||||
const optionsArray = [
|
const optionsArray = [
|
||||||
@ -25,6 +26,7 @@ describe('getFieldOptions', () => {
|
|||||||
type: 'select',
|
type: 'select',
|
||||||
options: optionsArray,
|
options: optionsArray,
|
||||||
},
|
},
|
||||||
|
fieldMetadataType: FieldMetadataType.SELECT,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'Name',
|
key: 'Name',
|
||||||
@ -33,6 +35,7 @@ describe('getFieldOptions', () => {
|
|||||||
fieldType: {
|
fieldType: {
|
||||||
type: 'input',
|
type: 'input',
|
||||||
},
|
},
|
||||||
|
fieldMetadataType: FieldMetadataType.TEXT,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import {
|
|||||||
} from '@/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep';
|
} from '@/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep';
|
||||||
import { Field } from '@/spreadsheet-import/types';
|
import { Field } from '@/spreadsheet-import/types';
|
||||||
import { getMatchedColumns } from '@/spreadsheet-import/utils/getMatchedColumns';
|
import { getMatchedColumns } from '@/spreadsheet-import/utils/getMatchedColumns';
|
||||||
|
import { FieldMetadataType } from 'twenty-shared';
|
||||||
|
|
||||||
describe('getMatchedColumns', () => {
|
describe('getMatchedColumns', () => {
|
||||||
const columns: Column<string>[] = [
|
const columns: Column<string>[] = [
|
||||||
@ -27,15 +28,23 @@ describe('getMatchedColumns', () => {
|
|||||||
key: 'Name',
|
key: 'Name',
|
||||||
label: 'Name',
|
label: 'Name',
|
||||||
fieldType: { type: 'input' },
|
fieldType: { type: 'input' },
|
||||||
|
fieldMetadataType: FieldMetadataType.TEXT,
|
||||||
icon: null,
|
icon: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'Location',
|
key: 'Location',
|
||||||
label: 'Location',
|
label: 'Location',
|
||||||
fieldType: { type: 'select', options: [] },
|
fieldType: { type: 'select', options: [] },
|
||||||
|
fieldMetadataType: FieldMetadataType.POSITION,
|
||||||
|
icon: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'Age',
|
||||||
|
label: 'Age',
|
||||||
|
fieldType: { type: 'input' },
|
||||||
|
fieldMetadataType: FieldMetadataType.NUMBER,
|
||||||
icon: null,
|
icon: null,
|
||||||
},
|
},
|
||||||
{ key: 'Age', label: 'Age', fieldType: { type: 'input' }, icon: null },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const data = [
|
const data = [
|
||||||
@ -110,12 +119,14 @@ describe('getMatchedColumns', () => {
|
|||||||
key: 'Hobby',
|
key: 'Hobby',
|
||||||
label: 'Hobby',
|
label: 'Hobby',
|
||||||
fieldType: { type: 'input' },
|
fieldType: { type: 'input' },
|
||||||
|
fieldMetadataType: FieldMetadataType.TEXT,
|
||||||
icon: null,
|
icon: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'Interest',
|
key: 'Interest',
|
||||||
label: 'Interest',
|
label: 'Interest',
|
||||||
fieldType: { type: 'input' },
|
fieldType: { type: 'input' },
|
||||||
|
fieldMetadataType: FieldMetadataType.TEXT,
|
||||||
icon: null,
|
icon: null,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import {
|
|||||||
} from '@/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep';
|
} from '@/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep';
|
||||||
import { Field } from '@/spreadsheet-import/types';
|
import { Field } from '@/spreadsheet-import/types';
|
||||||
import { normalizeTableData } from '@/spreadsheet-import/utils/normalizeTableData';
|
import { normalizeTableData } from '@/spreadsheet-import/utils/normalizeTableData';
|
||||||
|
import { FieldMetadataType } from 'twenty-shared';
|
||||||
|
|
||||||
describe('normalizeTableData', () => {
|
describe('normalizeTableData', () => {
|
||||||
const columns: Column<string>[] = [
|
const columns: Column<string>[] = [
|
||||||
@ -18,14 +19,27 @@ describe('normalizeTableData', () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const fields: Field<string>[] = [
|
const fields: Field<string>[] = [
|
||||||
{ key: 'name', label: 'Name', fieldType: { type: 'input' }, icon: null },
|
{
|
||||||
{ key: 'age', label: 'Age', fieldType: { type: 'input' }, icon: null },
|
key: 'name',
|
||||||
|
label: 'Name',
|
||||||
|
fieldType: { type: 'input' },
|
||||||
|
fieldMetadataType: FieldMetadataType.TEXT,
|
||||||
|
icon: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'age',
|
||||||
|
label: 'Age',
|
||||||
|
fieldType: { type: 'input' },
|
||||||
|
fieldMetadataType: FieldMetadataType.NUMBER,
|
||||||
|
icon: null,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'active',
|
key: 'active',
|
||||||
label: 'Active',
|
label: 'Active',
|
||||||
fieldType: {
|
fieldType: {
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
},
|
},
|
||||||
|
fieldMetadataType: FieldMetadataType.BOOLEAN,
|
||||||
icon: null,
|
icon: null,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@ -64,6 +78,7 @@ describe('normalizeTableData', () => {
|
|||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
booleanMatches: { yes: true, no: false },
|
booleanMatches: { yes: true, no: false },
|
||||||
},
|
},
|
||||||
|
fieldMetadataType: FieldMetadataType.BOOLEAN,
|
||||||
icon: null,
|
icon: null,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@ -100,6 +115,7 @@ describe('normalizeTableData', () => {
|
|||||||
{ label: 'Two', value: '2' },
|
{ label: 'Two', value: '2' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
fieldMetadataType: FieldMetadataType.SELECT,
|
||||||
icon: null,
|
icon: null,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import {
|
|||||||
} from '@/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep';
|
} from '@/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep';
|
||||||
import { Field } from '@/spreadsheet-import/types';
|
import { Field } from '@/spreadsheet-import/types';
|
||||||
import { setColumn } from '@/spreadsheet-import/utils/setColumn';
|
import { setColumn } from '@/spreadsheet-import/utils/setColumn';
|
||||||
|
import { FieldMetadataType } from 'twenty-shared';
|
||||||
|
|
||||||
describe('setColumn', () => {
|
describe('setColumn', () => {
|
||||||
const defaultField: Field<'Name'> = {
|
const defaultField: Field<'Name'> = {
|
||||||
@ -11,6 +12,7 @@ describe('setColumn', () => {
|
|||||||
label: 'label',
|
label: 'label',
|
||||||
key: 'Name',
|
key: 'Name',
|
||||||
fieldType: { type: 'input' },
|
fieldType: { type: 'input' },
|
||||||
|
fieldMetadataType: FieldMetadataType.TEXT,
|
||||||
};
|
};
|
||||||
|
|
||||||
const oldColumn: Column<'oldValue'> = {
|
const oldColumn: Column<'oldValue'> = {
|
||||||
|
|||||||
@ -2,14 +2,14 @@ import { PartialBlock } from '@blocknote/core';
|
|||||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||||
|
|
||||||
export const getFirstNonEmptyLineOfRichText = (
|
export const getFirstNonEmptyLineOfRichText = (
|
||||||
fieldValue: PartialBlock[] | null,
|
blocks: PartialBlock[] | null,
|
||||||
): string => {
|
): string => {
|
||||||
if (fieldValue === null) {
|
if (blocks === null) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
for (const node of fieldValue) {
|
for (const block of blocks) {
|
||||||
if (!isUndefinedOrNull(node.content)) {
|
if (!isUndefinedOrNull(block.content)) {
|
||||||
const contentArray = node.content as Array<
|
const contentArray = block.content as Array<
|
||||||
{ text: string } | { link: string }
|
{ text: string } | { link: string }
|
||||||
>;
|
>;
|
||||||
if (contentArray.length > 0) {
|
if (contentArray.length > 0) {
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { SettingsPageContainer } from '@/settings/components/SettingsPageContain
|
|||||||
import { SettingsDataModelNewFieldBreadcrumbDropDown } from '@/settings/data-model/components/SettingsDataModelNewFieldBreadcrumbDropDown';
|
import { SettingsDataModelNewFieldBreadcrumbDropDown } from '@/settings/data-model/components/SettingsDataModelNewFieldBreadcrumbDropDown';
|
||||||
import { SETTINGS_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsFieldTypeConfigs';
|
import { SETTINGS_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsFieldTypeConfigs';
|
||||||
import { SettingsObjectNewFieldSelector } from '@/settings/data-model/fields/forms/components/SettingsObjectNewFieldSelector';
|
import { SettingsObjectNewFieldSelector } from '@/settings/data-model/fields/forms/components/SettingsObjectNewFieldSelector';
|
||||||
|
import { FieldType } from '@/settings/data-model/types/FieldType';
|
||||||
import { SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType';
|
import { SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType';
|
||||||
import { AppPath } from '@/types/AppPath';
|
import { AppPath } from '@/types/AppPath';
|
||||||
import { SettingsPath } from '@/types/SettingsPath';
|
import { SettingsPath } from '@/types/SettingsPath';
|
||||||
@ -44,10 +45,11 @@ export const SettingsObjectNewFieldSelect = () => {
|
|||||||
type: FieldMetadataType.TEXT,
|
type: FieldMetadataType.TEXT,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const excludedFieldTypes: SettingsFieldType[] = (
|
const excludedFieldTypes: FieldType[] = (
|
||||||
[
|
[
|
||||||
FieldMetadataType.NUMERIC,
|
FieldMetadataType.NUMERIC,
|
||||||
FieldMetadataType.RICH_TEXT,
|
FieldMetadataType.RICH_TEXT,
|
||||||
|
FieldMetadataType.RICH_TEXT_V2,
|
||||||
FieldMetadataType.ACTOR,
|
FieldMetadataType.ACTOR,
|
||||||
] as const
|
] as const
|
||||||
).filter(isDefined);
|
).filter(isDefined);
|
||||||
|
|||||||
@ -10,6 +10,10 @@ export const mockedNotes: Array<MockedNote> = [
|
|||||||
updatedAt: '2023-04-26T10:23:42.33625+00:00',
|
updatedAt: '2023-04-26T10:23:42.33625+00:00',
|
||||||
title: 'My very first note',
|
title: 'My very first note',
|
||||||
body: null,
|
body: null,
|
||||||
|
bodyV2: {
|
||||||
|
blocknote: null,
|
||||||
|
markdown: null,
|
||||||
|
},
|
||||||
noteTargets: [
|
noteTargets: [
|
||||||
{
|
{
|
||||||
id: '89bb825c-171e-4bcc-9cf7-43448d6fb300',
|
id: '89bb825c-171e-4bcc-9cf7-43448d6fb300',
|
||||||
@ -65,6 +69,10 @@ export const mockedNotes: Array<MockedNote> = [
|
|||||||
updatedAt: new Date().toISOString(),
|
updatedAt: new Date().toISOString(),
|
||||||
title: 'Another note',
|
title: 'Another note',
|
||||||
body: null,
|
body: null,
|
||||||
|
bodyV2: {
|
||||||
|
blocknote: null,
|
||||||
|
markdown: null,
|
||||||
|
},
|
||||||
noteTargets: [
|
noteTargets: [
|
||||||
{
|
{
|
||||||
id: '89bb825c-171e-4bcc-9cf7-43448d6fb278t',
|
id: '89bb825c-171e-4bcc-9cf7-43448d6fb278t',
|
||||||
|
|||||||
@ -27,6 +27,10 @@ export const mockedTasks: Array<MockedTask> = [
|
|||||||
updatedAt: '2023-04-26T10:23:42.33625+00:00',
|
updatedAt: '2023-04-26T10:23:42.33625+00:00',
|
||||||
title: 'My very first note',
|
title: 'My very first note',
|
||||||
body: null,
|
body: null,
|
||||||
|
bodyV2: {
|
||||||
|
blocknote: null,
|
||||||
|
markdown: null,
|
||||||
|
},
|
||||||
dueAt: '2023-04-26T10:12:42.33625+00:00',
|
dueAt: '2023-04-26T10:12:42.33625+00:00',
|
||||||
status: null,
|
status: null,
|
||||||
assignee: workspaceMember,
|
assignee: workspaceMember,
|
||||||
|
|||||||
@ -35,7 +35,20 @@ export class ActivityQueryResultGetterHandler
|
|||||||
return activity;
|
return activity;
|
||||||
}
|
}
|
||||||
|
|
||||||
const blocknote: RichTextBody = JSON.parse(blocknoteJson);
|
let blocknote: RichTextBody = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
blocknote = JSON.parse(blocknoteJson);
|
||||||
|
} catch (error) {
|
||||||
|
blocknote = [];
|
||||||
|
// TODO: Remove this once we have removed the old rich text
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.warn(
|
||||||
|
`Failed to parse body for activity ${activity.id} in workspace ${workspaceId}, for rich text version ${isRichTextV2Enabled ? 'v2' : 'v1'}`,
|
||||||
|
);
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.warn(blocknoteJson);
|
||||||
|
}
|
||||||
|
|
||||||
const blocknoteWithSignedPayload = await Promise.all(
|
const blocknoteWithSignedPayload = await Promise.all(
|
||||||
blocknote.map(async (block: RichTextBlock) => {
|
blocknote.map(async (block: RichTextBlock) => {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user