feat(core): implement Clipboard Plugin#129
Conversation
|
⏭️ No files to mutate for |
Coverage report for
|
St.❔ |
Category | Percentage | Covered / Total |
|---|---|---|---|
| 🟢 | Statements | 96.43% | 27/28 |
| 🟢 | Branches | 86.96% | 20/23 |
| 🟢 | Functions | 100% | 5/5 |
| 🟢 | Lines | 96.43% | 27/28 |
Test suite run success
11 tests passing in 2 suites.
Report generated by 🧪jest coverage report action from bc3657c
Coverage report for
|
St.❔ |
Category | Percentage | Covered / Total |
|---|---|---|---|
| 🟢 | Statements | 90.85% (-9.15% 🔻) |
129/142 |
| 🟢 | Branches | 90.38% (-9.62% 🔻) |
47/52 |
| 🟢 | Functions | 90% (-10% 🔻) |
27/30 |
| 🟢 | Lines | 90.23% (-9.77% 🔻) |
120/133 |
Show new covered files 🐣
St.❔ |
File | Statements | Branches | Functions | Lines |
|---|---|---|---|---|---|
| 🟡 | api/SelectionAPI.ts | 80% | 100% | 66.67% | 75% |
Show files with reduced coverage 🔻
St.❔ |
File | Statements | Branches | Functions | Lines |
|---|---|---|---|---|---|
| 🟡 | ... / SelectionManager.ts |
80% (-20% 🔻) |
80% (-20% 🔻) |
66.67% (-33.33% 🔻) |
79.31% (-20.69% 🔻) |
Test suite run success
75 tests passing in 7 suites.
Report generated by 🧪jest coverage report action from bc3657c
Coverage report for
|
St.❔ |
Category | Percentage | Covered / Total |
|---|---|---|---|
| 🟢 | Statements | 99.7% (-0.1% 🔻) |
988/991 |
| 🟢 | Branches | 97.01% (-1.97% 🔻) |
292/301 |
| 🟢 | Functions | 97.03% (-0.41% 🔻) |
229/236 |
| 🟢 | Lines | 99.68% (-0.1% 🔻) |
948/951 |
Show files with reduced coverage 🔻
St.❔ |
File | Statements | Branches | Functions | Lines |
|---|---|---|---|---|---|
| 🟢 | ... / index.ts |
98.9% (-1.1% 🔻) |
94.23% (-5.77% 🔻) |
93.75% (-6.25% 🔻) |
98.86% (-1.14% 🔻) |
Test suite run success
538 tests passing in 25 suites.
Report generated by 🧪jest coverage report action from bc3657c
|
⏭️ No files to mutate for |
Coverage report for
|
St.❔ |
Category | Percentage | Covered / Total |
|---|---|---|---|
| 🟡 | Statements | 70.97% | 22/31 |
| 🔴 | Branches | 20% | 1/5 |
| 🟡 | Functions | 75% | 6/8 |
| 🟡 | Lines | 68.97% | 20/29 |
Test suite run success
4 tests passing in 1 suite.
Report generated by 🧪jest coverage report action from bc3657c
|
⏭️ No files to mutate for |
Coverage report for
|
St.❔ |
Category | Percentage | Covered / Total |
|---|---|---|---|
| 🟢 | Statements | 92.51% | 358/387 |
| 🟢 | Branches | 85.51% | 118/138 |
| 🟢 | Functions | 98.15% | 53/54 |
| 🟢 | Lines | 92.41% | 353/382 |
Test suite run success
117 tests passing in 7 suites.
Report generated by 🧪jest coverage report action from bc3657c
There was a problem hiding this comment.
Pull request overview
Implements initial “copy” pipeline support by introducing a UI-level ui:copy event, exposing selected blocks via the Selection API, and adding a Core ClipboardPlugin that writes editor-specific clipboard payloads when blocks are selected.
Changes:
- UI: dispatch a new
CopyUIEventon nativecopywithin the blocks holder. - SDK/Core APIs: add
selection.selectedBlocksand selection logic to derive selected blocks from caret/index state. - Core: auto-register a new
ClipboardPluginthat overrides native copy to populatetext/plain,text/html, andapplication/x-editor-js.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/ui/src/Blocks/Blocks.ts | Dispatches CopyUIEvent on native copy events from the blocks holder. |
| packages/sdk/src/entities/EventBus/events/ui/index.ts | Re-exports the new UI copy event from the UI events barrel. |
| packages/sdk/src/entities/EventBus/events/ui/CopyUIEvent.ts | Introduces CopyUIEvent, name constant, and payload type carrying the native ClipboardEvent. |
| packages/sdk/src/api/SelectionAPI.ts | Extends public Selection API with selectedBlocks. |
| packages/model/src/entities/Index/index.ts | Adds Index.isCompositeIndex predicate for composite selections. |
| packages/core/src/plugins/ClipboardPlugin.ts | New plugin that intercepts ui:copy and writes multiple clipboard formats, including editor-specific MIME data. |
| packages/core/src/index.ts | Registers ClipboardPlugin in Core default plugin set. |
| packages/core/src/components/SelectionManager.ts | Adds logic to derive selected blocks from caret index (block index or composite segments). |
| packages/core/src/api/SelectionAPI.ts | Implements SDK Selection API selectedBlocks getter via SelectionManager. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| nativeEvent.preventDefault(); | ||
|
|
||
| const currentDOMSelection = window.getSelection(); | ||
|
|
||
| if (!currentDOMSelection) { | ||
| return; | ||
| } | ||
|
|
||
| const selectionAsPlainText = currentDOMSelection?.toString() ?? ''; | ||
| const selectionAsHTML = this.#parseDOMSelectionToHTML(currentDOMSelection); | ||
|
|
||
| nativeEvent.clipboardData?.setData('text/plain', selectionAsPlainText); | ||
| nativeEvent.clipboardData?.setData('text/html', selectionAsHTML); | ||
| nativeEvent.clipboardData?.setData('application/x-editor-js', JSON.stringify(selectedBlocks)); |
| nativeEvent.clipboardData?.setData('text/plain', selectionAsPlainText); | ||
| nativeEvent.clipboardData?.setData('text/html', selectionAsHTML); | ||
| nativeEvent.clipboardData?.setData('application/x-editor-js', JSON.stringify(selectedBlocks)); |
| return index.compositeSegments.map((segment) => { | ||
| const { blockIndex } = segment; | ||
|
|
||
| return this.#model.serialized.blocks[blockIndex!]; | ||
| }); |
| * | ||
| */ | ||
| public get isCompositeIndex(): boolean { | ||
| /* Stryker disable next-line ConditionalExpression, LogicalOperator -- compound data-index predicate; .isDataIndex specs cover field combinations */ |
| /** | ||
| * |
| * Payload @todo update doc | ||
| */ | ||
| export interface CopyUIEventPayload { | ||
| /** | ||
| * Native ClipboardEvent | ||
| * UI does not call .preventDefault() for this event | ||
| */ | ||
| nativeEvent: ClipboardEvent; | ||
| } | ||
|
|
||
| /** | ||
| * Delegated copy event from the editor @todo update doc | ||
| */ | ||
| export class CopyUIEvent extends UIEventBase<CopyUIEventPayload> { | ||
| /** | ||
| * @param payload - carries the original DOM `ClipboardEvent` as `nativeEvent` for providing rich clipboard data |
| /** | ||
| * Delegated copy event from the editor @todo update doc | ||
| */ | ||
| export class CopyUIEvent extends UIEventBase<CopyUIEventPayload> { |
| /** | ||
| * @todo update doc | ||
| */ | ||
| export class ClipboardPlugin implements EditorjsPlugin { | ||
| public static readonly type = PluginType.Plugin; | ||
|
|
||
| readonly #api: EditorAPI; | ||
|
|
||
| /** | ||
| * @param params @todo update doc | ||
| */ | ||
| constructor(params: EditorjsPluginParams) { |
| public get isCompositeIndex(): boolean { | ||
| /* Stryker disable next-line ConditionalExpression, LogicalOperator -- compound data-index predicate; .isDataIndex specs cover field combinations */ | ||
| return this.compositeSegments !== undefined && this.compositeSegments.length > 0 && this.blockIndex === undefined && this.tuneName === undefined && this.dataKey === undefined && this.textRange === undefined; |
| * @todo update doc | ||
| */ | ||
| public destroy(): void { | ||
| // do nothing |
There was a problem hiding this comment.
Could unsubscribe from the copy event
| return ''; | ||
| } | ||
|
|
||
| const container = document.createElement('div'); |
There was a problem hiding this comment.
Template might be better instead of div
WIP