u
This commit is contained in:
@@ -966,16 +966,14 @@ const actions = {
|
||||
* @param {object[]} [maybeAttachments]
|
||||
*/
|
||||
async sendMessage(textOrPayload, maybeAttachments = []) {
|
||||
let trimmed = '';
|
||||
let fileAttachments = [];
|
||||
|
||||
if (typeof textOrPayload === 'object' && textOrPayload !== null) {
|
||||
trimmed = (textOrPayload.text || '').trim();
|
||||
fileAttachments = Array.isArray(textOrPayload.attachments) ? textOrPayload.attachments : [];
|
||||
} else {
|
||||
trimmed = (textOrPayload || '').trim();
|
||||
fileAttachments = Array.isArray(maybeAttachments) ? maybeAttachments : [];
|
||||
}
|
||||
const trimmed =
|
||||
typeof textOrPayload === 'object' && textOrPayload !== null
|
||||
? (textOrPayload.text || '').trim()
|
||||
: (textOrPayload || '').trim();
|
||||
const fileAttachments =
|
||||
typeof textOrPayload === 'object' && textOrPayload !== null
|
||||
? (Array.isArray(textOrPayload.attachments) ? textOrPayload.attachments : [])
|
||||
: (Array.isArray(maybeAttachments) ? maybeAttachments : []);
|
||||
|
||||
if (!trimmed && fileAttachments.length === 0) return;
|
||||
|
||||
|
||||
@@ -246,7 +246,7 @@ const actions = {
|
||||
* @param {string} [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');
|
||||
|
||||
vfsAutomat.setState({ isSaving: true, saveError: null, newFileDialogOpen: false });
|
||||
|
||||
@@ -890,6 +890,7 @@ export default class ArticleSearch extends PureComponent {
|
||||
return (
|
||||
<TextInput
|
||||
{...params}
|
||||
label={label}
|
||||
autoFocus={autoFocus}
|
||||
placeholder={placeholder || ''}
|
||||
InputProps={{
|
||||
@@ -904,6 +905,7 @@ export default class ArticleSearch extends PureComponent {
|
||||
return (
|
||||
<TextInput
|
||||
{...params}
|
||||
label={label}
|
||||
autoFocus={autoFocus}
|
||||
placeholder={placeholder || ''}
|
||||
slotProps={{
|
||||
|
||||
@@ -7,7 +7,6 @@ import DialogActions from '@mui/material/DialogActions';
|
||||
import Table from '@mui/material/Table';
|
||||
import TableBody from '@mui/material/TableBody';
|
||||
import TableCell from '@mui/material/TableCell';
|
||||
import TableHead from '@mui/material/TableHead';
|
||||
import TableRow from '@mui/material/TableRow';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import Chip from '@mui/material/Chip';
|
||||
@@ -29,7 +28,6 @@ import { article as articleApi } from '../../api';
|
||||
import brandAutomat from '../../automats/brandAutomat';
|
||||
import categoryAutomat from '../../automats/categoryAutomat';
|
||||
import { formatPrice } from '../../utils/price';
|
||||
import { LIGHT_4 } from '../../colors';
|
||||
|
||||
export default class ArticleUnifyDialog extends Component {
|
||||
constructor(props) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { PureComponent } from 'react';
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
import Autocomplete from '@mui/material/Autocomplete';
|
||||
import Box from '@mui/material/Box';
|
||||
import Button from '@mui/material/Button';
|
||||
import Checkbox from '@mui/material/Checkbox';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getCategoryBreadcrumbs, ROOT_CATEGORY_NAME } from '../../utils/categoryUtils';
|
||||
import { getCategoryBreadcrumbs } from '../../utils/categoryUtils';
|
||||
|
||||
export function parseFilter(filterStr) {
|
||||
if (!filterStr) return [];
|
||||
|
||||
@@ -123,7 +123,6 @@ export default class ArticleSettingsTab extends Component {
|
||||
|
||||
const isFather = isArticleFather(articleData);
|
||||
const isStueckliste = isArticleStueckliste(articleData);
|
||||
const cannotUnify = isFather || isStueckliste;
|
||||
const unifyDisabledReason = isFather
|
||||
? 'Ein Vaterartikel kann nicht vereint werden.'
|
||||
: isStueckliste
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { buildSearchUrl, queryFromParams, readArtikelQuery } from './artikelQuery';
|
||||
import { buildSearchUrl, readArtikelQuery } from './artikelQuery';
|
||||
|
||||
export default function withSearchParams(Wrapped) {
|
||||
function SearchParamsWrapper(props) {
|
||||
@@ -18,6 +18,7 @@ export default function withSearchParams(Wrapped) {
|
||||
buildSearchUrl(stored.target, stored.filter ?? '', stored.offset ?? 0),
|
||||
{ replace: true },
|
||||
);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
||||
@@ -16,7 +16,6 @@ import CodeRoundedIcon from '@mui/icons-material/CodeRounded';
|
||||
import DescriptionRoundedIcon from '@mui/icons-material/DescriptionRounded';
|
||||
import InsertDriveFileRoundedIcon from '@mui/icons-material/InsertDriveFileRounded';
|
||||
import InsertPhotoRoundedIcon from '@mui/icons-material/InsertPhotoRounded';
|
||||
import ErrorOutlineRoundedIcon from '@mui/icons-material/ErrorOutlineRounded';
|
||||
import OpenInNewRoundedIcon from '@mui/icons-material/OpenInNewRounded';
|
||||
import DownloadRoundedIcon from '@mui/icons-material/DownloadRounded';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
@@ -368,7 +367,6 @@ export class VfsAttachmentLink extends PureComponent {
|
||||
const { name, path, type, label, children, isUser = false } = this.props;
|
||||
const { isDownloading, isOpening, notFound } = this.state;
|
||||
const downloadName = name || (path ? path.split('/').pop() : 'datei');
|
||||
const displayName = label || children || name || downloadName;
|
||||
const effectiveType = type || inferMimeType(downloadName);
|
||||
const vfsUrl = resolveVfsUrl(path);
|
||||
const canView = isBrowserViewable(downloadName, effectiveType);
|
||||
|
||||
@@ -5,7 +5,6 @@ import Typography from '@mui/material/Typography';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import Button from '@mui/material/Button';
|
||||
import InputBase from '@mui/material/InputBase';
|
||||
import TextInput from '../TextInput';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import Select from '@mui/material/Select';
|
||||
import FormControl from '@mui/material/FormControl';
|
||||
@@ -32,7 +31,7 @@ import { setting as settingApi } from '../../api';
|
||||
import chatAutomat, { selectPendingAskUser } from '../../automats/chatAutomat';
|
||||
import userSettingsAutomat from '../../automats/userSettingsAutomat';
|
||||
import vfsAutomat from '../../automats/vfsAutomat';
|
||||
import { VfsEditorPanel } from '../vfs';
|
||||
import { VfsEditorPanel, VfsFileIcon } from '../vfs';
|
||||
import ChatFlowList from './ChatFlowList';
|
||||
import AskUserPrompt from './AskUserPrompt';
|
||||
import { ACCEPT_STRING, readFileAttachment } from './chatAttachments';
|
||||
@@ -71,7 +70,9 @@ export default class ChatDrawer extends PureComponent {
|
||||
sendError: s.sendError,
|
||||
activeTurns: s.activeTurns || [],
|
||||
inputText: '',
|
||||
isInputFocused: false,
|
||||
stagedAttachments: [],
|
||||
stagedReferences: [],
|
||||
isProcessingFiles: false,
|
||||
isDraggingFiles: false,
|
||||
attachmentError: null,
|
||||
@@ -150,12 +151,68 @@ export default class ChatDrawer extends PureComponent {
|
||||
};
|
||||
|
||||
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) => ({
|
||||
inputText: prev.inputText ? `${prev.inputText}\n${text}` : text,
|
||||
}));
|
||||
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) => {
|
||||
if (e.button !== undefined && e.button !== 0) return;
|
||||
e.preventDefault();
|
||||
@@ -327,12 +384,51 @@ export default class ChatDrawer extends PureComponent {
|
||||
|
||||
handleSend = async (e) => {
|
||||
e?.preventDefault();
|
||||
const text = this.state.inputText.trim();
|
||||
const rawText = this.state.inputText.trim();
|
||||
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;
|
||||
}
|
||||
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}${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 {
|
||||
await chatAutomat.actions.sendMessage(text, attachments);
|
||||
} catch {
|
||||
@@ -1009,8 +1105,10 @@ export default class ChatDrawer extends PureComponent {
|
||||
selectedConversationId,
|
||||
draftConversation,
|
||||
inputText,
|
||||
isInputFocused,
|
||||
sendError,
|
||||
stagedAttachments,
|
||||
stagedReferences,
|
||||
isProcessingFiles,
|
||||
isDraggingFiles,
|
||||
attachmentError,
|
||||
@@ -1020,7 +1118,10 @@ export default class ChatDrawer extends PureComponent {
|
||||
const isBusy = conversationState === 'busy';
|
||||
const isWorking = isBusy || isSending;
|
||||
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 (
|
||||
<Box
|
||||
@@ -1164,24 +1265,158 @@ export default class ChatDrawer extends PureComponent {
|
||||
style={{ display: 'none' }}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
inputRef={this.inputRef}
|
||||
multiline
|
||||
maxRows={4}
|
||||
fullWidth
|
||||
value={inputText}
|
||||
onChange={(e) => this.setState({ inputText: e.target.value })}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
onPaste={this.handlePaste}
|
||||
disabled={!canChat}
|
||||
placeholder={
|
||||
pendingAsk
|
||||
? 'Bitte Frage oben beantworten…'
|
||||
: stagedAttachments.length > 0
|
||||
? 'Nachricht hinzufügen oder direkt absenden…'
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
{/* Unified Copilot-style input container with context reference pills */}
|
||||
<Box
|
||||
onClick={() => this.focusInput(0)}
|
||||
sx={{
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
bgcolor: 'background.paper',
|
||||
border: '1px solid',
|
||||
borderColor: isInputFocused ? 'primary.main' : 'divider',
|
||||
boxShadow: isInputFocused ? (theme) => `0 0 0 1px ${theme.palette.primary.main}` : 'none',
|
||||
borderRadius: 2.5,
|
||||
p: 0.75,
|
||||
gap: 0.5,
|
||||
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
|
||||
type="submit"
|
||||
@@ -1318,7 +1553,10 @@ export default class ChatDrawer extends PureComponent {
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<VfsEditorPanel onInsertToChat={this.handleInsertToChat} />
|
||||
<VfsEditorPanel
|
||||
onInsertToChat={this.handleInsertToChat}
|
||||
onMentionReference={this.handleMentionReference}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
|
||||
@@ -25,8 +25,6 @@ import { VfsAttachmentLink, parseAttachmentTag, classifyLinkTarget, resolveVfsUr
|
||||
* ~~strike~~, [links](url) and .
|
||||
*/
|
||||
|
||||
const SAFE_URL = /^(https?:\/\/|mailto:|\/|#)/i;
|
||||
|
||||
/**
|
||||
* Resolve an image target to something the browser may load.
|
||||
*
|
||||
|
||||
@@ -523,7 +523,7 @@ class ManifestFormLine extends PureComponent {
|
||||
}
|
||||
|
||||
renderDeliverySecondLine(rawSku) {
|
||||
const { line, supplierArticle, loading } = this.props;
|
||||
const { supplierArticle, loading } = this.props;
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
import { BrandLabel } from '../brand';
|
||||
import { thumb } from '../../api';
|
||||
import {
|
||||
GREEN,
|
||||
GREEN_BRIGHT,
|
||||
INK_STRONG,
|
||||
SCRIM_LABEL,
|
||||
|
||||
@@ -62,18 +62,24 @@ export default class VfsEditorPanel extends PureComponent {
|
||||
|
||||
handleMentionInChat = (file, lineInfo = null) => {
|
||||
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) {
|
||||
const isImg = file.isImage;
|
||||
let tag = '';
|
||||
if (lineInfo?.startLine) {
|
||||
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})`;
|
||||
}
|
||||
const tag = 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}`})`
|
||||
: isImg
|
||||
? ``
|
||||
: `[${file.name}](${file.name})`;
|
||||
this.props.onInsertToChat(tag);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -466,11 +466,9 @@ export default class VfsFileExplorer extends PureComponent {
|
||||
render() {
|
||||
const {
|
||||
loading,
|
||||
onRefresh,
|
||||
onOpenNewDialog,
|
||||
searchQuery,
|
||||
onSearchChange,
|
||||
files,
|
||||
} = this.props;
|
||||
|
||||
const { menuAnchor, menuFile, isDraggingOver } = this.state;
|
||||
|
||||
@@ -125,7 +125,7 @@ export default class VfsFilePreview extends PureComponent {
|
||||
}
|
||||
|
||||
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`;
|
||||
|
||||
return (
|
||||
|
||||
@@ -56,7 +56,7 @@ export default class VfsNewFileDialog extends PureComponent {
|
||||
|
||||
handleSubmit = (e) => {
|
||||
e?.preventDefault();
|
||||
const clean = this.state.filename.trim().replace(/^[\/\\]+/, '');
|
||||
const clean = this.state.filename.trim().replace(/^[/\\]+/, '');
|
||||
if (!clean) {
|
||||
this.setState({ error: 'Bitte gib einen Dateinamen ein.' });
|
||||
return;
|
||||
|
||||
@@ -152,7 +152,6 @@ export default class VfsTextEditor extends PureComponent {
|
||||
loadingContent,
|
||||
onSave,
|
||||
onClose,
|
||||
onDownload,
|
||||
onMentionInChat,
|
||||
} = this.props;
|
||||
|
||||
@@ -316,7 +315,7 @@ export default class VfsTextEditor extends PureComponent {
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
wrap={this.state.wrapLines ? 'soft' : 'off'}
|
||||
sx={(theme) => ({
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
height: '100%',
|
||||
p: 1.5,
|
||||
@@ -337,7 +336,7 @@ export default class VfsTextEditor extends PureComponent {
|
||||
'&:focus': {
|
||||
outline: 'none',
|
||||
},
|
||||
})}
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -85,7 +85,7 @@ export function getVfsCategory(filePath, mime = '') {
|
||||
* @returns {{ dir: string, base: string }}
|
||||
*/
|
||||
export function splitVfsPath(path) {
|
||||
const clean = String(path || '').replace(/^[\/\\]+/, '');
|
||||
const clean = String(path || '').replace(/^[/\\]+/, '');
|
||||
const lastSlash = clean.lastIndexOf('/');
|
||||
if (lastSlash === -1) {
|
||||
return { dir: '', base: clean };
|
||||
|
||||
Reference in New Issue
Block a user