u
This commit is contained in:
184
src/components/vfs/CodeMirrorCore.jsx
Normal file
184
src/components/vfs/CodeMirrorCore.jsx
Normal file
@@ -0,0 +1,184 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import CodeMirror, { EditorView, keymap, scrollPastEnd } from '@uiw/react-codemirror';
|
||||
import { indentWithTab } from '@codemirror/commands';
|
||||
import { oneDark } from '@codemirror/theme-one-dark';
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
import { json } from '@codemirror/lang-json';
|
||||
import { html } from '@codemirror/lang-html';
|
||||
import { css } from '@codemirror/lang-css';
|
||||
import { markdown } from '@codemirror/lang-markdown';
|
||||
import { sql } from '@codemirror/lang-sql';
|
||||
import { xml } from '@codemirror/lang-xml';
|
||||
|
||||
function getLanguageExtension(fileName, mime) {
|
||||
if (!fileName && !mime) return null;
|
||||
const ext = fileName ? fileName.split('.').pop()?.toLowerCase() : '';
|
||||
|
||||
switch (ext) {
|
||||
case 'js':
|
||||
case 'jsx':
|
||||
case 'mjs':
|
||||
case 'cjs':
|
||||
return javascript({ jsx: true });
|
||||
case 'ts':
|
||||
case 'tsx':
|
||||
return javascript({ jsx: true, typescript: true });
|
||||
case 'json':
|
||||
case 'json5':
|
||||
return json();
|
||||
case 'html':
|
||||
case 'htm':
|
||||
return html();
|
||||
case 'css':
|
||||
case 'scss':
|
||||
case 'less':
|
||||
return css();
|
||||
case 'md':
|
||||
case 'markdown':
|
||||
return markdown();
|
||||
case 'sql':
|
||||
return sql();
|
||||
case 'xml':
|
||||
case 'svg':
|
||||
return xml();
|
||||
default:
|
||||
if (mime?.includes('json')) return json();
|
||||
if (mime?.includes('javascript')) return javascript({ jsx: true });
|
||||
if (mime?.includes('html')) return html();
|
||||
if (mime?.includes('xml') || mime?.includes('svg')) return xml();
|
||||
if (mime?.includes('markdown')) return markdown();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Softer dark theme for the editor code area (One Dark slate #282c34 / #21252b)
|
||||
const EDITOR_BG = '#282c34';
|
||||
const GUTTER_BG = '#21252b';
|
||||
|
||||
const customEditorTheme = EditorView.theme({
|
||||
'&': {
|
||||
height: '100%',
|
||||
maxHeight: '100%',
|
||||
fontSize: '13px',
|
||||
fontFamily: 'Consolas, Monaco, "Courier New", monospace',
|
||||
backgroundColor: `${EDITOR_BG} !important`,
|
||||
color: '#abb2bf',
|
||||
},
|
||||
'.cm-scroller': {
|
||||
overflow: 'auto !important',
|
||||
height: '100% !important',
|
||||
maxHeight: '100%',
|
||||
fontFamily: 'inherit',
|
||||
lineHeight: '21px',
|
||||
},
|
||||
'.cm-content': {
|
||||
padding: '10px 0',
|
||||
},
|
||||
'.cm-gutters': {
|
||||
backgroundColor: `${GUTTER_BG} !important`,
|
||||
color: '#636d83 !important',
|
||||
borderRight: '1px solid rgba(255, 255, 255, 0.08) !important',
|
||||
},
|
||||
'.cm-lineNumbers .cm-gutterElement': {
|
||||
padding: '0 10px 0 16px',
|
||||
minWidth: '40px',
|
||||
},
|
||||
'.cm-activeLine': {
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.04) !important',
|
||||
},
|
||||
'.cm-activeLineGutter': {
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.07) !important',
|
||||
color: '#abb2bf !important',
|
||||
},
|
||||
'&.cm-focused': {
|
||||
outline: 'none',
|
||||
},
|
||||
});
|
||||
|
||||
export default class CodeMirrorCore extends PureComponent {
|
||||
buildExtensions() {
|
||||
const { fileName, mime, wrapLines, readOnly, onSaveShortcut } = this.props;
|
||||
const langExt = getLanguageExtension(fileName, mime);
|
||||
|
||||
const keys = [indentWithTab];
|
||||
if (onSaveShortcut) {
|
||||
keys.push({
|
||||
key: 'Mod-s',
|
||||
run: () => {
|
||||
onSaveShortcut();
|
||||
return true;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const extensions = [
|
||||
customEditorTheme,
|
||||
scrollPastEnd(),
|
||||
keymap.of(keys),
|
||||
];
|
||||
|
||||
if (langExt) {
|
||||
extensions.push(langExt);
|
||||
}
|
||||
if (wrapLines) {
|
||||
extensions.push(EditorView.lineWrapping);
|
||||
}
|
||||
if (readOnly) {
|
||||
extensions.push(EditorView.editable.of(false));
|
||||
}
|
||||
|
||||
return extensions;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
value,
|
||||
readOnly,
|
||||
onCreateEditor,
|
||||
onUpdate,
|
||||
onChange,
|
||||
} = this.props;
|
||||
|
||||
const extensions = this.buildExtensions();
|
||||
|
||||
return (
|
||||
<CodeMirror
|
||||
value={value}
|
||||
height="100%"
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
theme={oneDark}
|
||||
extensions={extensions}
|
||||
basicSetup={{
|
||||
lineNumbers: true,
|
||||
highlightActiveLineGutter: true,
|
||||
highlightSpecialChars: true,
|
||||
history: true,
|
||||
foldGutter: true,
|
||||
dropCursor: true,
|
||||
allowMultipleSelections: true,
|
||||
indentOnInput: true,
|
||||
syntaxHighlighting: true,
|
||||
bracketMatching: true,
|
||||
closeBrackets: true,
|
||||
autocompletion: true,
|
||||
rectangularSelection: true,
|
||||
crosshairCursor: false,
|
||||
highlightActiveLine: true,
|
||||
highlightSelectionMatches: true,
|
||||
closeBracketsKeymap: true,
|
||||
defaultKeymap: true,
|
||||
searchKeymap: true,
|
||||
historyKeymap: true,
|
||||
foldKeymap: true,
|
||||
completionKeymap: true,
|
||||
lintKeymap: true,
|
||||
}}
|
||||
readOnly={readOnly}
|
||||
editable={!readOnly}
|
||||
onCreateEditor={onCreateEditor}
|
||||
onUpdate={onUpdate}
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -16,26 +16,16 @@ import ChatBubbleOutlineRoundedIcon from '@mui/icons-material/ChatBubbleOutlineR
|
||||
import PlayArrowRoundedIcon from '@mui/icons-material/PlayArrowRounded';
|
||||
import WrapTextRoundedIcon from '@mui/icons-material/WrapTextRounded';
|
||||
|
||||
import CodeMirror, { EditorView, keymap, scrollPastEnd } from '@uiw/react-codemirror';
|
||||
import { indentWithTab } from '@codemirror/commands';
|
||||
import { oneDark } from '@codemirror/theme-one-dark';
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
import { json } from '@codemirror/lang-json';
|
||||
import { html } from '@codemirror/lang-html';
|
||||
import { css } from '@codemirror/lang-css';
|
||||
import { markdown } from '@codemirror/lang-markdown';
|
||||
import { sql } from '@codemirror/lang-sql';
|
||||
import { xml } from '@codemirror/lang-xml';
|
||||
|
||||
import VfsFileIcon from './VfsFileIcon';
|
||||
import { formatVfsSize } from './vfsUtils';
|
||||
|
||||
const CodeMirrorCore = React.lazy(() => import('./CodeMirrorCore'));
|
||||
|
||||
/**
|
||||
* Returns CodeMirror language extension and human-readable language label
|
||||
* based on fileName extension and MIME type.
|
||||
* Returns human-readable language label based on fileName extension and MIME type.
|
||||
*/
|
||||
function getLanguageInfo(fileName, mime) {
|
||||
if (!fileName && !mime) return { extension: null, label: 'Plain Text' };
|
||||
function getLanguageLabel(fileName, mime) {
|
||||
if (!fileName && !mime) return 'Plain Text';
|
||||
const ext = fileName ? fileName.split('.').pop()?.toLowerCase() : '';
|
||||
|
||||
switch (ext) {
|
||||
@@ -43,81 +33,39 @@ function getLanguageInfo(fileName, mime) {
|
||||
case 'jsx':
|
||||
case 'mjs':
|
||||
case 'cjs':
|
||||
return { extension: javascript({ jsx: true }), label: 'JavaScript' };
|
||||
return 'JavaScript';
|
||||
case 'ts':
|
||||
case 'tsx':
|
||||
return { extension: javascript({ jsx: true, typescript: true }), label: 'TypeScript' };
|
||||
return 'TypeScript';
|
||||
case 'json':
|
||||
case 'json5':
|
||||
return { extension: json(), label: 'JSON' };
|
||||
return 'JSON';
|
||||
case 'html':
|
||||
case 'htm':
|
||||
return { extension: html(), label: 'HTML' };
|
||||
return 'HTML';
|
||||
case 'css':
|
||||
case 'scss':
|
||||
case 'less':
|
||||
return { extension: css(), label: 'CSS' };
|
||||
return 'CSS';
|
||||
case 'md':
|
||||
case 'markdown':
|
||||
return { extension: markdown(), label: 'Markdown' };
|
||||
return 'Markdown';
|
||||
case 'sql':
|
||||
return { extension: sql(), label: 'SQL' };
|
||||
return 'SQL';
|
||||
case 'xml':
|
||||
case 'svg':
|
||||
return { extension: xml(), label: ext === 'svg' ? 'SVG' : 'XML' };
|
||||
return ext === 'svg' ? 'SVG' : 'XML';
|
||||
default:
|
||||
if (mime?.includes('json')) return { extension: json(), label: 'JSON' };
|
||||
if (mime?.includes('javascript')) return { extension: javascript({ jsx: true }), label: 'JavaScript' };
|
||||
if (mime?.includes('html')) return { extension: html(), label: 'HTML' };
|
||||
if (mime?.includes('xml') || mime?.includes('svg')) return { extension: xml(), label: 'XML' };
|
||||
if (mime?.includes('markdown')) return { extension: markdown(), label: 'Markdown' };
|
||||
return { extension: null, label: 'Plain Text' };
|
||||
if (mime?.includes('json')) return 'JSON';
|
||||
if (mime?.includes('javascript')) return 'JavaScript';
|
||||
if (mime?.includes('html')) return 'HTML';
|
||||
if (mime?.includes('xml') || mime?.includes('svg')) return 'XML';
|
||||
if (mime?.includes('markdown')) return 'Markdown';
|
||||
return 'Plain Text';
|
||||
}
|
||||
}
|
||||
|
||||
// Softer dark theme for the editor code area (One Dark slate #282c34 / #21252b)
|
||||
const EDITOR_BG = '#282c34';
|
||||
const GUTTER_BG = '#21252b';
|
||||
|
||||
const customEditorTheme = EditorView.theme({
|
||||
'&': {
|
||||
height: '100%',
|
||||
maxHeight: '100%',
|
||||
fontSize: '13px',
|
||||
fontFamily: 'Consolas, Monaco, "Courier New", monospace',
|
||||
backgroundColor: `${EDITOR_BG} !important`,
|
||||
color: '#abb2bf',
|
||||
},
|
||||
'.cm-scroller': {
|
||||
overflow: 'auto !important',
|
||||
height: '100% !important',
|
||||
maxHeight: '100%',
|
||||
fontFamily: 'inherit',
|
||||
lineHeight: '21px',
|
||||
},
|
||||
'.cm-content': {
|
||||
padding: '10px 0',
|
||||
},
|
||||
'.cm-gutters': {
|
||||
backgroundColor: `${GUTTER_BG} !important`,
|
||||
color: '#636d83 !important',
|
||||
borderRight: '1px solid rgba(255, 255, 255, 0.08) !important',
|
||||
},
|
||||
'.cm-lineNumbers .cm-gutterElement': {
|
||||
padding: '0 10px 0 16px',
|
||||
minWidth: '40px',
|
||||
},
|
||||
'.cm-activeLine': {
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.04) !important',
|
||||
},
|
||||
'.cm-activeLineGutter': {
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.07) !important',
|
||||
color: '#abb2bf !important',
|
||||
},
|
||||
'&.cm-focused': {
|
||||
outline: 'none',
|
||||
},
|
||||
});
|
||||
|
||||
export default class VfsTextEditor extends PureComponent {
|
||||
constructor(props) {
|
||||
@@ -224,31 +172,7 @@ export default class VfsTextEditor extends PureComponent {
|
||||
const lineCount = text.split('\n').length;
|
||||
const charCount = text.length;
|
||||
|
||||
const { extension: langExt, label: langLabel } = getLanguageInfo(fileName, file?.mime);
|
||||
|
||||
// Build CodeMirror extensions list
|
||||
const extensions = [
|
||||
customEditorTheme,
|
||||
scrollPastEnd(),
|
||||
keymap.of([
|
||||
indentWithTab,
|
||||
{
|
||||
key: 'Mod-s',
|
||||
run: () => {
|
||||
this.handleSaveShortcut();
|
||||
return true;
|
||||
},
|
||||
},
|
||||
]),
|
||||
];
|
||||
|
||||
if (langExt) {
|
||||
extensions.push(langExt);
|
||||
}
|
||||
|
||||
if (wrapLines) {
|
||||
extensions.push(EditorView.lineWrapping);
|
||||
}
|
||||
const langLabel = getLanguageLabel(fileName, file?.mime);
|
||||
|
||||
return (
|
||||
<Box
|
||||
@@ -441,42 +365,39 @@ export default class VfsTextEditor extends PureComponent {
|
||||
},
|
||||
}}
|
||||
>
|
||||
<CodeMirror
|
||||
value={text}
|
||||
height="100%"
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
theme={oneDark}
|
||||
extensions={extensions}
|
||||
basicSetup={{
|
||||
lineNumbers: true,
|
||||
highlightActiveLineGutter: true,
|
||||
highlightSpecialChars: true,
|
||||
history: true,
|
||||
foldGutter: true,
|
||||
drawSelection: true,
|
||||
dropCursor: true,
|
||||
allowMultipleSelections: true,
|
||||
indentOnInput: true,
|
||||
syntaxHighlighting: true,
|
||||
bracketMatching: true,
|
||||
closeBrackets: true,
|
||||
autocompletion: true,
|
||||
rectangularSelection: true,
|
||||
crosshairCursor: false,
|
||||
highlightActiveLine: true,
|
||||
highlightSelectionMatches: true,
|
||||
closeBracketsKeymap: true,
|
||||
defaultKeymap: true,
|
||||
searchKeymap: true,
|
||||
historyKeymap: true,
|
||||
foldKeymap: true,
|
||||
completionKeymap: true,
|
||||
lintKeymap: true,
|
||||
}}
|
||||
onCreateEditor={this.handleEditorCreate}
|
||||
onUpdate={this.handleViewUpdate}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<React.Suspense
|
||||
fallback={
|
||||
<Box
|
||||
sx={{
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: 1.5,
|
||||
backgroundColor: EDITOR_BG,
|
||||
}}
|
||||
>
|
||||
<CircularProgress size={28} sx={{ color: '#61afef' }} />
|
||||
<Typography variant="caption" sx={{ color: '#abb2bf' }}>
|
||||
Editor wird geladen…
|
||||
</Typography>
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<CodeMirrorCore
|
||||
value={text}
|
||||
fileName={fileName}
|
||||
mime={file?.mime}
|
||||
wrapLines={wrapLines}
|
||||
readOnly={this.props.readOnly}
|
||||
onSaveShortcut={this.handleSaveShortcut}
|
||||
onCreateEditor={this.handleEditorCreate}
|
||||
onUpdate={this.handleViewUpdate}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</React.Suspense>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
@@ -361,6 +361,11 @@ export default defineConfig(({ command }) => ({
|
||||
name: 'jsquash-image',
|
||||
test: /node_modules\/@jsquash\/(avif|resize)\//,
|
||||
},
|
||||
{
|
||||
priority: 120,
|
||||
name: 'codemirror',
|
||||
test: /(?:node_modules\/(?:@codemirror|@uiw|@lezer|crelt|style-mod|w3c-keyname|@marijn\/find-cluster-break)|src\/components\/vfs\/CodeMirrorCore)/,
|
||||
},
|
||||
{
|
||||
priority: 110,
|
||||
name: 'ag-grid-vendor',
|
||||
@@ -379,7 +384,7 @@ export default defineConfig(({ command }) => ({
|
||||
modulePreload: {
|
||||
resolveDependencies(filename, deps, { hostType }) {
|
||||
if (hostType === 'html') {
|
||||
return deps.filter((dep) => !dep.includes('ag-grid') && !dep.includes('AutomatGrid'));
|
||||
return deps.filter((dep) => !dep.includes('ag-grid') && !dep.includes('AutomatGrid') && !dep.includes('codemirror'));
|
||||
}
|
||||
return deps;
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user