Skip to content

Commit ed2c337

Browse files
nperez0111matthewlipskiYousefED
authored
feat: Major Extensions & UI Refactor (#2143)
* refactor(extensions): rewrite to use new extension system * chore: minor fixes * Refactored almost all formatting toolbar components to use `useEditorState` * Refactored remaining uses of hooks with `useEditorState` where possible (excl. `BlockTypeSelect`) * feat: virtual element hooks * Refactored formatting toolbar, file panel, and suggestion menu controllers * Fixed popover position reference not updating when it should & fixed formatting toolbar items * Cleaned up hooks * - Cleaned up refactored UI plugins - Fixed `useDismiss` not working on mouse click outside - Cleaned up `LinkToolbarController` - Made `BlockPopover` use actual block element as reference instead of virtual element - Added more configuration props to `GenericPopover` - Made `GenericPopover` set element reference when a real element, or virtual element with `contextElement`, is passed - Added editor DOM element as `contextElement` in `PositionPopover` * Refactored side menu & fixed issue with `GenericPopover` memoized `innerHTML` not getting updated * Comment edit * Fixed core build * Misc fixes * Small fix * Refactored table handles * Fixed various build & UI issues * Fixed various UI issues * Fixed link creation/editing throwing error * Refactored comments * chore: update prosemirror versions * fix: y-prosemirror plugin ordering * Updated comments to fully use new components APIs * chore: fix build * build: get multi-column building again * refactor: rewrite the AI extension to use the new plugin API * fix: make mounting and unmounting extensions actually work properly * Fixed z-index clipping issues * refactor: normalize extension naming no longer plugins * refactor: always turn a DOMRect into JSON for easy comparisons * refactor: change imports of extensions to be found at `@blocknote/core/extensions` * fix: use an abort signal instead of sending an abortcontroller * chore: fix circular import * chore: use any blocknote editor * refactor: less derived state * refactor: rename `usePlugin` -> `useExtension` * refactor: remove class member * refactor: rename `plugins` -> `prosemirrorPlugins` and `init` -> `mount` * Fixed suggestion menus closing when clicking scroll bar * fix: always pull the latest extensions in React * Fixed table handle issues * add comments * Fixed most remaining table handle issues * fix: formatting toolbar, comments + cleanup * refactor: filepanel unsubs it's listeners * Fixed build * refactor: rewrite how extensions work * chore: fix lint * Fixed formatting & link toolbar issues * Small fix * refactor: PR comments * Small fix * fix: handle collaboration again * refactor: remove resolveUsers from editor * refactor: cleanup some more off the editor * remove zustand dep * refactor: move comments to their sub-package * refactor: rename extensions to have `Extension` suffix * refactor: delete some un-used hooks * fix: remove comments options from the editor instance * Fixed suggestion menu issues * Misc changes/fixes * Small fix * fix: put back menu handling * Removed logs * refactor(comments): make extension use store exclusively * fix: break circular import * fix: remove `onCreate` event since it is not needed with current system * Formatting toolbar and file panel fixes * test: add some checks for tests * fix: use correct key * Fixed tests * Fixed collab tests * test: fixes for test running * test: get all unit tests passing again * chore: fix lint * build: lint depends on nothing * Refactored/tested/fixed AI menu and other misc changes * fix: use a single prosemirror plugin instance for all inputRules * Fixed block keyboard shortcuts not getting registered and local unit test errors * Updated e2e snaps * Fixed e2e tests * Updated e2e screenshot * Fixed create link button not showing editor selection * Fixed `SideMenu`/`BlockPositioner` updating bug * Minor changes * Fixed issues when editor is not editable * Added comment * small fix to aimenu * Misc fixes after review * Fixed build * Updated docs * Changed file panel extension state from object with block ID string to just block ID string * Moved all `DOMRect` caching logic to `GenericPopover` --------- Co-authored-by: Matthew Lipski <matthewlipski@gmail.com> Co-authored-by: yousefed <yousefdardiry@gmail.com>
1 parent 4a34907 commit ed2c337

File tree

337 files changed

+7248
-7849
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

337 files changed

+7248
-7849
lines changed

docs/content/docs/features/ai/custom-commands.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ First, we define a new AI command. Let's create one that makes selected text mor
2626

2727
```tsx
2828
import {
29+
AIExtension,
2930
AIMenuSuggestionItem,
30-
getAIExtension,
3131
aiDocumentFormats,
3232
} from "@blocknote/xl-ai";
3333

@@ -40,7 +40,7 @@ export const makeInformal = (
4040
aliases: ["informal", "make informal", "casual"],
4141
icon: <RiEmotionHappyFill size={18} />,
4242
onItemClick: async () => {
43-
await getAIExtension(editor).invokeAI({
43+
await editor.getExtension(AIExtension)?.invokeAI({
4444
// The prompt to send to the LLM:
4545
userPrompt: "Give the selected text a more informal (casual) tone",
4646
// Tell the LLM to specifically use the selected content as context (instead of the whole document)

docs/content/docs/features/ai/reference.mdx

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,6 @@ type AIRequestHelpers = {
5252
};
5353
```
5454

55-
## `getAIExtension`
56-
57-
Use `getAIExtension` to retrieve the AI extension instance registered to the editor:
58-
59-
```typescript
60-
function getAIExtension(editor: BlockNoteEditor): AIExtension;
61-
```
62-
6355
## `AIExtension`
6456

6557
The `AIExtension` class is the main class for the AI extension. It exposes state and methods to interact with BlockNote's AI features.

docs/content/docs/features/extensions.mdx

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ BlockNote includes an extensions system which lets you expand the editor's behav
1414

1515
## Creating an extension
1616

17-
An extension is an instance of the [`BlockNoteExtension`](https://github.com/TypeCellOS/BlockNote/blob/10cdbfb5f77ef82f3617c0fa1191e0bf5b7358c5/packages/core/src/editor/BlockNoteExtension.ts#L13) class. However, it's recommended for most use cases to create extensions using the `createBlockNoteExtension` function, rather than instanciating the class directly:
17+
You can create extensions using the `createExtension` function:
1818

1919
```typescript
20-
type BlockNoteExtensionOptions = {
20+
type Extension = {
2121
key: string;
2222
keyboardShortcuts?: Record<
2323
string,
@@ -35,18 +35,16 @@ type BlockNoteExtensionOptions = {
3535
tiptapExtensions?: AnyExtension[];
3636
}
3737

38-
const customBlockExtensionOptions: BlockNoteExtensionOptions = {
38+
const CustomExtension = createExtension({
3939
key: "customBlockExtension",
4040
keyboardShortcuts: ...,
4141
inputRules: ...,
4242
plugins: ...,
4343
tiptapExtensions: ...,
44-
}
45-
46-
const CustomExtension = createBlockNoteExtension(customBlockExtensionOptions);
44+
});
4745
```
4846

49-
Let's go over the options that can be passed into `createBlockNoteExtension`:
47+
Let's go over the options that can be passed into `createExtension`:
5048

5149
`key:` The name of the extension.
5250

@@ -68,20 +66,20 @@ Extensions can be added to the editor on their own via the [editor options](/doc
6866

6967
### Adding directly to the editor
7068

71-
The `extensions` [editor option](/docs/reference/editor/overview#options) takes an array of `BlockNoteExtension`s to be added to the editor:
69+
The `extensions` [editor option](/docs/reference/editor/overview#options) takes an array of extensions to be added to the editor:
7270

7371
```typescript
7472
const editor = useCreateBlockNote({
7573
extensions: [
7674
// Add extensions here:
77-
createBlockNoteExtension({ ... })
75+
createExtension({ ... })
7876
],
7977
});
8078
```
8179

8280
### Adding to custom blocks
8381

84-
When creating a [custom block](/docs/features/custom-schemas/custom-blocks#creating-a-custom-block-type) using `createReactBlockSpec`, you can pass an array of `BlockNoteExtension`s to the third parameter:
82+
When creating a [custom block](/docs/features/custom-schemas/custom-blocks#creating-a-custom-block-type) using `createReactBlockSpec`, you can pass an array of extensions to the third parameter:
8583

8684
```typescript
8785
const createCustomBlock = createReactBlockSpec(
@@ -95,7 +93,7 @@ const createCustomBlock = createReactBlockSpec(
9593
}
9694
[
9795
// Add extensions here:
98-
createBlockNoteExtension({ ... })
96+
createExtension({ ... })
9997
],
10098
});
10199
```

docs/content/docs/reference/editor/events.mdx

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,6 @@ The editor emits events for:
1616
- **Content changes** - When blocks are inserted, updated, or deleted
1717
- **Selection changes** - When the cursor position or selection changes
1818

19-
## `onCreate`
20-
21-
The `onCreate` callback is called when the editor has been initialized and is ready for use.
22-
23-
```typescript
24-
editor.onCreate(() => {
25-
console.log("Editor is ready for use");
26-
// Initialize plugins, set up event listeners, etc.
27-
});
28-
```
29-
3019
## `onMount`
3120

3221
The `onMount` callback is called when the editor has been mounted.

docs/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@
7777
"better-sqlite3": "^11.10.0",
7878
"classnames": "2.3.2",
7979
"clsx": "2.1.1",
80-
"docx": "^9.0.2",
80+
"docx": "^9.5.1",
8181
"framer-motion": "^11.18.2",
8282
"fumadocs-core": "15.5.4",
8383
"fumadocs-docgen": "2.0.1",

examples/01-basic/03-multi-column/src/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import {
22
BlockNoteSchema,
33
combineByGroup,
4-
filterSuggestionItems,
54
} from "@blocknote/core";
5+
import { filterSuggestionItems } from "@blocknote/core/extensions";
66
import * as locales from "@blocknote/core/locales";
77
import "@blocknote/core/fonts/inter.css";
88
import { BlockNoteView } from "@blocknote/mantine";

examples/03-ui-components/02-formatting-toolbar-buttons/src/BlueButton.tsx

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@ import "@blocknote/mantine/style.css";
22
import {
33
useBlockNoteEditor,
44
useComponentsContext,
5-
useEditorContentOrSelectionChange,
5+
useEditorState,
66
useSelectedBlocks,
77
} from "@blocknote/react";
8-
import { useState } from "react";
98

109
// Custom Formatting Toolbar Button to toggle blue text & background color.
1110
export function BlueButton() {
@@ -14,18 +13,12 @@ export function BlueButton() {
1413
const Components = useComponentsContext()!;
1514

1615
// Tracks whether the text & background are both blue.
17-
const [isSelected, setIsSelected] = useState<boolean>(
18-
editor.getActiveStyles().textColor === "blue" &&
19-
editor.getActiveStyles().backgroundColor === "blue",
20-
);
21-
22-
// Updates state on content or selection change.
23-
useEditorContentOrSelectionChange(() => {
24-
setIsSelected(
16+
const isSelected = useEditorState({
17+
editor,
18+
selector: ({ editor }) =>
2519
editor.getActiveStyles().textColor === "blue" &&
26-
editor.getActiveStyles().backgroundColor === "blue",
27-
);
28-
}, editor);
20+
editor.getActiveStyles().backgroundColor === "blue",
21+
});
2922

3023
// Doesn't render unless a at least one block with inline content is
3124
// selected. You can use a similar pattern of returning `null` to

examples/03-ui-components/04-side-menu-buttons/src/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export default function App() {
3939
sideMenu={(props) => (
4040
<SideMenu {...props}>
4141
{/* Button which removes the hovered block. */}
42-
<RemoveBlockButton {...props} />
42+
<RemoveBlockButton />
4343
<DragHandleButton {...props} />
4444
</SideMenu>
4545
)}

examples/03-ui-components/04-side-menu-buttons/src/RemoveBlockButton.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,34 @@
1+
import {} from "@blocknote/core";
2+
import { SideMenuExtension } from "@blocknote/core/extensions";
13
import {
2-
SideMenuProps,
34
useBlockNoteEditor,
45
useComponentsContext,
6+
useExtensionState,
57
} from "@blocknote/react";
68
import { MdDelete } from "react-icons/md";
79

810
// Custom Side Menu button to remove the hovered block.
9-
export function RemoveBlockButton(props: SideMenuProps) {
11+
export function RemoveBlockButton() {
1012
const editor = useBlockNoteEditor();
1113

1214
const Components = useComponentsContext()!;
1315

16+
const block = useExtensionState(SideMenuExtension, {
17+
selector: (state) => state?.block,
18+
});
19+
20+
if (!block) {
21+
return null;
22+
}
23+
1424
return (
1525
<Components.SideMenu.Button
1626
label="Remove block"
1727
icon={
1828
<MdDelete
1929
size={24}
2030
onClick={() => {
21-
editor.removeBlocks([props.block]);
31+
editor.removeBlocks([block]);
2232
}}
2333
/>
2434
}

examples/03-ui-components/05-side-menu-drag-handle-items/src/App.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import "@blocknote/mantine/style.css";
44
import {
55
BlockColorsItem,
66
DragHandleMenu,
7-
DragHandleMenuProps,
87
RemoveBlockItem,
98
SideMenu,
109
SideMenuController,
@@ -16,12 +15,12 @@ import { ResetBlockTypeItem } from "./ResetBlockTypeItem";
1615
// To avoid rendering issues, it's good practice to define your custom drag
1716
// handle menu in a separate component, instead of inline within the `sideMenu`
1817
// prop of `SideMenuController`.
19-
const CustomDragHandleMenu = (props: DragHandleMenuProps) => (
20-
<DragHandleMenu {...props}>
21-
<RemoveBlockItem {...props}>Delete</RemoveBlockItem>
22-
<BlockColorsItem {...props}>Colors</BlockColorsItem>
18+
const CustomDragHandleMenu = () => (
19+
<DragHandleMenu>
20+
<RemoveBlockItem>Delete</RemoveBlockItem>
21+
<BlockColorsItem>Colors</BlockColorsItem>
2322
{/* Item which resets the hovered block's type. */}
24-
<ResetBlockTypeItem {...props}>Reset Type</ResetBlockTypeItem>
23+
<ResetBlockTypeItem>Reset Type</ResetBlockTypeItem>
2524
</DragHandleMenu>
2625
);
2726

0 commit comments

Comments
 (0)