This commit is contained in:
seb
2026-10-07 13:36:16 +02:00
parent 7045fe5e69
commit 8f46e59f08
19 changed files with 299 additions and 66 deletions

View File

@@ -966,16 +966,14 @@ const actions = {
* @param {object[]} [maybeAttachments] * @param {object[]} [maybeAttachments]
*/ */
async sendMessage(textOrPayload, maybeAttachments = []) { async sendMessage(textOrPayload, maybeAttachments = []) {
let trimmed = ''; const trimmed =
let fileAttachments = []; typeof textOrPayload === 'object' && textOrPayload !== null
? (textOrPayload.text || '').trim()
if (typeof textOrPayload === 'object' && textOrPayload !== null) { : (textOrPayload || '').trim();
trimmed = (textOrPayload.text || '').trim(); const fileAttachments =
fileAttachments = Array.isArray(textOrPayload.attachments) ? textOrPayload.attachments : []; typeof textOrPayload === 'object' && textOrPayload !== null
} else { ? (Array.isArray(textOrPayload.attachments) ? textOrPayload.attachments : [])
trimmed = (textOrPayload || '').trim(); : (Array.isArray(maybeAttachments) ? maybeAttachments : []);
fileAttachments = Array.isArray(maybeAttachments) ? maybeAttachments : [];
}
if (!trimmed && fileAttachments.length === 0) return; if (!trimmed && fileAttachments.length === 0) return;

View File

@@ -246,7 +246,7 @@ const actions = {
* @param {string} [initialContent=''] * @param {string} [initialContent='']
*/ */
async createFile(name, initialContent = '') { async createFile(name, initialContent = '') {
const clean = String(name || '').trim().replace(/^[\/\\]+/, ''); const clean = String(name || '').trim().replace(/^[/\\]+/, '');
if (!clean) throw new Error('Dateiname darf nicht leer sein'); if (!clean) throw new Error('Dateiname darf nicht leer sein');
vfsAutomat.setState({ isSaving: true, saveError: null, newFileDialogOpen: false }); vfsAutomat.setState({ isSaving: true, saveError: null, newFileDialogOpen: false });

View File

@@ -890,6 +890,7 @@ export default class ArticleSearch extends PureComponent {
return ( return (
<TextInput <TextInput
{...params} {...params}
label={label}
autoFocus={autoFocus} autoFocus={autoFocus}
placeholder={placeholder || ''} placeholder={placeholder || ''}
InputProps={{ InputProps={{
@@ -904,6 +905,7 @@ export default class ArticleSearch extends PureComponent {
return ( return (
<TextInput <TextInput
{...params} {...params}
label={label}
autoFocus={autoFocus} autoFocus={autoFocus}
placeholder={placeholder || ''} placeholder={placeholder || ''}
slotProps={{ slotProps={{

View File

@@ -7,7 +7,6 @@ import DialogActions from '@mui/material/DialogActions';
import Table from '@mui/material/Table'; import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody'; import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell'; import TableCell from '@mui/material/TableCell';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow'; import TableRow from '@mui/material/TableRow';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import Chip from '@mui/material/Chip'; import Chip from '@mui/material/Chip';
@@ -29,7 +28,6 @@ import { article as articleApi } from '../../api';
import brandAutomat from '../../automats/brandAutomat'; import brandAutomat from '../../automats/brandAutomat';
import categoryAutomat from '../../automats/categoryAutomat'; import categoryAutomat from '../../automats/categoryAutomat';
import { formatPrice } from '../../utils/price'; import { formatPrice } from '../../utils/price';
import { LIGHT_4 } from '../../colors';
export default class ArticleUnifyDialog extends Component { export default class ArticleUnifyDialog extends Component {
constructor(props) { constructor(props) {

View File

@@ -1,6 +1,5 @@
import { PureComponent } from 'react'; import { PureComponent } from 'react';
import { Link as RouterLink } from 'react-router-dom'; import { Link as RouterLink } from 'react-router-dom';
import Autocomplete from '@mui/material/Autocomplete';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import Button from '@mui/material/Button'; import Button from '@mui/material/Button';
import Checkbox from '@mui/material/Checkbox'; import Checkbox from '@mui/material/Checkbox';

View File

@@ -1,4 +1,4 @@
import { getCategoryBreadcrumbs, ROOT_CATEGORY_NAME } from '../../utils/categoryUtils'; import { getCategoryBreadcrumbs } from '../../utils/categoryUtils';
export function parseFilter(filterStr) { export function parseFilter(filterStr) {
if (!filterStr) return []; if (!filterStr) return [];

View File

@@ -123,7 +123,6 @@ export default class ArticleSettingsTab extends Component {
const isFather = isArticleFather(articleData); const isFather = isArticleFather(articleData);
const isStueckliste = isArticleStueckliste(articleData); const isStueckliste = isArticleStueckliste(articleData);
const cannotUnify = isFather || isStueckliste;
const unifyDisabledReason = isFather const unifyDisabledReason = isFather
? 'Ein Vaterartikel kann nicht vereint werden.' ? 'Ein Vaterartikel kann nicht vereint werden.'
: isStueckliste : isStueckliste

View File

@@ -1,6 +1,6 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom'; import { useNavigate, useSearchParams } from 'react-router-dom';
import { buildSearchUrl, queryFromParams, readArtikelQuery } from './artikelQuery'; import { buildSearchUrl, readArtikelQuery } from './artikelQuery';
export default function withSearchParams(Wrapped) { export default function withSearchParams(Wrapped) {
function SearchParamsWrapper(props) { function SearchParamsWrapper(props) {
@@ -18,6 +18,7 @@ export default function withSearchParams(Wrapped) {
buildSearchUrl(stored.target, stored.filter ?? '', stored.offset ?? 0), buildSearchUrl(stored.target, stored.filter ?? '', stored.offset ?? 0),
{ replace: true }, { replace: true },
); );
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);
return ( return (

View File

@@ -16,7 +16,6 @@ import CodeRoundedIcon from '@mui/icons-material/CodeRounded';
import DescriptionRoundedIcon from '@mui/icons-material/DescriptionRounded'; import DescriptionRoundedIcon from '@mui/icons-material/DescriptionRounded';
import InsertDriveFileRoundedIcon from '@mui/icons-material/InsertDriveFileRounded'; import InsertDriveFileRoundedIcon from '@mui/icons-material/InsertDriveFileRounded';
import InsertPhotoRoundedIcon from '@mui/icons-material/InsertPhotoRounded'; import InsertPhotoRoundedIcon from '@mui/icons-material/InsertPhotoRounded';
import ErrorOutlineRoundedIcon from '@mui/icons-material/ErrorOutlineRounded';
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 { enqueueSnackbar } from 'notistack'; import { enqueueSnackbar } from 'notistack';
@@ -368,7 +367,6 @@ export class VfsAttachmentLink extends PureComponent {
const { name, path, type, label, children, isUser = false } = this.props; const { name, path, type, label, children, isUser = false } = this.props;
const { isDownloading, isOpening, notFound } = this.state; const { isDownloading, isOpening, notFound } = this.state;
const downloadName = name || (path ? path.split('/').pop() : 'datei'); const downloadName = name || (path ? path.split('/').pop() : 'datei');
const displayName = label || children || name || downloadName;
const effectiveType = type || inferMimeType(downloadName); const effectiveType = type || inferMimeType(downloadName);
const vfsUrl = resolveVfsUrl(path); const vfsUrl = resolveVfsUrl(path);
const canView = isBrowserViewable(downloadName, effectiveType); const canView = isBrowserViewable(downloadName, effectiveType);

View File

@@ -5,7 +5,6 @@ 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 InputBase from '@mui/material/InputBase'; import InputBase from '@mui/material/InputBase';
import TextInput from '../TextInput';
import MenuItem from '@mui/material/MenuItem'; import MenuItem from '@mui/material/MenuItem';
import Select from '@mui/material/Select'; import Select from '@mui/material/Select';
import FormControl from '@mui/material/FormControl'; import FormControl from '@mui/material/FormControl';
@@ -32,7 +31,7 @@ import { setting as settingApi } from '../../api';
import chatAutomat, { selectPendingAskUser } from '../../automats/chatAutomat'; import chatAutomat, { selectPendingAskUser } from '../../automats/chatAutomat';
import userSettingsAutomat from '../../automats/userSettingsAutomat'; import userSettingsAutomat from '../../automats/userSettingsAutomat';
import vfsAutomat from '../../automats/vfsAutomat'; import vfsAutomat from '../../automats/vfsAutomat';
import { VfsEditorPanel } from '../vfs'; import { VfsEditorPanel, VfsFileIcon } from '../vfs';
import ChatFlowList from './ChatFlowList'; import ChatFlowList from './ChatFlowList';
import AskUserPrompt from './AskUserPrompt'; import AskUserPrompt from './AskUserPrompt';
import { ACCEPT_STRING, readFileAttachment } from './chatAttachments'; import { ACCEPT_STRING, readFileAttachment } from './chatAttachments';
@@ -71,7 +70,9 @@ export default class ChatDrawer extends PureComponent {
sendError: s.sendError, sendError: s.sendError,
activeTurns: s.activeTurns || [], activeTurns: s.activeTurns || [],
inputText: '', inputText: '',
isInputFocused: false,
stagedAttachments: [], stagedAttachments: [],
stagedReferences: [],
isProcessingFiles: false, isProcessingFiles: false,
isDraggingFiles: false, isDraggingFiles: false,
attachmentError: null, attachmentError: null,
@@ -150,12 +151,68 @@ export default class ChatDrawer extends PureComponent {
}; };
handleInsertToChat = (text) => { handleInsertToChat = (text) => {
// If it's a file / line tag, e.g. [file.js#L10-L20](file.js#L10-L20) or [file.js](file.js)
const match =
typeof text === 'string' &&
text.trim().match(/^(!?)\[([^#\]]+)(?:#L?(\d+)(?:-L?(\d+))?)?\]\(([^)]+)\)$/);
if (match) {
const [, isImg, fileName, startLine, endLine, path] = match;
this.handleMentionReference({
fileName,
path,
startLine: startLine ? parseInt(startLine, 10) : null,
endLine: endLine ? parseInt(endLine, 10) : (startLine ? parseInt(startLine, 10) : null),
isImage: Boolean(isImg),
});
return;
}
this.setState((prev) => ({ this.setState((prev) => ({
inputText: prev.inputText ? `${prev.inputText}\n${text}` : text, inputText: prev.inputText ? `${prev.inputText}\n${text}` : text,
})); }));
this.focusInput(50); this.focusInput(50);
}; };
handleMentionReference = (refData) => {
if (!refData || !refData.fileName) return;
const {
fileName,
path = refData.fileName,
startLine = null,
endLine = null,
selectedText = '',
isImage = false,
} = refData;
this.setState((prev) => {
const exists = prev.stagedReferences.some(
(r) => r.fileName === fileName && r.startLine === startLine && r.endLine === endLine
);
if (exists) {
return null;
}
const newRef = {
id: `ref-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
fileName,
path,
startLine,
endLine,
selectedText,
isImage,
};
return {
stagedReferences: [...prev.stagedReferences, newRef],
};
});
this.focusInput(50);
};
handleRemoveReference = (id) => {
this.setState((prev) => ({
stagedReferences: prev.stagedReferences.filter((r) => r.id !== id),
}));
this.focusInput(50);
};
handleVfsResizerStart = (e) => { handleVfsResizerStart = (e) => {
if (e.button !== undefined && e.button !== 0) return; if (e.button !== undefined && e.button !== 0) return;
e.preventDefault(); e.preventDefault();
@@ -327,12 +384,51 @@ export default class ChatDrawer extends PureComponent {
handleSend = async (e) => { handleSend = async (e) => {
e?.preventDefault(); e?.preventDefault();
const text = this.state.inputText.trim(); const rawText = this.state.inputText.trim();
const attachments = this.state.stagedAttachments || []; const attachments = this.state.stagedAttachments || [];
if ((!text && attachments.length === 0) || this.state.conversationState === 'busy' || this.state.isSending) { const references = this.state.stagedReferences || [];
if (
(!rawText && attachments.length === 0 && references.length === 0) ||
this.state.conversationState === 'busy' ||
this.state.isSending
) {
return; return;
} }
this.setState({ inputText: '', stagedAttachments: [], attachmentError: null });
let text = rawText;
if (references.length > 0) {
const refMarkdown = references
.map((ref) => {
const lineRef = ref.startLine
? (ref.startLine === ref.endLine ? `L${ref.startLine}` : `L${ref.startLine}-L${ref.endLine}`)
: null;
const lineLabel = ref.startLine
? (ref.startLine === ref.endLine ? `:${ref.startLine}` : `:${ref.startLine}-${ref.endLine}`)
: '';
const link =
ref.isImage && !lineRef
? `![${ref.fileName}](${ref.path || ref.fileName})`
: `[${ref.fileName}${lineLabel}](${ref.path || ref.fileName}${lineRef ? `#${lineRef}` : ''})`;
if (ref.selectedText && ref.selectedText.trim()) {
const ext = ref.fileName.split('.').pop() || '';
return `${link}\n\`\`\`${ext}\n${ref.selectedText.trim()}\n\`\`\``;
}
return link;
})
.join('\n\n');
text = text ? `${refMarkdown}\n\n${text}` : refMarkdown;
}
this.setState({
inputText: '',
stagedAttachments: [],
stagedReferences: [],
attachmentError: null,
});
try { try {
await chatAutomat.actions.sendMessage(text, attachments); await chatAutomat.actions.sendMessage(text, attachments);
} catch { } catch {
@@ -1009,8 +1105,10 @@ export default class ChatDrawer extends PureComponent {
selectedConversationId, selectedConversationId,
draftConversation, draftConversation,
inputText, inputText,
isInputFocused,
sendError, sendError,
stagedAttachments, stagedAttachments,
stagedReferences,
isProcessingFiles, isProcessingFiles,
isDraggingFiles, isDraggingFiles,
attachmentError, attachmentError,
@@ -1020,7 +1118,10 @@ export default class ChatDrawer extends PureComponent {
const isBusy = conversationState === 'busy'; const isBusy = conversationState === 'busy';
const isWorking = isBusy || isSending; const isWorking = isBusy || isSending;
const canChat = (Boolean(selectedConversationId) || draftConversation) && !pendingAsk; const canChat = (Boolean(selectedConversationId) || draftConversation) && !pendingAsk;
const canSend = !isWorking && canChat && (Boolean(inputText.trim()) || stagedAttachments.length > 0); const canSend =
!isWorking &&
canChat &&
(Boolean(inputText.trim()) || stagedAttachments.length > 0 || stagedReferences.length > 0);
return ( return (
<Box <Box
@@ -1164,24 +1265,158 @@ export default class ChatDrawer extends PureComponent {
style={{ display: 'none' }} style={{ display: 'none' }}
/> />
<TextInput {/* Unified Copilot-style input container with context reference pills */}
inputRef={this.inputRef} <Box
multiline onClick={() => this.focusInput(0)}
maxRows={4} sx={{
fullWidth flex: 1,
value={inputText} minWidth: 0,
onChange={(e) => this.setState({ inputText: e.target.value })} display: 'flex',
onKeyDown={this.handleKeyDown} flexDirection: 'column',
onPaste={this.handlePaste} bgcolor: 'background.paper',
disabled={!canChat} border: '1px solid',
placeholder={ borderColor: isInputFocused ? 'primary.main' : 'divider',
pendingAsk boxShadow: isInputFocused ? (theme) => `0 0 0 1px ${theme.palette.primary.main}` : 'none',
? 'Bitte Frage oben beantworten…' borderRadius: 2.5,
: stagedAttachments.length > 0 p: 0.75,
? 'Nachricht hinzufügen oder direkt absenden…' gap: 0.5,
: undefined cursor: 'text',
} transition: 'border-color 0.15s, box-shadow 0.15s',
/> minHeight: 40,
boxSizing: 'border-box',
}}
>
{stagedReferences.length > 0 && (
<Box
sx={{
display: 'flex',
flexWrap: 'wrap',
gap: 0.6,
alignItems: 'center',
mb: 0.25,
}}
>
{stagedReferences.map((ref) => (
<Box
key={ref.id}
onClick={(e) => {
e.stopPropagation();
vfsAutomat.actions.openVfs();
vfsAutomat.actions.selectFile(ref.fileName);
}}
sx={{
display: 'inline-flex',
alignItems: 'center',
gap: 0.6,
py: 0.2,
pl: 0.7,
pr: 0.4,
borderRadius: 1.5,
bgcolor: (theme) =>
theme.palette.mode === 'dark'
? 'rgba(255, 255, 255, 0.08)'
: 'rgba(0, 0, 0, 0.05)',
border: '1px solid',
borderColor: (theme) =>
theme.palette.mode === 'dark'
? 'rgba(255, 255, 255, 0.12)'
: 'rgba(0, 0, 0, 0.1)',
color: 'text.primary',
fontSize: '0.75rem',
lineHeight: 1.2,
userSelect: 'none',
cursor: 'pointer',
transition: 'all 0.15s ease',
'&:hover': {
bgcolor: (theme) =>
theme.palette.mode === 'dark'
? 'rgba(255, 255, 255, 0.14)'
: 'rgba(0, 0, 0, 0.09)',
borderColor: 'primary.light',
},
}}
>
<VfsFileIcon fileName={ref.fileName} sx={{ fontSize: 14 }} />
<Typography
component="span"
sx={{
fontFamily: "'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace",
fontSize: '0.74rem',
fontWeight: 600,
letterSpacing: '-0.2px',
display: 'inline-flex',
alignItems: 'center',
}}
>
{ref.fileName}
{ref.startLine && (
<Box
component="span"
sx={{
color: 'primary.main',
fontWeight: 700,
ml: 0.3,
}}
>
:{ref.startLine === ref.endLine ? ref.startLine : `${ref.startLine}-${ref.endLine}`}
</Box>
)}
</Typography>
<IconButton
size="small"
onClick={(e) => {
e.stopPropagation();
this.handleRemoveReference(ref.id);
}}
aria-label="Referenz entfernen"
sx={{
p: 0.2,
ml: 0.1,
color: 'text.secondary',
'&:hover': {
color: 'error.main',
bgcolor: 'action.hover',
},
}}
>
<CloseRoundedIcon sx={{ fontSize: 13 }} />
</IconButton>
</Box>
))}
</Box>
)}
<InputBase
inputRef={this.inputRef}
multiline
minRows={1}
maxRows={5}
fullWidth
value={inputText}
onChange={(e) => this.setState({ inputText: e.target.value })}
onKeyDown={this.handleKeyDown}
onPaste={this.handlePaste}
onFocus={() => this.setState({ isInputFocused: true })}
onBlur={() => this.setState({ isInputFocused: false })}
disabled={!canChat}
placeholder={
pendingAsk
? 'Bitte Frage oben beantworten…'
: stagedAttachments.length > 0
? 'Nachricht hinzufügen oder direkt absenden…'
: undefined
}
sx={{
px: 0.5,
py: 0.1,
fontSize: '0.875rem',
lineHeight: 1.45,
'& .MuiInputBase-input': {
p: 0,
},
}}
/>
</Box>
<IconButton <IconButton
type="submit" type="submit"
@@ -1318,7 +1553,10 @@ export default class ChatDrawer extends PureComponent {
overflow: 'hidden', overflow: 'hidden',
}} }}
> >
<VfsEditorPanel onInsertToChat={this.handleInsertToChat} /> <VfsEditorPanel
onInsertToChat={this.handleInsertToChat}
onMentionReference={this.handleMentionReference}
/>
</Box> </Box>
)} )}

View File

@@ -25,8 +25,6 @@ import { VfsAttachmentLink, parseAttachmentTag, classifyLinkTarget, resolveVfsUr
* ~~strike~~, [links](url) and ![images](url). * ~~strike~~, [links](url) and ![images](url).
*/ */
const SAFE_URL = /^(https?:\/\/|mailto:|\/|#)/i;
/** /**
* Resolve an image target to something the browser may load. * Resolve an image target to something the browser may load.
* *

View File

@@ -523,7 +523,7 @@ class ManifestFormLine extends PureComponent {
} }
renderDeliverySecondLine(rawSku) { renderDeliverySecondLine(rawSku) {
const { line, supplierArticle, loading } = this.props; const { supplierArticle, loading } = this.props;
if (loading) { if (loading) {
return ( return (

View File

@@ -16,7 +16,6 @@ import {
import { BrandLabel } from '../brand'; import { BrandLabel } from '../brand';
import { thumb } from '../../api'; import { thumb } from '../../api';
import { import {
GREEN,
GREEN_BRIGHT, GREEN_BRIGHT,
INK_STRONG, INK_STRONG,
SCRIM_LABEL, SCRIM_LABEL,

View File

@@ -62,18 +62,24 @@ export default class VfsEditorPanel extends PureComponent {
handleMentionInChat = (file, lineInfo = null) => { handleMentionInChat = (file, lineInfo = null) => {
if (!file) return; if (!file) return;
if (this.props.onMentionReference) {
this.props.onMentionReference({
fileName: file.name,
path: file.name,
startLine: lineInfo?.startLine || null,
endLine: lineInfo?.endLine || null,
selectedText: lineInfo?.selectedText || '',
isImage: file.isImage || false,
});
return;
}
if (this.props.onInsertToChat) { if (this.props.onInsertToChat) {
const isImg = file.isImage; const isImg = file.isImage;
let tag = ''; const tag = lineInfo?.startLine
if (lineInfo?.startLine) { ? `[${file.name}#${lineInfo.startLine === lineInfo.endLine ? `L${lineInfo.startLine}` : `L${lineInfo.startLine}-L${lineInfo.endLine}`}](${file.name}#${lineInfo.startLine === lineInfo.endLine ? `L${lineInfo.startLine}` : `L${lineInfo.startLine}-L${lineInfo.endLine}`})`
const lineRef = : isImg
lineInfo.startLine === lineInfo.endLine ? `![${file.name}](${file.name})`
? `L${lineInfo.startLine}` : `[${file.name}](${file.name})`;
: `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);
} }
}; };

View File

@@ -466,11 +466,9 @@ export default class VfsFileExplorer extends PureComponent {
render() { render() {
const { const {
loading, loading,
onRefresh,
onOpenNewDialog, onOpenNewDialog,
searchQuery, searchQuery,
onSearchChange, onSearchChange,
files,
} = this.props; } = this.props;
const { menuAnchor, menuFile, isDraggingOver } = this.state; const { menuAnchor, menuFile, isDraggingOver } = this.state;

View File

@@ -125,7 +125,7 @@ export default class VfsFilePreview extends PureComponent {
} }
render() { render() {
const { fileName, file, onClose, onDownload, onMentionInChat } = this.props; const { fileName, file, onClose, onMentionInChat } = this.props;
const vfsUrl = `/api/vfs/${encodeURIComponent(fileName).replace(/%2F/g, '/')}?raw=1`; const vfsUrl = `/api/vfs/${encodeURIComponent(fileName).replace(/%2F/g, '/')}?raw=1`;
return ( return (

View File

@@ -56,7 +56,7 @@ export default class VfsNewFileDialog extends PureComponent {
handleSubmit = (e) => { handleSubmit = (e) => {
e?.preventDefault(); e?.preventDefault();
const clean = this.state.filename.trim().replace(/^[\/\\]+/, ''); const clean = this.state.filename.trim().replace(/^[/\\]+/, '');
if (!clean) { if (!clean) {
this.setState({ error: 'Bitte gib einen Dateinamen ein.' }); this.setState({ error: 'Bitte gib einen Dateinamen ein.' });
return; return;

View File

@@ -152,7 +152,6 @@ export default class VfsTextEditor extends PureComponent {
loadingContent, loadingContent,
onSave, onSave,
onClose, onClose,
onDownload,
onMentionInChat, onMentionInChat,
} = this.props; } = this.props;
@@ -316,7 +315,7 @@ export default class VfsTextEditor extends PureComponent {
autoCorrect="off" autoCorrect="off"
autoCapitalize="off" autoCapitalize="off"
wrap={this.state.wrapLines ? 'soft' : 'off'} wrap={this.state.wrapLines ? 'soft' : 'off'}
sx={(theme) => ({ sx={{
flexGrow: 1, flexGrow: 1,
height: '100%', height: '100%',
p: 1.5, p: 1.5,
@@ -337,7 +336,7 @@ export default class VfsTextEditor extends PureComponent {
'&:focus': { '&:focus': {
outline: 'none', outline: 'none',
}, },
})} }}
/> />
</> </>
)} )}

View File

@@ -85,7 +85,7 @@ export function getVfsCategory(filePath, mime = '') {
* @returns {{ dir: string, base: string }} * @returns {{ dir: string, base: string }}
*/ */
export function splitVfsPath(path) { export function splitVfsPath(path) {
const clean = String(path || '').replace(/^[\/\\]+/, ''); const clean = String(path || '').replace(/^[/\\]+/, '');
const lastSlash = clean.lastIndexOf('/'); const lastSlash = clean.lastIndexOf('/');
if (lastSlash === -1) { if (lastSlash === -1) {
return { dir: '', base: clean }; return { dir: '', base: clean };