From cc4203ab1c996312e59a6561c97c44e5b59502df Mon Sep 17 00:00:00 2001 From: lucaronin Date: Fri, 5 Jun 2026 23:32:01 +0200 Subject: [PATCH] fix: expose mermaid raw edit action --- src/components/EditorTheme.css | 12 ++++++++- src/components/MermaidDiagram.test.tsx | 30 ++++++++++++++++++++-- src/components/MermaidDiagram.tsx | 35 +++++++++++++++++++++++++- 3 files changed, 73 insertions(+), 4 deletions(-) diff --git a/src/components/EditorTheme.css b/src/components/EditorTheme.css index 583ec102..c1d2dcb8 100644 --- a/src/components/EditorTheme.css +++ b/src/components/EditorTheme.css @@ -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; } diff --git a/src/components/MermaidDiagram.test.tsx b/src/components/MermaidDiagram.test.tsx index 518ae300..49abc920 100644 --- a/src/components/MermaidDiagram.test.tsx +++ b/src/components/MermaidDiagram.test.tsx @@ -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( + 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( - , + , ) await waitFor(() => { diff --git a/src/components/MermaidDiagram.tsx b/src/components/MermaidDiagram.tsx index 16245aad..3765a7f3 100644 --- a/src/components/MermaidDiagram.tsx +++ b/src/components/MermaidDiagram.tsx @@ -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 ( + + ) +} + function MermaidLightbox({ svg }: { svg: string }) { return ( @@ -186,6 +217,7 @@ export function MermaidDiagram({ diagram, source }: MermaidDiagramProps) { if (!diagram.trim() || currentState.error) { return (
+
Mermaid diagram unavailable
@@ -194,6 +226,7 @@ export function MermaidDiagram({ diagram, source }: MermaidDiagramProps) { return (
+