Activity as standard object (#6219)

In this PR I layout the first steps to migrate Activity to a traditional
Standard objects

Since this is a big transition, I'd rather split it into several
deployments / PRs

<img width="1512" alt="image"
src="https://github.com/user-attachments/assets/012e2bbf-9d1b-4723-aaf6-269ef588b050">

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
Co-authored-by: bosiraphael <71827178+bosiraphael@users.noreply.github.com>
Co-authored-by: Weiko <corentin@twenty.com>
Co-authored-by: Faisal-imtiyaz123 <142205282+Faisal-imtiyaz123@users.noreply.github.com>
Co-authored-by: Prateek Jain <prateekj1171998@gmail.com>
This commit is contained in:
Félix Malfait
2024-07-31 15:36:11 +02:00
committed by GitHub
parent defcee2a02
commit 80c0fc7ff1
239 changed files with 18418 additions and 8671 deletions

View File

@ -1,129 +0,0 @@
import styled from '@emotion/styled';
import { isNonEmptyString } from '@sniptt/guards';
import { useRecoilValue } from 'recoil';
import { v4 } from 'uuid';
import { Comment } from '@/activities/comment/Comment';
import { Comment as CommentType } from '@/activities/types/Comment';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import {
AutosizeTextInput,
AutosizeTextInputVariant,
} from '@/ui/input/components/AutosizeTextInput';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
const StyledThreadItemListContainer = styled.div`
align-items: flex-start;
border-top: 1px solid ${({ theme }) => theme.border.color.light};
box-sizing: border-box;
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(4)};
justify-content: flex-start;
padding: ${({ theme }) => theme.spacing(8)};
padding-left: ${({ theme }) => theme.spacing(12)};
width: 100%;
`;
const StyledCommentActionBar = styled.div`
background: ${({ theme }) => theme.background.primary};
border-top: 1px solid ${({ theme }) => theme.border.color.light};
display: flex;
padding: 16px 24px 16px 48px;
width: calc(
${({ theme }) => (useIsMobile() ? '100%' : theme.rightDrawerWidth)} - 72px
);
`;
const StyledThreadCommentTitle = styled.div`
color: ${({ theme }) => theme.font.color.tertiary};
font-size: ${({ theme }) => theme.font.size.xs};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
text-transform: uppercase;
`;
type ActivityCommentsProps = {
activityId: string;
scrollableContainerRef: React.RefObject<HTMLDivElement>;
};
export const ActivityComments = ({
activityId,
scrollableContainerRef,
}: ActivityCommentsProps) => {
const { createOneRecord: createOneComment } = useCreateOneRecord({
objectNameSingular: CoreObjectNameSingular.Comment,
});
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const { records: comments } = useFindManyRecords({
objectNameSingular: CoreObjectNameSingular.Comment,
skip: !isNonEmptyString(activityId),
filter: {
activityId: {
eq: activityId,
},
},
});
if (!currentWorkspaceMember) {
return <></>;
}
const handleSendComment = (commentText: string) => {
if (!isNonEmptyString(commentText)) {
return;
}
createOneComment?.({
id: v4(),
authorId: currentWorkspaceMember?.id ?? '',
author: currentWorkspaceMember,
activityId: activityId,
body: commentText,
createdAt: new Date().toISOString(),
});
};
const handleFocus = () => {
const scrollableContainer = scrollableContainerRef.current;
scrollableContainer?.scrollTo({
top: scrollableContainer.scrollHeight,
behavior: 'smooth',
});
};
return (
<>
{comments.length > 0 && (
<>
<StyledThreadItemListContainer>
<StyledThreadCommentTitle>Comments</StyledThreadCommentTitle>
{comments?.map((comment) => (
<Comment key={comment.id} comment={comment as CommentType} />
))}
</StyledThreadItemListContainer>
</>
)}
<StyledCommentActionBar>
{currentWorkspaceMember && (
<AutosizeTextInput
onValidate={handleSendComment}
onFocus={handleFocus}
variant={AutosizeTextInputVariant.Button}
placeholder={comments.length > 0 ? 'Reply...' : undefined}
/>
)}
</StyledCommentActionBar>
</>
);
};

View File

@ -1,43 +0,0 @@
import styled from '@emotion/styled';
import { useRecoilState } from 'recoil';
import { Activity } from '@/activities/types/Activity';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { beautifyPastDateRelativeToNow } from '~/utils/date-utils';
const StyledCreationDisplay = styled.span`
color: ${({ theme }) => theme.font.color.light};
display: flex;
font-size: ${({ theme }) => theme.font.size.sm};
user-select: none;
width: 100%;
`;
type ActivityCreationDateProps = {
activityId: string;
};
export const ActivityCreationDate = ({
activityId,
}: ActivityCreationDateProps) => {
const [activityInStore] = useRecoilState(recordStoreFamilyState(activityId));
const activity = activityInStore as Activity;
const beautifiedDate = activity.createdAt
? beautifyPastDateRelativeToNow(activity.createdAt)
: null;
const authorName = activity.author?.name
? `${activity.author.name.firstName} ${activity.author.name.lastName}`
: null;
if (!activity.createdAt || !authorName) {
return <></>;
}
return (
<StyledCreationDisplay>
Created {beautifiedDate} by {authorName}
</StyledCreationDisplay>
);
};

View File

@ -1,89 +0,0 @@
import styled from '@emotion/styled';
import { useRef } from 'react';
import { ActivityBodyEditor } from '@/activities/components/ActivityBodyEditor';
import { ActivityBodyEffect } from '@/activities/components/ActivityBodyEffect';
import { ActivityComments } from '@/activities/components/ActivityComments';
import { ActivityCreationDate } from '@/activities/components/ActivityCreationDate';
import { ActivityEditorFields } from '@/activities/components/ActivityEditorFields';
import { ActivityTitleEffect } from '@/activities/components/ActivityTitleEffect';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { ActivityTitle } from './ActivityTitle';
const StyledContainer = styled.div`
box-sizing: border-box;
display: flex;
flex-direction: column;
height: 100%;
overflow-y: auto;
gap: ${({ theme }) => theme.spacing(4)};
`;
const StyledUpperPartContainer = styled.div`
align-items: flex-start;
box-sizing: border-box;
display: flex;
flex-direction: column;
justify-content: flex-start;
`;
const StyledTitleContainer = styled.div`
display: flex;
flex-direction: column;
width: 100%;
gap: ${({ theme }) => theme.spacing(2)};
`;
const StyledTopContainer = styled.div`
align-items: flex-start;
align-self: stretch;
background: ${({ theme }) => theme.background.secondary};
border-bottom: ${({ theme }) =>
useIsMobile() ? 'none' : `1px solid ${theme.border.color.medium}`};
display: flex;
flex-direction: column;
gap: 24px;
padding: ${({ theme }) => theme.spacing(6)};
`;
type ActivityEditorProps = {
activityId: string;
showComment?: boolean;
fillTitleFromBody?: boolean;
};
export const ActivityEditor = ({
activityId,
showComment = true,
fillTitleFromBody = false,
}: ActivityEditorProps) => {
const containerRef = useRef<HTMLDivElement>(null);
return (
<StyledContainer ref={containerRef}>
<StyledUpperPartContainer>
<StyledTopContainer>
<ActivityTitleEffect activityId={activityId} />
<StyledTitleContainer>
<ActivityTitle activityId={activityId} />
<ActivityCreationDate activityId={activityId} />
</StyledTitleContainer>
<ActivityEditorFields activityId={activityId} />
</StyledTopContainer>
</StyledUpperPartContainer>
<ActivityBodyEffect activityId={activityId} />
<ActivityBodyEditor
activityId={activityId}
fillTitleFromBody={fillTitleFromBody}
/>
{showComment && (
<ActivityComments
activityId={activityId}
scrollableContainerRef={containerRef}
/>
)}
</StyledContainer>
);
};

View File

@ -1,102 +0,0 @@
import { useRecoilCallback } from 'recoil';
import { useUpsertActivity } from '@/activities/hooks/useUpsertActivity';
import { activityBodyFamilyState } from '@/activities/states/activityBodyFamilyState';
import { activityTitleFamilyState } from '@/activities/states/activityTitleFamilyState';
import { canCreateActivityState } from '@/activities/states/canCreateActivityState';
import { isActivityInCreateModeState } from '@/activities/states/isActivityInCreateModeState';
import { isUpsertingActivityInDBState } from '@/activities/states/isCreatingActivityInDBState';
import { Activity } from '@/activities/types/Activity';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useDeleteRecordFromCache } from '@/object-record/cache/hooks/useDeleteRecordFromCache';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { RIGHT_DRAWER_CLICK_OUTSIDE_LISTENER_ID } from '@/ui/layout/right-drawer/constants/RightDrawerClickOutsideListener';
import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener';
import { isDefined } from '~/utils/isDefined';
export const ActivityEditorEffect = ({
activityId,
}: {
activityId: string;
}) => {
const { useRegisterClickOutsideListenerCallback } = useClickOutsideListener(
RIGHT_DRAWER_CLICK_OUTSIDE_LISTENER_ID,
);
const { upsertActivity } = useUpsertActivity();
const deleteRecordFromCache = useDeleteRecordFromCache({
objectNameSingular: CoreObjectNameSingular.Activity,
});
const upsertActivityCallback = useRecoilCallback(
({ snapshot, set }) =>
() => {
const isUpsertingActivityInDB = snapshot
.getLoadable(isUpsertingActivityInDBState)
.getValue();
const canCreateActivity = snapshot
.getLoadable(canCreateActivityState)
.getValue();
const isActivityInCreateMode = snapshot
.getLoadable(isActivityInCreateModeState)
.getValue();
const activityFromStore = snapshot
.getLoadable(recordStoreFamilyState(activityId))
.getValue();
const activity = activityFromStore as Activity | null;
const activityTitle = snapshot
.getLoadable(activityTitleFamilyState({ activityId }))
.getValue();
const activityBody = snapshot
.getLoadable(activityBodyFamilyState({ activityId }))
.getValue();
if (isUpsertingActivityInDB || !activityFromStore) {
return;
}
if (isActivityInCreateMode && isDefined(activity)) {
if (canCreateActivity) {
upsertActivity({
activity,
input: {
title: activityFromStore.title,
body: activityFromStore.body,
},
});
} else {
deleteRecordFromCache(activity);
}
set(isActivityInCreateModeState, false);
} else if (isDefined(activity)) {
if (
activity.title !== activityTitle ||
activity.body !== activityBody
) {
upsertActivity({
activity,
input: {
title: activityTitle,
body: activityBody,
},
});
}
}
},
[activityId, deleteRecordFromCache, upsertActivity],
);
useRegisterClickOutsideListenerCallback({
callbackId: 'activity-editor',
callbackFunction: upsertActivityCallback,
});
return <></>;
};

