RICH_TEXT_V2 backend (#9848)
- Add RICH_TEXT_V2 composite type to backend. - Add `bodyV2` field to tasks and notes. - Minimum required frontend changes to avoid errors when creating a note [Testing instructions](https://github.com/twentyhq/twenty/pull/9690#issuecomment-2602378218) --------- Co-authored-by: ad-elias <elias@autodiligence.com> Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
@ -8,6 +8,7 @@
|
||||
"@aws-sdk/credential-providers": "^3.363.0",
|
||||
"@blocknote/mantine": "^0.22.0",
|
||||
"@blocknote/react": "^0.22.0",
|
||||
"@blocknote/server-util": "0.17.1",
|
||||
"@codesandbox/sandpack-react": "^2.13.5",
|
||||
"@dagrejs/dagre": "^1.1.2",
|
||||
"@emotion/react": "^11.11.1",
|
||||
|
||||
@ -541,6 +541,7 @@ export enum FieldMetadataType {
|
||||
RAW_JSON = 'RAW_JSON',
|
||||
RELATION = 'RELATION',
|
||||
RICH_TEXT = 'RICH_TEXT',
|
||||
RICH_TEXT_V2 = 'RICH_TEXT_V2',
|
||||
SELECT = 'SELECT',
|
||||
TEXT = 'TEXT',
|
||||
TS_VECTOR = 'TS_VECTOR',
|
||||
|
||||
@ -396,6 +396,7 @@ export enum FeatureFlagKey {
|
||||
IsMicrosoftSyncEnabled = 'IsMicrosoftSyncEnabled',
|
||||
IsNewRelationEnabled = 'IsNewRelationEnabled',
|
||||
IsPostgreSQLIntegrationEnabled = 'IsPostgreSQLIntegrationEnabled',
|
||||
IsRichTextV2Enabled = 'IsRichTextV2Enabled',
|
||||
IsStripeIntegrationEnabled = 'IsStripeIntegrationEnabled',
|
||||
IsUniqueIndexesEnabled = 'IsUniqueIndexesEnabled',
|
||||
IsWorkflowEnabled = 'IsWorkflowEnabled'
|
||||
@ -473,6 +474,7 @@ export enum FieldMetadataType {
|
||||
RAW_JSON = 'RAW_JSON',
|
||||
RELATION = 'RELATION',
|
||||
RICH_TEXT = 'RICH_TEXT',
|
||||
RICH_TEXT_V2 = 'RICH_TEXT_V2',
|
||||
SELECT = 'SELECT',
|
||||
TEXT = 'TEXT',
|
||||
TS_VECTOR = 'TS_VECTOR',
|
||||
|
||||
@ -203,6 +203,7 @@ export type FieldMetadata =
|
||||
| FieldPhoneMetadata
|
||||
| FieldRatingMetadata
|
||||
| FieldRelationMetadata
|
||||
| FieldRichTextMetadata
|
||||
| FieldSelectMetadata
|
||||
| FieldMultiSelectMetadata
|
||||
| FieldTextMetadata
|
||||
|
||||
@ -3,5 +3,5 @@ import { PickLiteral } from '~/types/PickLiteral';
|
||||
|
||||
export type SettingsExcludedFieldType = PickLiteral<
|
||||
FieldType,
|
||||
'POSITION' | 'TS_VECTOR'
|
||||
'POSITION' | 'TS_VECTOR' | 'RICH_TEXT_V2'
|
||||
>;
|
||||
|
||||
@ -23,5 +23,6 @@ export const DEFAULT_ICONS_BY_FIELD_TYPE: Record<FieldMetadataType, string> = {
|
||||
[FieldMetadataType.NUMERIC]: 'IconUsers',
|
||||
[FieldMetadataType.POSITION]: 'IconUsers',
|
||||
[FieldMetadataType.RICH_TEXT]: 'IconUsers',
|
||||
[FieldMetadataType.RICH_TEXT_V2]: 'IconUsers',
|
||||
[FieldMetadataType.TS_VECTOR]: 'IconUsers',
|
||||
};
|
||||
|
||||
@ -75,6 +75,11 @@ export const seedFeatureFlags = async (
|
||||
workspaceId: workspaceId,
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
key: FeatureFlagKey.IsRichTextV2Enabled,
|
||||
workspaceId: workspaceId,
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
key: FeatureFlagKey.IsNewRelationEnabled,
|
||||
workspaceId: workspaceId,
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { QueryResultGetterHandlerInterface } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/interfaces/query-result-getter-handler.interface';
|
||||
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { FileService } from 'src/engine/core-modules/file/services/file.service';
|
||||
import { NoteWorkspaceEntity } from 'src/modules/note/standard-objects/note.workspace-entity';
|
||||
import { TaskWorkspaceEntity } from 'src/modules/task/standard-objects/task.workspace-entity';
|
||||
@ -11,20 +13,32 @@ type RichTextBody = RichTextBlock[];
|
||||
export class ActivityQueryResultGetterHandler
|
||||
implements QueryResultGetterHandlerInterface
|
||||
{
|
||||
constructor(private readonly fileService: FileService) {}
|
||||
constructor(
|
||||
private readonly fileService: FileService,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
) {}
|
||||
|
||||
async handle(
|
||||
activity: TaskWorkspaceEntity | NoteWorkspaceEntity,
|
||||
workspaceId: string,
|
||||
): Promise<TaskWorkspaceEntity | NoteWorkspaceEntity> {
|
||||
if (!activity.id || !activity.body) {
|
||||
const isRichTextV2Enabled = await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IsRichTextV2Enabled,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const blocknoteJson = isRichTextV2Enabled
|
||||
? activity.bodyV2?.blocknote
|
||||
: activity.body;
|
||||
|
||||
if (!activity.id || !blocknoteJson) {
|
||||
return activity;
|
||||
}
|
||||
|
||||
const body: RichTextBody = JSON.parse(activity.body);
|
||||
const blocknote: RichTextBody = JSON.parse(blocknoteJson);
|
||||
|
||||
const bodyWithSignedPayload = await Promise.all(
|
||||
body.map(async (block: RichTextBlock) => {
|
||||
const blocknoteWithSignedPayload = await Promise.all(
|
||||
blocknote.map(async (block: RichTextBlock) => {
|
||||
if (block.type !== 'image' || !block.props.url) {
|
||||
return block;
|
||||
}
|
||||
@ -49,9 +63,19 @@ export class ActivityQueryResultGetterHandler
|
||||
}),
|
||||
);
|
||||
|
||||
if (isRichTextV2Enabled) {
|
||||
return {
|
||||
...activity,
|
||||
bodyV2: {
|
||||
blocknote: JSON.stringify(blocknoteWithSignedPayload),
|
||||
markdown: activity.bodyV2?.markdown ?? null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...activity,
|
||||
body: JSON.stringify(bodyWithSignedPayload),
|
||||
body: JSON.stringify(blocknoteWithSignedPayload),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,6 +16,7 @@ import { AttachmentQueryResultGetterHandler } from 'src/engine/api/graphql/works
|
||||
import { PersonQueryResultGetterHandler } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/handlers/person-query-result-getter.handler';
|
||||
import { WorkspaceMemberQueryResultGetterHandler } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/handlers/workspace-member-query-result-getter.handler';
|
||||
import { CompositeInputTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/composite-input-type-definition.factory';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { FileService } from 'src/engine/core-modules/file/services/file.service';
|
||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
|
||||
@ -31,7 +32,10 @@ export class QueryResultGettersFactory {
|
||||
);
|
||||
private handlers: Map<string, QueryResultGetterHandlerInterface>;
|
||||
|
||||
constructor(private readonly fileService: FileService) {
|
||||
constructor(
|
||||
private readonly fileService: FileService,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
) {
|
||||
this.initializeHandlers();
|
||||
}
|
||||
|
||||
@ -43,8 +47,20 @@ export class QueryResultGettersFactory {
|
||||
'workspaceMember',
|
||||
new WorkspaceMemberQueryResultGetterHandler(this.fileService),
|
||||
],
|
||||
['note', new ActivityQueryResultGetterHandler(this.fileService)],
|
||||
['task', new ActivityQueryResultGetterHandler(this.fileService)],
|
||||
[
|
||||
'note',
|
||||
new ActivityQueryResultGetterHandler(
|
||||
this.fileService,
|
||||
this.featureFlagService,
|
||||
),
|
||||
],
|
||||
[
|
||||
'task',
|
||||
new ActivityQueryResultGetterHandler(
|
||||
this.fileService,
|
||||
this.featureFlagService,
|
||||
),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { ServerBlockNoteEditor } from '@blocknote/server-util';
|
||||
import { FieldMetadataType } from 'twenty-shared';
|
||||
|
||||
import {
|
||||
@ -15,9 +16,15 @@ import {
|
||||
FindOneResolverArgs,
|
||||
ResolverArgs,
|
||||
ResolverArgsType,
|
||||
UpdateManyResolverArgs,
|
||||
UpdateOneResolverArgs,
|
||||
} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||
|
||||
import {
|
||||
RichTextV2Metadata,
|
||||
richTextV2ValueSchema,
|
||||
} from 'src/engine/metadata-modules/field-metadata/composite-types/rich-text-v2.composite-type';
|
||||
import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
|
||||
|
||||
import { RecordPositionFactory } from './record-position.factory';
|
||||
@ -77,6 +84,41 @@ export class QueryRunnerArgsFactory {
|
||||
) ?? [],
|
||||
),
|
||||
} satisfies CreateManyResolverArgs;
|
||||
case ResolverArgsType.UpdateOne:
|
||||
return {
|
||||
...args,
|
||||
id: (args as UpdateOneResolverArgs).id,
|
||||
data: await this.overrideDataByFieldMetadata(
|
||||
(args as UpdateOneResolverArgs).data,
|
||||
options,
|
||||
fieldMetadataMapByNameByName,
|
||||
{
|
||||
argIndex: 0,
|
||||
shouldBackfillPosition,
|
||||
},
|
||||
),
|
||||
} satisfies UpdateOneResolverArgs;
|
||||
case ResolverArgsType.UpdateMany:
|
||||
return {
|
||||
...args,
|
||||
filter: await this.overrideFilterByFieldMetadata(
|
||||
(args as UpdateManyResolverArgs).filter,
|
||||
fieldMetadataMapByNameByName,
|
||||
),
|
||||
data: await Promise.all(
|
||||
(args as UpdateManyResolverArgs).data?.map((arg, index) =>
|
||||
this.overrideDataByFieldMetadata(
|
||||
arg,
|
||||
options,
|
||||
fieldMetadataMapByNameByName,
|
||||
{
|
||||
argIndex: index,
|
||||
shouldBackfillPosition,
|
||||
},
|
||||
),
|
||||
) ?? [],
|
||||
),
|
||||
} satisfies UpdateManyResolverArgs;
|
||||
case ResolverArgsType.FindOne:
|
||||
return {
|
||||
...args,
|
||||
@ -130,47 +172,73 @@ export class QueryRunnerArgsFactory {
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
fieldMetadataMapByNameByName: Record<string, FieldMetadataInterface>,
|
||||
argPositionBackfillInput: ArgPositionBackfillInput,
|
||||
) {
|
||||
): Promise<Partial<ObjectRecord>> {
|
||||
if (!data) {
|
||||
return;
|
||||
return Promise.resolve({});
|
||||
}
|
||||
|
||||
let isFieldPositionPresent = false;
|
||||
|
||||
const createArgPromiseByArgKey = Object.entries(data).map(
|
||||
async ([key, value]) => {
|
||||
const fieldMetadata = fieldMetadataMapByNameByName[key];
|
||||
const createArgByArgKeyPromises: Promise<[string, any]>[] = Object.entries(
|
||||
data,
|
||||
).map(async ([key, value]): Promise<[string, any]> => {
|
||||
const fieldMetadata = fieldMetadataMapByNameByName[key];
|
||||
|
||||
if (!fieldMetadata) {
|
||||
return [key, await Promise.resolve(value)];
|
||||
if (!fieldMetadata) {
|
||||
return [key, value];
|
||||
}
|
||||
|
||||
switch (fieldMetadata.type) {
|
||||
case FieldMetadataType.POSITION: {
|
||||
isFieldPositionPresent = true;
|
||||
|
||||
const newValue = await this.recordPositionFactory.create(
|
||||
value,
|
||||
{
|
||||
isCustom: options.objectMetadataItemWithFieldMaps.isCustom,
|
||||
nameSingular:
|
||||
options.objectMetadataItemWithFieldMaps.nameSingular,
|
||||
},
|
||||
options.authContext.workspace.id,
|
||||
argPositionBackfillInput.argIndex,
|
||||
);
|
||||
|
||||
return [key, newValue];
|
||||
}
|
||||
case FieldMetadataType.NUMBER:
|
||||
return [key, Number(value)] as const;
|
||||
case FieldMetadataType.RICH_TEXT_V2: {
|
||||
const richTextV2Value = richTextV2ValueSchema.parse(value);
|
||||
|
||||
switch (fieldMetadata.type) {
|
||||
case FieldMetadataType.POSITION:
|
||||
isFieldPositionPresent = true;
|
||||
const serverBlockNoteEditor = ServerBlockNoteEditor.create();
|
||||
|
||||
return [
|
||||
key,
|
||||
await this.recordPositionFactory.create(
|
||||
value,
|
||||
{
|
||||
isCustom: options.objectMetadataItemWithFieldMaps.isCustom,
|
||||
nameSingular:
|
||||
options.objectMetadataItemWithFieldMaps.nameSingular,
|
||||
},
|
||||
options.authContext.workspace.id,
|
||||
argPositionBackfillInput.argIndex,
|
||||
),
|
||||
];
|
||||
case FieldMetadataType.NUMBER:
|
||||
return [key, Number(value)];
|
||||
default:
|
||||
return [key, await Promise.resolve(value)];
|
||||
const convertedMarkdown = richTextV2Value.blocknote
|
||||
? await serverBlockNoteEditor.blocksToMarkdownLossy(
|
||||
JSON.parse(richTextV2Value.blocknote),
|
||||
)
|
||||
: null;
|
||||
|
||||
const convertedBlocknote = richTextV2Value.markdown
|
||||
? JSON.stringify(
|
||||
await serverBlockNoteEditor.tryParseMarkdownToBlocks(
|
||||
richTextV2Value.markdown,
|
||||
),
|
||||
)
|
||||
: null;
|
||||
|
||||
const valueInBothFormats: RichTextV2Metadata = {
|
||||
markdown: richTextV2Value.markdown || convertedMarkdown,
|
||||
blocknote: richTextV2Value.blocknote || convertedBlocknote,
|
||||
};
|
||||
|
||||
return [key, valueInBothFormats];
|
||||
}
|
||||
},
|
||||
);
|
||||
default:
|
||||
return [key, value];
|
||||
}
|
||||
});
|
||||
|
||||
const newArgEntries = await Promise.all(createArgPromiseByArgKey);
|
||||
const newArgEntries = await Promise.all(createArgByArgKeyPromises);
|
||||
|
||||
if (
|
||||
!isFieldPositionPresent &&
|
||||
|
||||
@ -0,0 +1,16 @@
|
||||
import { GraphQLInputObjectType, GraphQLString } from 'graphql';
|
||||
|
||||
const richTextV2LeafFilter = new GraphQLInputObjectType({
|
||||
name: 'RichTextV2LeafFilter',
|
||||
fields: {
|
||||
ilike: { type: GraphQLString },
|
||||
},
|
||||
});
|
||||
|
||||
export const RichTextV2FilterType = new GraphQLInputObjectType({
|
||||
name: 'RichTextV2Filter',
|
||||
fields: {
|
||||
blocknote: { type: richTextV2LeafFilter },
|
||||
markdown: { type: richTextV2LeafFilter },
|
||||
},
|
||||
});
|
||||
@ -29,6 +29,7 @@ import {
|
||||
} from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input';
|
||||
import { IDFilterType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input/id-filter.input-type';
|
||||
import { MultiSelectFilterType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input/multi-select-filter.input-type';
|
||||
import { RichTextV2FilterType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input/rich-text.input-type';
|
||||
import { SelectFilterType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input/select-filter.input-type';
|
||||
import {
|
||||
BigFloatScalarType,
|
||||
@ -116,6 +117,7 @@ export class TypeMapperService {
|
||||
[FieldMetadataType.POSITION, FloatFilterType],
|
||||
[FieldMetadataType.RAW_JSON, RawJsonFilterType],
|
||||
[FieldMetadataType.RICH_TEXT, StringFilterType],
|
||||
[FieldMetadataType.RICH_TEXT_V2, RichTextV2FilterType],
|
||||
[FieldMetadataType.ARRAY, ArrayFilterType],
|
||||
[FieldMetadataType.MULTI_SELECT, MultiSelectFilterType],
|
||||
[FieldMetadataType.SELECT, SelectFilterType],
|
||||
|
||||
@ -155,5 +155,13 @@ export const mapFieldMetadataToGraphqlQuery = (
|
||||
additionalPhones
|
||||
}
|
||||
`;
|
||||
} else if (fieldType === FieldMetadataType.RICH_TEXT_V2) {
|
||||
return `
|
||||
${field.name}
|
||||
{
|
||||
blocknote
|
||||
markdown
|
||||
}
|
||||
`;
|
||||
}
|
||||
};
|
||||
|
||||
@ -13,5 +13,6 @@ export enum FeatureFlagKey {
|
||||
IsJsonFilterEnabled = 'IS_JSON_FILTER_ENABLED',
|
||||
IsLocalizationEnabled = 'IS_LOCALIZATION_ENABLED',
|
||||
IsBillingPlansEnabled = 'IS_BILLING_PLANS_ENABLED',
|
||||
IsRichTextV2Enabled = 'IS_RICH_TEXT_V2_ENABLED',
|
||||
IsNewRelationEnabled = 'IS_NEW_RELATION_ENABLED',
|
||||
}
|
||||
|
||||
@ -266,6 +266,19 @@ const getSchemaComponentsProperties = ({
|
||||
type: 'object',
|
||||
};
|
||||
break;
|
||||
case FieldMetadataType.RICH_TEXT_V2:
|
||||
itemProperty = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
blocknote: {
|
||||
type: 'string',
|
||||
},
|
||||
markdown: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
};
|
||||
break;
|
||||
default:
|
||||
itemProperty = getFieldProperties(field.type);
|
||||
break;
|
||||
|
||||
@ -9,6 +9,7 @@ import { emailsCompositeType } from 'src/engine/metadata-modules/field-metadata/
|
||||
import { fullNameCompositeType } from 'src/engine/metadata-modules/field-metadata/composite-types/full-name.composite-type';
|
||||
import { linksCompositeType } from 'src/engine/metadata-modules/field-metadata/composite-types/links.composite-type';
|
||||
import { phonesCompositeType } from 'src/engine/metadata-modules/field-metadata/composite-types/phones.composite-type';
|
||||
import { richTextV2CompositeType } from 'src/engine/metadata-modules/field-metadata/composite-types/rich-text-v2.composite-type';
|
||||
|
||||
export const compositeTypeDefinitions = new Map<
|
||||
FieldMetadataType,
|
||||
@ -21,4 +22,5 @@ export const compositeTypeDefinitions = new Map<
|
||||
[FieldMetadataType.ACTOR, actorCompositeType],
|
||||
[FieldMetadataType.EMAILS, emailsCompositeType],
|
||||
[FieldMetadataType.PHONES, phonesCompositeType],
|
||||
[FieldMetadataType.RICH_TEXT_V2, richTextV2CompositeType],
|
||||
]);
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
import { FieldMetadataType } from 'twenty-shared';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { CompositeType } from 'src/engine/metadata-modules/field-metadata/interfaces/composite-type.interface';
|
||||
|
||||
export const richTextV2CompositeType: CompositeType = {
|
||||
type: FieldMetadataType.RICH_TEXT_V2,
|
||||
properties: [
|
||||
{
|
||||
name: 'blocknote',
|
||||
type: FieldMetadataType.TEXT,
|
||||
hidden: false,
|
||||
isRequired: false,
|
||||
},
|
||||
{
|
||||
name: 'markdown',
|
||||
type: FieldMetadataType.TEXT,
|
||||
hidden: false,
|
||||
isRequired: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const richTextV2ValueSchema = z.object({
|
||||
blocknote: z.string().nullable(),
|
||||
markdown: z.string().nullable(),
|
||||
});
|
||||
|
||||
export type RichTextV2Metadata = z.infer<typeof richTextV2ValueSchema>;
|
||||
@ -35,11 +35,22 @@ export class FieldMetadataDefaultValueRawJson {
|
||||
value: object | null;
|
||||
}
|
||||
|
||||
export class FieldMetadataDefaultValueRichTextV2 {
|
||||
@ValidateIf((object, value) => value !== null)
|
||||
@IsQuotedString()
|
||||
blocknote: string | null;
|
||||
|
||||
@ValidateIf((object, value) => value !== null)
|
||||
@IsQuotedString()
|
||||
markdown: string | null;
|
||||
}
|
||||
|
||||
export class FieldMetadataDefaultValueRichText {
|
||||
@ValidateIf((_object, value) => value !== null)
|
||||
@IsString()
|
||||
value: string | null;
|
||||
}
|
||||
|
||||
export class FieldMetadataDefaultValueNumber {
|
||||
@ValidateIf((object, value) => value !== null)
|
||||
@IsNumber()
|
||||
|
||||
@ -0,0 +1,23 @@
|
||||
import { FieldMetadataType } from 'twenty-shared';
|
||||
|
||||
import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
||||
|
||||
describe('computeCompositeColumnName', () => {
|
||||
it('should compute composite column name for rich text v2 field', () => {
|
||||
const fieldMetadata = {
|
||||
name: 'bodyV2',
|
||||
type: FieldMetadataType.RICH_TEXT_V2,
|
||||
};
|
||||
|
||||
const property = {
|
||||
name: 'markdown',
|
||||
type: FieldMetadataType.TEXT,
|
||||
hidden: false,
|
||||
isRequired: false,
|
||||
};
|
||||
|
||||
expect(computeCompositeColumnName(fieldMetadata, property)).toEqual(
|
||||
'bodyV2Markdown',
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -47,6 +47,11 @@ export function generateDefaultValue(
|
||||
primaryPhoneCallingCode: "''",
|
||||
additionalPhones: null,
|
||||
};
|
||||
case FieldMetadataType.RICH_TEXT_V2:
|
||||
return {
|
||||
blocknote: "''",
|
||||
markdown: "''",
|
||||
};
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -9,7 +9,8 @@ export const isCompositeFieldMetadataType = (
|
||||
| FieldMetadataType.LINKS
|
||||
| FieldMetadataType.ACTOR
|
||||
| FieldMetadataType.EMAILS
|
||||
| FieldMetadataType.PHONES => {
|
||||
| FieldMetadataType.PHONES
|
||||
| FieldMetadataType.RICH_TEXT_V2 => {
|
||||
return [
|
||||
FieldMetadataType.CURRENCY,
|
||||
FieldMetadataType.FULL_NAME,
|
||||
@ -18,5 +19,6 @@ export const isCompositeFieldMetadataType = (
|
||||
FieldMetadataType.ACTOR,
|
||||
FieldMetadataType.EMAILS,
|
||||
FieldMetadataType.PHONES,
|
||||
FieldMetadataType.RICH_TEXT_V2,
|
||||
].includes(type);
|
||||
};
|
||||
|
||||
@ -21,6 +21,7 @@ import {
|
||||
FieldMetadataDefaultValueNumber,
|
||||
FieldMetadataDefaultValuePhones,
|
||||
FieldMetadataDefaultValueRawJson,
|
||||
FieldMetadataDefaultValueRichTextV2,
|
||||
FieldMetadataDefaultValueString,
|
||||
FieldMetadataDefaultValueStringArray,
|
||||
FieldMetadataDefaultValueUuidFunction,
|
||||
@ -47,6 +48,7 @@ export const defaultValueValidatorsMap = {
|
||||
[FieldMetadataType.SELECT]: [FieldMetadataDefaultValueString],
|
||||
[FieldMetadataType.MULTI_SELECT]: [FieldMetadataDefaultValueStringArray],
|
||||
[FieldMetadataType.ADDRESS]: [FieldMetadataDefaultValueAddress],
|
||||
[FieldMetadataType.RICH_TEXT_V2]: [FieldMetadataDefaultValueRichTextV2],
|
||||
[FieldMetadataType.RICH_TEXT]: [FieldMetadataDefaultValueString],
|
||||
[FieldMetadataType.RAW_JSON]: [FieldMetadataDefaultValueRawJson],
|
||||
[FieldMetadataType.LINKS]: [FieldMetadataDefaultValueLinks],
|
||||
|
||||
@ -26,7 +26,8 @@ export type CompositeFieldMetadataType =
|
||||
| FieldMetadataType.FULL_NAME
|
||||
| FieldMetadataType.LINKS
|
||||
| FieldMetadataType.EMAILS
|
||||
| FieldMetadataType.PHONES;
|
||||
| FieldMetadataType.PHONES
|
||||
| FieldMetadataType.RICH_TEXT_V2;
|
||||
|
||||
@Injectable()
|
||||
export class CompositeColumnActionFactory extends ColumnActionAbstractFactory<CompositeFieldMetadataType> {
|
||||
|
||||
@ -94,6 +94,10 @@ export class WorkspaceMigrationFactory {
|
||||
FieldMetadataType.TS_VECTOR,
|
||||
{ factory: this.tsVectorColumnActionFactory },
|
||||
],
|
||||
[
|
||||
FieldMetadataType.RICH_TEXT_V2,
|
||||
{ factory: this.compositeColumnActionFactory },
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@ -36,6 +36,8 @@ export const getSubfieldsForAggregateOperation = (
|
||||
'primaryPhoneCountryCode',
|
||||
'primaryPhoneCallingCode',
|
||||
];
|
||||
case FieldMetadataType.RICH_TEXT_V2:
|
||||
return ['blocknote', 'markdown'];
|
||||
default:
|
||||
throw new Error(`Unsupported composite field type: ${fieldType}`);
|
||||
}
|
||||
|
||||
@ -288,6 +288,7 @@ export const NOTE_STANDARD_FIELD_IDS = {
|
||||
position: '20202020-368d-4dc2-943f-ed8a49c7fdfb',
|
||||
title: '20202020-faeb-4c76-8ba6-ccbb0b4a965f',
|
||||
body: '20202020-e63d-4e70-95be-a78cd9abe7ef',
|
||||
bodyV2: '20202020-a7bb-4d94-be51-8f25181502c8',
|
||||
createdBy: '20202020-0d79-4e21-ab77-5a394eff97be',
|
||||
noteTargets: '20202020-1f25-43fe-8b00-af212fdde823',
|
||||
attachments: '20202020-4986-4c92-bf19-39934b149b16',
|
||||
@ -355,6 +356,7 @@ export const TASK_STANDARD_FIELD_IDS = {
|
||||
position: '20202020-7d47-4690-8a98-98b9a0c05dd8',
|
||||
title: '20202020-b386-4cb7-aa5a-08d4a4d92680',
|
||||
body: '20202020-ce13-43f4-8821-69388fe1fd26',
|
||||
bodyV2: '20202020-4aa0-4ae8-898d-7df0afd47ab1',
|
||||
dueAt: '20202020-fd99-40da-951b-4cb9a352fce3',
|
||||
status: '20202020-70bc-48f9-89c5-6aa730b151e0',
|
||||
createdBy: '20202020-1a04-48ab-a567-576965ae5387',
|
||||
|
||||
@ -13,58 +13,6 @@ const nameFullNameField = {
|
||||
const jobTitleTextField = { name: 'jobTitle', type: FieldMetadataType.TEXT };
|
||||
const emailsEmailsField = { name: 'emails', type: FieldMetadataType.EMAILS };
|
||||
|
||||
jest.mock(
|
||||
'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util',
|
||||
() => ({
|
||||
computeColumnName: jest.fn((name) => {
|
||||
if (name === 'name') {
|
||||
return 'name';
|
||||
}
|
||||
if (name === 'jobTitle') {
|
||||
return 'jobTitle';
|
||||
}
|
||||
if (name === 'emailsPrimaryEmail') {
|
||||
return 'emailsPrimaryEmail';
|
||||
}
|
||||
if (name === 'emailsAdditionalEmails') {
|
||||
return 'emailsAdditionalEmails';
|
||||
}
|
||||
if (name === 'nameFirstName') {
|
||||
return 'nameFirstName';
|
||||
}
|
||||
if (name === 'nameLastName') {
|
||||
return 'nameLastName';
|
||||
}
|
||||
}),
|
||||
computeCompositeColumnName: jest.fn((field, property) => {
|
||||
if (
|
||||
field.name === emailsEmailsField.name &&
|
||||
property.name === 'primaryEmail'
|
||||
) {
|
||||
return 'emailsPrimaryEmail';
|
||||
}
|
||||
if (
|
||||
field.name === emailsEmailsField.name &&
|
||||
property.name === 'additionalEmails'
|
||||
) {
|
||||
return 'emailsAdditionalEmails';
|
||||
}
|
||||
if (
|
||||
field.name === nameFullNameField.name &&
|
||||
property.name === 'firstName'
|
||||
) {
|
||||
return 'nameFirstName';
|
||||
}
|
||||
if (
|
||||
field.name === nameFullNameField.name &&
|
||||
property.name === 'lastName'
|
||||
) {
|
||||
return 'nameLastName';
|
||||
}
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
describe('getTsVectorColumnExpressionFromFields', () => {
|
||||
it('should generate correct expression for simple text field', () => {
|
||||
const fields = [nameTextField] as FieldTypeAndNameMetadata[];
|
||||
@ -95,4 +43,24 @@ describe('getTsVectorColumnExpressionFromFields', () => {
|
||||
|
||||
expect(result.trim()).toBe(expected);
|
||||
});
|
||||
|
||||
it('should handle rich text fields', () => {
|
||||
const fields = [
|
||||
{ name: 'body', type: FieldMetadataType.RICH_TEXT },
|
||||
] as FieldTypeAndNameMetadata[];
|
||||
const result = getTsVectorColumnExpressionFromFields(fields);
|
||||
|
||||
expect(result).toBe("to_tsvector('simple', COALESCE(\"body\", ''))");
|
||||
});
|
||||
|
||||
it('should handle rich text v2 fields', () => {
|
||||
const fields = [
|
||||
{ name: 'bodyV2', type: FieldMetadataType.RICH_TEXT_V2 },
|
||||
] as FieldTypeAndNameMetadata[];
|
||||
const result = getTsVectorColumnExpressionFromFields(fields);
|
||||
|
||||
expect(result).toBe(
|
||||
"to_tsvector('simple', COALESCE(\"bodyV2Markdown\", ''))",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -14,6 +14,7 @@ import {
|
||||
isSearchableFieldType,
|
||||
SearchableFieldType,
|
||||
} from 'src/engine/workspace-manager/workspace-sync-metadata/utils/is-searchable-field.util';
|
||||
import { isSearchableSubfield } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/is-searchable-subfield.util';
|
||||
|
||||
export type FieldTypeAndNameMetadata = {
|
||||
name: string;
|
||||
@ -55,7 +56,9 @@ const getColumnExpressionsFromField = (
|
||||
}
|
||||
|
||||
return compositeType.properties
|
||||
.filter((property) => property.type === FieldMetadataType.TEXT)
|
||||
.filter((property) =>
|
||||
isSearchableSubfield(compositeType.type, property.type, property.name),
|
||||
)
|
||||
.map((property) => {
|
||||
const columnName = computeCompositeColumnName(
|
||||
fieldMetadataTypeAndName,
|
||||
|
||||
@ -7,6 +7,7 @@ const SEARCHABLE_FIELD_TYPES = [
|
||||
FieldMetadataType.ADDRESS,
|
||||
FieldMetadataType.LINKS,
|
||||
FieldMetadataType.RICH_TEXT,
|
||||
FieldMetadataType.RICH_TEXT_V2,
|
||||
] as const;
|
||||
|
||||
export type SearchableFieldType = (typeof SEARCHABLE_FIELD_TYPES)[number];
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
import { FieldMetadataType } from 'twenty-shared';
|
||||
|
||||
export const isSearchableSubfield = (
|
||||
compositeFieldMetadataType: FieldMetadataType,
|
||||
subFieldMetadataType: FieldMetadataType,
|
||||
subFieldName: string,
|
||||
) => {
|
||||
if (subFieldMetadataType !== FieldMetadataType.TEXT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (compositeFieldMetadataType) {
|
||||
case FieldMetadataType.RICH_TEXT_V2:
|
||||
return ['markdown'].includes(subFieldName);
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
};
|
||||
@ -3,11 +3,13 @@ import { FieldMetadataType } from 'twenty-shared';
|
||||
|
||||
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
|
||||
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/constants/search-vector-field.constants';
|
||||
import {
|
||||
ActorMetadata,
|
||||
FieldActorSource,
|
||||
} from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
||||
import { RichTextV2Metadata } from 'src/engine/metadata-modules/field-metadata/composite-types/rich-text-v2.composite-type';
|
||||
import { IndexType } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
|
||||
import {
|
||||
RelationMetadataType,
|
||||
@ -17,6 +19,7 @@ import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity
|
||||
import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator';
|
||||
import { WorkspaceFieldIndex } from 'src/engine/twenty-orm/decorators/workspace-field-index.decorator';
|
||||
import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator';
|
||||
import { WorkspaceGate } from 'src/engine/twenty-orm/decorators/workspace-gate.decorator';
|
||||
import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator';
|
||||
import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator';
|
||||
import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator';
|
||||
@ -33,11 +36,9 @@ import { NoteTargetWorkspaceEntity } from 'src/modules/note/standard-objects/not
|
||||
import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity';
|
||||
|
||||
const TITLE_FIELD_NAME = 'title';
|
||||
const BODY_FIELD_NAME = 'body';
|
||||
|
||||
export const SEARCH_FIELDS_FOR_NOTES: FieldTypeAndNameMetadata[] = [
|
||||
{ name: TITLE_FIELD_NAME, type: FieldMetadataType.TEXT },
|
||||
{ name: BODY_FIELD_NAME, type: FieldMetadataType.RICH_TEXT },
|
||||
];
|
||||
|
||||
@WorkspaceEntity({
|
||||
@ -81,6 +82,19 @@ export class NoteWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
@WorkspaceIsNullable()
|
||||
body: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: NOTE_STANDARD_FIELD_IDS.bodyV2,
|
||||
type: FieldMetadataType.RICH_TEXT_V2,
|
||||
label: 'Body',
|
||||
description: 'Note body',
|
||||
icon: 'IconFilePencil',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
@WorkspaceGate({
|
||||
featureFlag: FeatureFlagKey.IsRichTextV2Enabled,
|
||||
})
|
||||
bodyV2: RichTextV2Metadata | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: NOTE_STANDARD_FIELD_IDS.createdBy,
|
||||
type: FieldMetadataType.ACTOR,
|
||||
|
||||
@ -8,6 +8,7 @@ import {
|
||||
ActorMetadata,
|
||||
FieldActorSource,
|
||||
} from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
||||
import { RichTextV2Metadata } from 'src/engine/metadata-modules/field-metadata/composite-types/rich-text-v2.composite-type';
|
||||
import { IndexType } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
|
||||
import {
|
||||
RelationMetadataType,
|
||||
@ -33,13 +34,13 @@ import { FavoriteWorkspaceEntity } from 'src/modules/favorite/standard-objects/f
|
||||
import { TaskTargetWorkspaceEntity } from 'src/modules/task/standard-objects/task-target.workspace-entity';
|
||||
import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity';
|
||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
import { WorkspaceGate } from 'src/engine/twenty-orm/decorators/workspace-gate.decorator';
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
|
||||
const TITLE_FIELD_NAME = 'title';
|
||||
const BODY_FIELD_NAME = 'body';
|
||||
|
||||
export const SEARCH_FIELDS_FOR_TASK: FieldTypeAndNameMetadata[] = [
|
||||
{ name: TITLE_FIELD_NAME, type: FieldMetadataType.TEXT },
|
||||
{ name: BODY_FIELD_NAME, type: FieldMetadataType.RICH_TEXT },
|
||||
];
|
||||
|
||||
@WorkspaceEntity({
|
||||
@ -83,6 +84,19 @@ export class TaskWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
@WorkspaceIsNullable()
|
||||
body: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: TASK_STANDARD_FIELD_IDS.bodyV2,
|
||||
type: FieldMetadataType.RICH_TEXT_V2,
|
||||
label: 'Body',
|
||||
description: 'Task body',
|
||||
icon: 'IconFilePencil',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
@WorkspaceGate({
|
||||
featureFlag: FeatureFlagKey.IsRichTextV2Enabled,
|
||||
})
|
||||
bodyV2: RichTextV2Metadata | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: TASK_STANDARD_FIELD_IDS.dueAt,
|
||||
type: FieldMetadataType.DATE_TIME,
|
||||
|
||||
@ -18,6 +18,7 @@ export enum FieldMetadataType {
|
||||
POSITION = 'POSITION',
|
||||
ADDRESS = 'ADDRESS',
|
||||
RAW_JSON = 'RAW_JSON',
|
||||
RICH_TEXT_V2 = 'RICH_TEXT_V2',
|
||||
RICH_TEXT = 'RICH_TEXT',
|
||||
ACTOR = 'ACTOR',
|
||||
ARRAY = 'ARRAY',
|
||||
|
||||
160
yarn.lock
160
yarn.lock
@ -3415,6 +3415,55 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@blocknote/core@npm:^0.17.1":
|
||||
version: 0.17.1
|
||||
resolution: "@blocknote/core@npm:0.17.1"
|
||||
dependencies:
|
||||
"@emoji-mart/data": "npm:^1.2.1"
|
||||
"@tiptap/core": "npm:^2.7.1"
|
||||
"@tiptap/extension-bold": "npm:^2.7.1"
|
||||
"@tiptap/extension-code": "npm:^2.7.1"
|
||||
"@tiptap/extension-collaboration": "npm:^2.7.1"
|
||||
"@tiptap/extension-collaboration-cursor": "npm:^2.7.1"
|
||||
"@tiptap/extension-dropcursor": "npm:^2.7.1"
|
||||
"@tiptap/extension-gapcursor": "npm:^2.7.1"
|
||||
"@tiptap/extension-hard-break": "npm:^2.7.1"
|
||||
"@tiptap/extension-history": "npm:^2.7.1"
|
||||
"@tiptap/extension-horizontal-rule": "npm:^2.7.1"
|
||||
"@tiptap/extension-italic": "npm:^2.7.1"
|
||||
"@tiptap/extension-link": "npm:^2.7.1"
|
||||
"@tiptap/extension-paragraph": "npm:^2.7.1"
|
||||
"@tiptap/extension-strike": "npm:^2.7.1"
|
||||
"@tiptap/extension-table-cell": "npm:^2.7.1"
|
||||
"@tiptap/extension-table-header": "npm:^2.7.1"
|
||||
"@tiptap/extension-table-row": "npm:^2.7.1"
|
||||
"@tiptap/extension-text": "npm:^2.7.1"
|
||||
"@tiptap/extension-underline": "npm:^2.7.1"
|
||||
"@tiptap/pm": "npm:^2.7.1"
|
||||
emoji-mart: "npm:^5.6.0"
|
||||
hast-util-from-dom: "npm:^4.2.0"
|
||||
prosemirror-model: "npm:^1.21.0"
|
||||
prosemirror-state: "npm:^1.4.3"
|
||||
prosemirror-tables: "npm:^1.3.7"
|
||||
prosemirror-transform: "npm:^1.9.0"
|
||||
prosemirror-view: "npm:^1.33.7"
|
||||
rehype-format: "npm:^5.0.0"
|
||||
rehype-parse: "npm:^8.0.4"
|
||||
rehype-remark: "npm:^9.1.2"
|
||||
rehype-stringify: "npm:^9.0.3"
|
||||
remark-gfm: "npm:^3.0.1"
|
||||
remark-parse: "npm:^10.0.1"
|
||||
remark-rehype: "npm:^10.1.0"
|
||||
remark-stringify: "npm:^10.0.2"
|
||||
unified: "npm:^10.1.2"
|
||||
uuid: "npm:^8.3.2"
|
||||
y-prosemirror: "npm:1.2.12"
|
||||
y-protocols: "npm:^1.0.6"
|
||||
yjs: "npm:^13.6.15"
|
||||
checksum: 10c0/0acd1a099832d8e271983924f19e59aa056ead278a8bac8ab7a64d6c7d40a787a27143bdee3d6b6f3dfa26c7723207d98d7124ffef8ff9c4cfdf3034140716ab
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@blocknote/core@npm:^0.22.0":
|
||||
version: 0.22.0
|
||||
resolution: "@blocknote/core@npm:0.22.0"
|
||||
@ -3483,6 +3532,25 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@blocknote/react@npm:^0.17.1":
|
||||
version: 0.17.1
|
||||
resolution: "@blocknote/react@npm:0.17.1"
|
||||
dependencies:
|
||||
"@blocknote/core": "npm:^0.17.1"
|
||||
"@floating-ui/react": "npm:^0.26.4"
|
||||
"@tiptap/core": "npm:^2.7.1"
|
||||
"@tiptap/react": "npm:^2.7.1"
|
||||
lodash.merge: "npm:^4.6.2"
|
||||
react: "npm:^18"
|
||||
react-dom: "npm:^18"
|
||||
react-icons: "npm:^5.2.1"
|
||||
peerDependencies:
|
||||
react: ^18
|
||||
react-dom: ^18
|
||||
checksum: 10c0/4914dce225f60905b3dfe59805d7cf1f0c0c6d87295a09204333b913f6449cc90c2947baeb65c1f06106beaea8b50ac5c2785782cb2528f7e913b2877427c3e4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@blocknote/react@npm:^0.22.0":
|
||||
version: 0.22.0
|
||||
resolution: "@blocknote/react@npm:0.22.0"
|
||||
@ -3500,6 +3568,27 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@blocknote/server-util@npm:0.17.1":
|
||||
version: 0.17.1
|
||||
resolution: "@blocknote/server-util@npm:0.17.1"
|
||||
dependencies:
|
||||
"@blocknote/core": "npm:^0.17.1"
|
||||
"@blocknote/react": "npm:^0.17.1"
|
||||
"@tiptap/core": "npm:^2.7.1"
|
||||
"@tiptap/pm": "npm:^2.7.1"
|
||||
jsdom: "npm:^21.1.0"
|
||||
react: "npm:^18"
|
||||
react-dom: "npm:^18"
|
||||
y-prosemirror: "npm:1.2.12"
|
||||
y-protocols: "npm:^1.0.6"
|
||||
yjs: "npm:^13.6.15"
|
||||
peerDependencies:
|
||||
react: ^18
|
||||
react-dom: ^18
|
||||
checksum: 10c0/7d400dbf19562f8827bc524f87d673d711fba95a50fb299e0eb638f01c2dc87fd840a132b33dae60c0944637208f18a632f72f7664cb03b8ce81f5be7f8e59f0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@blocknote/xl-docx-exporter@npm:^0.22.0":
|
||||
version: 0.22.0
|
||||
resolution: "@blocknote/xl-docx-exporter@npm:0.22.0"
|
||||
@ -15688,6 +15777,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tiptap/extension-dropcursor@npm:^2.7.1":
|
||||
version: 2.11.0
|
||||
resolution: "@tiptap/extension-dropcursor@npm:2.11.0"
|
||||
peerDependencies:
|
||||
"@tiptap/core": ^2.7.0
|
||||
"@tiptap/pm": ^2.7.0
|
||||
checksum: 10c0/12ace987deec4bd02f52ee7a8f837bd71d560bca1ce670d43c6a715526a336aa5431ed044cba44babd45f7f0ed79002d16f03430ce72899a4a9713679e924717
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tiptap/extension-floating-menu@npm:^2.10.4":
|
||||
version: 2.10.4
|
||||
resolution: "@tiptap/extension-floating-menu@npm:2.10.4"
|
||||
@ -32934,6 +33033,45 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jsdom@npm:^21.1.0":
|
||||
version: 21.1.2
|
||||
resolution: "jsdom@npm:21.1.2"
|
||||
dependencies:
|
||||
abab: "npm:^2.0.6"
|
||||
acorn: "npm:^8.8.2"
|
||||
acorn-globals: "npm:^7.0.0"
|
||||
cssstyle: "npm:^3.0.0"
|
||||
data-urls: "npm:^4.0.0"
|
||||
decimal.js: "npm:^10.4.3"
|
||||
domexception: "npm:^4.0.0"
|
||||
escodegen: "npm:^2.0.0"
|
||||
form-data: "npm:^4.0.0"
|
||||
html-encoding-sniffer: "npm:^3.0.0"
|
||||
http-proxy-agent: "npm:^5.0.0"
|
||||
https-proxy-agent: "npm:^5.0.1"
|
||||
is-potential-custom-element-name: "npm:^1.0.1"
|
||||
nwsapi: "npm:^2.2.4"
|
||||
parse5: "npm:^7.1.2"
|
||||
rrweb-cssom: "npm:^0.6.0"
|
||||
saxes: "npm:^6.0.0"
|
||||
symbol-tree: "npm:^3.2.4"
|
||||
tough-cookie: "npm:^4.1.2"
|
||||
w3c-xmlserializer: "npm:^4.0.0"
|
||||
webidl-conversions: "npm:^7.0.0"
|
||||
whatwg-encoding: "npm:^2.0.0"
|
||||
whatwg-mimetype: "npm:^3.0.0"
|
||||
whatwg-url: "npm:^12.0.1"
|
||||
ws: "npm:^8.13.0"
|
||||
xml-name-validator: "npm:^4.0.0"
|
||||
peerDependencies:
|
||||
canvas: ^2.5.0
|
||||
peerDependenciesMeta:
|
||||
canvas:
|
||||
optional: true
|
||||
checksum: 10c0/905012680891fa0c92b8c18acfa35fc0b3e4b15f778ee3494aec1aca3274875160d2be35917d666b8eacd0b3121f483bd95fbe35e14790a004b805b1cf01818a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jsdom@npm:~22.1.0":
|
||||
version: 22.1.0
|
||||
resolution: "jsdom@npm:22.1.0"
|
||||
@ -40128,7 +40266,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"prosemirror-tables@npm:^1.6.1":
|
||||
"prosemirror-tables@npm:^1.3.7, prosemirror-tables@npm:^1.6.1":
|
||||
version: 1.6.2
|
||||
resolution: "prosemirror-tables@npm:1.6.2"
|
||||
dependencies:
|
||||
@ -40164,7 +40302,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"prosemirror-transform@npm:^1.10.2":
|
||||
"prosemirror-transform@npm:^1.10.2, prosemirror-transform@npm:^1.9.0":
|
||||
version: 1.10.2
|
||||
resolution: "prosemirror-transform@npm:1.10.2"
|
||||
dependencies:
|
||||
@ -40649,7 +40787,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-dom@npm:^18.2.0":
|
||||
"react-dom@npm:^18, react-dom@npm:^18.2.0":
|
||||
version: 18.3.1
|
||||
resolution: "react-dom@npm:18.3.1"
|
||||
dependencies:
|
||||
@ -45818,6 +45956,7 @@ __metadata:
|
||||
"@babel/preset-typescript": "npm:^7.24.6"
|
||||
"@blocknote/mantine": "npm:^0.22.0"
|
||||
"@blocknote/react": "npm:^0.22.0"
|
||||
"@blocknote/server-util": "npm:0.17.1"
|
||||
"@codesandbox/sandpack-react": "npm:^2.13.5"
|
||||
"@crxjs/vite-plugin": "npm:^1.0.14"
|
||||
"@dagrejs/dagre": "npm:^1.1.2"
|
||||
@ -48799,6 +48938,21 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"y-prosemirror@npm:1.2.12":
|
||||
version: 1.2.12
|
||||
resolution: "y-prosemirror@npm:1.2.12"
|
||||
dependencies:
|
||||
lib0: "npm:^0.2.42"
|
||||
peerDependencies:
|
||||
prosemirror-model: ^1.7.1
|
||||
prosemirror-state: ^1.2.3
|
||||
prosemirror-view: ^1.9.10
|
||||
y-protocols: ^1.0.1
|
||||
yjs: ^13.5.38
|
||||
checksum: 10c0/c460aa9104c71806112a17b52449221343095c774bc929a3bcfaa6d752ce9af1a5a8359c974625c70de8bf48e10b2aa8702f12ca2027f85c6097d1621969beeb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"y-prosemirror@npm:1.2.13":
|
||||
version: 1.2.13
|
||||
resolution: "y-prosemirror@npm:1.2.13"
|
||||
|
||||
Reference in New Issue
Block a user