This commit is contained in:
seb
2026-10-07 13:23:44 +02:00
parent 6c0b1503e8
commit 7045fe5e69
5 changed files with 140 additions and 202 deletions

View File

@@ -797,11 +797,11 @@ export default class ChatDrawer extends PureComponent {
)} )}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, ml: 1, flexShrink: 0 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, ml: 1, flexShrink: 0 }}>
<Tooltip title={isVfsOpen ? 'VFS-Dateisystem schließen' : 'VFS-Dateisystem parallel öffnen'}> <Tooltip title={isVfsOpen ? 'Dateien schließen' : 'Dateien parallel öffnen'}>
<IconButton <IconButton
size="small" size="small"
onClick={() => vfsAutomat.actions.toggleVfs()} onClick={() => vfsAutomat.actions.toggleVfs()}
aria-label="VFS Dateisystem" aria-label="Dateien"
sx={{ sx={{
p: 0.5, p: 0.5,
color: isVfsOpen ? 'primary.main' : 'text.secondary', color: isVfsOpen ? 'primary.main' : 'text.secondary',
@@ -1322,36 +1322,54 @@ export default class ChatDrawer extends PureComponent {
</Box> </Box>
)} )}
{/* Vertical Split Resizer between VFS and Chat */} {/* Vertical Split Resizer between VFS and Chat (0px width so no layout shift or grey bar) */}
{isVfsOpen && ( {isVfsOpen && (
<Box <Box
onPointerDown={this.handleVfsResizerStart} onPointerDown={this.handleVfsResizerStart}
onMouseDown={this.handleVfsResizerStart} onMouseDown={this.handleVfsResizerStart}
title="Ziehen zum Anpassen der Spaltenbreiten"
sx={{ sx={{
width: 8, width: 0,
cursor: 'col-resize', position: 'relative',
bgcolor: 'divider',
flexShrink: 0, flexShrink: 0,
zIndex: 10,
display: { xs: 'none', sm: 'flex' }, display: { xs: 'none', sm: 'flex' },
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
cursor: 'col-resize',
touchAction: 'none', touchAction: 'none',
userSelect: 'none', userSelect: 'none',
transition: 'background-color 0.15s, box-shadow 0.15s', '&:hover .vfs-resizer-line, &:active .vfs-resizer-line': {
'&:hover, &:active': {
bgcolor: 'primary.main', bgcolor: 'primary.main',
opacity: 1,
height: 48,
width: 4,
boxShadow: `0 0 8px ${BLUE_GLOW}`, boxShadow: `0 0 8px ${BLUE_GLOW}`,
}, },
}} }}
> >
{/* Invisible wide hit area (16px wide, centered on the 1px border) */}
<Box <Box
sx={{ sx={{
position: 'absolute',
top: 0,
bottom: 0,
left: -8,
width: 16,
cursor: 'col-resize',
}}
/>
{/* Visual handle indicator on hover / active */}
<Box
className="vfs-resizer-line"
sx={{
position: 'absolute',
width: 2, width: 2,
height: 36, height: 32,
borderRadius: 1, borderRadius: 1,
bgcolor: (theme) => bgcolor: 'divider',
theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.35)' : 'rgba(0,0,0,0.25)', opacity: 0.5,
transition: 'background-color 0.15s, height 0.15s, width 0.15s, opacity 0.15s, box-shadow 0.15s',
pointerEvents: 'none',
}} }}
/> />
</Box> </Box>

View File

@@ -2,7 +2,6 @@ import React, { PureComponent } from 'react';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import IconButton from '@mui/material/IconButton'; import IconButton from '@mui/material/IconButton';
import Tooltip from '@mui/material/Tooltip';
import FolderOpenRoundedIcon from '@mui/icons-material/FolderOpenRounded'; import FolderOpenRoundedIcon from '@mui/icons-material/FolderOpenRounded';
import CloseRoundedIcon from '@mui/icons-material/CloseRounded'; import CloseRoundedIcon from '@mui/icons-material/CloseRounded';
@@ -61,13 +60,20 @@ export default class VfsEditorPanel extends PureComponent {
this.unsub?.(); this.unsub?.();
} }
handleMentionInChat = (file) => { handleMentionInChat = (file, lineInfo = null) => {
if (!file) return; if (!file) return;
if (this.props.onInsertToChat) { if (this.props.onInsertToChat) {
const isImg = file.isImage; const isImg = file.isImage;
const tag = isImg let tag = '';
? `![${file.name}](${file.name})` if (lineInfo?.startLine) {
: `[${file.name}](${file.name})`; const lineRef =
lineInfo.startLine === lineInfo.endLine
? `L${lineInfo.startLine}`
: `L${lineInfo.startLine}-L${lineInfo.endLine}`;
tag = `[${file.name}#${lineRef}](${file.name}#${lineRef})`;
} else {
tag = isImg ? `![${file.name}](${file.name})` : `[${file.name}](${file.name})`;
}
this.props.onInsertToChat(tag); this.props.onInsertToChat(tag);
} }
}; };
@@ -116,7 +122,7 @@ export default class VfsEditorPanel extends PureComponent {
onSave={() => vfsAutomat.actions.saveCurrentFile()} onSave={() => vfsAutomat.actions.saveCurrentFile()}
onClose={() => vfsAutomat.actions.selectFile(null)} onClose={() => vfsAutomat.actions.selectFile(null)}
onDownload={() => vfsAutomat.actions.downloadFile(selectedFileName)} onDownload={() => vfsAutomat.actions.downloadFile(selectedFileName)}
onMentionInChat={() => this.handleMentionInChat(selectedFile)} onMentionInChat={(lineInfo) => this.handleMentionInChat(selectedFile, lineInfo)}
/> />
) : selectedFileName && !selectedFile?.isText ? ( ) : selectedFileName && !selectedFile?.isText ? (
/* Single Column Mode: Preview (Image, PDF, Binary) */ /* Single Column Mode: Preview (Image, PDF, Binary) */
@@ -147,19 +153,17 @@ export default class VfsEditorPanel extends PureComponent {
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<FolderOpenRoundedIcon color="primary" sx={{ fontSize: 22 }} /> <FolderOpenRoundedIcon color="primary" sx={{ fontSize: 22 }} />
<Typography variant="subtitle1" fontWeight={700}> <Typography variant="subtitle1" fontWeight={700}>
VFS Dateisystem Dateien
</Typography> </Typography>
</Box> </Box>
<Tooltip title="VFS-Dateisystem schließen"> <IconButton
<IconButton size="small"
size="small" onClick={() => vfsAutomat.actions.closeVfs()}
onClick={() => vfsAutomat.actions.closeVfs()} aria-label="VFS schließen"
aria-label="VFS schließen" >
> <CloseRoundedIcon fontSize="small" />
<CloseRoundedIcon fontSize="small" /> </IconButton>
</IconButton>
</Tooltip>
</Box> </Box>
<Box sx={{ flexGrow: 1, minHeight: 0, overflow: 'hidden' }}> <Box sx={{ flexGrow: 1, minHeight: 0, overflow: 'hidden' }}>

View File

@@ -2,18 +2,14 @@ import React, { PureComponent } from 'react';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import IconButton from '@mui/material/IconButton'; import IconButton from '@mui/material/IconButton';
import Button from '@mui/material/Button';
import InputBase from '@mui/material/InputBase'; import InputBase from '@mui/material/InputBase';
import Tooltip from '@mui/material/Tooltip';
import CircularProgress from '@mui/material/CircularProgress'; import CircularProgress from '@mui/material/CircularProgress';
import Chip from '@mui/material/Chip';
import Menu from '@mui/material/Menu'; import Menu from '@mui/material/Menu';
import MenuItem from '@mui/material/MenuItem'; import MenuItem from '@mui/material/MenuItem';
import ListItemIcon from '@mui/material/ListItemIcon'; import ListItemIcon from '@mui/material/ListItemIcon';
import ListItemText from '@mui/material/ListItemText'; import ListItemText from '@mui/material/ListItemText';
import SearchRoundedIcon from '@mui/icons-material/SearchRounded'; import SearchRoundedIcon from '@mui/icons-material/SearchRounded';
import ClearRoundedIcon from '@mui/icons-material/ClearRounded'; import ClearRoundedIcon from '@mui/icons-material/ClearRounded';
import RefreshRoundedIcon from '@mui/icons-material/RefreshRounded';
import NoteAddRoundedIcon from '@mui/icons-material/NoteAddRounded'; import NoteAddRoundedIcon from '@mui/icons-material/NoteAddRounded';
import CloudUploadRoundedIcon from '@mui/icons-material/CloudUploadRounded'; import CloudUploadRoundedIcon from '@mui/icons-material/CloudUploadRounded';
import DownloadRoundedIcon from '@mui/icons-material/DownloadRounded'; import DownloadRoundedIcon from '@mui/icons-material/DownloadRounded';
@@ -28,7 +24,7 @@ import KeyboardArrowRightRoundedIcon from '@mui/icons-material/KeyboardArrowRigh
import ContentCopyRoundedIcon from '@mui/icons-material/ContentCopyRounded'; import ContentCopyRoundedIcon from '@mui/icons-material/ContentCopyRounded';
import ChatBubbleOutlineRoundedIcon from '@mui/icons-material/ChatBubbleOutlineRounded'; import ChatBubbleOutlineRoundedIcon from '@mui/icons-material/ChatBubbleOutlineRounded';
import VfsFileIcon, { getCategoryBadge } from './VfsFileIcon'; import VfsFileIcon from './VfsFileIcon';
import { formatVfsSize, formatVfsDate } from './vfsUtils'; import { formatVfsSize, formatVfsDate } from './vfsUtils';
/** /**
@@ -336,7 +332,6 @@ export default class VfsFileExplorer extends PureComponent {
renderFileNode = (file, depth) => { renderFileNode = (file, depth) => {
const { selectedFileName, onSelectFile } = this.props; const { selectedFileName, onSelectFile } = this.props;
const isSelected = file.path === selectedFileName; const isSelected = file.path === selectedFileName;
const badge = getCategoryBadge(file.path, file.fileData.mime);
return ( return (
<Box <Box
@@ -375,29 +370,14 @@ export default class VfsFileExplorer extends PureComponent {
/> />
<Box sx={{ minWidth: 0, flexGrow: 1, pr: 1 }}> <Box sx={{ minWidth: 0, flexGrow: 1, pr: 1 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75 }}> <Typography
<Typography variant="body2"
variant="body2" fontWeight={isSelected ? 600 : 500}
fontWeight={isSelected ? 600 : 500} noWrap
noWrap sx={{ fontFamily: 'monospace', fontSize: '0.82rem' }}
sx={{ fontFamily: 'monospace', fontSize: '0.82rem' }} >
> {file.name}
{file.name} </Typography>
</Typography>
<Chip
label={badge.label}
size="small"
sx={{
height: 17,
fontSize: '0.62rem',
fontWeight: 600,
borderRadius: 1,
bgcolor: (theme) =>
theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.06)',
color: badge.color,
}}
/>
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.25, mt: 0.15 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 1.25, mt: 0.15 }}>
<Typography variant="caption" color="text.secondary" sx={{ fontSize: '0.7rem' }}> <Typography variant="caption" color="text.secondary" sx={{ fontSize: '0.7rem' }}>
@@ -417,24 +397,14 @@ export default class VfsFileExplorer extends PureComponent {
sx={{ sx={{
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
gap: 0.2,
opacity: isSelected ? 1 : 0.7, opacity: isSelected ? 1 : 0.7,
'&:hover': { opacity: 1 }, '&:hover': { opacity: 1 },
}} }}
> >
<Tooltip title="Herunterladen">
<IconButton
size="small"
onClick={() => this.props.onDownload(file.path)}
sx={{ p: 0.5 }}
>
<DownloadRoundedIcon sx={{ fontSize: 16 }} />
</IconButton>
</Tooltip>
<IconButton <IconButton
size="small" size="small"
onClick={(e) => this.handleOpenMenu(e, file.fileData)} onClick={(e) => this.handleOpenMenu(e, file.fileData)}
aria-label="Weitere Optionen"
sx={{ p: 0.5 }} sx={{ p: 0.5 }}
> >
<MoreVertRoundedIcon sx={{ fontSize: 16 }} /> <MoreVertRoundedIcon sx={{ fontSize: 16 }} />
@@ -561,62 +531,22 @@ export default class VfsFileExplorer extends PureComponent {
</Box> </Box>
)} )}
{/* Explorer Header / Actions */} {/* Explorer Header / Actions Row */}
<Box <Box
sx={{ sx={{
p: 1.5, p: 1.25,
borderBottom: 1, borderBottom: 1,
borderColor: 'divider', borderColor: 'divider',
display: 'flex', display: 'flex',
flexDirection: 'column', alignItems: 'center',
gap: 1.25, gap: 0.5,
flexShrink: 0, flexShrink: 0,
}} }}
> >
{/* Top Bar with Action Buttons */}
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75 }}>
{loading && <CircularProgress size={16} thickness={4} />}
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, ml: 'auto' }}>
<Tooltip title="Aktualisieren">
<span>
<IconButton size="small" onClick={onRefresh} disabled={loading} sx={{ p: 0.6 }}>
<RefreshRoundedIcon fontSize="small" />
</IconButton>
</span>
</Tooltip>
<Tooltip title="Datei(en) hochladen">
<Button
size="small"
variant="outlined"
startIcon={<CloudUploadRoundedIcon />}
onClick={this.handleTriggerUpload}
sx={{ borderRadius: 2, py: 0.3, px: 1, fontSize: '0.75rem' }}
>
Upload
</Button>
</Tooltip>
<Tooltip title="Neue Datei anlegen">
<Button
size="small"
variant="contained"
startIcon={<NoteAddRoundedIcon />}
onClick={onOpenNewDialog}
sx={{ borderRadius: 2, py: 0.3, px: 1, fontSize: '0.75rem' }}
>
Neu
</Button>
</Tooltip>
</Box>
</Box>
{/* Search Filter Bar */} {/* Search Filter Bar */}
<Box <Box
sx={(theme) => ({ sx={(theme) => ({
flexGrow: 1,
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
px: 1, px: 1,
@@ -638,12 +568,33 @@ export default class VfsFileExplorer extends PureComponent {
onChange={(e) => onSearchChange(e.target.value)} onChange={(e) => onSearchChange(e.target.value)}
sx={{ fontSize: '0.82rem' }} sx={{ fontSize: '0.82rem' }}
/> />
{loading && <CircularProgress size={14} thickness={4} sx={{ mr: 0.5 }} />}
{searchQuery && ( {searchQuery && (
<IconButton size="small" onClick={() => onSearchChange('')} sx={{ p: 0.2 }}> <IconButton size="small" onClick={() => onSearchChange('')} sx={{ p: 0.2 }}>
<ClearRoundedIcon sx={{ fontSize: 16 }} /> <ClearRoundedIcon sx={{ fontSize: 16 }} />
</IconButton> </IconButton>
)} )}
</Box> </Box>
{/* Upload Icon Button (no word) */}
<IconButton
size="small"
onClick={this.handleTriggerUpload}
aria-label="Datei(en) hochladen"
sx={{ p: 0.75 }}
>
<CloudUploadRoundedIcon fontSize="small" />
</IconButton>
{/* New File Icon Button (no word) */}
<IconButton
size="small"
onClick={onOpenNewDialog}
aria-label="Neue Datei anlegen"
sx={{ p: 0.75 }}
>
<NoteAddRoundedIcon fontSize="small" />
</IconButton>
</Box> </Box>
{/* Scrollable File List */} {/* Scrollable File List */}

View File

@@ -3,15 +3,13 @@ import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import Button from '@mui/material/Button'; import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton'; import IconButton from '@mui/material/IconButton';
import Tooltip from '@mui/material/Tooltip';
import Chip from '@mui/material/Chip';
import OpenInNewRoundedIcon from '@mui/icons-material/OpenInNewRounded'; import OpenInNewRoundedIcon from '@mui/icons-material/OpenInNewRounded';
import DownloadRoundedIcon from '@mui/icons-material/DownloadRounded'; import DownloadRoundedIcon from '@mui/icons-material/DownloadRounded';
import CloseRoundedIcon from '@mui/icons-material/CloseRounded'; import CloseRoundedIcon from '@mui/icons-material/CloseRounded';
import ArrowBackRoundedIcon from '@mui/icons-material/ArrowBackRounded'; import ArrowBackRoundedIcon from '@mui/icons-material/ArrowBackRounded';
import ChatBubbleOutlineRoundedIcon from '@mui/icons-material/ChatBubbleOutlineRounded'; import ChatBubbleOutlineRoundedIcon from '@mui/icons-material/ChatBubbleOutlineRounded';
import VfsFileIcon, { getCategoryBadge } from './VfsFileIcon'; import VfsFileIcon from './VfsFileIcon';
import { formatVfsSize, formatVfsDate } from './vfsUtils'; import { formatVfsSize, formatVfsDate } from './vfsUtils';
export default class VfsFilePreview extends PureComponent { export default class VfsFilePreview extends PureComponent {
@@ -128,7 +126,6 @@ export default class VfsFilePreview extends PureComponent {
render() { render() {
const { fileName, file, onClose, onDownload, onMentionInChat } = this.props; const { fileName, file, onClose, onDownload, onMentionInChat } = this.props;
const badge = getCategoryBadge(fileName, file?.mime);
const vfsUrl = `/api/vfs/${encodeURIComponent(fileName).replace(/%2F/g, '/')}?raw=1`; const vfsUrl = `/api/vfs/${encodeURIComponent(fileName).replace(/%2F/g, '/')}?raw=1`;
return ( return (
@@ -149,11 +146,9 @@ export default class VfsFilePreview extends PureComponent {
}} }}
> >
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75, minWidth: 0, mr: 1 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75, minWidth: 0, mr: 1 }}>
<Tooltip title="Zurück zur Dateiliste"> <IconButton size="small" onClick={onClose} aria-label="Zurück zur Dateiliste" sx={{ p: 0.5, mr: 0.25 }}>
<IconButton size="small" onClick={onClose} sx={{ p: 0.5, mr: 0.25 }}> <ArrowBackRoundedIcon fontSize="small" />
<ArrowBackRoundedIcon fontSize="small" /> </IconButton>
</IconButton>
</Tooltip>
<VfsFileIcon fileName={fileName} mime={file?.mime} fontSize="small" /> <VfsFileIcon fileName={fileName} mime={file?.mime} fontSize="small" />
<Typography <Typography
variant="body2" variant="body2"
@@ -163,41 +158,18 @@ export default class VfsFilePreview extends PureComponent {
> >
{fileName} {fileName}
</Typography> </Typography>
<Chip
label={badge.label}
size="small"
sx={{
height: 20,
fontSize: '0.68rem',
fontWeight: 600,
borderRadius: 1,
bgcolor: (theme) =>
theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.06)',
color: badge.color,
}}
/>
</Box> </Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75, flexShrink: 0 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75, flexShrink: 0 }}>
{onMentionInChat && ( {onMentionInChat && (
<Tooltip title="In Chat erwähnen"> <IconButton size="small" onClick={onMentionInChat} aria-label="In Chat erwähnen" sx={{ p: 0.6 }}>
<IconButton size="small" onClick={onMentionInChat} sx={{ p: 0.6 }}> <ChatBubbleOutlineRoundedIcon fontSize="small" color="primary" />
<ChatBubbleOutlineRoundedIcon fontSize="small" color="primary" /> </IconButton>
</IconButton>
</Tooltip>
)} )}
<Tooltip title="Herunterladen"> <IconButton size="small" onClick={onClose} aria-label="Vorschau schließen" sx={{ p: 0.6, ml: 0.5 }}>
<IconButton size="small" onClick={onDownload} sx={{ p: 0.6 }}> <CloseRoundedIcon fontSize="small" />
<DownloadRoundedIcon fontSize="small" /> </IconButton>
</IconButton>
</Tooltip>
<Tooltip title="Vorschau schließen">
<IconButton size="small" onClick={onClose} sx={{ p: 0.6, ml: 0.5 }}>
<CloseRoundedIcon fontSize="small" />
</IconButton>
</Tooltip>
</Box> </Box>
</Box> </Box>

View File

@@ -3,16 +3,13 @@ import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import IconButton from '@mui/material/IconButton'; import IconButton from '@mui/material/IconButton';
import Button from '@mui/material/Button'; import Button from '@mui/material/Button';
import Tooltip from '@mui/material/Tooltip';
import CircularProgress from '@mui/material/CircularProgress'; import CircularProgress from '@mui/material/CircularProgress';
import Chip from '@mui/material/Chip'; import Chip from '@mui/material/Chip';
import SaveRoundedIcon from '@mui/icons-material/SaveRounded'; import SaveRoundedIcon from '@mui/icons-material/SaveRounded';
import UndoRoundedIcon from '@mui/icons-material/UndoRounded'; import UndoRoundedIcon from '@mui/icons-material/UndoRounded';
import DownloadRoundedIcon from '@mui/icons-material/DownloadRounded';
import CloseRoundedIcon from '@mui/icons-material/CloseRounded'; import CloseRoundedIcon from '@mui/icons-material/CloseRounded';
import ArrowBackRoundedIcon from '@mui/icons-material/ArrowBackRounded'; import ArrowBackRoundedIcon from '@mui/icons-material/ArrowBackRounded';
import ChatBubbleOutlineRoundedIcon from '@mui/icons-material/ChatBubbleOutlineRounded'; import ChatBubbleOutlineRoundedIcon from '@mui/icons-material/ChatBubbleOutlineRounded';
import WrapTextRoundedIcon from '@mui/icons-material/WrapTextRounded';
import Alert from '@mui/material/Alert'; import Alert from '@mui/material/Alert';
import VfsFileIcon from './VfsFileIcon'; import VfsFileIcon from './VfsFileIcon';
@@ -69,6 +66,37 @@ export default class VfsTextEditor extends PureComponent {
} }
}; };
handleMentionInChat = () => {
const ta = this.textareaRef.current;
let lineInfo = null;
if (ta) {
const start = ta.selectionStart;
const end = ta.selectionEnd;
if (typeof start === 'number' && typeof end === 'number' && end > start) {
const content = this.props.content || '';
const beforeStart = content.substring(0, start);
const startLine = beforeStart.split('\n').length;
let effectiveEnd = end;
if (effectiveEnd > start && content[effectiveEnd - 1] === '\n') {
effectiveEnd -= 1;
}
const beforeEnd = content.substring(0, effectiveEnd);
const endLine = Math.max(startLine, beforeEnd.split('\n').length);
lineInfo = {
startLine,
endLine,
selectedText: content.substring(start, end),
};
}
}
if (this.props.onMentionInChat) {
this.props.onMentionInChat(lineInfo);
}
};
handleKeyDown = (e) => { handleKeyDown = (e) => {
// Ctrl+S or Cmd+S -> Save // Ctrl+S or Cmd+S -> Save
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 's') { if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 's') {
@@ -159,11 +187,9 @@ export default class VfsTextEditor extends PureComponent {
> >
{/* File Name & Status */} {/* File Name & Status */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75, minWidth: 0, mr: 1 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75, minWidth: 0, mr: 1 }}>
<Tooltip title="Zurück zur Dateiliste"> <IconButton size="small" onClick={onClose} aria-label="Zurück zur Dateiliste" sx={{ p: 0.5, mr: 0.25 }}>
<IconButton size="small" onClick={onClose} sx={{ p: 0.5, mr: 0.25 }}> <ArrowBackRoundedIcon fontSize="small" />
<ArrowBackRoundedIcon fontSize="small" /> </IconButton>
</IconButton>
</Tooltip>
<VfsFileIcon fileName={fileName} mime={file?.mime} fontSize="small" /> <VfsFileIcon fileName={fileName} mime={file?.mime} fontSize="small" />
<Typography <Typography
variant="body2" variant="body2"
@@ -174,60 +200,29 @@ export default class VfsTextEditor extends PureComponent {
{fileName} {fileName}
</Typography> </Typography>
{isDirty ? ( {isDirty && (
<Chip <Chip
label="Ungespeichert" label="Ungespeichert"
size="small" size="small"
color="warning" color="warning"
sx={{ height: 20, fontSize: '0.68rem', fontWeight: 600, borderRadius: 1 }} sx={{ height: 20, fontSize: '0.68rem', fontWeight: 600, borderRadius: 1 }}
/> />
) : (
<Chip
label="Gespeichert"
size="small"
variant="outlined"
sx={{ height: 20, fontSize: '0.68rem', borderRadius: 1, opacity: 0.7 }}
/>
)} )}
</Box> </Box>
{/* Action Buttons */} {/* Action Buttons */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75, flexShrink: 0 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75, flexShrink: 0 }}>
{onMentionInChat && ( {onMentionInChat && (
<Tooltip title="In Chat erwähnen"> <IconButton size="small" onClick={this.handleMentionInChat} aria-label="In Chat erwähnen" sx={{ p: 0.6 }}>
<IconButton size="small" onClick={onMentionInChat} sx={{ p: 0.6 }}> <ChatBubbleOutlineRoundedIcon fontSize="small" color="primary" />
<ChatBubbleOutlineRoundedIcon fontSize="small" color="primary" />
</IconButton>
</Tooltip>
)}
<Tooltip title="Herunterladen">
<IconButton size="small" onClick={onDownload} sx={{ p: 0.6 }}>
<DownloadRoundedIcon fontSize="small" />
</IconButton> </IconButton>
</Tooltip> )}
{isDirty && ( {isDirty && (
<Tooltip title="Änderungen verwerfen"> <IconButton size="small" onClick={this.handleRevert} aria-label="Änderungen verwerfen" sx={{ p: 0.6, color: 'text.secondary' }}>
<IconButton size="small" onClick={this.handleRevert} sx={{ p: 0.6, color: 'text.secondary' }}> <UndoRoundedIcon fontSize="small" />
<UndoRoundedIcon fontSize="small" />
</IconButton>
</Tooltip>
)}
<Tooltip title={this.state.wrapLines ? 'Zeilenumbruch ausschalten' : 'Zeilenumbruch einschalten'}>
<IconButton
size="small"
onClick={this.toggleWrap}
color={this.state.wrapLines ? 'primary' : 'default'}
sx={{
p: 0.6,
bgcolor: this.state.wrapLines ? 'action.selected' : 'transparent',
}}
>
<WrapTextRoundedIcon fontSize="small" />
</IconButton> </IconButton>
</Tooltip> )}
<Button <Button
size="small" size="small"
@@ -247,11 +242,9 @@ export default class VfsTextEditor extends PureComponent {
{isSaving ? 'Speichern…' : 'Speichern (Strg+S)'} {isSaving ? 'Speichern…' : 'Speichern (Strg+S)'}
</Button> </Button>
<Tooltip title="Editor schließen"> <IconButton size="small" onClick={onClose} aria-label="Editor schließen" sx={{ p: 0.6, ml: 0.5 }}>
<IconButton size="small" onClick={onClose} sx={{ p: 0.6, ml: 0.5 }}> <CloseRoundedIcon fontSize="small" />
<CloseRoundedIcon fontSize="small" /> </IconButton>
</IconButton>
</Tooltip>
</Box> </Box>
</Box> </Box>