View File

@ -1,115 +0,0 @@
import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
import { useUpsertActivity } from '@/activities/hooks/useUpsertActivity';
import { ActivityTargetsInlineCell } from '@/activities/inline-cell/components/ActivityTargetsInlineCell';
import { Activity } from '@/activities/types/Activity';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
import { useFieldContext } from '@/object-record/hooks/useFieldContext';
import {
RecordUpdateHook,
RecordUpdateHookParams,
} from '@/object-record/record-field/contexts/FieldContext';
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { isRightDrawerAnimationCompletedState } from '@/ui/layout/right-drawer/states/isRightDrawerAnimationCompletedState';
import { isDefined } from '~/utils/isDefined';
const StyledPropertyBox = styled(PropertyBox)`
padding: 0;
`;
export const ActivityEditorFields = ({
activityId,
}: {
activityId: string;
}) => {
const { upsertActivity } = useUpsertActivity();
const isRightDrawerAnimationCompleted = useRecoilValue(
isRightDrawerAnimationCompletedState,
);
const getRecordFromCache = useGetRecordFromCache({
objectNameSingular: CoreObjectNameSingular.Activity,
});
const activityFromCache = getRecordFromCache<Activity>(activityId);
const activityFromStore = useRecoilValue(recordStoreFamilyState(activityId));
const activity = activityFromStore as Activity;
const useUpsertOneActivityMutation: RecordUpdateHook = () => {
const upsertActivityMutation = async ({
variables,
}: RecordUpdateHookParams) => {
if (isDefined(activityFromStore)) {
await upsertActivity({
activity: activityFromStore as Activity,
input: variables.updateOneRecordInput,
});
}
};
return [upsertActivityMutation, { loading: false }];
};
const { FieldContextProvider: ReminderAtFieldContextProvider } =
useFieldContext({
objectNameSingular: CoreObjectNameSingular.Activity,
objectRecordId: activityId,
fieldMetadataName: 'reminderAt',
fieldPosition: 0,
clearable: true,
customUseUpdateOneObjectHook: useUpsertOneActivityMutation,
});
const { FieldContextProvider: DueAtFieldContextProvider } = useFieldContext({
objectNameSingular: CoreObjectNameSingular.Activity,
objectRecordId: activityId,
fieldMetadataName: 'dueAt',
fieldPosition: 1,
clearable: true,
customUseUpdateOneObjectHook: useUpsertOneActivityMutation,
});
const { FieldContextProvider: AssigneeFieldContextProvider } =
useFieldContext({
objectNameSingular: CoreObjectNameSingular.Activity,
objectRecordId: activityId,
fieldMetadataName: 'assignee',
fieldPosition: 2,
clearable: true,
customUseUpdateOneObjectHook: useUpsertOneActivityMutation,
});
return (
<StyledPropertyBox>
{activity.type === 'Task' &&
ReminderAtFieldContextProvider &&
DueAtFieldContextProvider &&
AssigneeFieldContextProvider && (
<>
<ReminderAtFieldContextProvider>
<RecordInlineCell />
</ReminderAtFieldContextProvider>
<DueAtFieldContextProvider>
<RecordInlineCell />
</DueAtFieldContextProvider>
<AssigneeFieldContextProvider>
<RecordInlineCell />
</AssigneeFieldContextProvider>
</>
)}
{isDefined(activityFromCache) && isRightDrawerAnimationCompleted && (
<ActivityTargetsInlineCell
activity={activityFromCache}
maxWidth={340}
/>
)}
</StyledPropertyBox>
);
};

