u
This commit is contained in:
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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 });
|
||||||
|
|||||||
@@ -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={{
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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 [];
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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}${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>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -25,8 +25,6 @@ import { VfsAttachmentLink, parseAttachmentTag, classifyLinkTarget, resolveVfsUr
|
|||||||
* ~~strike~~, [links](url) and .
|
* ~~strike~~, [links](url) and .
|
||||||
*/
|
*/
|
||||||
|
|
||||||
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.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
? ``
|
||||||
? `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})`;
|
|
||||||
}
|
|
||||||
this.props.onInsertToChat(tag);
|
this.props.onInsertToChat(tag);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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',
|
||||||
},
|
},
|
||||||
})}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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 };
|
||||||
|
|||||||
Reference in New Issue
Block a user