u
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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 = '';
|
||||||
? ``
|
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})`;
|
||||||
|
}
|
||||||
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' }}>
|
||||||
|
|||||||
@@ -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 */}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user