diff --git a/packages/twenty-front/src/modules/page-layout/hooks/useSavePageLayout.ts b/packages/twenty-front/src/modules/page-layout/hooks/useSavePageLayout.ts
index 57e19ec342..96d7451638 100644
--- a/packages/twenty-front/src/modules/page-layout/hooks/useSavePageLayout.ts
+++ b/packages/twenty-front/src/modules/page-layout/hooks/useSavePageLayout.ts
@@ -1,7 +1,7 @@
-import { usePageLayoutDraftState } from '@/page-layout/hooks/usePageLayoutDraftState';
import { useUpdatePageLayoutWithTabsAndWidgets } from '@/page-layout/hooks/useUpdatePageLayoutWithTabsAndWidgets';
import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext';
import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState';
+import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
import { pageLayoutPersistedComponentState } from '@/page-layout/states/pageLayoutPersistedComponentState';
import { type PageLayout } from '@/page-layout/types/PageLayout';
import { convertPageLayoutDraftToUpdateInput } from '@/page-layout/utils/convertPageLayoutDraftToUpdateInput';
@@ -28,14 +28,20 @@ export const useSavePageLayout = (pageLayoutIdFromProps: string) => {
pageLayoutId,
);
- const { pageLayoutDraft } = usePageLayoutDraftState(pageLayoutId);
+ const pageLayoutDraftCallbackState = useRecoilComponentCallbackState(
+ pageLayoutDraftComponentState,
+ pageLayoutId,
+ );
const { updatePageLayoutWithTabsAndWidgets } =
useUpdatePageLayoutWithTabsAndWidgets();
const savePageLayout = useRecoilCallback(
- ({ set }) =>
+ ({ set, snapshot }) =>
async () => {
+ const pageLayoutDraft = snapshot
+ .getLoadable(pageLayoutDraftCallbackState)
+ .getValue();
const updateInput =
convertPageLayoutDraftToUpdateInput(pageLayoutDraft);
@@ -64,7 +70,7 @@ export const useSavePageLayout = (pageLayoutIdFromProps: string) => {
},
[
pageLayoutCurrentLayoutsCallbackState,
- pageLayoutDraft,
+ pageLayoutDraftCallbackState,
pageLayoutId,
pageLayoutPersistedCallbackState,
updatePageLayoutWithTabsAndWidgets,
diff --git a/packages/twenty-front/src/modules/page-layout/widgets/standalone-rich-text/components/DashboardsBlockEditor.tsx b/packages/twenty-front/src/modules/page-layout/widgets/standalone-rich-text/components/DashboardsBlockEditor.tsx
index c3d7291341..f9e57eb1c0 100644
--- a/packages/twenty-front/src/modules/page-layout/widgets/standalone-rich-text/components/DashboardsBlockEditor.tsx
+++ b/packages/twenty-front/src/modules/page-layout/widgets/standalone-rich-text/components/DashboardsBlockEditor.tsx
@@ -35,6 +35,7 @@ const StyledEditor = styled.div`
background: transparent;
font-size: 13px;
color: ${({ theme }) => theme.font.color.primary};
+ user-select: text;
}
& .editor [class^='_inlineContent']:before {
color: ${({ theme }) => theme.font.color.tertiary};
@@ -179,19 +180,23 @@ export const DashboardsBlockEditor = ({
formattingToolbar={false}
editable={!readonly}
>
-
-
- {
- const items = getSlashMenu(editor);
- return filterSuggestionItems(items, query);
- }}
- suggestionMenuComponent={CustomSlashMenu}
- />
+ {!readonly && (
+ <>
+
+
+ {
+ const items = getSlashMenu(editor);
+ return filterSuggestionItems(items, query);
+ }}
+ suggestionMenuComponent={CustomSlashMenu}
+ />
+ >
+ )}
);
diff --git a/packages/twenty-front/src/modules/page-layout/widgets/standalone-rich-text/components/StandaloneRichTextEditorContent.tsx b/packages/twenty-front/src/modules/page-layout/widgets/standalone-rich-text/components/StandaloneRichTextEditorContent.tsx
new file mode 100644
index 0000000000..00263ea161
--- /dev/null
+++ b/packages/twenty-front/src/modules/page-layout/widgets/standalone-rich-text/components/StandaloneRichTextEditorContent.tsx
@@ -0,0 +1,168 @@
+import { useCallback, useMemo } from 'react';
+
+import { BLOCK_SCHEMA } from '@/activities/blocks/constants/Schema';
+import { useUploadAttachmentFile } from '@/activities/files/hooks/useUploadAttachmentFile';
+import { type Attachment } from '@/activities/files/types/Attachment';
+import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
+import { useUpdatePageLayoutWidget } from '@/page-layout/hooks/useUpdatePageLayoutWidget';
+import { isPageLayoutInEditModeComponentState } from '@/page-layout/states/isPageLayoutInEditModeComponentState';
+import { pageLayoutEditingWidgetIdComponentState } from '@/page-layout/states/pageLayoutEditingWidgetIdComponentState';
+import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
+import { DashboardsBlockEditor } from '@/page-layout/widgets/standalone-rich-text/components/DashboardsBlockEditor';
+import { StandaloneRichTextWidgetAutoFocusEffect } from '@/page-layout/widgets/standalone-rich-text/components/StandaloneRichTextWidgetAutoFocusEffect';
+import { BLOCK_EDITOR_GLOBAL_HOTKEYS_CONFIG } from '@/ui/input/editor/constants/BlockEditorGlobalHotkeysConfig';
+import { useAttachmentSync } from '@/ui/input/editor/hooks/useAttachmentSync';
+import { parseInitialBlocknote } from '@/ui/input/editor/utils/parseInitialBlocknote';
+import { prepareBodyWithSignedUrls } from '@/ui/input/editor/utils/prepareBodyWithSignedUrls';
+import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
+import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById';
+import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
+import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
+import '@blocknote/core/fonts/inter.css';
+import '@blocknote/mantine/style.css';
+import { useCreateBlockNote } from '@blocknote/react';
+import '@blocknote/react/style.css';
+import { useRecoilCallback } from 'recoil';
+import { useDebouncedCallback } from 'use-debounce';
+import { WidgetConfigurationType } from '~/generated/graphql';
+
+type StandaloneRichTextEditorContentProps = {
+ widget: PageLayoutWidget;
+ dashboardId: string;
+ currentBody: string;
+ attachments: Attachment[];
+ isEditable: boolean;
+ containerElement: HTMLDivElement | null;
+};
+
+export const StandaloneRichTextEditorContent = ({
+ widget,
+ dashboardId,
+ currentBody,
+ attachments,
+ isEditable,
+ containerElement,
+}: StandaloneRichTextEditorContentProps) => {
+ const { updatePageLayoutWidget } = useUpdatePageLayoutWidget();
+ const { uploadAttachmentFile } = useUploadAttachmentFile();
+ const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
+ const { removeFocusItemFromFocusStackById } =
+ useRemoveFocusItemFromFocusStackById();
+ const isPageLayoutInEditModeState = useRecoilComponentCallbackState(
+ isPageLayoutInEditModeComponentState,
+ );
+ const pageLayoutEditingWidgetIdState = useRecoilComponentCallbackState(
+ pageLayoutEditingWidgetIdComponentState,
+ );
+
+ const { syncAttachments } = useAttachmentSync(attachments);
+
+ const shouldPersistDraft = useRecoilCallback(
+ ({ snapshot }) =>
+ () => {
+ const isPageLayoutInEditMode = snapshot
+ .getLoadable(isPageLayoutInEditModeState)
+ .getValue();
+ const editingWidgetId = snapshot
+ .getLoadable(pageLayoutEditingWidgetIdState)
+ .getValue();
+
+ return isPageLayoutInEditMode && editingWidgetId === widget.id;
+ },
+ [isPageLayoutInEditModeState, pageLayoutEditingWidgetIdState, widget.id],
+ );
+
+ const handleUploadAttachment = async (file: File) => {
+ return await uploadAttachmentFile(file, {
+ id: dashboardId,
+ targetObjectNameSingular: CoreObjectNameSingular.Dashboard,
+ });
+ };
+
+ const handleEditorBuiltInUploadFile = async (file: File) => {
+ const { attachmentAbsoluteURL } = await handleUploadAttachment(file);
+ return attachmentAbsoluteURL;
+ };
+
+ const initialContent = useMemo(
+ () => parseInitialBlocknote(currentBody),
+ [currentBody],
+ );
+
+ const editor = useCreateBlockNote({
+ initialContent,
+ domAttributes: { editor: { class: 'editor' } },
+ schema: BLOCK_SCHEMA,
+ uploadFile: handleEditorBuiltInUploadFile,
+ sideMenuDetection: 'editor',
+ });
+
+ const handlePersistBody = useDebouncedCallback((blocknote: string) => {
+ if (!shouldPersistDraft()) {
+ return;
+ }
+ updatePageLayoutWidget(widget.id, {
+ configuration: {
+ configurationType: WidgetConfigurationType.STANDALONE_RICH_TEXT,
+ body: {
+ blocknote,
+ markdown: null,
+ },
+ },
+ });
+ }, 300);
+
+ const handleAttachmentSync = useDebouncedCallback(
+ async (newStringifiedBody: string, previousBody: string) => {
+ if (!shouldPersistDraft()) {
+ return;
+ }
+ await syncAttachments(newStringifiedBody, previousBody);
+ },
+ 500,
+ );
+
+ const handleEditorChange = () => {
+ const newStringifiedBody = JSON.stringify(editor.document) ?? '';
+ const preparedBody = prepareBodyWithSignedUrls(newStringifiedBody);
+
+ handlePersistBody(preparedBody);
+ handleAttachmentSync(newStringifiedBody, currentBody);
+ };
+
+ const handleBlockEditorFocus = useCallback(() => {
+ pushFocusItemToFocusStack({
+ component: {
+ instanceId: widget.id,
+ type: FocusComponentType.STANDALONE_RICH_TEXT_WIDGET,
+ },
+ focusId: widget.id,
+ globalHotkeysConfig: BLOCK_EDITOR_GLOBAL_HOTKEYS_CONFIG,
+ });
+ }, [pushFocusItemToFocusStack, widget.id]);
+
+ const handleBlockEditorBlur = useCallback(() => {
+ handlePersistBody.flush();
+ removeFocusItemFromFocusStackById({
+ focusId: widget.id,
+ });
+ }, [handlePersistBody, removeFocusItemFromFocusStackById, widget.id]);
+
+ return (
+ <>
+
+
+ >
+ );
+};
diff --git a/packages/twenty-front/src/modules/page-layout/widgets/standalone-rich-text/components/StandaloneRichTextWidget.tsx b/packages/twenty-front/src/modules/page-layout/widgets/standalone-rich-text/components/StandaloneRichTextWidget.tsx
index 215163a4ba..5675d0dac2 100644
--- a/packages/twenty-front/src/modules/page-layout/widgets/standalone-rich-text/components/StandaloneRichTextWidget.tsx
+++ b/packages/twenty-front/src/modules/page-layout/widgets/standalone-rich-text/components/StandaloneRichTextWidget.tsx
@@ -1,39 +1,22 @@
-import { useCallback, useMemo, useRef } from 'react';
+import { useRef } from 'react';
-import { BLOCK_SCHEMA } from '@/activities/blocks/constants/Schema';
-import { useUploadAttachmentFile } from '@/activities/files/hooks/useUploadAttachmentFile';
import { type Attachment } from '@/activities/files/types/Attachment';
import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getActivityTargetObjectFieldIdName';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
-import { useUpdatePageLayoutWidget } from '@/page-layout/hooks/useUpdatePageLayoutWidget';
import { isPageLayoutInEditModeComponentState } from '@/page-layout/states/isPageLayoutInEditModeComponentState';
import { pageLayoutEditingWidgetIdComponentState } from '@/page-layout/states/pageLayoutEditingWidgetIdComponentState';
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
-import { DashboardsBlockEditor } from '@/page-layout/widgets/standalone-rich-text/components/DashboardsBlockEditor';
-import { StandaloneRichTextWidgetAutoFocusEffect } from '@/page-layout/widgets/standalone-rich-text/components/StandaloneRichTextWidgetAutoFocusEffect';
-import { BLOCK_EDITOR_GLOBAL_HOTKEYS_CONFIG } from '@/ui/input/editor/constants/BlockEditorGlobalHotkeysConfig';
-import { useAttachmentSync } from '@/ui/input/editor/hooks/useAttachmentSync';
-import { parseInitialBlocknote } from '@/ui/input/editor/utils/parseInitialBlocknote';
-import { prepareBodyWithSignedUrls } from '@/ui/input/editor/utils/prepareBodyWithSignedUrls';
+import { StandaloneRichTextEditorContent } from '@/page-layout/widgets/standalone-rich-text/components/StandaloneRichTextEditorContent';
import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext';
-import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
-import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById';
-import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
-import '@blocknote/core/fonts/inter.css';
-import '@blocknote/mantine/style.css';
-import { useCreateBlockNote } from '@blocknote/react';
-import '@blocknote/react/style.css';
import styled from '@emotion/styled';
import { isDefined } from 'twenty-shared/utils';
-import { useDebouncedCallback } from 'use-debounce';
import {
FeatureFlagKey,
PageLayoutType,
- WidgetConfigurationType,
type StandaloneRichTextConfiguration,
} from '~/generated/graphql';
@@ -64,17 +47,11 @@ export const StandaloneRichTextWidget = ({
pageLayoutEditingWidgetIdComponentState,
);
- const { updatePageLayoutWidget } = useUpdatePageLayoutWidget();
const { targetRecordIdentifier, layoutType } = useLayoutRenderingContext();
- const { uploadAttachmentFile } = useUploadAttachmentFile();
const isAttachmentMigrated = useIsFeatureEnabled(
FeatureFlagKey.IS_ATTACHMENT_MIGRATED,
);
- const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
- const { removeFocusItemFromFocusStackById } =
- useRemoveFocusItemFromFocusStackById();
-
const isDashboard = layoutType === PageLayoutType.DASHBOARD;
const dashboardId = isDashboard ? targetRecordIdentifier?.id : undefined;
const attachmentTargetFieldIdName = getActivityTargetObjectFieldIdName({
@@ -96,84 +73,9 @@ export const StandaloneRichTextWidget = ({
skip: !isDefined(dashboardId),
});
- const { syncAttachments } = useAttachmentSync(attachments);
-
- const handleUploadAttachment = async (file: File) => {
- if (!isDefined(dashboardId)) return { attachmentAbsoluteURL: '' };
-
- return await uploadAttachmentFile(file, {
- id: dashboardId,
- targetObjectNameSingular: CoreObjectNameSingular.Dashboard,
- });
- };
-
- const handleEditorBuiltInUploadFile = async (file: File) => {
- const { attachmentAbsoluteURL } = await handleUploadAttachment(file);
- return attachmentAbsoluteURL;
- };
-
- const initialContent = useMemo(() => {
- if (isDefined(configuration) && 'body' in configuration) {
- return parseInitialBlocknote(configuration.body?.blocknote);
- }
- return undefined;
- }, [configuration]);
-
- const editor = useCreateBlockNote({
- initialContent,
- domAttributes: { editor: { class: 'editor' } },
- schema: BLOCK_SCHEMA,
- uploadFile: handleEditorBuiltInUploadFile,
- sideMenuDetection: 'editor',
- });
-
- const handlePersistBody = useDebouncedCallback((blocknote: string) => {
- updatePageLayoutWidget(widget.id, {
- configuration: {
- configurationType: WidgetConfigurationType.STANDALONE_RICH_TEXT,
- body: {
- blocknote,
- markdown: null,
- },
- },
- });
- }, 300);
-
- const handleAttachmentSync = useDebouncedCallback(
- async (newStringifiedBody: string, previousBody: string) => {
- await syncAttachments(newStringifiedBody, previousBody);
- },
- 500,
- );
-
- const handleEditorChange = () => {
- const newStringifiedBody = JSON.stringify(editor.document) ?? '';
- const preparedBody = prepareBodyWithSignedUrls(newStringifiedBody);
-
- handlePersistBody(preparedBody);
- handleAttachmentSync(newStringifiedBody, currentBody);
- };
-
const isThisWidgetBeingEdited = editingWidgetId === widget.id;
const isEditable = isPageLayoutInEditMode && isThisWidgetBeingEdited;
- const handleBlockEditorFocus = useCallback(() => {
- pushFocusItemToFocusStack({
- component: {
- instanceId: widget.id,
- type: FocusComponentType.STANDALONE_RICH_TEXT_WIDGET,
- },
- focusId: widget.id,
- globalHotkeysConfig: BLOCK_EDITOR_GLOBAL_HOTKEYS_CONFIG,
- });
- }, [pushFocusItemToFocusStack, widget.id]);
-
- const handleBlockEditorBlur = useCallback(() => {
- removeFocusItemFromFocusStackById({
- focusId: widget.id,
- });
- }, [removeFocusItemFromFocusStackById, widget.id]);
-
if (!isDefined(dashboardId)) {
return null;
}
@@ -183,21 +85,17 @@ export const StandaloneRichTextWidget = ({
ref={containerElementRef}
isPageLayoutInEditMode={isPageLayoutInEditMode}
>
-
-
diff --git a/packages/twenty-front/src/modules/page-layout/widgets/standalone-rich-text/components/StandaloneRichTextWidgetAutoFocusEffect.tsx b/packages/twenty-front/src/modules/page-layout/widgets/standalone-rich-text/components/StandaloneRichTextWidgetAutoFocusEffect.tsx
index 748f870b02..122e77cf2a 100644
--- a/packages/twenty-front/src/modules/page-layout/widgets/standalone-rich-text/components/StandaloneRichTextWidgetAutoFocusEffect.tsx
+++ b/packages/twenty-front/src/modules/page-layout/widgets/standalone-rich-text/components/StandaloneRichTextWidgetAutoFocusEffect.tsx
@@ -1,6 +1,5 @@
-import { useEffect } from 'react';
-
import { type BLOCK_SCHEMA } from '@/activities/blocks/constants/Schema';
+import { useEffect } from 'react';
type StandaloneRichTextWidgetAutoFocusEffectProps = {
shouldFocus: boolean;