fix: remove unsupported block colors menu

This commit is contained in:
lucaronin
2026-04-17 17:48:12 +02:00
parent 91a0247187
commit fb55ed2f6d
7 changed files with 86 additions and 1 deletions

View File

@@ -436,9 +436,10 @@ Defined in `src/components/editorSchema.tsx` and styled in `src/components/Edito
Defined in `src/components/tolariaEditorFormatting.tsx` and `src/components/tolariaEditorFormattingConfig.ts`:
- `SingleEditorView` disables BlockNote's default formatting toolbar and `/` menu, then mounts Tolaria-owned controllers so the visible formatting surface matches Tolaria's markdown round-trip guarantees.
- `SingleEditorView` disables BlockNote's default formatting toolbar, `/` menu, and side menu, then mounts Tolaria-owned controllers so the visible formatting surface matches Tolaria's markdown round-trip guarantees.
- The formatting toolbar only exposes inline controls that persist through `blocksToMarkdownLossy()` in Tolaria's save pipeline: bold, italic, strike, nesting, and link creation. Controls that BlockNote can render temporarily but Tolaria cannot faithfully persist, such as underline, color, alignment, and the block-type dropdown, are hidden instead of appearing to work and later disappearing.
- The `/` slash menu remains the supported path for markdown-safe block transformations such as headings, quotes, and list blocks. Tolaria filters out BlockNote's toggle-heading and toggle-list variants because those do not map cleanly to the markdown note model.
- The block-handle side menu keeps only actions that survive Tolaria's markdown round-trip. Delete and table-header toggles remain available; BlockNote's `Colors` submenu is removed because block colors are not part of Tolaria's supported markdown surface.
### Markdown-to-BlockNote Pipeline

View File

@@ -1,4 +1,5 @@
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import type { ReactNode } from 'react'
import { describe, it, expect, vi, beforeEach } from 'vitest'
// Provide a localStorage mock that supports all methods (jsdom's may be incomplete)
@@ -125,6 +126,12 @@ vi.mock('@blocknote/core/extensions', () => ({
vi.mock('@blocknote/react', () => ({
createReactInlineContentSpec: () => ({ render: () => null }),
BlockNoteViewRaw: ({ children }: { children?: ReactNode }) => (
<div data-testid="blocknote-view">{children}</div>
),
ComponentsContext: {
Provider: ({ children }: { children?: ReactNode }) => <>{children}</>,
},
useCreateBlockNote: () => ({
tryParseMarkdownToBlocks: async () => [],
replaceBlocks: () => {},
@@ -132,6 +139,7 @@ vi.mock('@blocknote/react', () => ({
insertInlineContent: () => {},
onMount: (cb: () => void) => { cb(); return () => {} },
}),
SideMenuController: () => null,
SuggestionMenuController: () => null,
}))

View File

@@ -70,6 +70,7 @@ vi.mock('@blocknote/react', () => ({
</div>
),
FormattingToolbarController: () => null,
SideMenuController: () => null,
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- mock
SuggestionMenuController: (props: any) => {
capturedGetItemsByTrigger[props.triggerCharacter] = props.getItems

View File

@@ -5,6 +5,7 @@ import {
SuggestionMenuController,
BlockNoteViewRaw,
ComponentsContext,
SideMenuController,
} from '@blocknote/react'
import { components } from '@blocknote/mantine'
import { MantineContext, MantineProvider } from '@mantine/core'
@@ -23,6 +24,7 @@ import {
TolariaFormattingToolbar,
TolariaFormattingToolbarController,
} from './tolariaEditorFormatting'
import { TolariaSideMenu } from './tolariaBlockNoteSideMenu'
import { useEditorLinkActivation } from './useEditorLinkActivation'
const TEST_TABLE_MARKDOWN = `| Head 1 | Head 2 | Head 3 |
@@ -223,7 +225,9 @@ export function SingleEditorView({ editor, entries, onNavigateWikilink, onChange
editable={editable}
formattingToolbar={false}
slashMenu={false}
sideMenu={false}
>
<SideMenuController sideMenu={TolariaSideMenu} />
<TolariaFormattingToolbarController
formattingToolbar={TolariaFormattingToolbar}
floatingUIOptions={{

View File

@@ -0,0 +1,44 @@
import { render, screen } from '@testing-library/react'
import type { ComponentType, PropsWithChildren } from 'react'
import { describe, expect, it, vi } from 'vitest'
import { TolariaSideMenu } from './tolariaBlockNoteSideMenu'
let capturedDragHandleMenu: ComponentType | null = null
vi.mock('@blocknote/react', () => ({
DragHandleMenu: ({ children }: PropsWithChildren) => (
<div data-testid="drag-handle-menu">{children}</div>
),
RemoveBlockItem: ({ children }: PropsWithChildren) => <div>{children}</div>,
SideMenu: ({ dragHandleMenu }: { dragHandleMenu?: ComponentType }) => {
capturedDragHandleMenu = dragHandleMenu ?? null
return <div data-testid="side-menu" />
},
TableColumnHeaderItem: ({ children }: PropsWithChildren) => <div>{children}</div>,
TableRowHeaderItem: ({ children }: PropsWithChildren) => <div>{children}</div>,
useDictionary: () => ({
drag_handle: {
delete_menuitem: 'Delete',
header_row_menuitem: 'Header row',
header_column_menuitem: 'Header column',
colors_menuitem: 'Colors',
},
}),
}))
describe('TolariaSideMenu', () => {
it('replaces BlockNote block colors with markdown-safe drag-handle items', () => {
render(<TolariaSideMenu />)
expect(screen.getByTestId('side-menu')).toBeInTheDocument()
expect(capturedDragHandleMenu).not.toBeNull()
const DragHandleMenuComponent = capturedDragHandleMenu!
render(<DragHandleMenuComponent />)
expect(screen.getByText('Delete')).toBeInTheDocument()
expect(screen.getByText('Header row')).toBeInTheDocument()
expect(screen.getByText('Header column')).toBeInTheDocument()
expect(screen.queryByText('Colors')).not.toBeInTheDocument()
})
})

View File

@@ -0,0 +1,25 @@
import {
DragHandleMenu,
RemoveBlockItem,
SideMenu,
TableColumnHeaderItem,
TableRowHeaderItem,
type SideMenuProps,
useDictionary,
} from '@blocknote/react'
function TolariaDragHandleMenu() {
const dict = useDictionary()
return (
<DragHandleMenu>
<RemoveBlockItem>{dict.drag_handle.delete_menuitem}</RemoveBlockItem>
<TableRowHeaderItem>{dict.drag_handle.header_row_menuitem}</TableRowHeaderItem>
<TableColumnHeaderItem>{dict.drag_handle.header_column_menuitem}</TableColumnHeaderItem>
</DragHandleMenu>
)
}
export function TolariaSideMenu(props: SideMenuProps) {
return <SideMenu {...props} dragHandleMenu={TolariaDragHandleMenu} />
}

View File

@@ -66,6 +66,8 @@ async function expectZoomedHandleClickToWork(
const menu = page.locator('.mantine-Menu-dropdown').filter({ has: page.locator('.mantine-Menu-item') }).first()
await expect(menu).toBeVisible({ timeout: 5_000 })
await expect(menu.getByRole('menuitem', { name: 'Delete' })).toBeVisible()
await expect(menu.getByRole('menuitem', { name: 'Colors' })).toHaveCount(0)
await page.keyboard.press('Escape')
await expect(menu).toBeHidden({ timeout: 5_000 })
}