View File

@ -1,199 +0,0 @@
import { useRef } from 'react';
import { useApolloClient } from '@apollo/client';
import styled from '@emotion/styled';
import { isNonEmptyString } from '@sniptt/guards';
import { useRecoilState } from 'recoil';
import { Key } from 'ts-key-enum';
import { useDebouncedCallback } from 'use-debounce';
import { useUpsertActivity } from '@/activities/hooks/useUpsertActivity';
import { activityTitleFamilyState } from '@/activities/states/activityTitleFamilyState';
import { activityTitleHasBeenSetFamilyState } from '@/activities/states/activityTitleHasBeenSetFamilyState';
import { canCreateActivityState } from '@/activities/states/canCreateActivityState';
import { Activity } from '@/activities/types/Activity';
import { ActivityEditorHotkeyScope } from '@/activities/types/ActivityEditorHotkeyScope';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { modifyRecordFromCache } from '@/object-record/cache/utils/modifyRecordFromCache';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import {
Checkbox,
CheckboxShape,
CheckboxSize,
} from '@/ui/input/components/Checkbox';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { isDefined } from '~/utils/isDefined';
const StyledEditableTitleInput = styled.input<{
completed: boolean;
value: string;
}>`
background: transparent;
border: none;
color: ${({ theme, value }) =>
value ? theme.font.color.primary : theme.font.color.light};
display: flex;
flex-direction: column;
font-size: ${({ theme }) => theme.font.size.xl};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
line-height: ${({ theme }) => theme.text.lineHeight.md};
outline: none;
text-decoration: ${({ completed }) => (completed ? 'line-through' : 'none')};
&::placeholder {
color: ${({ theme }) => theme.font.color.light};
}
width: calc(100% - ${({ theme }) => theme.spacing(2)});
`;
const StyledContainer = styled.div`
display: flex;
flex-direction: row;
gap: ${({ theme }) => theme.spacing(2)};
width: 100%;
`;
type ActivityTitleProps = {
activityId: string;
};
export const ActivityTitle = ({ activityId }: ActivityTitleProps) => {
const [activityInStore, setActivityInStore] = useRecoilState(
recordStoreFamilyState(activityId),
);
const cache = useApolloClient().cache;
const [activityTitle, setActivityTitle] = useRecoilState(
activityTitleFamilyState({ activityId }),
);
const activity = activityInStore as Activity;
const [canCreateActivity, setCanCreateActivity] = useRecoilState(
canCreateActivityState,
);
const { upsertActivity } = useUpsertActivity();
const {
setHotkeyScopeAndMemorizePreviousScope,
goBackToPreviousHotkeyScope,
} = usePreviousHotkeyScope();
const titleInputRef = useRef<HTMLInputElement>(null);
useScopedHotkeys(
Key.Escape,
() => {
handleBlur();
},
ActivityEditorHotkeyScope.ActivityTitle,
);
const handleBlur = () => {
goBackToPreviousHotkeyScope();
titleInputRef.current?.blur();
};
const handleFocus = () => {
setHotkeyScopeAndMemorizePreviousScope(
ActivityEditorHotkeyScope.ActivityTitle,
);
};
const [activityTitleHasBeenSet, setActivityTitleHasBeenSet] = useRecoilState(
activityTitleHasBeenSetFamilyState({
activityId: activityId,
}),
);
const { objectMetadataItem: objectMetadataItemActivity } =
useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.Activity,
});
const persistTitleDebounced = useDebouncedCallback((newTitle: string) => {
upsertActivity({
activity,
input: {
title: newTitle,
},
});
if (!activityTitleHasBeenSet) {
setActivityTitleHasBeenSet(true);
}
}, 500);
const setTitleDebounced = useDebouncedCallback((newTitle: string) => {
setActivityInStore((currentActivity) => {
return {
...currentActivity,
id: activity.id,
title: newTitle,
__typename: activity.__typename,
};
});
if (isNonEmptyString(newTitle) && !canCreateActivity) {
setCanCreateActivity(true);
}
modifyRecordFromCache({
recordId: activity.id,
fieldModifiers: {
title: () => {
return newTitle;
},
},
cache: cache,
objectMetadataItem: objectMetadataItemActivity,
});
}, 500);
const handleTitleChange = (newTitle: string) => {
setActivityTitle(newTitle);
setTitleDebounced(newTitle);
persistTitleDebounced(newTitle);
};
const handleActivityCompletionChange = (value: boolean) => {
upsertActivity({
activity,
input: {
completedAt: value ? new Date().toISOString() : null,
},
});
};
const completed = isDefined(activity.completedAt);
return (
<StyledContainer>
{activity.type === 'Task' && (
<Checkbox
size={CheckboxSize.Large}
shape={CheckboxShape.Rounded}
checked={completed}
onCheckedChange={(value) => handleActivityCompletionChange(value)}
/>
)}
<StyledEditableTitleInput
autoComplete="off"
autoFocus
ref={titleInputRef}
placeholder={`${activity.type} title`}
onChange={(event) => handleTitleChange(event.target.value)}
value={activityTitle}
completed={completed}
onBlur={handleBlur}
onFocus={handleFocus}
/>
</StyledContainer>
);
};

