Skip to content

Commit 2a5f8eb

Browse files
ilyamore88claude
andauthored
feat(core): extract ShortcutsPlugin into @editorjs/shortcuts-plugin (#180)
Move the last in-tree EditorjsPlugin out of packages/core into a standalone @editorjs/shortcuts-plugin package, mirroring the clipboard-plugin extraction (#129). Closes #171. - New package packages/plugins/shortcuts-plugin scaffolded from clipboard-plugin (package.json, tsconfigs, eslint, jest, stryker). - ShortcutsPlugin.ts moved verbatim into src/index.ts (no source changes) and covered by a new index.spec.ts (12 cases, 100% coverage) that closes the pre-existing test gap. - Wire core: add the workspace dependency, tsconfig project references (tsconfig + tsconfig.build), and switch the import to the package; delete the now-empty src/plugins directory. - Dedicated CI workflow shortcuts-plugin.yml (package-check shape). - Move the "Keyboard shortcuts plugin" requirement out of the core OpenSpec spec into a new specs/shortcuts-plugin/spec.md. - Ignore .claude/worktrees/ in .gitignore. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 1ab84ae commit 2a5f8eb

20 files changed

Lines changed: 546 additions & 11 deletions

File tree

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
name: Shortcuts plugin check
2+
on:
3+
pull_request:
4+
merge_group:
5+
6+
permissions:
7+
contents: read
8+
pull-requests: write
9+
issues: write
10+
actions: read
11+
12+
jobs:
13+
package-check:
14+
uses: ./.github/workflows/package-check.yml
15+
with:
16+
package-name: '@editorjs/shortcuts-plugin'
17+
working-directory: './packages/plugins/shortcuts-plugin'
18+
include-mutations: true
19+
secrets:
20+
stryker_dashboard_api_key: ${{ secrets.STRYKER_DASHBOARD_API_KEY }}

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,4 @@ jest-report.json
3131
**/.env
3232
/--help/
3333
.claude/settings.local.json
34+
.claude/worktrees/

openspec/specs/core/spec.md

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -95,13 +95,3 @@ The system SHALL provide `UndoRedoManager`, which batches consecutive model even
9595
- **THEN** the default undo/redo behavior is suppressed
9696

9797
Implemented in `src/components/UndoRedoManager.ts`, validated by its co-located `.spec.ts`.
98-
99-
### Requirement: Keyboard shortcuts plugin
100-
The system SHALL provide a `ShortcutsPlugin` (an `EditorjsPlugin`) that maps keyboard shortcuts declared in a tool's `options.shortcut` to inline-tool application through the `EditorAPI`.
101-
102-
#### Scenario: Triggering an inline tool via shortcut
103-
- **GIVEN** an inline tool is registered with `options.shortcut` set to a key combination (e.g. `CMD+B`)
104-
- **WHEN** that key combination is pressed while the editor has focus
105-
- **THEN** `ShortcutsPlugin` applies the corresponding inline tool to the current selection via the `EditorAPI`
106-
107-
Implemented in `src/plugins/ShortcutsPlugin.ts`.
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Shortcuts Plugin
2+
3+
## Purpose
4+
5+
`@editorjs/shortcuts-plugin` is a built-in `EditorjsPlugin` that maps keyboard shortcuts declared in a tool's `options.shortcut` to inline-tool application through the `EditorAPI`. It subscribes to tool-loaded events to collect shortcuts and to delegated keydown events to dispatch them.
6+
7+
## Requirements
8+
9+
### Requirement: Keyboard shortcuts plugin
10+
The system SHALL provide a `ShortcutsPlugin` (an `EditorjsPlugin`) that, on construction, subscribes to the `core:tool:loaded` and `ui:key-down` events, registers each loaded tool's string `options.shortcut`, and applies the matching inline tool to the current selection via the `EditorAPI` when its shortcut is pressed.
11+
12+
#### Scenario: Registering a shortcut from a loaded tool
13+
- **GIVEN** a tool is loaded whose merged `options.shortcut` is a string (e.g. `CMD+B`)
14+
- **WHEN** the `core:tool:loaded` event fires
15+
- **THEN** the plugin registers a mapping from that shortcut string to the tool's name
16+
17+
#### Scenario: Ignoring tools without a string shortcut
18+
- **GIVEN** a tool is loaded whose `options.shortcut` is absent or is not a string
19+
- **WHEN** the `core:tool:loaded` event fires
20+
- **THEN** the plugin registers no shortcut for that tool
21+
22+
#### Scenario: Triggering an inline tool via shortcut
23+
- **GIVEN** an inline tool is registered with `options.shortcut` set to a key combination (e.g. `CMD+B`)
24+
- **WHEN** that key combination is pressed while the editor has focus
25+
- **THEN** the plugin prevents the native event's default action and applies the corresponding inline tool to the current selection via `api.selection.applyInlineTool`
26+
27+
#### Scenario: Matching only the first registered shortcut
28+
- **GIVEN** more than one registered shortcut would match the keydown
29+
- **WHEN** the `ui:key-down` event fires
30+
- **THEN** the plugin applies only the first matching tool and stops
31+
32+
#### Scenario: Ignoring keydown during IME composition
33+
- **GIVEN** the native keydown event has `isComposing === true`
34+
- **WHEN** the `ui:key-down` event fires
35+
- **THEN** the plugin performs no matching and leaves the native event untouched
36+
37+
#### Scenario: Tolerating a missing caret when applying a tool
38+
- **GIVEN** applying the inline tool throws an `IndexError` (e.g. no caret in a text input)
39+
- **WHEN** a matching shortcut is dispatched
40+
- **THEN** the plugin swallows the error and leaves the editor unchanged, while any other error propagates
41+
42+
#### Scenario: Releasing shortcuts on destroy
43+
- **GIVEN** a `ShortcutsPlugin` instance has registered shortcuts
44+
- **WHEN** `destroy()` is called
45+
- **THEN** it clears the registered shortcuts so subsequent keydowns dispatch nothing
46+
47+
Shortcuts for block tools and block tunes (a `shortcuts` map in tool `options`) are reserved for future work and not yet implemented.
48+
49+
Implemented in `src/index.ts`, validated by its co-located `.spec.ts`.

packages/core/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
"@editorjs/model": "workspace:^",
5050
"@editorjs/paragraph": "workspace:^",
5151
"@editorjs/sdk": "workspace:^",
52+
"@editorjs/shortcuts-plugin": "workspace:^",
5253
"inversify": "^8.1.0",
5354
"reflect-metadata": "^0.2.2"
5455
}

packages/core/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import { Paragraph } from '@editorjs/paragraph';
2323
import { BoldInlineTool } from '@editorjs/bold';
2424
import { ItalicInlineTool } from '@editorjs/italic';
2525
import { LinkInlineTool } from '@editorjs/inline-link';
26-
import { ShortcutsPlugin } from './plugins/ShortcutsPlugin.js';
26+
import { ShortcutsPlugin } from '@editorjs/shortcuts-plugin';
2727
import { DOMAdapters } from '@editorjs/dom-adapters';
2828
import { BlocksManager } from './components/BlockManager.js';
2929
import { BlockRenderer } from './components/BlockRenderer.js';

packages/core/tsconfig.build.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@
3030
},
3131
{
3232
"path": "../plugins/clipboard-plugin/tsconfig.build.json"
33+
},
34+
{
35+
"path": "../plugins/shortcuts-plugin/tsconfig.build.json"
3336
}
3437
]
3538
}

packages/core/tsconfig.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@
4949
},
5050
{
5151
"path": "../plugins/clipboard-plugin/tsconfig.build.json"
52+
},
53+
{
54+
"path": "../plugins/shortcuts-plugin/tsconfig.build.json"
5255
}
5356
]
5457
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
.yarn/*
2+
!.yarn/patches
3+
!.yarn/plugins
4+
!.yarn/releases
5+
!.yarn/sdks
6+
!.yarn/versions
7+
8+
# Swap the comments on the following lines if you don't wish to use zero-installs
9+
# Documentation here: https://yarnpkg.com/features/zero-installs
10+
#!.yarn/cache
11+
#.pnp.*
12+
13+
# IDE
14+
.idea/*
15+
16+
node_modules/*
17+
dist/*
18+
19+
# tests
20+
coverage/
21+
reports/
22+
23+
# stryker temp files
24+
.stryker-tmp
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Shortcuts Plugin
2+
3+
Built-in Editor.js Plugin that maps keyboard shortcuts declared in a tool's `options.shortcut` to inline-tool application.

0 commit comments

Comments
 (0)