@@ -13,11 +13,15 @@ import type { ReactNodeViewProps } from '@tiptap/react'
1313import { NodeViewContent , NodeViewWrapper , ReactNodeViewRenderer } from '@tiptap/react'
1414import { DASHBOARD_EMBED_LANGUAGE } from '@/lib/dashboards/embed-language'
1515import { DIFF_EMBED_LANGUAGE } from '@/lib/diff/embed-language'
16+ import {
17+ looksLikeMermaid ,
18+ MermaidDiagram ,
19+ } from '@/app/workspace/[workspaceId]/files/components/file-viewer/mermaid-diagram'
20+ import { MarkdownCodeBlock } from '@/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/code-block-schema'
21+ import { detectLanguage } from '@/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/detect-language'
1622import { MarkdownStreamingContext } from '@/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/markdown-streaming-context'
17- import { looksLikeMermaid , MermaidDiagram } from '../mermaid-diagram'
18- import { MarkdownCodeBlock } from './code-block-schema'
19- import { detectLanguage } from './detect-language'
20- import { useEditorEditable } from './use-editor-editable'
23+ import { ToolbarButton } from '@/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/menus/toolbar-button'
24+ import { useEditorEditable } from '@/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/use-editor-editable'
2125
2226/** Kept out of every rich-markdown surface's graph until a document actually holds a dashboard. */
2327const DashboardEmbed = lazy ( ( ) =>
@@ -53,9 +57,6 @@ const LANGUAGE_OPTIONS = [
5357 { value : 'yaml' , label : 'YAML' } ,
5458] as const
5559
56- const CONTROL_CLASS =
57- 'flex size-[24px] items-center justify-center rounded-lg text-[var(--text-icon)] outline-hidden transition-colors hover-hover:bg-[var(--surface-hover)] hover-hover:text-[var(--text-body)] focus-visible:bg-[var(--surface-hover)] [&_svg]:size-[14px]'
58-
5960/**
6061 * Code block view with hover controls (language picker, line-wrap, copy). When the block holds
6162 * Mermaid — tagged ```mermaid or {@link looksLikeMermaid auto-detected} — it renders as a diagram
@@ -66,7 +67,7 @@ const CONTROL_CLASS =
6667 * dashboard panels the same way.
6768 */
6869function CodeBlockView ( { node, updateAttributes, editor, getPos } : ReactNodeViewProps ) {
69- const [ wrap , setWrap ] = useState ( false )
70+ const [ wrap , setWrap ] = useState ( ( ) => node . attrs . language === DIFF_EMBED_LANGUAGE )
7071 const [ menuOpen , setMenuOpen ] = useState ( false )
7172 const [ editingInline , setEditingInline ] = useState ( false )
7273 const [ peekSource , setPeekSource ] = useState ( false )
@@ -144,15 +145,17 @@ function CodeBlockView({ node, updateAttributes, editor, getPos }: ReactNodeView
144145 < NodeViewWrapper className = 'group relative' >
145146 < div
146147 className = { cn (
147- 'absolute top-1.5 right-2 z-10 flex items-center gap-0.5 opacity-0 transition-opacity group-focus-within:opacity-100 group-hover:opacity-100' ,
148+ 'absolute top-1.5 right-2 z-10 flex items-center gap-0.5 opacity-0 transition-opacity group-focus-within:opacity-100 group-hover:opacity-100 [@media(any-pointer:coarse)]:opacity-100 [@media(hover:none)]:opacity-100' ,
149+ isDiff && showRendered && 'top-3 right-3 opacity-100' ,
150+ isRendered && showSource && 'static mb-1 justify-end opacity-100' ,
148151 menuOpen && 'opacity-100'
149152 ) }
150153 contentEditable = { false }
151154 >
152155 { isRendered && (
153- < button
154- type = 'button'
155- aria- label= {
156+ < ToolbarButton
157+ icon = { showSource ? Eye : Code }
158+ label = {
156159 showSource
157160 ? isDashboard
158161 ? 'Show dashboard'
@@ -161,12 +164,8 @@ function CodeBlockView({ node, updateAttributes, editor, getPos }: ReactNodeView
161164 : 'Show diagram'
162165 : 'Show source'
163166 }
164- onMouseDown = { ( event ) => event . preventDefault ( ) }
165167 onClick = { toggleSource }
166- className = { CONTROL_CLASS }
167- >
168- { showSource ? < Eye /> : < Code /> }
169- </ button >
168+ />
170169 ) }
171170 { ! isRendered &&
172171 ( editable ? (
@@ -204,30 +203,19 @@ function CodeBlockView({ node, updateAttributes, editor, getPos }: ReactNodeView
204203 { label }
205204 </ span >
206205 ) ) }
207- { ! isRendered && editable && (
208- < button
209- type = 'button'
210- aria-label = 'Toggle line wrap'
211- aria-pressed = { wrap }
212- onMouseDown = { ( event ) => event . preventDefault ( ) }
206+ { ( isDiff || ( ! isRendered && editable ) ) && (
207+ < ToolbarButton
208+ icon = { Wrap }
209+ label = 'Toggle line wrap'
210+ isActive = { wrap }
213211 onClick = { ( ) => setWrap ( ( value ) => ! value ) }
214- className = { cn (
215- CONTROL_CLASS ,
216- wrap && 'bg-[var(--surface-active)] text-[var(--text-body)]'
217- ) }
218- >
219- < Wrap />
220- </ button >
212+ />
221213 ) }
222- < button
223- type = 'button'
224- aria-label = 'Copy code'
225- onMouseDown = { ( event ) => event . preventDefault ( ) }
214+ < ToolbarButton
215+ icon = { copied ? Check : Duplicate }
216+ label = { copied ? 'Copied' : isDiff ? 'Copy diff' : 'Copy code' }
226217 onClick = { ( ) => copy ( text ) }
227- className = { CONTROL_CLASS }
228- >
229- { copied ? < Check /> : < Duplicate /> }
230- </ button >
218+ />
231219 </ div >
232220 < pre className = { cn ( 'code-editor-theme pr-20' , showRendered && 'hidden' ) } data-wrap = { wrap } >
233221 < NodeViewContent < 'code' > as = 'code' />
@@ -252,7 +240,7 @@ function CodeBlockView({ node, updateAttributes, editor, getPos }: ReactNodeView
252240 </ Suspense >
253241 ) : isDiff ? (
254242 < Suspense fallback = { null } >
255- < DiffEmbed source = { text } isStreaming = { isStreaming } />
243+ < DiffEmbed source = { text } isStreaming = { isStreaming } wrapLines = { wrap } />
256244 </ Suspense >
257245 ) : (
258246 < MermaidDiagram definition = { text } className = 'mermaid-diagram-frame' />
0 commit comments