feat(workspace): borderless code blocks, round code/audio/dictation blocks
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m15s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m15s
Code blocks lose border and header bar: language at the top left, a round copy button at the bottom right on hover; converting back to text moves to the bullet's action bubble (also for numbered items). Code, audio and dictation blocks use fixed round corners whatever radius the theme sets. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e243e328c7
commit
546bcba666
7 files changed
+57
-83
No files matched your search
@@ -1,6 +1,6 @@
|
||||
import React, { useState, useEffect, useLayoutEffect, useMemo, useRef, useCallback } from 'react';
|
||||
import Prism from 'prismjs';
|
||||
import { Code, Copy, Check } from 'lucide-react';
|
||||
import { Copy, Check } from 'lucide-react';
|
||||
import { DocumentBlock } from '../../lib/types';
|
||||
|
||||
Prism.manual = true;
|
||||
@@ -311,6 +311,7 @@ export interface CodeBlockEditorProps {
|
||||
registerTextareaRef?: (blockId: string, el: HTMLTextAreaElement | null) => void;
|
||||
onChangeContent?: (content: string) => void;
|
||||
onChangeLang?: (lang: string) => void;
|
||||
/** Kept for callers; converting back to text lives in the bullet's action bubble now */
|
||||
onConvertToText?: () => void;
|
||||
handleFocus?: (e: React.FocusEvent<HTMLTextAreaElement>) => void;
|
||||
handleBlur?: (e: React.FocusEvent<HTMLTextAreaElement>) => void;
|
||||
@@ -326,7 +327,6 @@ export const CodeBlockEditor: React.FC<CodeBlockEditorProps> = ({
|
||||
registerTextareaRef,
|
||||
onChangeContent,
|
||||
onChangeLang,
|
||||
onConvertToText,
|
||||
handleFocus,
|
||||
handleBlur,
|
||||
handleContentChange,
|
||||
@@ -542,73 +542,50 @@ export const CodeBlockEditor: React.FC<CodeBlockEditorProps> = ({
|
||||
const hasCustomUnknownLang = Boolean(selectValue && !KNOWN_OPTION_IDS.has(selectValue));
|
||||
|
||||
return (
|
||||
<div className="bg-code border border-border rounded-btn my-0.5 max-w-full overflow-hidden">
|
||||
{/* Code Block Header */}
|
||||
<div className="flex items-center justify-between gap-2 px-2 py-1 border-b border-border text-[11px] font-mono text-faint">
|
||||
<div className="flex items-center gap-1.5 min-w-0">
|
||||
<Code className="w-3.5 h-3.5 text-muted shrink-0" />
|
||||
{canEdit ? (
|
||||
<select
|
||||
aria-label="Sprache auswählen"
|
||||
value={selectValue}
|
||||
onChange={(e) => onChangeLang?.(e.target.value)}
|
||||
className="min-h-[44px] sm:min-h-0 bg-transparent text-muted hover:text-main focus:text-main font-mono text-[11px] font-medium border border-transparent hover:border-border focus:border-accent rounded-btn px-1.5 py-0.5 outline-none cursor-pointer"
|
||||
>
|
||||
<option value="" className="bg-panel text-main">
|
||||
Text
|
||||
</option>
|
||||
{hasCustomUnknownLang && (
|
||||
<option value={selectValue} className="bg-panel text-main">
|
||||
{selectValue}
|
||||
</option>
|
||||
)}
|
||||
{CODE_LANGUAGE_GROUPS.map((group) => (
|
||||
<optgroup key={group.label} label={group.label} className="bg-panel text-muted">
|
||||
{group.options.map((opt) => (
|
||||
<option key={opt.id} value={opt.id} className="bg-panel text-main">
|
||||
{opt.label}
|
||||
</option>
|
||||
))}
|
||||
</optgroup>
|
||||
))}
|
||||
</select>
|
||||
) : (
|
||||
<span className="font-mono text-[11px] text-muted px-1">{block.lang || 'Text'}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1.5 shrink-0">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleCopy}
|
||||
className="min-h-[44px] sm:min-h-0 px-2 py-0.5 rounded-btn text-[11px] font-mono text-muted hover:text-main hover:bg-panel-elevated transition-colors inline-flex items-center gap-1 cursor-pointer"
|
||||
title="In die Zwischenablage kopieren"
|
||||
<div className="group/code relative bg-code rounded-2xl my-0.5 max-w-full overflow-hidden">
|
||||
{/* Language at the top left; no header bar */}
|
||||
<div className="flex items-center px-1.5 pt-1 text-[11px] font-mono text-faint">
|
||||
{canEdit ? (
|
||||
<select
|
||||
aria-label="Sprache auswählen"
|
||||
value={selectValue}
|
||||
onChange={(e) => onChangeLang?.(e.target.value)}
|
||||
className="min-h-[44px] sm:min-h-0 bg-transparent text-faint hover:text-main focus:text-main font-mono text-[11px] font-medium border border-transparent hover:border-border focus:border-accent rounded-btn px-1 py-0.5 outline-none cursor-pointer"
|
||||
>
|
||||
{copied ? (
|
||||
<>
|
||||
<Check className="w-3 h-3 text-status-ok" />
|
||||
<span className="text-status-ok">Kopiert!</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Copy className="w-3 h-3 opacity-75" />
|
||||
<span>Kopieren</span>
|
||||
</>
|
||||
<option value="" className="bg-panel text-main">
|
||||
Text
|
||||
</option>
|
||||
{hasCustomUnknownLang && (
|
||||
<option value={selectValue} className="bg-panel text-main">
|
||||
{selectValue}
|
||||
</option>
|
||||
)}
|
||||
</button>
|
||||
|
||||
{canEdit && onConvertToText && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onConvertToText}
|
||||
className="min-h-[44px] sm:min-h-0 px-2 py-0.5 rounded-btn text-[11px] font-mono text-muted hover:text-accent hover:bg-panel-elevated transition-colors inline-flex items-center cursor-pointer"
|
||||
>
|
||||
Als Text
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{CODE_LANGUAGE_GROUPS.map((group) => (
|
||||
<optgroup key={group.label} label={group.label} className="bg-panel text-muted">
|
||||
{group.options.map((opt) => (
|
||||
<option key={opt.id} value={opt.id} className="bg-panel text-main">
|
||||
{opt.label}
|
||||
</option>
|
||||
))}
|
||||
</optgroup>
|
||||
))}
|
||||
</select>
|
||||
) : (
|
||||
<span className="font-mono text-[11px] text-faint px-1">{block.lang || 'Text'}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Copy: round icon button at the bottom right, shown on hover (always on touch screens) */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleCopy}
|
||||
className="absolute bottom-2 right-2 z-10 h-9 w-9 sm:h-8 sm:w-8 rounded-full bg-panel border border-border shadow-panel flex items-center justify-center text-muted hover:text-main opacity-100 sm:opacity-0 sm:group-hover/code:opacity-100 focus-visible:opacity-100 transition-opacity cursor-pointer"
|
||||
title={copied ? 'Kopiert!' : 'In die Zwischenablage kopieren'}
|
||||
aria-label={copied ? 'Kopiert!' : 'Kopieren'}
|
||||
>
|
||||
{copied ? <Check className="w-3.5 h-3.5 text-status-ok" /> : <Copy className="w-3.5 h-3.5" />}
|
||||
</button>
|
||||
|
||||
{/* Live Highlighted Overlay + Textarea with Line Numbers */}
|
||||
<div className="flex relative w-full overflow-hidden">
|
||||
{/* Line Numbers Column */}
|
||||
@@ -618,7 +595,7 @@ export const CodeBlockEditor: React.FC<CodeBlockEditorProps> = ({
|
||||
onScroll={handleLineNumbersScroll}
|
||||
onWheel={handleLineNumbersWheel}
|
||||
data-testid="code-line-numbers"
|
||||
className="select-none py-2 pl-2.5 pr-2 font-mono text-xs text-faint leading-relaxed text-right border-r border-border/40 shrink-0 overflow-hidden"
|
||||
className="select-none py-2 pl-2.5 pr-2 font-mono text-xs text-faint leading-relaxed text-right shrink-0 overflow-hidden"
|
||||
>
|
||||
{Array.from({ length: lineCount }, (_, i) => (
|
||||
<div key={i + 1} data-line-number={i + 1}>
|
||||
|
||||
Reference in new issue
Block a user