Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactored and improved seeds #8695

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ export const FieldInput = ({
onShiftTab={onShiftTab}
/>
) : isFieldRichText(fieldDefinition) ? (
<RichTextFieldInput />
<RichTextFieldInput onClickOutside={onClickOutside} />
lucasbordeau marked this conversation as resolved.
Show resolved Hide resolved
) : isFieldArray(fieldDefinition) ? (
<ArrayFieldInput onCancel={onCancel} />
) : (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { useContext } from 'react';

import { isFieldActor } from '@/object-record/record-field/types/guards/isFieldActor';
import { isFieldRichText } from '@/object-record/record-field/types/guards/isFieldRichText';
import { FieldContext } from '../contexts/FieldContext';
import { isFieldMetadataReadOnly } from '../utils/isFieldMetadataReadOnly';

Expand All @@ -10,9 +9,5 @@ export const useIsFieldReadOnly = () => {

const { metadata } = fieldDefinition;

return (
isFieldActor(fieldDefinition) ||
isFieldRichText(fieldDefinition) ||
isFieldMetadataReadOnly(metadata)
);
return isFieldActor(fieldDefinition) || isFieldMetadataReadOnly(metadata);
};
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import { EntityForSelect } from '@/object-record/relation-picker/types/EntityFor

import { isFieldArray } from '@/object-record/record-field/types/guards/isFieldArray';
import { isFieldArrayValue } from '@/object-record/record-field/types/guards/isFieldArrayValue';
import { isFieldRichText } from '@/object-record/record-field/types/guards/isFieldRichText';
import { isFieldRichTextValue } from '@/object-record/record-field/types/guards/isFieldRichTextValue';
import { FieldContext } from '../contexts/FieldContext';
import { isFieldBoolean } from '../types/guards/isFieldBoolean';
import { isFieldBooleanValue } from '../types/guards/isFieldBooleanValue';
Expand Down Expand Up @@ -111,6 +113,10 @@ export const usePersistField = () => {
isFieldRawJson(fieldDefinition) &&
isFieldRawJsonValue(valueToPersist);

const fieldIsRichText =
isFieldRichText(fieldDefinition) &&
isFieldRichTextValue(valueToPersist);

const fieldIsArray =
isFieldArray(fieldDefinition) && isFieldArrayValue(valueToPersist);

Expand All @@ -131,7 +137,8 @@ export const usePersistField = () => {
fieldIsMultiSelect ||
fieldIsAddress ||
fieldIsRawJson ||
fieldIsArray;
fieldIsArray ||
fieldIsRichText;

if (isValuePersistable) {
const fieldName = fieldDefinition.metadata.fieldName;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { useTextFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useTextFieldDisplay';
import { useRichTextFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useRichTextFieldDisplay';
import { getFirstNonEmptyLineOfRichText } from '@/ui/input/editor/utils/getFirstNonEmptyLineOfRichText';
import { PartialBlock } from '@blocknote/core';

export const RichTextFieldDisplay = () => {
const { fieldValue } = useTextFieldDisplay();
const parsedField =
fieldValue === '' ? null : (JSON.parse(fieldValue) as PartialBlock[]);
const { fieldValue } = useRichTextFieldDisplay();

return <>{getFirstNonEmptyLineOfRichText(parsedField)}</>;
return (
<div>
<span>{getFirstNonEmptyLineOfRichText(fieldValue)}</span>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { useContext } from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';

import { useRecordFieldInput } from '@/object-record/record-field/hooks/useRecordFieldInput';
import { FieldRichTextValue } from '@/object-record/record-field/types/FieldMetadata';
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
import { FieldMetadataType } from '~/generated-metadata/graphql';

import { usePersistField } from '@/object-record/record-field/hooks/usePersistField';
import { isFieldRichText } from '@/object-record/record-field/types/guards/isFieldRichText';
import { isFieldRichTextValue } from '@/object-record/record-field/types/guards/isFieldRichTextValue';
import { PartialBlock } from '@blocknote/core';
import { isNonEmptyString } from '@sniptt/guards';
import { FieldContext } from '../../contexts/FieldContext';
import { assertFieldMetadata } from '../../types/guards/assertFieldMetadata';

export const useRichTextField = () => {
const { recordId, fieldDefinition, hotkeyScope, maxWidth } =
useContext(FieldContext);

assertFieldMetadata(
FieldMetadataType.RichText,
isFieldRichText,
fieldDefinition,
);

const fieldName = fieldDefinition.metadata.fieldName;

const [fieldValue, setFieldValue] = useRecoilState<FieldRichTextValue>(
recordStoreFamilySelector({
recordId,
fieldName: fieldName,
}),
);
const fieldRichTextValue = isFieldRichTextValue(fieldValue) ? fieldValue : '';
lucasbordeau marked this conversation as resolved.
Show resolved Hide resolved

const { setDraftValue, getDraftValueSelector } =
useRecordFieldInput<FieldRichTextValue>(`${recordId}-${fieldName}`);

const draftValue = useRecoilValue(getDraftValueSelector());

const draftValueParsed: PartialBlock[] = isNonEmptyString(draftValue)
? JSON.parse(draftValue)
: draftValue;
Comment on lines +42 to +44
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: JSON.parse could throw if draftValue is malformed JSON - needs try/catch


const persistField = usePersistField();

const persistRichTextField = (nextValue: PartialBlock[]) => {
if (!nextValue) persistField(null);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Missing return after setting null - could cause undefined behavior by continuing execution


const parsedValueToPersist = JSON.stringify(nextValue);

try {
persistField(parsedValueToPersist);
} catch (error) {
console.error('Failed to persist rich text field', error);
}
};

return {
draftValue: draftValueParsed,
setDraftValue,
maxWidth,
fieldDefinition,
fieldValue: fieldRichTextValue,
setFieldValue,
hotkeyScope,
persistRichTextField,
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useContext } from 'react';

import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';

import { FieldRichTextValue } from '@/object-record/record-field/types/FieldMetadata';
import { assertFieldMetadata } from '@/object-record/record-field/types/guards/assertFieldMetadata';
import { isFieldRichText } from '@/object-record/record-field/types/guards/isFieldRichText';
import { PartialBlock } from '@blocknote/core';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { parseJson } from '~/utils/parseJson';
import { FieldContext } from '../../contexts/FieldContext';

export const useRichTextFieldDisplay = () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: hook uses FieldContext but doesn't handle case where context is undefined

const { recordId, fieldDefinition, hotkeyScope } = useContext(FieldContext);

assertFieldMetadata(
FieldMetadataType.RichText,
isFieldRichText,
fieldDefinition,
);

const fieldName = fieldDefinition.metadata.fieldName;

const fieldValue = useRecordFieldValue<FieldRichTextValue | undefined>(
recordId,
fieldName,
);

const fieldValueParsed = parseJson<PartialBlock[]>(fieldValue);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: parseJson could return undefined/null - need to handle this case explicitly to avoid runtime errors when using fieldValueParsed


return {
fieldDefinition,
fieldValue: fieldValueParsed,
hotkeyScope,
};
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,101 @@
import { RichTextFieldDisplay } from '@/object-record/record-field/meta-types/display/components/RichTextFieldDisplay';
import { BLOCK_SCHEMA } from '@/activities/blocks/constants/Schema';
import { useRichTextField } from '@/object-record/record-field/meta-types/hooks/useRichTextField';
import { useRegisterInputEvents } from '@/object-record/record-field/meta-types/input/hooks/useRegisterInputEvents';
import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent';
import { BlockEditor } from '@/ui/input/editor/components/BlockEditor';
import { getFirstNonEmptyLineOfRichText } from '@/ui/input/editor/utils/getFirstNonEmptyLineOfRichText';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
import { PartialBlock } from '@blocknote/core';
import { useCreateBlockNote } from '@blocknote/react';
import styled from '@emotion/styled';

export const RichTextFieldInput = () => {
return <RichTextFieldDisplay />;
import {
autoUpdate,
flip,
FloatingPortal,
offset,
size,
useFloating,
} from '@floating-ui/react';
import { useRef } from 'react';

const StyledRichTextDropdownMenu = styled(DropdownMenu)`
overflow: scroll;
`;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: using 'overflow: scroll' forces scrollbars even when not needed. Consider using 'overflow: auto' instead

export type RichTextFieldInputProps = {
onClickOutside?: FieldInputEvent;
};

export const RichTextFieldInput = ({
onClickOutside,
}: RichTextFieldInputProps) => {
const containerRef = useRef<HTMLDivElement>(null);
const { draftValue, hotkeyScope, persistRichTextField } = useRichTextField();

const firstLine = getFirstNonEmptyLineOfRichText(draftValue);

const editor = useCreateBlockNote({
initialContent: draftValue,
domAttributes: { editor: { class: 'editor' } },
schema: BLOCK_SCHEMA,
});

lucasbordeau marked this conversation as resolved.
Show resolved Hide resolved
const { refs, floatingStyles } = useFloating({
placement: 'bottom',
middleware: [
flip(),
size({
padding: 32,
apply: ({ availableHeight, elements }) => {
elements.floating.style.maxHeight =
availableHeight >= elements.floating.scrollHeight
? ''
: `${availableHeight}px`;

elements.floating.style.height = 'auto';
},
boundary: document.querySelector('#root') ?? undefined,
}),
lucasbordeau marked this conversation as resolved.
Show resolved Hide resolved
offset({
crossAxis: -6,
mainAxis: 8,
}),
],
whileElementsMounted: autoUpdate,
strategy: 'absolute',
});

const handleClickOutside = () => {
onClickOutside?.(() => persistRichTextField(editor.document));
};

useRegisterInputEvents<PartialBlock[]>({
inputRef: containerRef,
copyRef: refs.floating,
inputValue: draftValue,
onClickOutside: handleClickOutside,
hotkeyScope,
});

return (
<div ref={containerRef}>
<div
ref={refs.setReference}
style={{ width: '100%', height: '100%', padding: 6 }}
>
{firstLine}
</div>
<FloatingPortal>
<StyledRichTextDropdownMenu
width={500}
data-select-disable
ref={refs.setFloating}
style={floatingStyles}
>
<BlockEditor editor={editor} />
</StyledRichTextDropdownMenu>
</FloatingPortal>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ export const useRegisterInputEvents = <T>({
inputRef: React.RefObject<any>;
copyRef?: React.RefObject<any>;
inputValue: T;
onEscape: (inputValue: T) => void;
onEnter: (inputValue: T) => void;
onEscape?: (inputValue: T) => void;
onEnter?: (inputValue: T) => void;
onTab?: (inputValue: T) => void;
onShiftTab?: (inputValue: T) => void;
onClickOutside?: (event: MouseEvent | TouchEvent, inputValue: T) => void;
Expand Down
Loading
Loading