This commit is contained in:
seb
2026-10-06 12:53:32 +02:00
parent b99b39f640
commit 1b5d8efbc3
2 changed files with 119 additions and 160 deletions

View File

@@ -9,7 +9,6 @@ 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 CircularProgress from '@mui/material/CircularProgress'; import CircularProgress from '@mui/material/CircularProgress';
import Tooltip from '@mui/material/Tooltip';
import CloseRoundedIcon from '@mui/icons-material/CloseRounded'; import CloseRoundedIcon from '@mui/icons-material/CloseRounded';
import PictureAsPdfRoundedIcon from '@mui/icons-material/PictureAsPdfRounded'; import PictureAsPdfRoundedIcon from '@mui/icons-material/PictureAsPdfRounded';
import TableChartRoundedIcon from '@mui/icons-material/TableChartRounded'; import TableChartRoundedIcon from '@mui/icons-material/TableChartRounded';
@@ -399,7 +398,6 @@ export class VfsAttachmentLink extends PureComponent {
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
onClick={this.handleClick} onClick={this.handleClick}
title={notFound ? 'Nicht mehr vorhanden (404)' : `VFS Bild im neuen Tab öffnen: ${downloadName}`}
sx={{ sx={{
display: 'block', display: 'block',
cursor: notFound ? 'not-allowed' : 'pointer', cursor: notFound ? 'not-allowed' : 'pointer',
@@ -437,18 +435,17 @@ export class VfsAttachmentLink extends PureComponent {
}} }}
> >
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, minWidth: 0, flex: 1 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, minWidth: 0, flex: 1 }}>
<InsertPhotoRoundedIcon sx={{ fontSize: 16, flexShrink: 0 }} />
<Typography <Typography
variant="caption" variant="caption"
sx={{ sx={{
fontWeight: 600, fontWeight: 500,
fontSize: '0.72rem', fontSize: '0.72rem',
overflow: 'hidden', overflow: 'hidden',
textOverflow: 'ellipsis', textOverflow: 'ellipsis',
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
}} }}
> >
{downloadName} {downloadName} - {effectiveType || 'image'}
</Typography> </Typography>
</Box> </Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.2, flexShrink: 0 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.2, flexShrink: 0 }}>
@@ -457,24 +454,22 @@ export class VfsAttachmentLink extends PureComponent {
)} )}
{!notFound && ( {!notFound && (
<> <>
<Tooltip title="In neuem Tab öffnen" arrow>
<IconButton <IconButton
size="small" size="small"
aria-label="In neuem Tab öffnen"
onClick={this.handleOpenInNewTab} onClick={this.handleOpenInNewTab}
sx={{ p: 0.35, color: 'inherit', opacity: 0.85, '&:hover': { opacity: 1 } }} sx={{ p: 0.35, color: 'inherit', opacity: 0.85, '&:hover': { opacity: 1 } }}
> >
<OpenInNewRoundedIcon sx={{ fontSize: 15 }} /> <OpenInNewRoundedIcon sx={{ fontSize: 15 }} />
</IconButton> </IconButton>
</Tooltip>
<Tooltip title="Herunterladen" arrow>
<IconButton <IconButton
size="small" size="small"
aria-label="Herunterladen"
onClick={this.handleDownload} onClick={this.handleDownload}
sx={{ p: 0.35, color: 'inherit', opacity: 0.85, '&:hover': { opacity: 1 } }} sx={{ p: 0.35, color: 'inherit', opacity: 0.85, '&:hover': { opacity: 1 } }}
> >
<DownloadRoundedIcon sx={{ fontSize: 15 }} /> <DownloadRoundedIcon sx={{ fontSize: 15 }} />
</IconButton> </IconButton>
</Tooltip>
</> </>
)} )}
</Box> </Box>
@@ -483,18 +478,7 @@ export class VfsAttachmentLink extends PureComponent {
); );
} }
const titleText = notFound
? `Datei nicht mehr im VFS vorhanden (404): ${downloadName}`
: isOpening
? `Wird im neuen Tab geöffnet…`
: isDownloading
? `Wird heruntergeladen…`
: canView
? `In neuem Tab öffnen (${downloadName})`
: `Herunterladen (${downloadName})`;
return ( return (
<Tooltip title={titleText} arrow>
<Box <Box
component="a" component="a"
href={notFound ? undefined : vfsUrl} href={notFound ? undefined : vfsUrl}
@@ -536,45 +520,21 @@ export class VfsAttachmentLink extends PureComponent {
}, },
}} }}
> >
{isDownloading || isOpening ? (
<CircularProgress size={16} color="inherit" thickness={5} />
) : notFound ? (
<ErrorOutlineRoundedIcon sx={{ fontSize: 20, color: isUser ? 'inherit' : 'error.main' }} />
) : (
getAttachmentIcon(effectiveType, downloadName, isUser)
)}
<Box sx={{ minWidth: 0 }}>
<Typography <Typography
component="span" component="span"
sx={{ sx={{
fontSize: '0.82rem', fontSize: '0.82rem',
fontWeight: 600, fontWeight: 500,
color: 'inherit', color: 'inherit',
display: 'block',
overflow: 'hidden', overflow: 'hidden',
textOverflow: 'ellipsis', textOverflow: 'ellipsis',
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
maxWidth: 300,
lineHeight: 1.25, lineHeight: 1.25,
textDecoration: notFound ? 'line-through' : 'none', textDecoration: notFound ? 'line-through' : 'none',
}} }}
> >
{displayName} {downloadName} - {effectiveType || 'VFS'}{notFound ? ' (404)' : ''}
</Typography> </Typography>
<Typography
component="span"
sx={{
fontSize: '0.68rem',
opacity: 0.8,
display: 'block',
lineHeight: 1,
}}
>
{notFound
? 'nicht mehr vorhanden (404)'
: (label && label !== downloadName ? `${downloadName} · ${effectiveType || 'VFS'}` : (effectiveType || 'VFS Datei'))}
</Typography>
</Box>
{!notFound && ( {!notFound && (
<Box <Box
sx={{ sx={{
@@ -582,13 +542,17 @@ export class VfsAttachmentLink extends PureComponent {
alignItems: 'center', alignItems: 'center',
gap: 0.2, gap: 0.2,
ml: 0.5, ml: 0.5,
flexShrink: 0,
}} }}
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
{(isDownloading || isOpening) && (
<CircularProgress size={13} color="inherit" thickness={4} sx={{ mx: 0.4 }} />
)}
{canView && ( {canView && (
<Tooltip title="In neuem Tab öffnen" arrow>
<IconButton <IconButton
size="small" size="small"
aria-label="In neuem Tab öffnen"
onClick={this.handleOpenInNewTab} onClick={this.handleOpenInNewTab}
sx={{ sx={{
p: 0.4, p: 0.4,
@@ -599,11 +563,10 @@ export class VfsAttachmentLink extends PureComponent {
> >
<OpenInNewRoundedIcon sx={{ fontSize: 15 }} /> <OpenInNewRoundedIcon sx={{ fontSize: 15 }} />
</IconButton> </IconButton>
</Tooltip>
)} )}
<Tooltip title="Herunterladen" arrow>
<IconButton <IconButton
size="small" size="small"
aria-label="Herunterladen"
onClick={this.handleDownload} onClick={this.handleDownload}
sx={{ sx={{
p: 0.4, p: 0.4,
@@ -614,11 +577,9 @@ export class VfsAttachmentLink extends PureComponent {
> >
<DownloadRoundedIcon sx={{ fontSize: 15 }} /> <DownloadRoundedIcon sx={{ fontSize: 15 }} />
</IconButton> </IconButton>
</Tooltip>
</Box> </Box>
)} )}
</Box> </Box>
</Tooltip>
); );
} }
} }

View File

@@ -9,7 +9,7 @@ import {
TABLE_HEADER_PAIR, TABLE_HEADER_PAIR,
byMode, byMode,
} from '../../colors'; } from '../../colors';
import { VfsAttachmentLink, parseAttachmentTag, classifyLinkTarget } from './ChatAttachmentItems'; import { VfsAttachmentLink, parseAttachmentTag, classifyLinkTarget, resolveVfsUrl } from './ChatAttachmentItems';
/** /**
* Healing markdown renderer. * Healing markdown renderer.
@@ -43,16 +43,14 @@ function resolveImageUrl(rawUrl) {
const url = String(rawUrl ?? '').trim(); const url = String(rawUrl ?? '').trim();
if (!url) return null; if (!url) return null;
if (/^data:image\//i.test(url)) return url; if (/^data:image\//i.test(url)) return url;
if (/^https?:\/\//i.test(url) || url.startsWith('/')) return url; const classified = classifyLinkTarget(url);
if (classified.kind === 'web') return classified.url;
// Everything else is a VFS name: strip a leading `./`, encode each segment, keep subdirs. if (classified.kind === 'vfs') {
const segments = url if (classified.path.includes('..')) return null;
.replace(/^\.\//, '') return resolveVfsUrl(classified.path);
.split('/') }
.filter((s) => s && s !== '.') if (classified.kind === 'nav') return classified.url;
.map((s) => encodeURIComponent(s)); return null;
if (segments.length === 0 || segments.includes('..')) return null;
return `/api/vfs/${segments.join('/')}`;
} }
/** /**