View File

@ -1,28 +0,0 @@
import { useEffect } from 'react';
import { useRecoilState } from 'recoil';
import { activityTitleFamilyState } from '@/activities/states/activityTitleFamilyState';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { isDefined } from '~/utils/isDefined';
export const ActivityTitleEffect = ({ activityId }: { activityId: string }) => {
const [activityFromStore] = useRecoilState(
recordStoreFamilyState(activityId),
);
const [activityTitle, setActivityTitle] = useRecoilState(
activityTitleFamilyState({ activityId }),
);
useEffect(() => {
if (
activityTitle === '' &&
isDefined(activityFromStore) &&
activityTitle !== activityFromStore.title
) {
setActivityTitle(activityFromStore.title);
}
}, [activityFromStore, activityTitle, setActivityTitle]);
return <></>;
};

View File

@ -1,40 +0,0 @@
import { useTheme } from '@emotion/react';
import { useRecoilState } from 'recoil';
import {
Chip,
ChipAccent,
ChipSize,
ChipVariant,
IconCheckbox,
IconNotes,
} from 'twenty-ui';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
type ActivityTypeDropdownProps = {
activityId: string;
};
export const ActivityTypeDropdown = ({
activityId,
}: ActivityTypeDropdownProps) => {
const [activityInStore] = useRecoilState(recordStoreFamilyState(activityId));
const theme = useTheme();
return (
<Chip
label={activityInStore?.type}
leftComponent={
activityInStore?.type === 'Note' ? (
<IconNotes size={theme.icon.size.md} />
) : (
<IconCheckbox size={theme.icon.size.md} />
)
}
size={ChipSize.Large}
accent={ChipAccent.TextSecondary}
variant={ChipVariant.Highlighted}
/>
);
};

