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 { gql } from '@apollo/client';
|
||||
export type Maybe<T> = T | null;
|
||||
export type InputMaybe<T> = Maybe<T>;
|
||||
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 activity = activityInStore as Task | Note | null;
|
||||
|
||||
const isRichTextV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsRichTextV2Enabled,
|
||||
);
|
||||
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
@ -67,13 +71,20 @@ export const ActivityRichTextEditor = ({
|
||||
activityObjectNameSingular: activityObjectNameSingular,
|
||||
});
|
||||
|
||||
const persistBodyDebounced = useDebouncedCallback((newBody: string) => {
|
||||
const persistBodyDebounced = useDebouncedCallback((blocknote: string) => {
|
||||
const input = isRichTextV2Enabled
|
||||
? {
|
||||
bodyV2: {
|
||||
blocknote,
|
||||
markdown: null,
|
||||
},
|
||||
}
|
||||
: { body: blocknote };
|
||||
|
||||
if (isDefined(activity)) {
|
||||
upsertActivity({
|
||||
activity,
|
||||
input: {
|
||||
body: newBody,
|
||||
},
|
||||
input,
|
||||
});
|
||||
}
|
||||
}, 300);
|
||||
@ -163,14 +174,18 @@ export const ActivityRichTextEditor = ({
|
||||
};
|
||||
|
||||
const initialBody = useMemo(() => {
|
||||
const blocknote = isRichTextV2Enabled
|
||||
? activity?.bodyV2?.blocknote
|
||||
: activity?.body;
|
||||
|
||||
if (
|
||||
isDefined(activity) &&
|
||||
isNonEmptyString(activity.body) &&
|
||||
activity?.body !== '{}'
|
||||
isNonEmptyString(blocknote) &&
|
||||
blocknote !== '{}'
|
||||
) {
|
||||
return JSON.parse(activity.body);
|
||||
return JSON.parse(blocknote);
|
||||
}
|
||||
}, [activity]);
|
||||
}, [activity, isRichTextV2Enabled]);
|
||||
|
||||
const handleEditorBuiltInUploadFile = async (file: File) => {
|
||||
const { attachmentAbsoluteURL } = await handleUploadAttachment(file);
|
||||
|
||||
@ -94,7 +94,11 @@ const task = {
|
||||
createdAt: '2023-04-26T10:12:42.33625+00:00',
|
||||
updatedAt: '2023-04-26T10:23:42.33625+00:00',
|
||||
title: 'Task title',
|
||||
body: null,
|
||||
body: '',
|
||||
bodyV2: {
|
||||
blocknote: null,
|
||||
markdown: null,
|
||||
},
|
||||
assigneeId: null,
|
||||
status: null,
|
||||
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 { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
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 }>`
|
||||
align-items: flex-start;
|
||||
@ -71,7 +73,12 @@ export const NoteCard = ({
|
||||
const openActivityRightDrawer = useOpenActivityRightDrawer({
|
||||
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({
|
||||
objectNameSingular: CoreObjectNameSingular.Note,
|
||||
|
||||
@ -16,6 +16,8 @@ import { ActivityRow } from '@/activities/components/ActivityRow';
|
||||
import { Task } from '@/activities/types/Task';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useFieldContext } from '@/object-record/hooks/useFieldContext';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
import { useCompleteTask } from '../hooks/useCompleteTask';
|
||||
|
||||
const StyledTaskBody = styled.div`
|
||||
@ -82,7 +84,14 @@ export const TaskRow = ({ task }: { task: 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 { FieldContextProvider: TaskTargetsContextProvider } = useFieldContext({
|
||||
|
||||
@ -11,6 +11,10 @@ const task: Task = {
|
||||
status: 'DONE',
|
||||
title: 'Test',
|
||||
body: 'Test',
|
||||
bodyV2: {
|
||||
blocknote: 'Test',
|
||||
markdown: 'Test',
|
||||
},
|
||||
dueAt: '2024-03-15T07:33:14.212Z',
|
||||
createdAt: '2024-03-15T07:33:14.212Z',
|
||||
updatedAt: '2024-03-15T07:33:14.212Z',
|
||||
|
||||
@ -4,4 +4,8 @@ export type Activity = {
|
||||
updatedAt: string;
|
||||
title: string;
|
||||
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 { useMultiObjectSearchQueryResultFormattedAsObjectRecordsMap } from '@/object-record/relation-picker/hooks/useMultiObjectSearchQueryResultFormattedAsObjectRecordsMap';
|
||||
import { makeOrFilterVariables } from '@/object-record/utils/makeOrFilterVariables';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import isEmpty from 'lodash.isempty';
|
||||
import { useMemo } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { Avatar, IconCheckbox, IconNotes } from 'twenty-ui';
|
||||
import { useDebounce } from 'use-debounce';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
import { getLogoUrlFromDomainName } from '~/utils';
|
||||
|
||||
const MAX_SEARCH_RESULTS_PER_OBJECT = 8;
|
||||
@ -23,6 +25,10 @@ const MAX_SEARCH_RESULTS_PER_OBJECT = 8;
|
||||
export const useSearchRecords = () => {
|
||||
const commandMenuSearch = useRecoilValue(commandMenuSearchState);
|
||||
|
||||
const isRichTextV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsRichTextV2Enabled,
|
||||
);
|
||||
|
||||
const [deferredCommandMenuSearch] = useDebounce(commandMenuSearch, 300);
|
||||
|
||||
const {
|
||||
@ -45,7 +51,13 @@ export const useSearchRecords = () => {
|
||||
filter: deferredCommandMenuSearch
|
||||
? makeOrFilterVariables([
|
||||
{ title: { ilike: `%${deferredCommandMenuSearch}%` } },
|
||||
{ body: { ilike: `%${deferredCommandMenuSearch}%` } },
|
||||
isRichTextV2Enabled
|
||||
? {
|
||||
bodyV2: {
|
||||
markdown: { ilike: `%${deferredCommandMenuSearch}%` },
|
||||
},
|
||||
}
|
||||
: { body: { ilike: `%${deferredCommandMenuSearch}%` } },
|
||||
])
|
||||
: undefined,
|
||||
limit: MAX_SEARCH_RESULTS_PER_OBJECT,
|
||||
@ -56,7 +68,13 @@ export const useSearchRecords = () => {
|
||||
filter: deferredCommandMenuSearch
|
||||
? makeOrFilterVariables([
|
||||
{ title: { ilike: `%${deferredCommandMenuSearch}%` } },
|
||||
{ body: { ilike: `%${deferredCommandMenuSearch}%` } },
|
||||
isRichTextV2Enabled
|
||||
? {
|
||||
bodyV2: {
|
||||
markdown: { ilike: `%${deferredCommandMenuSearch}%` },
|
||||
},
|
||||
}
|
||||
: { body: { ilike: `%${deferredCommandMenuSearch}%` } },
|
||||
])
|
||||
: undefined,
|
||||
limit: MAX_SEARCH_RESULTS_PER_OBJECT,
|
||||
|
||||
@ -163,5 +163,13 @@ ${mapObjectMetadataToGraphQLQuery({
|
||||
}`;
|
||||
}
|
||||
|
||||
if (fieldType === FieldMetadataType.RICH_TEXT_V2) {
|
||||
return `${field.name}
|
||||
{
|
||||
blocknote
|
||||
markdown
|
||||
}`;
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -7,6 +7,7 @@ import {
|
||||
FieldFullNameValue,
|
||||
FieldLinksValue,
|
||||
FieldPhonesValue,
|
||||
FieldRichTextV2Value,
|
||||
} from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { SettingsFieldTypeConfig } from '@/settings/data-model/constants/SettingsNonCompositeFieldTypeConfigs';
|
||||
import { CompositeFieldType } from '@/settings/data-model/types/CompositeFieldType';
|
||||
@ -18,6 +19,7 @@ import {
|
||||
IllustrationIconMap,
|
||||
IllustrationIconPhone,
|
||||
IllustrationIconSetting,
|
||||
IllustrationIconText,
|
||||
IllustrationIconUser,
|
||||
} from 'twenty-ui';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
@ -185,4 +187,19 @@ export const SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS = {
|
||||
context: { provider: ConnectedAccountProvider.GOOGLE },
|
||||
},
|
||||
} 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;
|
||||
|
||||
@ -6,7 +6,6 @@ import {
|
||||
IllustrationIconJson,
|
||||
IllustrationIconNumbers,
|
||||
IllustrationIconOneToMany,
|
||||
IllustrationIconSetting,
|
||||
IllustrationIconStar,
|
||||
IllustrationIconTag,
|
||||
IllustrationIconTags,
|
||||
@ -25,7 +24,6 @@ import {
|
||||
FieldNumberValue,
|
||||
FieldRatingValue,
|
||||
FieldRelationValue,
|
||||
FieldRichTextValue,
|
||||
FieldSelectValue,
|
||||
FieldTextValue,
|
||||
FieldUUidValue,
|
||||
@ -122,12 +120,6 @@ export const SETTINGS_NON_COMPOSITE_FIELD_TYPE_CONFIGS: SettingsNonCompositeFiel
|
||||
exampleValue: { key: 'value' },
|
||||
category: 'Advanced',
|
||||
} 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]: {
|
||||
label: 'Array',
|
||||
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 { useCurrencySettingsFormInitialValues } from '@/settings/data-model/fields/forms/currency/hooks/useCurrencySettingsFormInitialValues';
|
||||
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 { SettingsPath } from '@/types/SettingsPath';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
@ -22,7 +23,7 @@ import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||
|
||||
type SettingsObjectNewFieldSelectorProps = {
|
||||
className?: string;
|
||||
excludedFieldTypes?: SettingsFieldType[];
|
||||
excludedFieldTypes?: FieldType[];
|
||||
fieldMetadataItem?: Pick<
|
||||
FieldMetadataItem,
|
||||
'defaultValue' | 'options' | 'type'
|
||||
|
||||
@ -10,6 +10,7 @@ export const COMPOSITE_FIELD_TYPES = [
|
||||
'PHONES',
|
||||
'FULL_NAME',
|
||||
'ACTOR',
|
||||
'RICH_TEXT_V2',
|
||||
] as const;
|
||||
|
||||
type CompositeFieldTypeBaseLiteral = (typeof COMPOSITE_FIELD_TYPES)[number];
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import { CompositeFieldType } from '@/settings/data-model/types/CompositeFieldType';
|
||||
import { SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType';
|
||||
import { PickLiteral } from '~/types/PickLiteral';
|
||||
|
||||
export type SettingsCompositeFieldType = PickLiteral<
|
||||
export type SettingsCompositeFieldType = Extract<
|
||||
SettingsFieldType,
|
||||
CompositeFieldType
|
||||
>;
|
||||
|
||||
@ -3,5 +3,5 @@ import { PickLiteral } from '~/types/PickLiteral';
|
||||
|
||||
export type SettingsExcludedFieldType = PickLiteral<
|
||||
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 { SettingsExcludedFieldType } from '@/settings/data-model/types/SettingsExcludedFieldType';
|
||||
import { ExcludeLiteral } from '~/types/ExcludeLiteral';
|
||||
import { SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType';
|
||||
|
||||
export type SettingsNonCompositeFieldType = ExcludeLiteral<
|
||||
export type SettingsNonCompositeFieldType = Extract<
|
||||
NonCompositeFieldType,
|
||||
SettingsExcludedFieldType
|
||||
SettingsFieldType
|
||||
>;
|
||||
|
||||
@ -4,6 +4,7 @@ import {
|
||||
Fields,
|
||||
SpreadsheetImportDialogOptions,
|
||||
} from '@/spreadsheet-import/types';
|
||||
import { FieldMetadataType } from 'twenty-shared';
|
||||
import { sleep } from '~/utils/sleep';
|
||||
|
||||
const fields = [
|
||||
@ -22,6 +23,7 @@ const fields = [
|
||||
errorMessage: 'Name is required',
|
||||
},
|
||||
],
|
||||
fieldMetadataType: FieldMetadataType.TEXT,
|
||||
},
|
||||
{
|
||||
icon: null,
|
||||
|
||||
@ -4,6 +4,9 @@ import { IconForbid } from 'twenty-ui';
|
||||
import { MatchColumnSelect } from '@/spreadsheet-import/components/MatchColumnSelect';
|
||||
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';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
@ -24,26 +27,35 @@ export const TemplateColumn = <T extends string>({
|
||||
columnIndex,
|
||||
onChange,
|
||||
}: TemplateColumnProps<T>) => {
|
||||
const isRichTextV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsRichTextV2Enabled,
|
||||
);
|
||||
const { fields } = useSpreadsheetImportInternal<T>();
|
||||
const column = columns[columnIndex];
|
||||
const isIgnored = column.type === ColumnType.ignored;
|
||||
|
||||
const fieldOptions = fields.map(({ icon, label, key }) => {
|
||||
const isSelected =
|
||||
columns.findIndex((column) => {
|
||||
if ('value' in column) {
|
||||
return column.value === key;
|
||||
}
|
||||
return false;
|
||||
}) !== -1;
|
||||
const fieldOptions = fields
|
||||
.filter((field) =>
|
||||
isRichTextV2Enabled
|
||||
? field.fieldMetadataType !== FieldMetadataType.RICH_TEXT
|
||||
: true,
|
||||
)
|
||||
.map(({ icon, label, key }) => {
|
||||
const isSelected =
|
||||
columns.findIndex((column) => {
|
||||
if ('value' in column) {
|
||||
return column.value === key;
|
||||
}
|
||||
return false;
|
||||
}) !== -1;
|
||||
|
||||
return {
|
||||
icon: icon,
|
||||
value: key,
|
||||
label: label,
|
||||
disabled: isSelected,
|
||||
} as const;
|
||||
});
|
||||
return {
|
||||
icon: icon,
|
||||
value: key,
|
||||
label: label,
|
||||
disabled: isSelected,
|
||||
} as const;
|
||||
});
|
||||
|
||||
const selectOptions = [
|
||||
{
|
||||
|
||||
@ -4,6 +4,7 @@ import { ReadonlyDeep } from 'type-fest';
|
||||
import { Columns } from '@/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep';
|
||||
import { ImportedStructuredRowMetadata } from '@/spreadsheet-import/steps/components/ValidationStep/types';
|
||||
import { SpreadsheetImportStep } from '@/spreadsheet-import/steps/types/SpreadsheetImportStep';
|
||||
import { FieldMetadataType } from 'twenty-shared';
|
||||
|
||||
export type SpreadsheetImportDialogOptions<FieldNames extends string> = {
|
||||
// Is modal visible.
|
||||
@ -122,6 +123,8 @@ export type Field<T extends string> = {
|
||||
fieldValidationDefinitions?: FieldValidationDefinition[];
|
||||
// Field entry component, default: Input
|
||||
fieldType: SpreadsheetImportFieldType;
|
||||
// Field metadata type
|
||||
fieldMetadataType: FieldMetadataType;
|
||||
// UI-facing values shown to user as field examples pre-upload phase
|
||||
example?: string;
|
||||
};
|
||||
|
||||
@ -6,6 +6,7 @@ import {
|
||||
TableHook,
|
||||
} from '@/spreadsheet-import/types';
|
||||
import { addErrorsAndRunHooks } from '@/spreadsheet-import/utils/dataMutations';
|
||||
import { FieldMetadataType } from 'twenty-shared';
|
||||
|
||||
describe('addErrorsAndRunHooks', () => {
|
||||
type FullData = ImportedStructuredRow<'name' | 'age' | 'country'>;
|
||||
@ -15,6 +16,7 @@ describe('addErrorsAndRunHooks', () => {
|
||||
fieldValidationDefinitions: [{ rule: 'required' }],
|
||||
icon: null,
|
||||
fieldType: { type: 'input' },
|
||||
fieldMetadataType: FieldMetadataType.TEXT,
|
||||
};
|
||||
|
||||
const regexField: Field<'age'> = {
|
||||
@ -25,6 +27,7 @@ describe('addErrorsAndRunHooks', () => {
|
||||
],
|
||||
icon: null,
|
||||
fieldType: { type: 'input' },
|
||||
fieldMetadataType: FieldMetadataType.NUMBER,
|
||||
};
|
||||
|
||||
const uniqueField: Field<'country'> = {
|
||||
@ -33,6 +36,7 @@ describe('addErrorsAndRunHooks', () => {
|
||||
fieldValidationDefinitions: [{ rule: 'unique' }],
|
||||
icon: null,
|
||||
fieldType: { type: 'input' },
|
||||
fieldMetadataType: FieldMetadataType.SELECT,
|
||||
};
|
||||
|
||||
const functionValidationFieldTrue: Field<'email'> = {
|
||||
@ -47,6 +51,7 @@ describe('addErrorsAndRunHooks', () => {
|
||||
],
|
||||
icon: null,
|
||||
fieldType: { type: 'input' },
|
||||
fieldMetadataType: FieldMetadataType.EMAILS,
|
||||
};
|
||||
|
||||
const functionValidationFieldFalse: Field<'email'> = {
|
||||
@ -61,6 +66,7 @@ describe('addErrorsAndRunHooks', () => {
|
||||
],
|
||||
icon: null,
|
||||
fieldType: { type: 'input' },
|
||||
fieldMetadataType: FieldMetadataType.EMAILS,
|
||||
};
|
||||
|
||||
const validData: ImportedStructuredRow<'name' | 'age'> = {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Field } from '@/spreadsheet-import/types';
|
||||
import { findMatch } from '@/spreadsheet-import/utils/findMatch';
|
||||
|
||||
import { FieldMetadataType } from 'twenty-shared';
|
||||
describe('findMatch', () => {
|
||||
const defaultField: Field<'defaultField'> = {
|
||||
key: 'defaultField',
|
||||
@ -9,6 +9,7 @@ describe('findMatch', () => {
|
||||
fieldType: {
|
||||
type: 'input',
|
||||
},
|
||||
fieldMetadataType: FieldMetadataType.TEXT,
|
||||
alternateMatches: ['Full Name', 'First Name'],
|
||||
};
|
||||
|
||||
@ -19,6 +20,7 @@ describe('findMatch', () => {
|
||||
fieldType: {
|
||||
type: 'input',
|
||||
},
|
||||
fieldMetadataType: FieldMetadataType.TEXT,
|
||||
};
|
||||
|
||||
const fields = [defaultField, secondaryField];
|
||||
|
||||
@ -4,6 +4,7 @@ import {
|
||||
} from '@/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep';
|
||||
import { Field, FieldValidationDefinition } from '@/spreadsheet-import/types';
|
||||
import { findUnmatchedRequiredFields } from '@/spreadsheet-import/utils/findUnmatchedRequiredFields';
|
||||
import { FieldMetadataType } from 'twenty-shared';
|
||||
|
||||
const nameField: Field<'Name'> = {
|
||||
key: 'Name',
|
||||
@ -12,6 +13,7 @@ const nameField: Field<'Name'> = {
|
||||
fieldType: {
|
||||
type: 'input',
|
||||
},
|
||||
fieldMetadataType: FieldMetadataType.TEXT,
|
||||
};
|
||||
|
||||
const ageField: Field<'Age'> = {
|
||||
@ -21,7 +23,9 @@ const ageField: Field<'Age'> = {
|
||||
fieldType: {
|
||||
type: 'input',
|
||||
},
|
||||
fieldMetadataType: FieldMetadataType.NUMBER,
|
||||
};
|
||||
|
||||
const validations: FieldValidationDefinition[] = [{ rule: 'required' }];
|
||||
const nameFieldWithValidations: Field<'Name'> = {
|
||||
...nameField,
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { Field } from '@/spreadsheet-import/types';
|
||||
import { generateExampleRow } from '@/spreadsheet-import/utils/generateExampleRow';
|
||||
import { FieldMetadataType } from 'twenty-shared';
|
||||
|
||||
describe('generateExampleRow', () => {
|
||||
const defaultField: Field<'defaultField'> = {
|
||||
@ -9,6 +10,7 @@ describe('generateExampleRow', () => {
|
||||
fieldType: {
|
||||
type: 'input',
|
||||
},
|
||||
fieldMetadataType: FieldMetadataType.TEXT,
|
||||
};
|
||||
|
||||
it('should generate an example row from input field type', () => {
|
||||
@ -24,6 +26,7 @@ describe('generateExampleRow', () => {
|
||||
{
|
||||
...defaultField,
|
||||
fieldType: { type: 'checkbox' },
|
||||
fieldMetadataType: FieldMetadataType.BOOLEAN,
|
||||
},
|
||||
];
|
||||
|
||||
@ -37,6 +40,7 @@ describe('generateExampleRow', () => {
|
||||
{
|
||||
...defaultField,
|
||||
fieldType: { type: 'select', options: [] },
|
||||
fieldMetadataType: FieldMetadataType.SELECT,
|
||||
},
|
||||
];
|
||||
|
||||
@ -50,6 +54,7 @@ describe('generateExampleRow', () => {
|
||||
{
|
||||
...defaultField,
|
||||
example: 'Example',
|
||||
fieldMetadataType: FieldMetadataType.TEXT,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { Field } from '@/spreadsheet-import/types';
|
||||
import { getFieldOptions } from '@/spreadsheet-import/utils/getFieldOptions';
|
||||
import { FieldMetadataType } from 'twenty-shared';
|
||||
|
||||
describe('getFieldOptions', () => {
|
||||
const optionsArray = [
|
||||
@ -25,6 +26,7 @@ describe('getFieldOptions', () => {
|
||||
type: 'select',
|
||||
options: optionsArray,
|
||||
},
|
||||
fieldMetadataType: FieldMetadataType.SELECT,
|
||||
},
|
||||
{
|
||||
key: 'Name',
|
||||
@ -33,6 +35,7 @@ describe('getFieldOptions', () => {
|
||||
fieldType: {
|
||||
type: 'input',
|
||||
},
|
||||
fieldMetadataType: FieldMetadataType.TEXT,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ import {
|
||||
} from '@/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep';
|
||||
import { Field } from '@/spreadsheet-import/types';
|
||||
import { getMatchedColumns } from '@/spreadsheet-import/utils/getMatchedColumns';
|
||||
import { FieldMetadataType } from 'twenty-shared';
|
||||
|
||||
describe('getMatchedColumns', () => {
|
||||
const columns: Column<string>[] = [
|
||||
@ -27,15 +28,23 @@ describe('getMatchedColumns', () => {
|
||||
key: 'Name',
|
||||
label: 'Name',
|
||||
fieldType: { type: 'input' },
|
||||
fieldMetadataType: FieldMetadataType.TEXT,
|
||||
icon: null,
|
||||
},
|
||||
{
|
||||
key: 'Location',
|
||||
label: 'Location',
|
||||
fieldType: { type: 'select', options: [] },
|
||||
fieldMetadataType: FieldMetadataType.POSITION,
|
||||
icon: null,
|
||||
},
|
||||
{
|
||||
key: 'Age',
|
||||
label: 'Age',
|
||||
fieldType: { type: 'input' },
|
||||
fieldMetadataType: FieldMetadataType.NUMBER,
|
||||
icon: null,
|
||||
},
|
||||
{ key: 'Age', label: 'Age', fieldType: { type: 'input' }, icon: null },
|
||||
];
|
||||
|
||||
const data = [
|
||||
@ -110,12 +119,14 @@ describe('getMatchedColumns', () => {
|
||||
key: 'Hobby',
|
||||
label: 'Hobby',
|
||||
fieldType: { type: 'input' },
|
||||
fieldMetadataType: FieldMetadataType.TEXT,
|
||||
icon: null,
|
||||
},
|
||||
{
|
||||
key: 'Interest',
|
||||
label: 'Interest',
|
||||
fieldType: { type: 'input' },
|
||||
fieldMetadataType: FieldMetadataType.TEXT,
|
||||
icon: null,
|
||||
},
|
||||
];
|
||||
|
||||
@ -4,6 +4,7 @@ import {
|
||||
} from '@/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep';
|
||||
import { Field } from '@/spreadsheet-import/types';
|
||||
import { normalizeTableData } from '@/spreadsheet-import/utils/normalizeTableData';
|
||||
import { FieldMetadataType } from 'twenty-shared';
|
||||
|
||||
describe('normalizeTableData', () => {
|
||||
const columns: Column<string>[] = [
|
||||
@ -18,14 +19,27 @@ describe('normalizeTableData', () => {
|
||||
];
|
||||
|
||||
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',
|
||||
label: 'Active',
|
||||
fieldType: {
|
||||
type: 'checkbox',
|
||||
},
|
||||
fieldMetadataType: FieldMetadataType.BOOLEAN,
|
||||
icon: null,
|
||||
},
|
||||
];
|
||||
@ -64,6 +78,7 @@ describe('normalizeTableData', () => {
|
||||
type: 'checkbox',
|
||||
booleanMatches: { yes: true, no: false },
|
||||
},
|
||||
fieldMetadataType: FieldMetadataType.BOOLEAN,
|
||||
icon: null,
|
||||
},
|
||||
];
|
||||
@ -100,6 +115,7 @@ describe('normalizeTableData', () => {
|
||||
{ label: 'Two', value: '2' },
|
||||
],
|
||||
},
|
||||
fieldMetadataType: FieldMetadataType.SELECT,
|
||||
icon: null,
|
||||
},
|
||||
];
|
||||
|
||||
@ -4,6 +4,7 @@ import {
|
||||
} from '@/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep';
|
||||
import { Field } from '@/spreadsheet-import/types';
|
||||
import { setColumn } from '@/spreadsheet-import/utils/setColumn';
|
||||
import { FieldMetadataType } from 'twenty-shared';
|
||||
|
||||
describe('setColumn', () => {
|
||||
const defaultField: Field<'Name'> = {
|
||||
@ -11,6 +12,7 @@ describe('setColumn', () => {
|
||||
label: 'label',
|
||||
key: 'Name',
|
||||
fieldType: { type: 'input' },
|
||||
fieldMetadataType: FieldMetadataType.TEXT,
|
||||
};
|
||||
|
||||
const oldColumn: Column<'oldValue'> = {
|
||||
|
||||
@ -2,14 +2,14 @@ import { PartialBlock } from '@blocknote/core';
|
||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||
|
||||
export const getFirstNonEmptyLineOfRichText = (
|
||||
fieldValue: PartialBlock[] | null,
|
||||
blocks: PartialBlock[] | null,
|
||||
): string => {
|
||||
if (fieldValue === null) {
|
||||
if (blocks === null) {
|
||||
return '';
|
||||
}
|
||||
for (const node of fieldValue) {
|
||||
if (!isUndefinedOrNull(node.content)) {
|
||||
const contentArray = node.content as Array<
|
||||
for (const block of blocks) {
|
||||
if (!isUndefinedOrNull(block.content)) {
|
||||
const contentArray = block.content as Array<
|
||||
{ text: string } | { link: string }
|
||||
>;
|
||||
if (contentArray.length > 0) {
|
||||
|
||||
@ -4,6 +4,7 @@ import { SettingsPageContainer } from '@/settings/components/SettingsPageContain
|
||||
import { SettingsDataModelNewFieldBreadcrumbDropDown } from '@/settings/data-model/components/SettingsDataModelNewFieldBreadcrumbDropDown';
|
||||
import { SETTINGS_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsFieldTypeConfigs';
|
||||
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 { AppPath } from '@/types/AppPath';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
@ -44,10 +45,11 @@ export const SettingsObjectNewFieldSelect = () => {
|
||||
type: FieldMetadataType.TEXT,
|
||||
},
|
||||
});
|
||||
const excludedFieldTypes: SettingsFieldType[] = (
|
||||
const excludedFieldTypes: FieldType[] = (
|
||||
[
|
||||
FieldMetadataType.NUMERIC,
|
||||
FieldMetadataType.RICH_TEXT,
|
||||
FieldMetadataType.RICH_TEXT_V2,
|
||||
FieldMetadataType.ACTOR,
|
||||
] as const
|
||||
).filter(isDefined);
|
||||
|
||||
@ -10,6 +10,10 @@ export const mockedNotes: Array<MockedNote> = [
|
||||
updatedAt: '2023-04-26T10:23:42.33625+00:00',
|
||||
title: 'My very first note',
|
||||
body: null,
|
||||
bodyV2: {
|
||||
blocknote: null,
|
||||
markdown: null,
|
||||
},
|
||||
noteTargets: [
|
||||
{
|
||||
id: '89bb825c-171e-4bcc-9cf7-43448d6fb300',
|
||||
@ -65,6 +69,10 @@ export const mockedNotes: Array<MockedNote> = [
|
||||
updatedAt: new Date().toISOString(),
|
||||
title: 'Another note',
|
||||
body: null,
|
||||
bodyV2: {
|
||||
blocknote: null,
|
||||
markdown: null,
|
||||
},
|
||||
noteTargets: [
|
||||
{
|
||||
id: '89bb825c-171e-4bcc-9cf7-43448d6fb278t',
|
||||
|
||||
@ -27,6 +27,10 @@ export const mockedTasks: Array<MockedTask> = [
|
||||
updatedAt: '2023-04-26T10:23:42.33625+00:00',
|
||||
title: 'My very first note',
|
||||
body: null,
|
||||
bodyV2: {
|
||||
blocknote: null,
|
||||
markdown: null,
|
||||
},
|
||||
dueAt: '2023-04-26T10:12:42.33625+00:00',
|
||||
status: null,
|
||||
assignee: workspaceMember,
|
||||
|
||||
@ -35,7 +35,20 @@ export class ActivityQueryResultGetterHandler
|
||||
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(
|
||||
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