fix: expose mermaid raw edit action
This commit is contained in:
@@ -321,16 +321,25 @@
|
||||
background: var(--surface-editor);
|
||||
}
|
||||
|
||||
.editor__blocknote-container .mermaid-diagram__edit-button,
|
||||
.editor__blocknote-container .mermaid-diagram__expand-button {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
z-index: 1;
|
||||
opacity: 0;
|
||||
background: var(--surface-editor);
|
||||
box-shadow: 0 6px 16px rgb(15 23 42 / 0.14);
|
||||
}
|
||||
|
||||
.editor__blocknote-container .mermaid-diagram__edit-button {
|
||||
right: 44px;
|
||||
}
|
||||
|
||||
.editor__blocknote-container .mermaid-diagram__expand-button {
|
||||
right: 8px;
|
||||
}
|
||||
|
||||
.editor__blocknote-container .mermaid-diagram:is(:hover, :focus-within) .mermaid-diagram__edit-button,
|
||||
.editor__blocknote-container .mermaid-diagram:is(:hover, :focus-within) .mermaid-diagram__expand-button {
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -349,6 +358,7 @@
|
||||
}
|
||||
|
||||
@media (hover: none) {
|
||||
.editor__blocknote-container .mermaid-diagram__edit-button,
|
||||
.editor__blocknote-container .mermaid-diagram__expand-button {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { APP_COMMAND_EVENT_NAME, APP_COMMAND_IDS } from '../hooks/appCommandDispatcher'
|
||||
import { RUNTIME_STYLE_NONCE } from '../lib/runtimeStyleNonce'
|
||||
import { MermaidDiagram } from './MermaidDiagram'
|
||||
|
||||
@@ -59,15 +60,40 @@ describe('MermaidDiagram', () => {
|
||||
expect(screen.getByTestId('mermaid-diagram-dialog-viewport').querySelector('svg')).not.toBeNull()
|
||||
})
|
||||
|
||||
it('offers a raw editor action for editing Mermaid source immediately', () => {
|
||||
const commands: string[] = []
|
||||
const handleCommand = (event: Event) => {
|
||||
if (event instanceof CustomEvent && typeof event.detail === 'string') {
|
||||
commands.push(event.detail)
|
||||
}
|
||||
}
|
||||
window.addEventListener(APP_COMMAND_EVENT_NAME, handleCommand)
|
||||
|
||||
try {
|
||||
render(
|
||||
<MermaidDiagram
|
||||
diagram={'flowchart LR\nA --> B'}
|
||||
source={'```mermaid\nflowchart LR\nA --> B\n```'}
|
||||
/>,
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Open the raw editor' }))
|
||||
|
||||
expect(commands).toEqual([APP_COMMAND_IDS.editToggleRawEditor])
|
||||
} finally {
|
||||
window.removeEventListener(APP_COMMAND_EVENT_NAME, handleCommand)
|
||||
}
|
||||
})
|
||||
|
||||
it('keeps rendered SVG pointer events inside the Mermaid block', async () => {
|
||||
const onBlockPointer = vi.fn()
|
||||
render(
|
||||
<button type="button" onClick={onBlockPointer} onMouseDown={onBlockPointer}>
|
||||
<div onClick={onBlockPointer} onMouseDown={onBlockPointer}>
|
||||
<MermaidDiagram
|
||||
diagram={'flowchart LR\nA --> B'}
|
||||
source={'```mermaid\nflowchart LR\nA --> B\n```'}
|
||||
/>
|
||||
</button>,
|
||||
</div>,
|
||||
)
|
||||
|
||||
await waitFor(() => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ArrowsOut as Maximize2 } from '@phosphor-icons/react'
|
||||
import { ArrowsOut as Maximize2, PencilSimpleLine } from '@phosphor-icons/react'
|
||||
import { useEffect, useId, useMemo, useState, type SyntheticEvent } from 'react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
@@ -8,6 +8,9 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@/components/ui/dialog'
|
||||
import { APP_COMMAND_EVENT_NAME, APP_COMMAND_IDS } from '../hooks/appCommandDispatcher'
|
||||
import { translate } from '../lib/i18n'
|
||||
import { trackEvent } from '../lib/telemetry'
|
||||
import { SafeSvgDiv } from './SafeMarkup'
|
||||
|
||||
type MermaidApi = typeof import('mermaid')['default']
|
||||
@@ -41,6 +44,7 @@ const MERMAID_RENDER_HOST_STYLE = [
|
||||
'height:0',
|
||||
'overflow:hidden',
|
||||
].join(';')
|
||||
const OPEN_RAW_EDITOR_LABEL = translate('en', 'editor.toolbar.rawOpen')
|
||||
|
||||
function renderIdFromReactId(reactId: string): string {
|
||||
const safeId = reactId.replace(/[^a-zA-Z0-9_-]/g, '')
|
||||
@@ -127,6 +131,33 @@ function stopMermaidViewportEvent(event: SyntheticEvent): void {
|
||||
event.stopPropagation()
|
||||
}
|
||||
|
||||
function openRawEditorForMermaidSource(event: SyntheticEvent): void {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
trackEvent('editor_mermaid_raw_edit_requested')
|
||||
window.dispatchEvent(new CustomEvent(APP_COMMAND_EVENT_NAME, {
|
||||
detail: APP_COMMAND_IDS.editToggleRawEditor,
|
||||
}))
|
||||
}
|
||||
|
||||
function MermaidRawEditorButton() {
|
||||
return (
|
||||
<Button
|
||||
aria-label={OPEN_RAW_EDITOR_LABEL}
|
||||
className="mermaid-diagram__edit-button"
|
||||
contentEditable={false}
|
||||
onClick={openRawEditorForMermaidSource}
|
||||
onMouseDown={stopMermaidViewportEvent}
|
||||
size="icon-sm"
|
||||
title={OPEN_RAW_EDITOR_LABEL}
|
||||
type="button"
|
||||
variant="outline"
|
||||
>
|
||||
<PencilSimpleLine aria-hidden="true" />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
function MermaidLightbox({ svg }: { svg: string }) {
|
||||
return (
|
||||
<Dialog>
|
||||
@@ -186,6 +217,7 @@ export function MermaidDiagram({ diagram, source }: MermaidDiagramProps) {
|
||||
if (!diagram.trim() || currentState.error) {
|
||||
return (
|
||||
<figure className="mermaid-diagram mermaid-diagram--error" data-testid="mermaid-diagram-error">
|
||||
<MermaidRawEditorButton />
|
||||
<figcaption>Mermaid diagram unavailable</figcaption>
|
||||
<MermaidSourceFallback source={source} />
|
||||
</figure>
|
||||
@@ -194,6 +226,7 @@ export function MermaidDiagram({ diagram, source }: MermaidDiagramProps) {
|
||||
|
||||
return (
|
||||
<figure className="mermaid-diagram" data-testid="mermaid-diagram">
|
||||
<MermaidRawEditorButton />
|
||||
<MermaidLightbox svg={currentState.svg} />
|
||||
<MermaidSvgViewport
|
||||
ariaLabel="Mermaid diagram"
|
||||
|
||||
Reference in New Issue
Block a user