View File

@ -12,10 +12,8 @@ import { useUpsertActivity } from '@/activities/hooks/useUpsertActivity';
import { activityBodyFamilyState } from '@/activities/states/activityBodyFamilyState';
import { activityTitleHasBeenSetFamilyState } from '@/activities/states/activityTitleHasBeenSetFamilyState';
import { canCreateActivityState } from '@/activities/states/canCreateActivityState';
import { Activity } from '@/activities/types/Activity';
import { ActivityEditorHotkeyScope } from '@/activities/types/ActivityEditorHotkeyScope';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { modifyRecordFromCache } from '@/object-record/cache/utils/modifyRecordFromCache';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { BlockEditor } from '@/ui/input/editor/components/BlockEditor';
@ -29,22 +27,31 @@ import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
import { getFileType } from '../files/utils/getFileType';
import { getFileAbsoluteURI } from '~/utils/file/getFileAbsoluteURI';
import { Note } from '@/activities/types/Note';
import { Task } from '@/activities/types/Task';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import '@blocknote/core/fonts/inter.css';
import '@blocknote/mantine/style.css';
import '@blocknote/react/style.css';
import { getFileAbsoluteURI } from '~/utils/file/getFileAbsoluteURI';
type ActivityBodyEditorProps = {
type RichTextEditorProps = {
activityId: string;
fillTitleFromBody: boolean;
activityObjectNameSingular:
| CoreObjectNameSingular.Task
| CoreObjectNameSingular.Note;
};
export const ActivityBodyEditor = ({
export const RichTextEditor = ({
activityId,
fillTitleFromBody,
}: ActivityBodyEditorProps) => {
activityObjectNameSingular,
}: RichTextEditorProps) => {
const [activityInStore] = useRecoilState(recordStoreFamilyState(activityId));
const cache = useApolloClient().cache;
const activity = activityInStore as Activity | null;
const activity = activityInStore as Task | Note | null;
const [activityTitleHasBeenSet, setActivityTitleHasBeenSet] = useRecoilState(
activityTitleHasBeenSetFamilyState({
@ -60,7 +67,7 @@ export const ActivityBodyEditor = ({
const { objectMetadataItem: objectMetadataItemActivity } =
useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.Activity,
objectNameSingular: activityObjectNameSingular,
});
const {
@ -68,7 +75,9 @@ export const ActivityBodyEditor = ({
setHotkeyScopeAndMemorizePreviousScope,
} = usePreviousHotkeyScope();
const { upsertActivity } = useUpsertActivity();
const { upsertActivity } = useUpsertActivity({
activityObjectNameSingular: activityObjectNameSingular,
});
const persistBodyDebounced = useDebouncedCallback((newBody: string) => {
if (isDefined(activity)) {