diff --git a/packages/hash/api/src/collab/Instance.ts b/packages/hash/api/src/collab/Instance.ts index 4bf424b4f12..647eb19b4d3 100644 --- a/packages/hash/api/src/collab/Instance.ts +++ b/packages/hash/api/src/collab/Instance.ts @@ -15,17 +15,13 @@ import { entityStorePluginState, } from "@hashintel/hash-shared/entityStorePlugin"; import { - LatestEntityRef, GetBlocksQuery, GetBlocksQueryVariables, GetPageQuery, GetPageQueryVariables, + LatestEntityRef, } from "@hashintel/hash-shared/graphql/apiTypes.gen"; -import { - getComponentNodeAttrs, - isComponentNode, - isEntityNode, -} from "@hashintel/hash-shared/prosemirror"; +import { isEntityNode } from "@hashintel/hash-shared/prosemirror"; import { ProsemirrorSchemaManager } from "@hashintel/hash-shared/ProsemirrorSchemaManager"; import { getBlocksQuery, @@ -39,7 +35,7 @@ import { Response } from "express"; import { isEqual, memoize, pick } from "lodash"; import { Schema } from "prosemirror-model"; import { EditorState } from "prosemirror-state"; -import { Mapping, Step, Transform } from "prosemirror-transform"; +import { Step } from "prosemirror-transform"; import { logger } from "../logger"; import { EntityWatcher } from "./EntityWatcher"; import { InvalidVersionError } from "./errors"; @@ -81,7 +77,6 @@ export class Instance { lastActive = Date.now(); waiting: Waiting[] = []; saveChain = Promise.resolve(); - saveMapping: Mapping | null = null; clientIds = new WeakMap(); positionPollers: CollabPositionPoller[] = []; @@ -288,9 +283,6 @@ export class Instance { const result = tr.maybeStep(steps[i]); if (!result.doc) return false; - if (this.saveMapping) { - this.saveMapping.appendMap(steps[i].getMap()); - } } for (const action of actions) { @@ -317,8 +309,6 @@ export class Instance { }; save = (apolloClient: ApolloClient) => (clientID: string) => { - const mapping = new Mapping(); - this.saveChain = this.saveChain .catch() .then(async () => { @@ -340,7 +330,6 @@ export class Instance { return createdEntities; }) .then((createdEntities) => { - this.saveMapping = mapping; const { doc } = this.state; const store = entityStorePluginState(this.state); @@ -353,13 +342,6 @@ export class Instance { apolloClient, createdEntities, ).then((newPage) => { - /** - * This is purposefully based on the current doc, not the doc at - * the time of save, because we need to apply transforms to the - * current doc based on the result of the save query (in order to - * insert entity ids for new blocks) - */ - const transform = new Transform(this.state.doc); const actions: EntityStorePluginAction[] = []; /** @@ -426,23 +408,9 @@ export class Instance { }, }); } - } else if (isComponentNode(node) && !node.attrs.blockEntityId) { - transform.setNodeMarkup( - mapping.map(pos), - undefined, - getComponentNodeAttrs(blockEntity), - ); } }); - /** - * We're posting actions and steps from the post-save - * transformation process for maximum safety – as we need to - * ensure the actions are processed before the steps, and that's - * not easy to do in one message right now - * - * @todo combine the two calls to addEvents - */ if (actions.length) { this.addEvents(apolloClient)( this.version, @@ -453,27 +421,11 @@ export class Instance { ); } - if (transform.docChanged) { - this.addEvents(apolloClient)( - this.version, - transform.steps, - `${clientID}-server`, - [], - false, - ); - } - this.updateSavedContents(newPage.properties.contents); }); }) .catch((err) => { logger.error("could not save", err); - }) - - .finally(() => { - if (this.saveMapping === mapping) { - this.saveMapping = null; - } }); }; diff --git a/packages/hash/frontend/src/blocks/page/BlockView.tsx b/packages/hash/frontend/src/blocks/page/BlockView.tsx index f675cd3edb7..24303c90d3b 100644 --- a/packages/hash/frontend/src/blocks/page/BlockView.tsx +++ b/packages/hash/frontend/src/blocks/page/BlockView.tsx @@ -1,25 +1,25 @@ -import { BlockVariant } from "blockprotocol"; import { BlockMeta } from "@hashintel/hash-shared/blockMeta"; +import { EntityStore } from "@hashintel/hash-shared/entityStore"; +import { + entityStorePluginState, + subscribeToEntityStore, +} from "@hashintel/hash-shared/entityStorePlugin"; +import { isEntityNode } from "@hashintel/hash-shared/prosemirror"; import { ProsemirrorSchemaManager } from "@hashintel/hash-shared/ProsemirrorSchemaManager"; -import { findComponentNodes } from "@hashintel/hash-shared/prosemirror"; +import { BlockVariant } from "blockprotocol"; import { ProsemirrorNode, Schema } from "prosemirror-model"; import { NodeSelection } from "prosemirror-state"; import { EditorView, NodeView } from "prosemirror-view"; import { createRef, forwardRef, RefObject, useMemo, useState } from "react"; import { useOutsideClick } from "rooks"; import { tw } from "twind"; -import { EntityStore } from "@hashintel/hash-shared/entityStore"; -import { - entityStorePluginState, - subscribeToEntityStore, -} from "@hashintel/hash-shared/entityStorePlugin"; import { BlockContextMenu } from "../../components/BlockContextMenu/BlockContextMenu"; -import { BlockSuggesterProps } from "./createSuggester/BlockSuggester"; import { DragVerticalIcon } from "../../components/icons"; +import { BlockViewContext } from "./BlockViewContext"; +import { CollabPositionIndicators } from "./CollabPositionIndicators"; +import { BlockSuggesterProps } from "./createSuggester/BlockSuggester"; import styles from "./style.module.css"; import { RenderPortal } from "./usePortals"; -import { CollabPositionIndicators } from "./CollabPositionIndicators"; -import { BlockViewContext } from "./BlockViewContext"; type BlockHandleProps = { entityId: string | null; @@ -85,14 +85,19 @@ export class BlockView implements NodeView { private unsubscribe: Function; getBlockEntityIdFromNode = (node: ProsemirrorNode) => { - const componentNodes = findComponentNodes(node); - if (componentNodes.length !== 1) { + const blockEntityNode = node.firstChild; + + if (!blockEntityNode || !isEntityNode(blockEntityNode)) { + throw new Error("Unexpected prosemirror structure"); + } + + if (!blockEntityNode.attrs.draftId) { return null; } - const { blockEntityId } = componentNodes[0][0].attrs; - // The current default for this is an empty string, not null. - return blockEntityId === "" ? null : blockEntityId; + const draftEntity = this.store.draft[blockEntityNode.attrs.draftId]; + + return draftEntity?.entityId ?? null; }; constructor( diff --git a/packages/hash/frontend/src/blocks/page/ComponentView.tsx b/packages/hash/frontend/src/blocks/page/ComponentView.tsx index c7123b49a1b..e1c4bdcaccb 100644 --- a/packages/hash/frontend/src/blocks/page/ComponentView.tsx +++ b/packages/hash/frontend/src/blocks/page/ComponentView.tsx @@ -126,10 +126,11 @@ export class ComponentView implements NodeView { .resolve(this.getPos()) .node(2).attrs.draftId; - // @todo handle entity id not being defined - const entityId = node.attrs.blockEntityId ?? ""; - const entity = this.store.draft[blockDraftId]; const mappedUrl = componentIdToUrl(this.componentId); + const entity = this.store.draft[blockDraftId]; + + // @todo handle entity id not being defined + const entityId = entity.entityId ?? ""; /** used by collaborative editing feature `FocusTracker` */ this.target.setAttribute("data-entity-id", entityId); diff --git a/packages/hash/playwright/tests/page-creation.spec.ts b/packages/hash/playwright/tests/page-creation.spec.ts index 95c09138d76..4f4d8e48a76 100644 --- a/packages/hash/playwright/tests/page-creation.spec.ts +++ b/packages/hash/playwright/tests/page-creation.spec.ts @@ -76,20 +76,19 @@ test("user can create page", async ({ page }) => { // TODO: Move the cursor below the new divider and update the test? - // TODO: Uncomment after fixing flaky ProseMirror behavior - // // Insert a paragraph creation with newlines - // await sleep(100); // TODO: investigate flakiness in FF and Webkit - // await page.keyboard.type("Second paragraph"); - // await sleep(100); // TODO: investigate flakiness in FF and Webkit - // await page.keyboard.press("Shift+Enter"); - // await sleep(100); // TODO: investigate flakiness in FF and Webkit - // await page.keyboard.press("Shift+Enter"); - // await sleep(100); // TODO: investigate flakiness in FF and Webkit - // await page.keyboard.type("with"); - // await page.keyboard.press("Shift+Enter"); - // await sleep(100); // TODO: investigate flakiness in FF and Webkit - // await page.keyboard.type("line breaks"); - // await sleep(100); // TODO: investigate flakiness in FF and Webkit + // Insert a paragraph creation with newlines + await sleep(100); // TODO: investigate flakiness in FF and Webkit + await page.keyboard.type("Second paragraph"); + await sleep(100); // TODO: investigate flakiness in FF and Webkit + await page.keyboard.press("Shift+Enter"); + await sleep(100); // TODO: investigate flakiness in FF and Webkit + await page.keyboard.press("Shift+Enter"); + await sleep(100); // TODO: investigate flakiness in FF and Webkit + await page.keyboard.type("with"); + await page.keyboard.press("Shift+Enter"); + await sleep(100); // TODO: investigate flakiness in FF and Webkit + await page.keyboard.type("line breaks"); + await sleep(100); // TODO: investigate flakiness in FF and Webkit // Expect just inserted content to be present on the page await expect(blockRegionLocator).toContainText( @@ -124,11 +123,10 @@ test("user can create page", async ({ page }) => { blockRegionLocator.locator("p").nth(0).locator("em"), ).toContainText("italics"); - // TODO: Uncomment after fixing flaky ProseMirror behavior - // await expect(blockRegionLocator.locator("p").nth(1)).toContainText( - // "Second paragraph\n\nwith\nline breaks", - // { useInnerText: true }, - // ); + await expect(blockRegionLocator.locator("p").nth(1)).toContainText( + "Second paragraph\n\nwith\nline breaks", + { useInnerText: true }, + ); await expect(blockRegionLocator.locator("hr")).toBeVisible(); diff --git a/packages/hash/shared/src/ProsemirrorSchemaManager.ts b/packages/hash/shared/src/ProsemirrorSchemaManager.ts index 0f8cfd76f0d..34344044a08 100644 --- a/packages/hash/shared/src/ProsemirrorSchemaManager.ts +++ b/packages/hash/shared/src/ProsemirrorSchemaManager.ts @@ -1,6 +1,6 @@ import { BlockVariant } from "blockprotocol"; import { isString } from "lodash"; -import { ProsemirrorNode, NodeSpec, Schema } from "prosemirror-model"; +import { NodeSpec, ProsemirrorNode, Schema } from "prosemirror-model"; import { EditorState, Transaction } from "prosemirror-state"; import { EditorProps, EditorView } from "prosemirror-view"; @@ -28,21 +28,35 @@ import { entityStorePluginStateFromTransaction, newDraftId, } from "./entityStorePlugin"; -import { childrenForTextEntity, getComponentNodeAttrs } from "./prosemirror"; +import { + childrenForTextEntity, + componentNodeGroupName, + isComponentNode, + isComponentNodeType, +} from "./prosemirror"; declare interface OrderedMapPrivateInterface { content: (string | T)[]; } -const createComponentNodeSpec = (spec: Partial): NodeSpec => ({ - ...spec, - selectable: false, - group: "componentNode", - attrs: { - ...(spec.attrs ?? {}), - // @todo remove this - blockEntityId: { default: "" }, +const createComponentNodeSpec = ( + spec: Omit, "group">, +): NodeSpec => ({ + /** + * @todo consider if we should encode the component id here / any other + * information + */ + toDOM: (node) => { + if (node.type.isTextblock) { + return ["span", { "data-hash-type": "component" }, 0]; + } else { + return ["span", { "data-hash-type": "component" }]; + } }, + selectable: false, + + ...spec, + group: componentNodeGroupName, }); type NodeViewFactory = NonNullable["nodeViews"]>[string]; @@ -122,18 +136,6 @@ export class ProsemirrorSchemaManager { this.prepareToDisableBlankDefaultComponentNode(); const spec = createComponentNodeSpec({ - /** - * @todo consider if we should encode the component id here / any other - * information - */ - toDOM: (node) => { - if (node.type.isTextblock) { - return ["span", { "data-hash-type": "component" }, 0]; - } else { - return ["span", { "data-hash-type": "component" }]; - } - }, - /** * Currently we detect whether a block takes editable text by detecting if * it has an editableRef prop in its schema – we need a more sophisticated @@ -167,16 +169,21 @@ export class ProsemirrorSchemaManager { private prepareToDisableBlankDefaultComponentNode() { const blankType = this.schema.nodes.blank; - if (blankType.spec.group === "componentNode") { - delete blankType.spec.group; - } + if (isComponentNodeType(blankType)) { + if (blankType.spec.group?.includes(componentNodeGroupName)) { + if (blankType.spec.group !== componentNodeGroupName) { + throw new Error( + "Blank node type has group expression more complicated than we can handle", + ); + } - // Accessing private API here, hence the casting - const groups = (blankType as any).groups as string[]; + delete blankType.spec.group; + } - const idx = groups.indexOf("componentNode"); - if (idx > -1) { - groups.splice(idx, 1); + blankType.groups!.splice( + blankType.groups!.indexOf(componentNodeGroupName), + 1, + ); } } @@ -239,10 +246,7 @@ export class ProsemirrorSchemaManager { async ensureDocDefined(doc: ProsemirrorNode) { const componentIds = new Set(); doc.descendants((node) => { - if ( - node.type.spec.group === "componentNode" && - node.type.name.startsWith("http") - ) { + if (isComponentNode(node) && node.type.name.startsWith("http")) { componentIds.add(node.type.name); } @@ -289,8 +293,6 @@ export class ProsemirrorSchemaManager { } } - const componentNodeAttributes = getComponentNodeAttrs(blockEntity); - if (requiresText) { const draftTextEntity = draftBlockId && entityStore @@ -317,12 +319,7 @@ export class ProsemirrorSchemaManager { { draftId: draftTextEntity?.draftId, }, - [ - this.schema.nodes[targetComponentId].create( - componentNodeAttributes, - content, - ), - ], + [this.schema.nodes[targetComponentId].create({}, content)], ), ]), ]); @@ -340,12 +337,7 @@ export class ProsemirrorSchemaManager { ? blockEntity.properties.entity.draftId : null, }, - [ - this.schema.nodes[targetComponentId].create( - componentNodeAttributes, - [], - ), - ], + [this.schema.nodes[targetComponentId].create({}, [])], ), ), ]); diff --git a/packages/hash/shared/src/entityStorePlugin.ts b/packages/hash/shared/src/entityStorePlugin.ts index 75fbc855533..eaf9e444ca0 100644 --- a/packages/hash/shared/src/entityStorePlugin.ts +++ b/packages/hash/shared/src/entityStorePlugin.ts @@ -448,7 +448,7 @@ class ProsemirrorStateChangeHandler { // @todo in what circumstances does this occur if (!isDraftBlockEntity(parentEntity)) { - const componentNodeChild = findComponentNodes(node)[0][0]; + const componentNodeChild = findComponentNodes(node)[0]; addEntityStoreAction(this.state, this.tr, { type: "updateEntityProperties", diff --git a/packages/hash/shared/src/prosemirror.ts b/packages/hash/shared/src/prosemirror.ts index 80dba6cc46c..55a160fc9ce 100644 --- a/packages/hash/shared/src/prosemirror.ts +++ b/packages/hash/shared/src/prosemirror.ts @@ -1,4 +1,4 @@ -import { ProsemirrorNode, Schema } from "prosemirror-model"; +import { NodeType, ProsemirrorNode, Schema } from "prosemirror-model"; import { Text } from "./graphql/apiTypes.gen"; import { TextToken } from "./graphql/types"; @@ -91,9 +91,8 @@ type NodeWithAttrs = Omit< "attrs" > & { attrs: Attrs }; -export type ComponentNode = NodeWithAttrs<{ - blockEntityId: string | null; -}>; +type ComponentNodeAttrs = {}; +export type ComponentNode = NodeWithAttrs; export type EntityNode = NodeWithAttrs<{ draftId: string | null; @@ -103,20 +102,23 @@ export const isEntityNode = ( node: ProsemirrorNode | null, ): node is EntityNode => !!node && node.type === node.type.schema.nodes.entity; -/** - * @todo use group name for this - */ +export const componentNodeGroupName = "componentNode"; + +export const isComponentNodeType = (nodeType: NodeType) => + nodeType.groups?.includes(componentNodeGroupName) ?? false; + export const isComponentNode = ( node: ProsemirrorNode, -): node is ComponentNode => - !!node.type.spec.attrs && "blockEntityId" in node.type.spec.attrs; +): node is ComponentNode => isComponentNodeType(node.type); -export const findComponentNodes = (doc: ProsemirrorNode) => { - const componentNodes: [ComponentNode, number][] = []; +export const findComponentNodes = ( + containingNode: ProsemirrorNode, +): ComponentNode[] => { + const componentNodes: ComponentNode[] = []; - doc.descendants((node, pos) => { + containingNode.descendants((node) => { if (isComponentNode(node)) { - componentNodes.push([node, pos]); + componentNodes.push(node); } return true; @@ -125,10 +127,23 @@ export const findComponentNodes = (doc: ProsemirrorNode) => { return componentNodes; }; -export const getComponentNodeAttrs = ( - entity?: { entityId?: string | null } | null, -) => ({ - blockEntityId: entity?.entityId ?? "", -}); +export const findComponentNode = ( + containingNode: ProsemirrorNode, + containingNodePosition: number, +): [ComponentNode, number] | null => { + let result: [ComponentNode, number] | null = null; + + containingNode.descendants((node, pos) => { + if (isComponentNode(node)) { + result = [node, containingNodePosition + 1 + pos]; + + return false; + } + + return true; + }); + + return result; +}; export const componentNodeToId = (node: ComponentNode) => node.type.name; diff --git a/packages/hash/shared/src/save.ts b/packages/hash/shared/src/save.ts index 21f96c74bb7..dcbee768008 100644 --- a/packages/hash/shared/src/save.ts +++ b/packages/hash/shared/src/save.ts @@ -19,7 +19,12 @@ import { getTextEntityFromSavedBlock, isDraftTextContainingEntityProperties, } from "./entity"; -import { EntityStore, isBlockEntity, isDraftBlockEntity } from "./entityStore"; +import { + DraftEntity, + EntityStore, + isBlockEntity, + isDraftBlockEntity, +} from "./entityStore"; import { CreateEntityMutation, CreateEntityMutationVariables, @@ -33,9 +38,9 @@ import { UpdatePageContentsMutationVariables, } from "./graphql/apiTypes.gen"; import { - ComponentNode, componentNodeToId, - findComponentNodes, + EntityNode, + findComponentNode, isComponentNode, isEntityNode, textBlockNodeToEntityProperties, @@ -66,14 +71,12 @@ const defineOperation = }; const removeBlocks = defineOperation( - (entities: BlockEntity[], nodes: [ComponentNode, number][]) => { - const draftBlockEntityIds = new Set( - nodes.map(([node]) => node.attrs.blockEntityId), - ); + (entities: BlockEntity[], draftBlockEntityIds: DraftEntity["entityId"][]) => { + const draftBlockEntityIdsSet = new Set(draftBlockEntityIds); const removedBlockEntities = entities .map((block, position) => [block, position] as const) - .filter(([block]) => !draftBlockEntityIds.has(block.entityId)); + .filter(([block]) => !draftBlockEntityIdsSet.has(block.entityId)); const updatedEntities = entities.filter( (_, position) => @@ -98,9 +101,9 @@ const removeBlocks = defineOperation( ); const moveBlocks = defineOperation( - (entities: BlockEntity[], nodes: [ComponentNode, number][]) => { - const entitiesWithoutNewBlocks = nodes.filter( - ([node]) => !!node.attrs.blockEntityId, + (entities: BlockEntity[], draftBlockEntityIds: DraftEntity["entityId"][]) => { + const otherExistingBlockEntityIds = draftBlockEntityIds.filter( + (blockEntityId) => !!blockEntityId, ); const actions: UpdatePageAction[] = []; @@ -108,8 +111,8 @@ const moveBlocks = defineOperation( for (let position = 0; position < entities.length; position++) { const block = entities[position]; - const positionInDoc = entitiesWithoutNewBlocks.findIndex( - ([node]) => node.attrs.blockEntityId === block.entityId, + const positionInDoc = otherExistingBlockEntityIds.findIndex( + (blockEntityId) => blockEntityId === block.entityId, ); if (positionInDoc < 0) { @@ -141,6 +144,7 @@ const moveBlocks = defineOperation( type CreatedEntities = Map; +type BlockEntityNodeDescriptor = [EntityNode, number, string | null]; /** * @warning this does not apply its actions to the entities it returns as it is * not necessary for the pipeline of calculations. Be wary of this. @@ -148,18 +152,32 @@ type CreatedEntities = Map; const insertBlocks = defineOperation( ( entities: BlockEntity[], - nodes: [ComponentNode, number][], + blockEntityNodes: BlockEntityNodeDescriptor[], accountId: string, createdEntities: CreatedEntities, ) => { const actions: UpdatePageAction[] = []; const exists = blockEntityIdExists(entities); - for (const [position, [node, nodePosition]] of Object.entries(nodes)) { - if (exists(node.attrs.blockEntityId)) { + for (const [ + position, + [blockNode, blockNodePosition, blockEntityId], + ] of Object.entries(blockEntityNodes)) { + if (exists(blockEntityId)) { continue; } + const componentNodeResult = findComponentNode( + blockNode, + blockNodePosition, + ); + + if (!componentNodeResult) { + throw new Error("Unexpected prosemirror structure"); + } + + const [node, nodePosition] = componentNodeResult; + if (createdEntities.has(nodePosition)) { const createdEntity = createdEntities.get(nodePosition)!; @@ -208,7 +226,7 @@ const insertBlocks = defineOperation( const updateBlocks = defineOperation( ( entities: BlockEntity[], - nodes: [ComponentNode, number][], + nodes: BlockEntityNodeDescriptor[], entityStore: EntityStore, ) => { const exists = blockEntityIdExists(entities); @@ -230,12 +248,17 @@ const updateBlocks = defineOperation( * create a list of entities that we need to post updates to via * GraphQL */ - .flatMap(([node]) => { - const { blockEntityId } = node.attrs; + .flatMap(([blockNode, blockNodePos, blockEntityId]) => { if (!exists(blockEntityId)) { return []; } + const node = findComponentNode(blockNode, blockNodePos)?.[0]; + + if (!node) { + throw new Error("Unexpected prosemirror structure"); + } + const savedEntity = entityStore.saved[blockEntityId]; if (!savedEntity) { @@ -326,20 +349,43 @@ const calculateSaveActions = ( entityStore: EntityStore, createdEntities: CreatedEntities, ) => { - const componentNodes = findComponentNodes(doc); let actions: UpdatePageAction[] = []; + const draftBlockEntityIds: DraftEntity["entityId"][] = []; + const draftBlockEntityNodes: BlockEntityNodeDescriptor[] = []; + + doc.descendants((node, pos) => { + if (isEntityNode(node)) { + if (!node.attrs.draftId) { + throw new Error("Unexpected prosemirror structure"); + } + + const draftEntity = entityStore.draft[node.attrs.draftId]; + + if (!draftEntity || !isDraftBlockEntity(draftEntity)) { + throw new Error("Unexpected prosemirror structure"); + } + + // @todo handle entityId not being set + + draftBlockEntityNodes.push([node, pos, draftEntity.entityId]); + draftBlockEntityIds.push(draftEntity.entityId); + + return false; + } + }); + blocks = [...blocks]; - [actions, blocks] = removeBlocks(actions, blocks, componentNodes); - [actions, blocks] = moveBlocks(actions, blocks, componentNodes); + [actions, blocks] = removeBlocks(actions, blocks, draftBlockEntityIds); + [actions, blocks] = moveBlocks(actions, blocks, draftBlockEntityIds); [actions, blocks] = insertBlocks( actions, blocks, - componentNodes, + draftBlockEntityNodes, accountId, createdEntities, ); - [actions] = updateBlocks(actions, blocks, componentNodes, entityStore); + [actions] = updateBlocks(actions, blocks, draftBlockEntityNodes, entityStore); return actions; }; diff --git a/packages/hash/shared/src/schema.ts b/packages/hash/shared/src/schema.ts index c23b3167484..63cab4a2adf 100644 --- a/packages/hash/shared/src/schema.ts +++ b/packages/hash/shared/src/schema.ts @@ -1,3 +1,4 @@ +import { componentNodeGroupName } from "@hashintel/hash-shared/prosemirror"; import { Schema } from "prosemirror-model"; export const createSchema = () => @@ -15,8 +16,10 @@ export const createSchema = () => * node from the componentNode group, which ensures that when * Prosemirror attempts to instantiate a componentNode it uses that * node instead of the blank one + * + * @see import("./ProsemirrorSchemaManager").ProsemirrorSchemaManager#prepareToDisableBlankDefaultComponentNode */ - group: "componentNode", + group: componentNodeGroupName, toDOM: () => ["div", 0] as const, }, block: { diff --git a/packages/hash/shared/src/wrapEntitiesPlugin.ts b/packages/hash/shared/src/wrapEntitiesPlugin.ts index e4409ccab44..b3de8459160 100644 --- a/packages/hash/shared/src/wrapEntitiesPlugin.ts +++ b/packages/hash/shared/src/wrapEntitiesPlugin.ts @@ -59,16 +59,6 @@ const ensureEntitiesAreWrapped = ( throw new Error("Cannot rewrap"); } - /** - * @todo we won't need this once we remove blockEntityId from the - * component node - */ - if (wrappers && !wrapperNodes) { - tr.setNodeMarkup(tr.mapping.map(position), undefined, { - blockEntityId: null, - }); - } - /** * In the event that a block is not fully wrapped (i.e. is _not_ a block node), we provide a fallback * in case wrapperNodes were not provided. diff --git a/patches/@types+prosemirror-model+1.16.0.patch b/patches/@types+prosemirror-model+1.16.0.patch index 13720333cd6..95d3cb5515b 100644 --- a/patches/@types+prosemirror-model+1.16.0.patch +++ b/patches/@types+prosemirror-model+1.16.0.patch @@ -1,5 +1,5 @@ diff --git a/node_modules/@types/prosemirror-model/index.d.ts b/node_modules/@types/prosemirror-model/index.d.ts -index 3e5aa92..4040b63 100755 +index 3e5aa92..243b5d6 100755 --- a/node_modules/@types/prosemirror-model/index.d.ts +++ b/node_modules/@types/prosemirror-model/index.d.ts @@ -103,8 +103,14 @@ export class Fragment { @@ -33,3 +33,14 @@ index 3e5aa92..4040b63 100755 /** * Error type raised by [`Node.replace`](#model.Node.replace) when * given an invalid replacement. +@@ -984,6 +998,10 @@ export class NodeType { + * A link back to the `Schema` the node type belongs to. + */ + schema: S; ++ /** ++ * Parsed set of groups from spec.group ++ */ ++ groups?: string[]; + /** + * The spec that this type is based on + */