/wp on an empty line should create a block work-package link, not an inline one#142
/wp on an empty line should create a block work-package link, not an inline one#142ihordubas99 wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adjusts the editor’s slash-menu “Link existing work package” insertion behavior so that selecting it on an empty line inserts a full-width block work-package card, while keeping the existing inline-chip behavior when triggered on a non-empty line. It introduces a local-only pending-block registry to control which client sees the search UI, and removes the previous coupling to TipTap private internals.
Changes:
- Route slash-menu selection to either block-card insertion (empty line) or inline-chip insertion (non-empty line).
- Add a module-level
pendingBlockRegistryused by the block component to decide whether to show the search dropdown. - Update the block card’s outside-click handling to use
MouseEvent.composedPath()for Shadow DOM compatibility, and add browser tests covering block-vs-inline routing.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| test/lib/components/integration/editor.slashMenu.browser.test.tsx | Adds integration tests asserting block insertion on empty line and inline insertion on non-empty line. |
| lib/components/SlashMenu.tsx | Adds empty-block detection and routes insertion to either block-card or inline-chip flow; registers pending block IDs. |
| lib/components/BlockWorkPackage/pendingBlockRegistry.ts | Introduces a local in-memory registry for pending block IDs (not synced to document). |
| lib/components/BlockWorkPackage/BlockWorkPackage.tsx | Drives search dropdown visibility via the pending registry, removes TipTap private selection tracking, and improves outside-click detection via composedPath(). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const isPending = pendingBlockRegistry.has(block.id); | ||
|
|
||
| return ( | ||
| <Block | ||
| tabIndex={disableFocus ? -1 : 0} | ||
| style={disableFocus ? { pointerEvents: "none" } : undefined} | ||
| > | ||
| <Block $pending={isPending}> | ||
| <div contentEditable={false} style={{ userSelect: "none" }}> | ||
| {!block.props.wpid && !block.props.initialized && isActive && ( | ||
| {isPending && ( | ||
| <SearchContainer> |
acc27f1 to
89434c4
Compare
|
Now if you put a space " " before the slash, it will link the inline chip. Not sure what's the best way to do it, leave it like that, or link the block anyway in this case? What do you think @judithroth? |
@ihordubas99 good question. I think I'd leave it like it is. The question is where would we draw the line, what about 2 spaces? 4 or 6? Or should we remove them? However, let's wait with merging this until we finished all the Bugs for 17.5. Since this changes the setup of op-blocknote-extensions in OpenProject again. |
Ticket
https://community.openproject.org/projects/communicator-stream/work_packages/75310
What are you trying to accomplish?
When a user triggers the slash menu on an empty line and selects "Link existing work package", the work package should be inserted as a full-width block card rather than an inline chip. On a non-empty line the existing inline chip behaviour is preserved.
What approach did you choose and why?
At slash menu selection time we check whether the current block's content is empty. If it is, we insert an openProjectWorkPackageBlock in place of the empty paragraph and register its ID in a module-level pendingBlockRegistry - a local in-memory Set, never synced to the document. The block component reads this registry to decide whether to render the search dropdown, which means only the user who triggered the insertion sees it; collaborators sharing the same document never get a search UI they did not ask for. This mirrors the existing pattern used by inline chips (registerInlineWpCallbacks). On cancel the block is removed entirely; on confirm the registry entry is cleared and the block receives its wpid. The mousedown outside-click handler in BlockWorkPackage was also updated to use e.composedPath() instead of contains(e.target) so the popover close logic works correctly inside a Shadow DOM.
As a side effect, the previous implementation accessed TipTap's private _tiptapEditor instance to subscribe to selectionUpdate events in order to track whether the block was "active". That coupling is now removed - the search dropdown visibility is driven entirely by the registry, with no dependency on editor internals.
Merge checklist