From 2f36c2e82ee3d8cfd84eb556362d5c08ba860f7f Mon Sep 17 00:00:00 2001
From: nitin <142569587+ehconitin@users.noreply.github.com>
Date: Thu, 4 Dec 2025 18:28:33 +0530
Subject: [PATCH] [Dashboards] Server: Rich Text Widget (#16296)
closes https://github.com/twentyhq/core-team-issues/issues/1893
sync in the transformation layer -
with just the blocknote format -
with just the mardown format -
attaching response since in screen shot its getting cut -
```
{
"id": "7189a10a-177c-4cc2-8946-950b8c125913",
"pageLayoutTabId": "82ac56dd-4351-43ad-bf3d-49624c057e17",
"workspaceId": "20202020-1c25-4d02-bf25-6aeccf7ea419",
"title": "awdawd awdawdawd",
"type": "STANDALONE_RICH_TEXT",
"objectMetadataId": null,
"gridPosition": {
"row": 1,
"column": 1,
"rowSpan": 1,
"columnSpan": 1
},
"configuration": {
"body": {
"markdown": "# Hello World",
"blocknote": "[{\"id\":\"2e744f21-7c71-414d-b87f-fb48400c593c\",\"type\":\"heading\",\"props\":{\"textColor\":\"default\",\"backgroundColor\":\"default\",\"textAlignment\":\"left\",\"level\":1},\"content\":[{\"type\":\"text\",\"text\":\"Hello World\",\"styles\":{}}],\"children\":[]}]"
}
},
"createdAt": "2025-12-04T11:14:52.212Z",
"updatedAt": "2025-12-04T11:14:52.212Z",
"deletedAt": null
}
```
---
.../src/generated-metadata/graphql.ts | 16 +-
.../twenty-front/src/generated/graphql.ts | 26 ++-
.../record-show/layouts/note-record-layout.ts | 2 +-
.../record-show/layouts/task-record-layout.ts | 2 +-
.../record-show/types/CardConfiguration.ts | 2 +-
.../record-show/types/CardType.ts | 2 +-
.../record-show/utils/getCardComponent.tsx | 6 +-
.../constants/DefaultNoteRecordPageLayout.ts | 4 +-
.../constants/DefaultTaskRecordPageLayout.ts | 4 +-
.../fragments/pageLayoutWidgetFragment.ts | 6 +
.../components/WidgetContentRenderer.tsx | 6 +-
.../components/FieldRichTextWidget.tsx} | 10 +-
...RichTextCard.tsx => FieldRichTextCard.tsx} | 2 +-
...ToFieldRichTextAndAddStandaloneRichText.ts | 47 ++++
.../page-layout/dtos/rich-text-v2-body.dto.ts | 17 ++
.../standalone-rich-text-configuration.dto.ts | 15 ++
.../dtos/widget-configuration.interface.ts | 12 +-
.../enums/widget-configuration-type.enum.ts | 1 +
.../page-layout/enums/widget-type.enum.ts | 3 +-
.../services/page-layout-widget.service.ts | 4 +-
...ransform-widget-configuration.util.spec.ts | 204 ++++++++++++------
...widget-configuration-discriminator.util.ts | 11 +-
...and-transform-widget-configuration.util.ts | 32 ++-
...idget-configuration-test-data.constants.ts | 31 +++
24 files changed, 366 insertions(+), 99 deletions(-)
rename packages/twenty-front/src/modules/page-layout/widgets/{rich-text/components/RichTextWidget.tsx => field-rich-text/components/FieldRichTextWidget.tsx} (54%)
rename packages/twenty-front/src/modules/ui/layout/show-page/components/{RichTextCard.tsx => FieldRichTextCard.tsx} (98%)
create mode 100644 packages/twenty-server/src/database/typeorm/core/migrations/common/1764846384501-renameRichTextToFieldRichTextAndAddStandaloneRichText.ts
create mode 100644 packages/twenty-server/src/engine/core-modules/page-layout/dtos/rich-text-v2-body.dto.ts
create mode 100644 packages/twenty-server/src/engine/core-modules/page-layout/dtos/standalone-rich-text-configuration.dto.ts
diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts
index d9a0383d62..25d12b57fa 100644
--- a/packages/twenty-front/src/generated-metadata/graphql.ts
+++ b/packages/twenty-front/src/generated-metadata/graphql.ts
@@ -3768,6 +3768,12 @@ export type RevokeApiKeyInput = {
id: Scalars['UUID'];
};
+export type RichTextV2Body = {
+ __typename?: 'RichTextV2Body';
+ blocknote?: Maybe;
+ markdown?: Maybe;
+};
+
export type Role = {
__typename?: 'Role';
agents: Array;
@@ -3977,6 +3983,11 @@ export type SignedFile = {
token: Scalars['String'];
};
+export type StandaloneRichTextConfiguration = {
+ __typename?: 'StandaloneRichTextConfiguration';
+ body: RichTextV2Body;
+};
+
export type StandardOverrides = {
__typename?: 'StandardOverrides';
description?: Maybe;
@@ -4702,17 +4713,18 @@ export type Webhook = {
workspaceId: Scalars['UUID'];
};
-export type WidgetConfiguration = AggregateChartConfiguration | BarChartConfiguration | GaugeChartConfiguration | IframeConfiguration | LineChartConfiguration | PieChartConfiguration;
+export type WidgetConfiguration = AggregateChartConfiguration | BarChartConfiguration | GaugeChartConfiguration | IframeConfiguration | LineChartConfiguration | PieChartConfiguration | StandaloneRichTextConfiguration;
export enum WidgetType {
CALENDAR = 'CALENDAR',
EMAILS = 'EMAILS',
FIELDS = 'FIELDS',
+ FIELD_RICH_TEXT = 'FIELD_RICH_TEXT',
FILES = 'FILES',
GRAPH = 'GRAPH',
IFRAME = 'IFRAME',
NOTES = 'NOTES',
- RICH_TEXT = 'RICH_TEXT',
+ STANDALONE_RICH_TEXT = 'STANDALONE_RICH_TEXT',
TASKS = 'TASKS',
TIMELINE = 'TIMELINE',
VIEW = 'VIEW',
diff --git a/packages/twenty-front/src/generated/graphql.ts b/packages/twenty-front/src/generated/graphql.ts
index 1d076b5b19..32d76a6937 100644
--- a/packages/twenty-front/src/generated/graphql.ts
+++ b/packages/twenty-front/src/generated/graphql.ts
@@ -3605,6 +3605,12 @@ export type RevokeApiKeyInput = {
id: Scalars['UUID'];
};
+export type RichTextV2Body = {
+ __typename?: 'RichTextV2Body';
+ blocknote?: Maybe;
+ markdown?: Maybe;
+};
+
export type Role = {
__typename?: 'Role';
agents: Array;
@@ -3814,6 +3820,11 @@ export type SignedFile = {
token: Scalars['String'];
};
+export type StandaloneRichTextConfiguration = {
+ __typename?: 'StandaloneRichTextConfiguration';
+ body: RichTextV2Body;
+};
+
export type StandardOverrides = {
__typename?: 'StandardOverrides';
description?: Maybe;
@@ -4521,17 +4532,18 @@ export type Webhook = {
workspaceId: Scalars['UUID'];
};
-export type WidgetConfiguration = AggregateChartConfiguration | BarChartConfiguration | GaugeChartConfiguration | IframeConfiguration | LineChartConfiguration | PieChartConfiguration;
+export type WidgetConfiguration = AggregateChartConfiguration | BarChartConfiguration | GaugeChartConfiguration | IframeConfiguration | LineChartConfiguration | PieChartConfiguration | StandaloneRichTextConfiguration;
export enum WidgetType {
CALENDAR = 'CALENDAR',
EMAILS = 'EMAILS',
FIELDS = 'FIELDS',
+ FIELD_RICH_TEXT = 'FIELD_RICH_TEXT',
FILES = 'FILES',
GRAPH = 'GRAPH',
IFRAME = 'IFRAME',
NOTES = 'NOTES',
- RICH_TEXT = 'RICH_TEXT',
+ STANDALONE_RICH_TEXT = 'STANDALONE_RICH_TEXT',
TASKS = 'TASKS',
TIMELINE = 'TIMELINE',
VIEW = 'VIEW',
@@ -4792,7 +4804,7 @@ export type SearchQueryVariables = Exact<{
export type SearchQuery = { __typename?: 'Query', search: { __typename?: 'SearchResultConnection', edges: Array<{ __typename?: 'SearchResultEdge', cursor: string, node: { __typename?: 'SearchRecord', recordId: any, objectNameSingular: string, label: string, imageUrl?: string | null, tsRankCD: number, tsRank: number } }>, pageInfo: { __typename?: 'SearchResultPageInfo', hasNextPage: boolean, endCursor?: string | null } } };
-export type PageLayoutWidgetFragmentFragment = { __typename?: 'PageLayoutWidget', id: any, title: string, type: WidgetType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, pageLayoutTabId: any, gridPosition: { __typename?: 'GridPosition', column: number, columnSpan: number, row: number, rowSpan: number }, configuration?: { __typename?: 'AggregateChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, label?: string | null, displayDataLabel?: boolean | null, format?: string | null, description?: string | null, filter?: any | null, prefix?: string | null, suffix?: string | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'BarChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, groupMode?: BarChartGroupMode | null, isCumulative?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'GaugeChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'IframeConfiguration', url?: string | null } | { __typename?: 'LineChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, isStacked?: boolean | null, isCumulative?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'PieChartConfiguration', graphType: GraphType, groupByFieldMetadataId: any, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, groupBySubFieldName?: string | null, dateGranularity?: ObjectRecordGroupByDateGranularity | null, orderBy?: GraphOrderBy | null, displayDataLabel?: boolean | null, showCenterMetric?: boolean | null, displayLegend?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | null };
+export type PageLayoutWidgetFragmentFragment = { __typename?: 'PageLayoutWidget', id: any, title: string, type: WidgetType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, pageLayoutTabId: any, gridPosition: { __typename?: 'GridPosition', column: number, columnSpan: number, row: number, rowSpan: number }, configuration?: { __typename?: 'AggregateChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, label?: string | null, displayDataLabel?: boolean | null, format?: string | null, description?: string | null, filter?: any | null, prefix?: string | null, suffix?: string | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'BarChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, groupMode?: BarChartGroupMode | null, isCumulative?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'GaugeChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'IframeConfiguration', url?: string | null } | { __typename?: 'LineChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, isStacked?: boolean | null, isCumulative?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'PieChartConfiguration', graphType: GraphType, groupByFieldMetadataId: any, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, groupBySubFieldName?: string | null, dateGranularity?: ObjectRecordGroupByDateGranularity | null, orderBy?: GraphOrderBy | null, displayDataLabel?: boolean | null, showCenterMetric?: boolean | null, displayLegend?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'StandaloneRichTextConfiguration', body: { __typename?: 'RichTextV2Body', blocknote?: string | null, markdown?: string | null } } | null };
export type UpdatePageLayoutWithTabsAndWidgetsMutationVariables = Exact<{
id: Scalars['String'];
@@ -4800,7 +4812,7 @@ export type UpdatePageLayoutWithTabsAndWidgetsMutationVariables = Exact<{
}>;
-export type UpdatePageLayoutWithTabsAndWidgetsMutation = { __typename?: 'Mutation', updatePageLayoutWithTabsAndWidgets: { __typename?: 'PageLayout', id: any, name: string, type: PageLayoutType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, tabs?: Array<{ __typename?: 'PageLayoutTab', id: any, title: string, position: number, pageLayoutId: any, createdAt: string, updatedAt: string, widgets?: Array<{ __typename?: 'PageLayoutWidget', id: any, title: string, type: WidgetType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, pageLayoutTabId: any, gridPosition: { __typename?: 'GridPosition', column: number, columnSpan: number, row: number, rowSpan: number }, configuration?: { __typename?: 'AggregateChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, label?: string | null, displayDataLabel?: boolean | null, format?: string | null, description?: string | null, filter?: any | null, prefix?: string | null, suffix?: string | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'BarChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, groupMode?: BarChartGroupMode | null, isCumulative?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'GaugeChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'IframeConfiguration', url?: string | null } | { __typename?: 'LineChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, isStacked?: boolean | null, isCumulative?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'PieChartConfiguration', graphType: GraphType, groupByFieldMetadataId: any, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, groupBySubFieldName?: string | null, dateGranularity?: ObjectRecordGroupByDateGranularity | null, orderBy?: GraphOrderBy | null, displayDataLabel?: boolean | null, showCenterMetric?: boolean | null, displayLegend?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | null }> | null }> | null } };
+export type UpdatePageLayoutWithTabsAndWidgetsMutation = { __typename?: 'Mutation', updatePageLayoutWithTabsAndWidgets: { __typename?: 'PageLayout', id: any, name: string, type: PageLayoutType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, tabs?: Array<{ __typename?: 'PageLayoutTab', id: any, title: string, position: number, pageLayoutId: any, createdAt: string, updatedAt: string, widgets?: Array<{ __typename?: 'PageLayoutWidget', id: any, title: string, type: WidgetType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, pageLayoutTabId: any, gridPosition: { __typename?: 'GridPosition', column: number, columnSpan: number, row: number, rowSpan: number }, configuration?: { __typename?: 'AggregateChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, label?: string | null, displayDataLabel?: boolean | null, format?: string | null, description?: string | null, filter?: any | null, prefix?: string | null, suffix?: string | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'BarChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, groupMode?: BarChartGroupMode | null, isCumulative?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'GaugeChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'IframeConfiguration', url?: string | null } | { __typename?: 'LineChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, isStacked?: boolean | null, isCumulative?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'PieChartConfiguration', graphType: GraphType, groupByFieldMetadataId: any, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, groupBySubFieldName?: string | null, dateGranularity?: ObjectRecordGroupByDateGranularity | null, orderBy?: GraphOrderBy | null, displayDataLabel?: boolean | null, showCenterMetric?: boolean | null, displayLegend?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'StandaloneRichTextConfiguration', body: { __typename?: 'RichTextV2Body', blocknote?: string | null, markdown?: string | null } } | null }> | null }> | null } };
export type OnDbEventSubscriptionVariables = Exact<{
input: OnDbEventInput;
@@ -5208,6 +5220,12 @@ export const PageLayoutWidgetFragmentFragmentDoc = gql`
... on IframeConfiguration {
url
}
+ ... on StandaloneRichTextConfiguration {
+ body {
+ blocknote
+ markdown
+ }
+ }
}
pageLayoutTabId
}
diff --git a/packages/twenty-front/src/modules/object-record/record-show/layouts/note-record-layout.ts b/packages/twenty-front/src/modules/object-record/record-show/layouts/note-record-layout.ts
index fde98f3a9d..e5e7f424d9 100644
--- a/packages/twenty-front/src/modules/object-record/record-show/layouts/note-record-layout.ts
+++ b/packages/twenty-front/src/modules/object-record/record-show/layouts/note-record-layout.ts
@@ -7,7 +7,7 @@ export const NOTE_RECORD_LAYOUT: RecordLayout = {
title: 'Note',
position: 101,
icon: 'IconNotes',
- cards: [{ type: CardType.RichTextCard }],
+ cards: [{ type: CardType.FieldRichTextCard }],
hide: {
ifMobile: false,
ifDesktop: false,
diff --git a/packages/twenty-front/src/modules/object-record/record-show/layouts/task-record-layout.ts b/packages/twenty-front/src/modules/object-record/record-show/layouts/task-record-layout.ts
index 3c0579b934..38f2a99421 100644
--- a/packages/twenty-front/src/modules/object-record/record-show/layouts/task-record-layout.ts
+++ b/packages/twenty-front/src/modules/object-record/record-show/layouts/task-record-layout.ts
@@ -7,7 +7,7 @@ export const TASK_RECORD_LAYOUT: RecordLayout = {
title: 'Note',
position: 101,
icon: 'IconNotes',
- cards: [{ type: CardType.RichTextCard }],
+ cards: [{ type: CardType.FieldRichTextCard }],
hide: {
ifMobile: false,
ifDesktop: false,
diff --git a/packages/twenty-front/src/modules/object-record/record-show/types/CardConfiguration.ts b/packages/twenty-front/src/modules/object-record/record-show/types/CardConfiguration.ts
index a61fde86ee..ad5655f789 100644
--- a/packages/twenty-front/src/modules/object-record/record-show/types/CardConfiguration.ts
+++ b/packages/twenty-front/src/modules/object-record/record-show/types/CardConfiguration.ts
@@ -18,7 +18,7 @@ export type CardTypeToConfiguration = {
[CardType.FileCard]: EmptyCardConfiguration;
[CardType.EmailCard]: EmptyCardConfiguration;
[CardType.CalendarCard]: EmptyCardConfiguration;
- [CardType.RichTextCard]: EmptyCardConfiguration;
+ [CardType.FieldRichTextCard]: EmptyCardConfiguration;
[CardType.WorkflowCard]: EmptyCardConfiguration;
[CardType.WorkflowVersionCard]: EmptyCardConfiguration;
[CardType.WorkflowRunCard]: EmptyCardConfiguration;
diff --git a/packages/twenty-front/src/modules/object-record/record-show/types/CardType.ts b/packages/twenty-front/src/modules/object-record/record-show/types/CardType.ts
index eba0ffb2b3..a80aa4497d 100644
--- a/packages/twenty-front/src/modules/object-record/record-show/types/CardType.ts
+++ b/packages/twenty-front/src/modules/object-record/record-show/types/CardType.ts
@@ -9,6 +9,6 @@ export enum CardType {
WorkflowCard = 'WorkflowCard',
WorkflowVersionCard = 'WorkflowVersionCard',
WorkflowRunCard = 'WorkflowRunCard',
- RichTextCard = 'RichTextCard',
+ FieldRichTextCard = 'FieldRichTextCard',
DashboardCard = 'DashboardCard',
}
diff --git a/packages/twenty-front/src/modules/object-record/record-show/utils/getCardComponent.tsx b/packages/twenty-front/src/modules/object-record/record-show/utils/getCardComponent.tsx
index c47ded4f59..f7ec0f487d 100644
--- a/packages/twenty-front/src/modules/object-record/record-show/utils/getCardComponent.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-show/utils/getCardComponent.tsx
@@ -12,7 +12,7 @@ import {
} from '@/object-record/record-show/types/CardConfiguration';
import { CardType } from '@/object-record/record-show/types/CardType';
import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext';
-import { RichTextCard } from '@/ui/layout/show-page/components/RichTextCard';
+import { FieldRichTextCard } from '@/ui/layout/show-page/components/FieldRichTextCard';
import { WorkflowCard } from '@/workflow/workflow-diagram/components/WorkflowCard';
import { WorkflowRunCard } from '@/workflow/workflow-diagram/components/WorkflowRunCard';
import { WorkflowVersionCard } from '@/workflow/workflow-diagram/components/WorkflowVersionCard';
@@ -58,8 +58,8 @@ export const getCardComponent = (
/>
);
- case CardType.RichTextCard:
- return ;
+ case CardType.FieldRichTextCard:
+ return ;
case CardType.TaskCard:
return ;
diff --git a/packages/twenty-front/src/modules/page-layout/constants/DefaultNoteRecordPageLayout.ts b/packages/twenty-front/src/modules/page-layout/constants/DefaultNoteRecordPageLayout.ts
index 54b9fcbcd4..0e7ab61295 100644
--- a/packages/twenty-front/src/modules/page-layout/constants/DefaultNoteRecordPageLayout.ts
+++ b/packages/twenty-front/src/modules/page-layout/constants/DefaultNoteRecordPageLayout.ts
@@ -48,7 +48,7 @@ export const DEFAULT_NOTE_RECORD_PAGE_LAYOUT: PageLayout = {
id: 'note-widget-note',
pageLayoutTabId: 'note-tab-fields',
title: 'Note',
- type: WidgetType.RICH_TEXT,
+ type: WidgetType.FIELD_RICH_TEXT,
objectMetadataId: null,
gridPosition: {
__typename: 'GridPosition',
@@ -87,7 +87,7 @@ export const DEFAULT_NOTE_RECORD_PAGE_LAYOUT: PageLayout = {
id: 'note-widget-note',
pageLayoutTabId: 'note-tab-note',
title: 'Note',
- type: WidgetType.RICH_TEXT,
+ type: WidgetType.FIELD_RICH_TEXT,
objectMetadataId: null,
gridPosition: {
__typename: 'GridPosition',
diff --git a/packages/twenty-front/src/modules/page-layout/constants/DefaultTaskRecordPageLayout.ts b/packages/twenty-front/src/modules/page-layout/constants/DefaultTaskRecordPageLayout.ts
index 1078b6a5a8..6914ce9d1c 100644
--- a/packages/twenty-front/src/modules/page-layout/constants/DefaultTaskRecordPageLayout.ts
+++ b/packages/twenty-front/src/modules/page-layout/constants/DefaultTaskRecordPageLayout.ts
@@ -48,7 +48,7 @@ export const DEFAULT_TASK_RECORD_PAGE_LAYOUT: PageLayout = {
id: 'task-widget-note',
pageLayoutTabId: 'task-tab-fields',
title: 'Note',
- type: WidgetType.RICH_TEXT,
+ type: WidgetType.FIELD_RICH_TEXT,
objectMetadataId: null,
gridPosition: {
__typename: 'GridPosition',
@@ -87,7 +87,7 @@ export const DEFAULT_TASK_RECORD_PAGE_LAYOUT: PageLayout = {
id: 'task-widget-note',
pageLayoutTabId: 'task-tab-note',
title: 'Note',
- type: WidgetType.RICH_TEXT,
+ type: WidgetType.FIELD_RICH_TEXT,
objectMetadataId: null,
gridPosition: {
__typename: 'GridPosition',
diff --git a/packages/twenty-front/src/modules/page-layout/graphql/fragments/pageLayoutWidgetFragment.ts b/packages/twenty-front/src/modules/page-layout/graphql/fragments/pageLayoutWidgetFragment.ts
index 45901c715f..5bb63c89fb 100644
--- a/packages/twenty-front/src/modules/page-layout/graphql/fragments/pageLayoutWidgetFragment.ts
+++ b/packages/twenty-front/src/modules/page-layout/graphql/fragments/pageLayoutWidgetFragment.ts
@@ -113,6 +113,12 @@ export const PAGE_LAYOUT_WIDGET_FRAGMENT = gql`
... on IframeConfiguration {
url
}
+ ... on StandaloneRichTextConfiguration {
+ body {
+ blocknote
+ markdown
+ }
+ }
}
pageLayoutTabId
}
diff --git a/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetContentRenderer.tsx b/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetContentRenderer.tsx
index 49be52a072..2d13b3953e 100644
--- a/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetContentRenderer.tsx
+++ b/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetContentRenderer.tsx
@@ -1,11 +1,11 @@
import { CalendarWidget } from '@/page-layout/widgets/calendar/components/CalendarWidget';
import { EmailWidget } from '@/page-layout/widgets/emails/components/EmailWidget';
+import { FieldRichTextWidget } from '@/page-layout/widgets/field-rich-text/components/FieldRichTextWidget';
import { FieldsWidget } from '@/page-layout/widgets/fields/components/FieldsWidget';
import { FileWidget } from '@/page-layout/widgets/files/components/FileWidget';
import { GraphWidgetRenderer } from '@/page-layout/widgets/graph/components/GraphWidgetRenderer';
import { IframeWidget } from '@/page-layout/widgets/iframe/components/IframeWidget';
import { NoteWidget } from '@/page-layout/widgets/notes/components/NoteWidget';
-import { RichTextWidget } from '@/page-layout/widgets/rich-text/components/RichTextWidget';
import { TaskWidget } from '@/page-layout/widgets/tasks/components/TaskWidget';
import { TimelineWidget } from '@/page-layout/widgets/timeline/components/TimelineWidget';
import { WorkflowRunWidget } from '@/page-layout/widgets/workflow/components/WorkflowRunWidget';
@@ -42,8 +42,8 @@ export const WidgetContentRenderer = ({
case WidgetType.NOTES:
return ;
- case WidgetType.RICH_TEXT:
- return ;
+ case WidgetType.FIELD_RICH_TEXT:
+ return ;
case WidgetType.FILES:
return ;
diff --git a/packages/twenty-front/src/modules/page-layout/widgets/rich-text/components/RichTextWidget.tsx b/packages/twenty-front/src/modules/page-layout/widgets/field-rich-text/components/FieldRichTextWidget.tsx
similarity index 54%
rename from packages/twenty-front/src/modules/page-layout/widgets/rich-text/components/RichTextWidget.tsx
rename to packages/twenty-front/src/modules/page-layout/widgets/field-rich-text/components/FieldRichTextWidget.tsx
index 63f0056d86..0bd40e4676 100644
--- a/packages/twenty-front/src/modules/page-layout/widgets/rich-text/components/RichTextWidget.tsx
+++ b/packages/twenty-front/src/modules/page-layout/widgets/field-rich-text/components/FieldRichTextWidget.tsx
@@ -1,4 +1,4 @@
-import { RichTextCard } from '@/ui/layout/show-page/components/RichTextCard';
+import { FieldRichTextCard } from '@/ui/layout/show-page/components/FieldRichTextCard';
import styled from '@emotion/styled';
import { type PageLayoutWidget } from '~/generated/graphql';
@@ -8,14 +8,16 @@ const StyledContainer = styled.div`
width: 100%;
`;
-type RichTextWidgetProps = {
+type FieldRichTextWidgetProps = {
widget: PageLayoutWidget;
};
-export const RichTextWidget = ({ widget: _widget }: RichTextWidgetProps) => {
+export const FieldRichTextWidget = ({
+ widget: _widget,
+}: FieldRichTextWidgetProps) => {
return (
-
+
);
};
diff --git a/packages/twenty-front/src/modules/ui/layout/show-page/components/RichTextCard.tsx b/packages/twenty-front/src/modules/ui/layout/show-page/components/FieldRichTextCard.tsx
similarity index 98%
rename from packages/twenty-front/src/modules/ui/layout/show-page/components/RichTextCard.tsx
rename to packages/twenty-front/src/modules/ui/layout/show-page/components/FieldRichTextCard.tsx
index ecb51787de..256f943f17 100644
--- a/packages/twenty-front/src/modules/ui/layout/show-page/components/RichTextCard.tsx
+++ b/packages/twenty-front/src/modules/ui/layout/show-page/components/FieldRichTextCard.tsx
@@ -46,7 +46,7 @@ const LoadingSkeleton = () => {
);
};
-export const RichTextCard = () => {
+export const FieldRichTextCard = () => {
const targetRecord = useTargetRecord();
const activityBodyV2 = useRecoilValue(
recordStoreFamilySelector({
diff --git a/packages/twenty-server/src/database/typeorm/core/migrations/common/1764846384501-renameRichTextToFieldRichTextAndAddStandaloneRichText.ts b/packages/twenty-server/src/database/typeorm/core/migrations/common/1764846384501-renameRichTextToFieldRichTextAndAddStandaloneRichText.ts
new file mode 100644
index 0000000000..589344cfdf
--- /dev/null
+++ b/packages/twenty-server/src/database/typeorm/core/migrations/common/1764846384501-renameRichTextToFieldRichTextAndAddStandaloneRichText.ts
@@ -0,0 +1,47 @@
+import { type MigrationInterface, type QueryRunner } from 'typeorm';
+
+export class RenameRichTextToFieldRichTextAndAddStandaloneRichText1764846384501
+ implements MigrationInterface
+{
+ name = 'RenameRichTextToFieldRichTextAndAddStandaloneRichText1764846384501';
+
+ public async up(queryRunner: QueryRunner): Promise {
+ await queryRunner.query(
+ `ALTER TYPE "core"."pageLayoutWidget_type_enum" RENAME TO "pageLayoutWidget_type_enum_old"`,
+ );
+ await queryRunner.query(
+ `CREATE TYPE "core"."pageLayoutWidget_type_enum" AS ENUM('VIEW', 'IFRAME', 'FIELDS', 'GRAPH', 'STANDALONE_RICH_TEXT', 'TIMELINE', 'TASKS', 'NOTES', 'FILES', 'EMAILS', 'CALENDAR', 'FIELD_RICH_TEXT', 'WORKFLOW', 'WORKFLOW_VERSION', 'WORKFLOW_RUN')`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "core"."pageLayoutWidget" ALTER COLUMN "type" DROP DEFAULT`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "core"."pageLayoutWidget" ALTER COLUMN "type" TYPE "core"."pageLayoutWidget_type_enum" USING "type"::"text"::"core"."pageLayoutWidget_type_enum"`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "core"."pageLayoutWidget" ALTER COLUMN "type" SET DEFAULT 'VIEW'`,
+ );
+ await queryRunner.query(
+ `DROP TYPE "core"."pageLayoutWidget_type_enum_old"`,
+ );
+ }
+
+ public async down(queryRunner: QueryRunner): Promise {
+ await queryRunner.query(
+ `CREATE TYPE "core"."pageLayoutWidget_type_enum_old" AS ENUM('VIEW', 'IFRAME', 'FIELDS', 'GRAPH', 'TIMELINE', 'TASKS', 'NOTES', 'FILES', 'EMAILS', 'CALENDAR', 'RICH_TEXT', 'WORKFLOW', 'WORKFLOW_VERSION', 'WORKFLOW_RUN')`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "core"."pageLayoutWidget" ALTER COLUMN "type" DROP DEFAULT`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "core"."pageLayoutWidget" ALTER COLUMN "type" TYPE "core"."pageLayoutWidget_type_enum_old" USING "type"::"text"::"core"."pageLayoutWidget_type_enum_old"`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "core"."pageLayoutWidget" ALTER COLUMN "type" SET DEFAULT 'VIEW'`,
+ );
+ await queryRunner.query(`DROP TYPE "core"."pageLayoutWidget_type_enum"`);
+ await queryRunner.query(
+ `ALTER TYPE "core"."pageLayoutWidget_type_enum_old" RENAME TO "pageLayoutWidget_type_enum"`,
+ );
+ }
+}
diff --git a/packages/twenty-server/src/engine/core-modules/page-layout/dtos/rich-text-v2-body.dto.ts b/packages/twenty-server/src/engine/core-modules/page-layout/dtos/rich-text-v2-body.dto.ts
new file mode 100644
index 0000000000..5696803e16
--- /dev/null
+++ b/packages/twenty-server/src/engine/core-modules/page-layout/dtos/rich-text-v2-body.dto.ts
@@ -0,0 +1,17 @@
+import { Field, ObjectType } from '@nestjs/graphql';
+
+import { IsOptional, IsString } from 'class-validator';
+import { type RichTextV2Metadata } from 'twenty-shared/types';
+
+@ObjectType('RichTextV2Body')
+export class RichTextV2BodyDTO implements RichTextV2Metadata {
+ @Field(() => String, { nullable: true })
+ @IsString()
+ @IsOptional()
+ blocknote?: string | null;
+
+ @Field(() => String, { nullable: true })
+ @IsString()
+ @IsOptional()
+ markdown: string | null;
+}
diff --git a/packages/twenty-server/src/engine/core-modules/page-layout/dtos/standalone-rich-text-configuration.dto.ts b/packages/twenty-server/src/engine/core-modules/page-layout/dtos/standalone-rich-text-configuration.dto.ts
new file mode 100644
index 0000000000..4614c4cd57
--- /dev/null
+++ b/packages/twenty-server/src/engine/core-modules/page-layout/dtos/standalone-rich-text-configuration.dto.ts
@@ -0,0 +1,15 @@
+import { Field, ObjectType } from '@nestjs/graphql';
+
+import { Type } from 'class-transformer';
+import { IsNotEmpty, ValidateNested } from 'class-validator';
+
+import { RichTextV2BodyDTO } from 'src/engine/core-modules/page-layout/dtos/rich-text-v2-body.dto';
+
+@ObjectType('StandaloneRichTextConfiguration')
+export class StandaloneRichTextConfigurationDTO {
+ @Field(() => RichTextV2BodyDTO)
+ @ValidateNested()
+ @Type(() => RichTextV2BodyDTO)
+ @IsNotEmpty()
+ body: RichTextV2BodyDTO;
+}
diff --git a/packages/twenty-server/src/engine/core-modules/page-layout/dtos/widget-configuration.interface.ts b/packages/twenty-server/src/engine/core-modules/page-layout/dtos/widget-configuration.interface.ts
index 2f2b1c0ad1..a969bf13f2 100644
--- a/packages/twenty-server/src/engine/core-modules/page-layout/dtos/widget-configuration.interface.ts
+++ b/packages/twenty-server/src/engine/core-modules/page-layout/dtos/widget-configuration.interface.ts
@@ -6,6 +6,7 @@ import { GaugeChartConfigurationDTO } from 'src/engine/core-modules/page-layout/
import { IframeConfigurationDTO } from 'src/engine/core-modules/page-layout/dtos/iframe-configuration.dto';
import { LineChartConfigurationDTO } from 'src/engine/core-modules/page-layout/dtos/line-chart-configuration.dto';
import { PieChartConfigurationDTO } from 'src/engine/core-modules/page-layout/dtos/pie-chart-configuration.dto';
+import { StandaloneRichTextConfigurationDTO } from 'src/engine/core-modules/page-layout/dtos/standalone-rich-text-configuration.dto';
import { GraphType } from 'src/engine/core-modules/page-layout/enums/graph-type.enum';
import { WidgetConfigurationType } from 'src/engine/core-modules/page-layout/enums/widget-configuration-type.enum';
@@ -18,6 +19,7 @@ export const WidgetConfiguration = createUnionType({
AggregateChartConfigurationDTO,
GaugeChartConfigurationDTO,
IframeConfigurationDTO,
+ StandaloneRichTextConfigurationDTO,
],
resolveType(configuration: Record) {
if (!('configurationType' in configuration)) {
@@ -52,6 +54,13 @@ export const WidgetConfiguration = createUnionType({
return IframeConfigurationDTO;
}
+ if (
+ configuration.configurationType ===
+ WidgetConfigurationType.STANDALONE_RICH_TEXT_CONFIG
+ ) {
+ return StandaloneRichTextConfigurationDTO;
+ }
+
throw new Error(
`Unknown widget configuration type: ${configuration.configurationType}`,
);
@@ -64,4 +73,5 @@ export type WidgetConfigurationInterface =
| PieChartConfigurationDTO
| AggregateChartConfigurationDTO
| GaugeChartConfigurationDTO
- | IframeConfigurationDTO;
+ | IframeConfigurationDTO
+ | StandaloneRichTextConfigurationDTO;
diff --git a/packages/twenty-server/src/engine/core-modules/page-layout/enums/widget-configuration-type.enum.ts b/packages/twenty-server/src/engine/core-modules/page-layout/enums/widget-configuration-type.enum.ts
index bc8c8ec1e4..e8c7bee55b 100644
--- a/packages/twenty-server/src/engine/core-modules/page-layout/enums/widget-configuration-type.enum.ts
+++ b/packages/twenty-server/src/engine/core-modules/page-layout/enums/widget-configuration-type.enum.ts
@@ -1,4 +1,5 @@
export enum WidgetConfigurationType {
CHART_CONFIG = 'CHART_CONFIG',
IFRAME_CONFIG = 'IFRAME_CONFIG',
+ STANDALONE_RICH_TEXT_CONFIG = 'STANDALONE_RICH_TEXT_CONFIG',
}
diff --git a/packages/twenty-server/src/engine/core-modules/page-layout/enums/widget-type.enum.ts b/packages/twenty-server/src/engine/core-modules/page-layout/enums/widget-type.enum.ts
index bbc50b566c..108dc645a9 100644
--- a/packages/twenty-server/src/engine/core-modules/page-layout/enums/widget-type.enum.ts
+++ b/packages/twenty-server/src/engine/core-modules/page-layout/enums/widget-type.enum.ts
@@ -3,13 +3,14 @@ export enum WidgetType {
IFRAME = 'IFRAME',
FIELDS = 'FIELDS',
GRAPH = 'GRAPH',
+ STANDALONE_RICH_TEXT = 'STANDALONE_RICH_TEXT',
TIMELINE = 'TIMELINE',
TASKS = 'TASKS',
NOTES = 'NOTES',
FILES = 'FILES',
EMAILS = 'EMAILS',
CALENDAR = 'CALENDAR',
- RICH_TEXT = 'RICH_TEXT',
+ FIELD_RICH_TEXT = 'FIELD_RICH_TEXT',
WORKFLOW = 'WORKFLOW',
WORKFLOW_VERSION = 'WORKFLOW_VERSION',
WORKFLOW_RUN = 'WORKFLOW_RUN',
diff --git a/packages/twenty-server/src/engine/core-modules/page-layout/services/page-layout-widget.service.ts b/packages/twenty-server/src/engine/core-modules/page-layout/services/page-layout-widget.service.ts
index 05442277fe..4cc6df5b81 100644
--- a/packages/twenty-server/src/engine/core-modules/page-layout/services/page-layout-widget.service.ts
+++ b/packages/twenty-server/src/engine/core-modules/page-layout/services/page-layout-widget.service.ts
@@ -142,7 +142,7 @@ export class PageLayoutWidgetService {
);
try {
- validatedConfig = validateAndTransformWidgetConfiguration({
+ validatedConfig = await validateAndTransformWidgetConfiguration({
type: pageLayoutWidgetData.type,
configuration: pageLayoutWidgetData.configuration,
isDashboardV2Enabled,
@@ -246,7 +246,7 @@ export class PageLayoutWidgetService {
);
try {
- validatedConfig = validateAndTransformWidgetConfiguration({
+ validatedConfig = await validateAndTransformWidgetConfiguration({
type: typeForValidation,
configuration: updateData.configuration,
isDashboardV2Enabled,
diff --git a/packages/twenty-server/src/engine/core-modules/page-layout/utils/__tests__/validate-and-transform-widget-configuration.util.spec.ts b/packages/twenty-server/src/engine/core-modules/page-layout/utils/__tests__/validate-and-transform-widget-configuration.util.spec.ts
index 5000aaf463..8666f956ff 100644
--- a/packages/twenty-server/src/engine/core-modules/page-layout/utils/__tests__/validate-and-transform-widget-configuration.util.spec.ts
+++ b/packages/twenty-server/src/engine/core-modules/page-layout/utils/__tests__/validate-and-transform-widget-configuration.util.spec.ts
@@ -4,6 +4,9 @@ import {
INVALID_IFRAME_CONFIG_EMPTY_URL,
INVALID_NUMBER_CHART_CONFIG_BAD_UUID,
INVALID_NUMBER_CHART_CONFIG_MISSING_FIELDS,
+ INVALID_STANDALONE_RICH_TEXT_CONFIG_BODY_WRONG_TYPE,
+ INVALID_STANDALONE_RICH_TEXT_CONFIG_INVALID_SUBFIELDS,
+ INVALID_STANDALONE_RICH_TEXT_CONFIG_MISSING_BODY,
INVALID_VERTICAL_BAR_CHART_CONFIG_MISSING_GROUP_BY,
TEST_GAUGE_CHART_CONFIG,
TEST_HORIZONTAL_BAR_CHART_CONFIG,
@@ -13,6 +16,8 @@ import {
TEST_NUMBER_CHART_CONFIG,
TEST_NUMBER_CHART_CONFIG_MINIMAL,
TEST_PIE_CHART_CONFIG,
+ TEST_STANDALONE_RICH_TEXT_CONFIG,
+ TEST_STANDALONE_RICH_TEXT_CONFIG_MINIMAL,
TEST_VERTICAL_BAR_CHART_CONFIG,
TEST_VERTICAL_BAR_CHART_CONFIG_MINIMAL,
} from 'test/integration/constants/widget-configuration-test-data.constants';
@@ -20,10 +25,22 @@ import {
import { WidgetType } from 'src/engine/core-modules/page-layout/enums/widget-type.enum';
import { validateAndTransformWidgetConfiguration } from 'src/engine/core-modules/page-layout/utils/validate-and-transform-widget-configuration.util';
+jest.mock(
+ 'src/engine/core-modules/record-transformer/utils/transform-rich-text-v2.util',
+ () => ({
+ transformRichTextV2Value: jest.fn((value) =>
+ Promise.resolve({
+ blocknote: value.blocknote ?? null,
+ markdown: value.markdown ?? null,
+ }),
+ ),
+ }),
+);
+
describe('validateAndTransformWidgetConfiguration', () => {
describe('IFRAME widget', () => {
- it('should validate and transform valid iframe configuration', () => {
- const result = validateAndTransformWidgetConfiguration({
+ it('should validate and transform valid iframe configuration', async () => {
+ const result = await validateAndTransformWidgetConfiguration({
type: WidgetType.IFRAME,
configuration: TEST_IFRAME_CONFIG,
isDashboardV2Enabled: false,
@@ -32,31 +49,86 @@ describe('validateAndTransformWidgetConfiguration', () => {
expect(result).toMatchObject(TEST_IFRAME_CONFIG);
});
- it('should throw error for invalid URL', () => {
- expect(() =>
+ it('should throw error for invalid URL', async () => {
+ await expect(
validateAndTransformWidgetConfiguration({
type: WidgetType.IFRAME,
configuration: INVALID_IFRAME_CONFIG_BAD_URL,
isDashboardV2Enabled: false,
}),
- ).toThrow(/url must be a URL address/);
+ ).rejects.toThrow(/url must be a URL address/);
});
- it('should throw error for empty URL', () => {
- expect(() =>
+ it('should throw error for empty URL', async () => {
+ await expect(
validateAndTransformWidgetConfiguration({
type: WidgetType.IFRAME,
configuration: INVALID_IFRAME_CONFIG_EMPTY_URL,
isDashboardV2Enabled: false,
}),
- ).toThrow(/url must be a URL address/);
+ ).rejects.toThrow(/url must be a URL address/);
+ });
+ });
+
+ describe('STANDALONE_RICH_TEXT widget', () => {
+ it('should validate and transform valid standalone rich text configuration', async () => {
+ const result = await validateAndTransformWidgetConfiguration({
+ type: WidgetType.STANDALONE_RICH_TEXT,
+ configuration: TEST_STANDALONE_RICH_TEXT_CONFIG,
+ isDashboardV2Enabled: false,
+ });
+
+ expect(result).toMatchObject(TEST_STANDALONE_RICH_TEXT_CONFIG);
+ });
+
+ it('should validate minimal standalone rich text configuration', async () => {
+ const result = await validateAndTransformWidgetConfiguration({
+ type: WidgetType.STANDALONE_RICH_TEXT,
+ configuration: TEST_STANDALONE_RICH_TEXT_CONFIG_MINIMAL,
+ isDashboardV2Enabled: false,
+ });
+
+ expect(result).toMatchObject(TEST_STANDALONE_RICH_TEXT_CONFIG_MINIMAL);
+ });
+
+ it('should throw error for missing body', async () => {
+ await expect(
+ validateAndTransformWidgetConfiguration({
+ type: WidgetType.STANDALONE_RICH_TEXT,
+ configuration: INVALID_STANDALONE_RICH_TEXT_CONFIG_MISSING_BODY,
+ isDashboardV2Enabled: false,
+ }),
+ ).rejects.toThrow(/body/);
+ });
+
+ it('should throw error when body is wrong type', async () => {
+ await expect(
+ validateAndTransformWidgetConfiguration({
+ type: WidgetType.STANDALONE_RICH_TEXT,
+ configuration: INVALID_STANDALONE_RICH_TEXT_CONFIG_BODY_WRONG_TYPE,
+ isDashboardV2Enabled: false,
+ }),
+ ).rejects.toThrow();
+ });
+
+ it('should strip invalid subfields from body', async () => {
+ const result = await validateAndTransformWidgetConfiguration({
+ type: WidgetType.STANDALONE_RICH_TEXT,
+ configuration: INVALID_STANDALONE_RICH_TEXT_CONFIG_INVALID_SUBFIELDS,
+ isDashboardV2Enabled: false,
+ });
+
+ expect(result).toBeDefined();
+ expect((result as any).body.blocknote).toBeDefined();
+ expect((result as any).body.markdown).toBe('valid');
+ expect((result as any).body.invalidField).toBeUndefined();
});
});
describe('GRAPH widget', () => {
describe('NUMBER graph', () => {
- it('should validate full number graph configuration', () => {
- const result = validateAndTransformWidgetConfiguration({
+ it('should validate full number graph configuration', async () => {
+ const result = await validateAndTransformWidgetConfiguration({
type: WidgetType.GRAPH,
configuration: TEST_NUMBER_CHART_CONFIG,
isDashboardV2Enabled: false,
@@ -65,8 +137,8 @@ describe('validateAndTransformWidgetConfiguration', () => {
expect(result).toMatchObject(TEST_NUMBER_CHART_CONFIG);
});
- it('should validate minimal number graph configuration', () => {
- const result = validateAndTransformWidgetConfiguration({
+ it('should validate minimal number graph configuration', async () => {
+ const result = await validateAndTransformWidgetConfiguration({
type: WidgetType.GRAPH,
configuration: TEST_NUMBER_CHART_CONFIG_MINIMAL,
isDashboardV2Enabled: false,
@@ -75,30 +147,30 @@ describe('validateAndTransformWidgetConfiguration', () => {
expect(result).toMatchObject(TEST_NUMBER_CHART_CONFIG_MINIMAL);
});
- it('should throw error for partial number graph configuration with missing required fields', () => {
- expect(() =>
+ it('should throw error for partial number graph configuration with missing required fields', async () => {
+ await expect(
validateAndTransformWidgetConfiguration({
type: WidgetType.GRAPH,
configuration: INVALID_NUMBER_CHART_CONFIG_MISSING_FIELDS,
isDashboardV2Enabled: false,
}),
- ).toThrow(/aggregateFieldMetadataId.*aggregateOperation/);
+ ).rejects.toThrow(/aggregateFieldMetadataId.*aggregateOperation/);
});
- it('should throw error for invalid UUID', () => {
- expect(() =>
+ it('should throw error for invalid UUID', async () => {
+ await expect(
validateAndTransformWidgetConfiguration({
type: WidgetType.GRAPH,
configuration: INVALID_NUMBER_CHART_CONFIG_BAD_UUID,
isDashboardV2Enabled: false,
}),
- ).toThrow(/aggregateFieldMetadataId must be a UUID/);
+ ).rejects.toThrow(/aggregateFieldMetadataId must be a UUID/);
});
});
describe('VERTICAL_BAR graph', () => {
- it('should validate full vertical bar graph configuration', () => {
- const result = validateAndTransformWidgetConfiguration({
+ it('should validate full vertical bar graph configuration', async () => {
+ const result = await validateAndTransformWidgetConfiguration({
type: WidgetType.GRAPH,
configuration: TEST_VERTICAL_BAR_CHART_CONFIG,
isDashboardV2Enabled: false,
@@ -107,8 +179,8 @@ describe('validateAndTransformWidgetConfiguration', () => {
expect(result).toMatchObject(TEST_VERTICAL_BAR_CHART_CONFIG);
});
- it('should validate minimal vertical bar graph configuration', () => {
- const result = validateAndTransformWidgetConfiguration({
+ it('should validate minimal vertical bar graph configuration', async () => {
+ const result = await validateAndTransformWidgetConfiguration({
type: WidgetType.GRAPH,
configuration: TEST_VERTICAL_BAR_CHART_CONFIG_MINIMAL,
isDashboardV2Enabled: false,
@@ -117,20 +189,20 @@ describe('validateAndTransformWidgetConfiguration', () => {
expect(result).toMatchObject(TEST_VERTICAL_BAR_CHART_CONFIG_MINIMAL);
});
- it('should throw error for partial vertical bar graph configuration with missing required fields', () => {
- expect(() =>
+ it('should throw error for partial vertical bar graph configuration with missing required fields', async () => {
+ await expect(
validateAndTransformWidgetConfiguration({
type: WidgetType.GRAPH,
configuration: INVALID_VERTICAL_BAR_CHART_CONFIG_MISSING_GROUP_BY,
isDashboardV2Enabled: false,
}),
- ).toThrow(/primaryAxisGroupByFieldMetadataId/);
+ ).rejects.toThrow(/primaryAxisGroupByFieldMetadataId/);
});
});
describe('HORIZONTAL_BAR graph', () => {
- it('should validate full horizontal bar graph configuration', () => {
- const result = validateAndTransformWidgetConfiguration({
+ it('should validate full horizontal bar graph configuration', async () => {
+ const result = await validateAndTransformWidgetConfiguration({
type: WidgetType.GRAPH,
configuration: TEST_HORIZONTAL_BAR_CHART_CONFIG,
isDashboardV2Enabled: false,
@@ -139,8 +211,8 @@ describe('validateAndTransformWidgetConfiguration', () => {
expect(result).toMatchObject(TEST_HORIZONTAL_BAR_CHART_CONFIG);
});
- it('should validate minimal horizontal bar graph configuration', () => {
- const result = validateAndTransformWidgetConfiguration({
+ it('should validate minimal horizontal bar graph configuration', async () => {
+ const result = await validateAndTransformWidgetConfiguration({
type: WidgetType.GRAPH,
configuration: TEST_HORIZONTAL_BAR_CHART_CONFIG_MINIMAL,
isDashboardV2Enabled: false,
@@ -149,24 +221,24 @@ describe('validateAndTransformWidgetConfiguration', () => {
expect(result).toMatchObject(TEST_HORIZONTAL_BAR_CHART_CONFIG_MINIMAL);
});
- it('should throw error for partial horizontal bar graph configuration with missing required fields', () => {
- expect(() =>
+ it('should throw error for partial horizontal bar graph configuration with missing required fields', async () => {
+ await expect(
validateAndTransformWidgetConfiguration({
type: WidgetType.GRAPH,
configuration: INVALID_HORIZONTAL_BAR_CHART_CONFIG_MISSING_GROUP_BY,
isDashboardV2Enabled: false,
}),
- ).toThrow(/primaryAxisGroupByFieldMetadataId/);
+ ).rejects.toThrow(/primaryAxisGroupByFieldMetadataId/);
});
});
- it('should return null for unsupported graph type', () => {
+ it('should return null for unsupported graph type', async () => {
const configuration = {
graphType: 'UNSUPPORTED',
viewId: '550e8400-e29b-41d4-a716-446655440000',
};
- const result = validateAndTransformWidgetConfiguration({
+ const result = await validateAndTransformWidgetConfiguration({
type: WidgetType.GRAPH,
configuration: configuration,
isDashboardV2Enabled: false,
@@ -175,12 +247,12 @@ describe('validateAndTransformWidgetConfiguration', () => {
expect(result).toBeNull();
});
- it('should return null for missing graph type', () => {
+ it('should return null for missing graph type', async () => {
const configuration = {
viewId: '550e8400-e29b-41d4-a716-446655440000',
};
- const result = validateAndTransformWidgetConfiguration({
+ const result = await validateAndTransformWidgetConfiguration({
type: WidgetType.GRAPH,
configuration: configuration,
isDashboardV2Enabled: false,
@@ -191,40 +263,40 @@ describe('validateAndTransformWidgetConfiguration', () => {
});
describe('Edge cases', () => {
- it('should throw error for null configuration', () => {
- expect(() =>
+ it('should throw error for null configuration', async () => {
+ await expect(
validateAndTransformWidgetConfiguration({
type: WidgetType.IFRAME,
configuration: null,
isDashboardV2Enabled: false,
}),
- ).toThrow('Invalid configuration: not an object');
+ ).rejects.toThrow('Invalid configuration: not an object');
});
- it('should throw error for undefined configuration', () => {
- expect(() =>
+ it('should throw error for undefined configuration', async () => {
+ await expect(
validateAndTransformWidgetConfiguration({
type: WidgetType.IFRAME,
configuration: undefined,
isDashboardV2Enabled: false,
}),
- ).toThrow('Invalid configuration: not an object');
+ ).rejects.toThrow('Invalid configuration: not an object');
});
- it('should throw error for non-object configuration', () => {
- expect(() =>
+ it('should throw error for non-object configuration', async () => {
+ await expect(
validateAndTransformWidgetConfiguration({
type: WidgetType.IFRAME,
configuration: 'string',
isDashboardV2Enabled: false,
}),
- ).toThrow('Invalid configuration: not an object');
+ ).rejects.toThrow('Invalid configuration: not an object');
});
- it('should return null for unsupported widget type', () => {
+ it('should return null for unsupported widget type', async () => {
const configuration = { someField: 'value' };
- const result = validateAndTransformWidgetConfiguration({
+ const result = await validateAndTransformWidgetConfiguration({
type: 'UNSUPPORTED' as WidgetType,
configuration: configuration,
isDashboardV2Enabled: false,
@@ -235,72 +307,72 @@ describe('validateAndTransformWidgetConfiguration', () => {
});
describe('Error messages', () => {
- it('should include validation details in error message', () => {
- expect(() =>
+ it('should include validation details in error message', async () => {
+ await expect(
validateAndTransformWidgetConfiguration({
type: WidgetType.GRAPH,
configuration: INVALID_NUMBER_CHART_CONFIG_BAD_UUID,
isDashboardV2Enabled: false,
}),
- ).toThrow(/aggregateFieldMetadataId must be a UUID/);
+ ).rejects.toThrow(/aggregateFieldMetadataId must be a UUID/);
});
});
describe('Feature flags', () => {
- it('should throw error for GAUGE chart type when IS_DASHBOARD_V2_ENABLED is false', () => {
- expect(() =>
+ it('should throw error for GAUGE chart type when IS_DASHBOARD_V2_ENABLED is false', async () => {
+ await expect(
validateAndTransformWidgetConfiguration({
type: WidgetType.GRAPH,
configuration: TEST_GAUGE_CHART_CONFIG,
isDashboardV2Enabled: false,
}),
- ).toThrow(/IS_DASHBOARD_V2_ENABLED feature flag/);
+ ).rejects.toThrow(/IS_DASHBOARD_V2_ENABLED feature flag/);
});
- it('should not throw error for GAUGE chart type when IS_DASHBOARD_V2_ENABLED is true', () => {
- expect(() =>
+ it('should not throw error for GAUGE chart type when IS_DASHBOARD_V2_ENABLED is true', async () => {
+ await expect(
validateAndTransformWidgetConfiguration({
type: WidgetType.GRAPH,
configuration: TEST_GAUGE_CHART_CONFIG,
isDashboardV2Enabled: true,
}),
- ).not.toThrow();
+ ).resolves.not.toThrow();
});
- it('should not throw error for PIE chart type regardless of IS_DASHBOARD_V2_ENABLED', () => {
- expect(() =>
+ it('should not throw error for PIE chart type regardless of IS_DASHBOARD_V2_ENABLED', async () => {
+ await expect(
validateAndTransformWidgetConfiguration({
type: WidgetType.GRAPH,
configuration: TEST_PIE_CHART_CONFIG,
isDashboardV2Enabled: false,
}),
- ).not.toThrow();
+ ).resolves.not.toThrow();
- expect(() =>
+ await expect(
validateAndTransformWidgetConfiguration({
type: WidgetType.GRAPH,
configuration: TEST_PIE_CHART_CONFIG,
isDashboardV2Enabled: true,
}),
- ).not.toThrow();
+ ).resolves.not.toThrow();
});
- it('should not throw error for LINE chart type regardless of IS_DASHBOARD_V2_ENABLED', () => {
- expect(() =>
+ it('should not throw error for LINE chart type regardless of IS_DASHBOARD_V2_ENABLED', async () => {
+ await expect(
validateAndTransformWidgetConfiguration({
type: WidgetType.GRAPH,
configuration: TEST_LINE_CHART_CONFIG,
isDashboardV2Enabled: false,
}),
- ).not.toThrow();
+ ).resolves.not.toThrow();
- expect(() =>
+ await expect(
validateAndTransformWidgetConfiguration({
type: WidgetType.GRAPH,
configuration: TEST_LINE_CHART_CONFIG,
isDashboardV2Enabled: true,
}),
- ).not.toThrow();
+ ).resolves.not.toThrow();
});
});
});
diff --git a/packages/twenty-server/src/engine/core-modules/page-layout/utils/inject-widget-configuration-discriminator.util.ts b/packages/twenty-server/src/engine/core-modules/page-layout/utils/inject-widget-configuration-discriminator.util.ts
index d6ee0f2712..67b78e395a 100644
--- a/packages/twenty-server/src/engine/core-modules/page-layout/utils/inject-widget-configuration-discriminator.util.ts
+++ b/packages/twenty-server/src/engine/core-modules/page-layout/utils/inject-widget-configuration-discriminator.util.ts
@@ -18,14 +18,21 @@ export const injectWidgetConfigurationDiscriminator = (
return {
...configuration,
configurationType: WidgetConfigurationType.IFRAME_CONFIG,
- } as ConfigurationWithDiscriminator;
+ } satisfies ConfigurationWithDiscriminator;
}
if (widgetType === WidgetType.GRAPH && 'graphType' in configuration) {
return {
...configuration,
configurationType: WidgetConfigurationType.CHART_CONFIG,
- } as ConfigurationWithDiscriminator;
+ } satisfies ConfigurationWithDiscriminator;
+ }
+
+ if (widgetType === WidgetType.STANDALONE_RICH_TEXT) {
+ return {
+ ...configuration,
+ configurationType: WidgetConfigurationType.STANDALONE_RICH_TEXT_CONFIG,
+ } satisfies ConfigurationWithDiscriminator;
}
return configuration as ConfigurationWithDiscriminator;
diff --git a/packages/twenty-server/src/engine/core-modules/page-layout/utils/validate-and-transform-widget-configuration.util.ts b/packages/twenty-server/src/engine/core-modules/page-layout/utils/validate-and-transform-widget-configuration.util.ts
index 68f8b17a28..bdfbd07747 100644
--- a/packages/twenty-server/src/engine/core-modules/page-layout/utils/validate-and-transform-widget-configuration.util.ts
+++ b/packages/twenty-server/src/engine/core-modules/page-layout/utils/validate-and-transform-widget-configuration.util.ts
@@ -3,11 +3,13 @@ import { validateSync, type ValidationError } from 'class-validator';
import { isDefined } from 'twenty-shared/utils';
import { AggregateChartConfigurationDTO } from 'src/engine/core-modules/page-layout/dtos/aggregate-chart-configuration.dto';
+import { transformRichTextV2Value } from 'src/engine/core-modules/record-transformer/utils/transform-rich-text-v2.util';
import { BarChartConfigurationDTO } from 'src/engine/core-modules/page-layout/dtos/bar-chart-configuration.dto';
import { GaugeChartConfigurationDTO } from 'src/engine/core-modules/page-layout/dtos/gauge-chart-configuration.dto';
import { IframeConfigurationDTO } from 'src/engine/core-modules/page-layout/dtos/iframe-configuration.dto';
import { LineChartConfigurationDTO } from 'src/engine/core-modules/page-layout/dtos/line-chart-configuration.dto';
import { PieChartConfigurationDTO } from 'src/engine/core-modules/page-layout/dtos/pie-chart-configuration.dto';
+import { StandaloneRichTextConfigurationDTO } from 'src/engine/core-modules/page-layout/dtos/standalone-rich-text-configuration.dto';
import { type WidgetConfigurationInterface } from 'src/engine/core-modules/page-layout/dtos/widget-configuration.interface';
import { BarChartGroupMode } from 'src/engine/core-modules/page-layout/enums/bar-chart-group-mode.enum';
import { GraphType } from 'src/engine/core-modules/page-layout/enums/graph-type.enum';
@@ -161,7 +163,31 @@ const validateIframeConfiguration = (
return instance;
};
-export const validateAndTransformWidgetConfiguration = ({
+const validateStandaloneRichTextConfiguration = async (
+ configuration: unknown,
+): Promise => {
+ const instance = plainToInstance(
+ StandaloneRichTextConfigurationDTO,
+ configuration,
+ );
+
+ const errors = validateSync(instance, {
+ whitelist: true,
+ forbidUnknownValues: true,
+ });
+
+ if (errors.length > 0) {
+ throw errors;
+ }
+
+ if (instance.body) {
+ instance.body = await transformRichTextV2Value(instance.body);
+ }
+
+ return instance;
+};
+
+export const validateAndTransformWidgetConfiguration = async ({
type,
configuration,
isDashboardV2Enabled,
@@ -169,7 +195,7 @@ export const validateAndTransformWidgetConfiguration = ({
type: WidgetType;
configuration: unknown;
isDashboardV2Enabled: boolean;
-}): WidgetConfigurationInterface | null => {
+}): Promise => {
if (!configuration || typeof configuration !== 'object') {
throw new Error('Invalid configuration: not an object');
}
@@ -183,6 +209,8 @@ export const validateAndTransformWidgetConfiguration = ({
});
case WidgetType.IFRAME:
return validateIframeConfiguration(configuration);
+ case WidgetType.STANDALONE_RICH_TEXT:
+ return await validateStandaloneRichTextConfiguration(configuration);
default:
return null;
}
diff --git a/packages/twenty-server/test/integration/constants/widget-configuration-test-data.constants.ts b/packages/twenty-server/test/integration/constants/widget-configuration-test-data.constants.ts
index 17fd6ca071..ac648dae32 100644
--- a/packages/twenty-server/test/integration/constants/widget-configuration-test-data.constants.ts
+++ b/packages/twenty-server/test/integration/constants/widget-configuration-test-data.constants.ts
@@ -12,6 +12,34 @@ export const TEST_IFRAME_CONFIG = {
url: 'https://example.com/dashboard',
};
+export const TEST_STANDALONE_RICH_TEXT_CONFIG = {
+ body: {
+ blocknote:
+ '{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"Hello world"}]}]}',
+ markdown: '# Hello world',
+ },
+};
+
+export const TEST_STANDALONE_RICH_TEXT_CONFIG_MINIMAL = {
+ body: {
+ markdown: 'Simple text',
+ },
+};
+
+export const INVALID_STANDALONE_RICH_TEXT_CONFIG_MISSING_BODY = {};
+
+export const INVALID_STANDALONE_RICH_TEXT_CONFIG_BODY_WRONG_TYPE = {
+ body: 'not an object',
+};
+
+export const INVALID_STANDALONE_RICH_TEXT_CONFIG_INVALID_SUBFIELDS = {
+ body: {
+ blocknote: 'valid',
+ markdown: 'valid',
+ invalidField: 'should not be here',
+ },
+};
+
export const TEST_IFRAME_CONFIG_ALTERNATIVE = {
url: 'https://app.twenty.com/analytics',
};
@@ -263,6 +291,8 @@ export function getValidConfigForWidgetType(widgetType: string): any {
return configs[Math.floor(Math.random() * configs.length)];
}
+ case 'STANDALONE_RICH_TEXT':
+ return TEST_STANDALONE_RICH_TEXT_CONFIG;
case 'VIEW':
case 'FIELDS':
case 'TIMELINE':
@@ -271,6 +301,7 @@ export function getValidConfigForWidgetType(widgetType: string): any {
case 'FILES':
case 'EMAILS':
case 'CALENDAR':
+ case 'FIELD_RICH_TEXT':
return null;
default:
return null;