ui: update codemirror editor

This commit is contained in:
Matthew Penner 2021-07-21 12:18:53 -06:00
parent d2d62b7463
commit 3b1a0e22a7
8 changed files with 529 additions and 478 deletions

View file

@ -1,10 +1,10 @@
import { shell } from '@codemirror/legacy-modes/mode/shell';
import React from 'react';
import tw from 'twin.macro';
import AdminBox from '@/components/admin/AdminBox';
import { Context } from '@/components/admin/nests/eggs/EggRouter';
import Editor2 from '@/components/elements/Editor2';
import { shell } from '@codemirror/legacy-modes/mode/shell';
import Button from '@/components/elements/Button';
import Editor from '@/components/elements/Editor';
import Input from '@/components/elements/Input';
import Label from '@/components/elements/Label';
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
@ -28,7 +28,7 @@ export default () => {
<div css={tw`relative pb-4`}>
<SpinnerOverlay visible={false}/>
<Editor2 overrides={tw`h-96 mb-4`} mode={shell} initialContent={initialContent}/>
<Editor overrides={tw`h-96 mb-4`} initialContent={initialContent} mode={shell}/>
<div css={tw`mx-6 mb-4`}>
<div css={tw`grid grid-cols-3 gap-x-8 gap-y-6`}>

View file

@ -1,216 +0,0 @@
import React, { useCallback, useEffect, useState } from 'react';
import CodeMirror from 'codemirror';
import styled from 'styled-components/macro';
import tw from 'twin.macro';
import modes from '@/modes';
require('codemirror/lib/codemirror.css');
require('codemirror/theme/ayu-mirage.css');
require('codemirror/addon/edit/closebrackets');
require('codemirror/addon/edit/closetag');
require('codemirror/addon/edit/matchbrackets');
require('codemirror/addon/edit/matchtags');
require('codemirror/addon/edit/trailingspace');
require('codemirror/addon/fold/foldcode');
require('codemirror/addon/fold/foldgutter.css');
require('codemirror/addon/fold/foldgutter');
require('codemirror/addon/fold/brace-fold');
require('codemirror/addon/fold/comment-fold');
require('codemirror/addon/fold/indent-fold');
require('codemirror/addon/fold/markdown-fold');
require('codemirror/addon/fold/xml-fold');
require('codemirror/addon/hint/css-hint');
require('codemirror/addon/hint/html-hint');
require('codemirror/addon/hint/javascript-hint');
require('codemirror/addon/hint/show-hint.css');
require('codemirror/addon/hint/show-hint');
require('codemirror/addon/hint/sql-hint');
require('codemirror/addon/hint/xml-hint');
require('codemirror/addon/mode/simple');
require('codemirror/addon/dialog/dialog.css');
require('codemirror/addon/dialog/dialog');
require('codemirror/addon/scroll/annotatescrollbar');
require('codemirror/addon/scroll/scrollpastend');
require('codemirror/addon/scroll/simplescrollbars.css');
require('codemirror/addon/scroll/simplescrollbars');
require('codemirror/addon/search/jump-to-line');
require('codemirror/addon/search/match-highlighter');
require('codemirror/addon/search/matchesonscrollbar.css');
require('codemirror/addon/search/matchesonscrollbar');
require('codemirror/addon/search/search');
require('codemirror/addon/search/searchcursor');
require('codemirror/mode/brainfuck/brainfuck');
require('codemirror/mode/clike/clike');
require('codemirror/mode/css/css');
require('codemirror/mode/dart/dart');
require('codemirror/mode/diff/diff');
require('codemirror/mode/dockerfile/dockerfile');
require('codemirror/mode/erlang/erlang');
require('codemirror/mode/gfm/gfm');
require('codemirror/mode/go/go');
require('codemirror/mode/handlebars/handlebars');
require('codemirror/mode/htmlembedded/htmlembedded');
require('codemirror/mode/htmlmixed/htmlmixed');
require('codemirror/mode/http/http');
require('codemirror/mode/javascript/javascript');
require('codemirror/mode/jsx/jsx');
require('codemirror/mode/julia/julia');
require('codemirror/mode/lua/lua');
require('codemirror/mode/markdown/markdown');
require('codemirror/mode/nginx/nginx');
require('codemirror/mode/perl/perl');
require('codemirror/mode/php/php');
require('codemirror/mode/properties/properties');
require('codemirror/mode/protobuf/protobuf');
require('codemirror/mode/pug/pug');
require('codemirror/mode/python/python');
require('codemirror/mode/rpm/rpm');
require('codemirror/mode/ruby/ruby');
require('codemirror/mode/rust/rust');
require('codemirror/mode/sass/sass');
require('codemirror/mode/shell/shell');
require('codemirror/mode/smarty/smarty');
require('codemirror/mode/sql/sql');
require('codemirror/mode/swift/swift');
require('codemirror/mode/toml/toml');
require('codemirror/mode/twig/twig');
require('codemirror/mode/vue/vue');
require('codemirror/mode/xml/xml');
require('codemirror/mode/yaml/yaml');
const EditorContainer = styled.div`
min-height: 16rem;
height: calc(100vh - 20rem);
${tw`relative`};
> div {
${tw`rounded h-full`};
}
.CodeMirror {
font-size: 12px;
line-height: 1.375rem;
}
.CodeMirror-linenumber {
padding: 1px 12px 0 12px !important;
}
.CodeMirror-foldmarker {
color: #CBCCC6;
text-shadow: none;
margin-left: 0.25rem;
margin-right: 0.25rem;
}
`;
export interface Props {
style?: React.CSSProperties;
initialContent?: string;
mode: string;
filename?: string;
onModeChanged: (mode: string) => void;
fetchContent: (callback: () => Promise<string>) => void;
onContentSaved: () => void;
}
const findModeByFilename = (filename: string) => {
for (let i = 0; i < modes.length; i++) {
const info = modes[i];
if (info.file && info.file.test(filename)) {
return info;
}
}
const dot = filename.lastIndexOf('.');
const ext = dot > -1 && filename.substring(dot + 1, filename.length);
if (ext) {
for (let i = 0; i < modes.length; i++) {
const info = modes[i];
if (info.ext) {
for (let j = 0; j < info.ext.length; j++) {
if (info.ext[j] === ext) {
return info;
}
}
}
}
}
return undefined;
};
export default ({ style, initialContent, filename, mode, fetchContent, onContentSaved, onModeChanged }: Props) => {
const [ editor, setEditor ] = useState<CodeMirror.Editor>();
const ref = useCallback((node) => {
if (!node) return;
const e = CodeMirror.fromTextArea(node, {
mode: 'text/plain',
theme: 'ayu-mirage',
indentUnit: 4,
smartIndent: true,
tabSize: 4,
indentWithTabs: false,
lineWrapping: true,
lineNumbers: true,
// @ts-ignore
foldGutter: true,
fixedGutter: true,
scrollbarStyle: 'native',
coverGutterNextToScrollbar: false,
readOnly: false,
showCursorWhenSelecting: false,
autofocus: false,
spellcheck: true,
autocorrect: false,
autocapitalize: false,
lint: false,
autoCloseBrackets: true,
matchBrackets: true,
gutters: [ 'CodeMirror-linenumbers', 'CodeMirror-foldgutter' ],
});
setEditor(e);
}, []);
useEffect(() => {
if (filename === undefined) {
return;
}
onModeChanged(findModeByFilename(filename)?.mime || 'text/plain');
}, [ filename ]);
useEffect(() => {
editor && editor.setOption('mode', mode);
}, [ editor, mode ]);
useEffect(() => {
editor && editor.setValue(initialContent || '');
}, [ editor, initialContent ]);
useEffect(() => {
if (!editor) {
fetchContent(() => Promise.reject(new Error('no editor session has been configured')));
return;
}
editor.addKeyMap({
'Ctrl-S': () => onContentSaved(),
'Cmd-S': () => onContentSaved(),
});
fetchContent(() => Promise.resolve(editor.getValue()));
}, [ editor, fetchContent, onContentSaved ]);
return (
<EditorContainer style={style}>
<textarea ref={ref}/>
</EditorContainer>
);
};

View file

@ -0,0 +1,287 @@
import { autocompletion, completionKeymap } from '@codemirror/autocomplete';
import { closeBrackets, closeBracketsKeymap } from '@codemirror/closebrackets';
import { defaultKeymap, defaultTabBinding } from '@codemirror/commands';
import { commentKeymap } from '@codemirror/comment';
import { foldGutter, foldKeymap } from '@codemirror/fold';
import { lineNumbers, highlightActiveLineGutter } from '@codemirror/gutter';
import { defaultHighlightStyle } from '@codemirror/highlight';
import { history, historyKeymap } from '@codemirror/history';
import { indentOnInput, LanguageSupport, LezerLanguage } from '@codemirror/language';
import { lintKeymap } from '@codemirror/lint';
import { bracketMatching } from '@codemirror/matchbrackets';
import { rectangularSelection } from '@codemirror/rectangular-selection';
import { searchKeymap, highlightSelectionMatches } from '@codemirror/search';
import { Compartment, Extension, EditorState } from '@codemirror/state';
import { StreamLanguage, StreamParser } from '@codemirror/stream-parser';
import { keymap, highlightSpecialChars, drawSelection, highlightActiveLine, EditorView } from '@codemirror/view';
import { clike } from '@codemirror/legacy-modes/mode/clike';
import { cppLanguage } from '@codemirror/lang-cpp';
import { cssLanguage } from '@codemirror/lang-css';
import { Cassandra, MariaSQL, MSSQL, MySQL, PostgreSQL, sql, SQLite, StandardSQL } from '@codemirror/lang-sql';
import { diff } from '@codemirror/legacy-modes/mode/diff';
import { dockerFile } from '@codemirror/legacy-modes/mode/dockerfile';
import { markdown, markdownLanguage } from '@codemirror/lang-markdown';
import { go } from '@codemirror/legacy-modes/mode/go';
import { htmlLanguage } from '@codemirror/lang-html';
import { http } from '@codemirror/legacy-modes/mode/http';
import { javascriptLanguage, typescriptLanguage } from '@codemirror/lang-javascript';
import { jsonLanguage } from '@codemirror/lang-json';
import { lua } from '@codemirror/legacy-modes/mode/lua';
import { properties } from '@codemirror/legacy-modes/mode/properties';
import { python } from '@codemirror/legacy-modes/mode/python';
import { ruby } from '@codemirror/legacy-modes/mode/ruby';
import { rustLanguage } from '@codemirror/lang-rust';
import { shell } from '@codemirror/legacy-modes/mode/shell';
import { toml } from '@codemirror/legacy-modes/mode/toml';
import { xmlLanguage } from '@codemirror/lang-xml';
import { yaml } from '@codemirror/legacy-modes/mode/yaml';
import React, { useCallback, useEffect, useState } from 'react';
import styled from 'styled-components/macro';
import tw, { TwStyle } from 'twin.macro';
import { ayuMirage } from '@/components/elements/EditorTheme';
type EditorMode = LanguageSupport | LezerLanguage | StreamParser<any>;
export interface Mode {
name: string,
mime: string,
mimes?: string[],
mode?: EditorMode,
ext?: string[],
alias?: string[],
file?: RegExp,
}
export const modes: Mode[] = [
{ name: 'C', mime: 'text/x-csrc', mode: clike({}), ext: [ 'c', 'h', 'ino' ] },
{ name: 'C++', mime: 'text/x-c++src', mode: cppLanguage, ext: [ 'cpp', 'c++', 'cc', 'cxx', 'hpp', 'h++', 'hh', 'hxx' ], alias: [ 'cpp' ] },
{ name: 'C#', mime: 'text/x-csharp', mode: clike({}), ext: [ 'cs' ], alias: [ 'csharp', 'cs' ] },
{ name: 'CSS', mime: 'text/css', mode: cssLanguage, ext: [ 'css' ] },
{ name: 'CQL', mime: 'text/x-cassandra', mode: sql({ dialect: Cassandra }), ext: [ 'cql' ] },
{ name: 'Diff', mime: 'text/x-diff', mode: diff, ext: [ 'diff', 'patch' ] },
{ name: 'Dockerfile', mime: 'text/x-dockerfile', mode: dockerFile, file: /^Dockerfile$/ },
{ name: 'Git Markdown', mime: 'text/x-gfm', mode: markdown({ defaultCodeLanguage: markdownLanguage }), file: /^(readme|contributing|history|license).md$/i },
{ name: 'Golang', mime: 'text/x-go', mode: go, ext: [ 'go' ] },
{ name: 'HTML', mime: 'text/html', mode: htmlLanguage, ext: [ 'html', 'htm', 'handlebars', 'hbs' ], alias: [ 'xhtml' ] },
{ name: 'HTTP', mime: 'message/http', mode: http },
{ name: 'JavaScript', mime: 'text/javascript', mimes: [ 'text/javascript', 'text/ecmascript', 'application/javascript', 'application/x-javascript', 'application/ecmascript' ], mode: javascriptLanguage, ext: [ 'js' ], alias: [ 'ecmascript', 'js', 'node' ] },
{ name: 'JSON', mime: 'application/json', mimes: [ 'application/json', 'application/x-json' ], mode: jsonLanguage, ext: [ 'json', 'map' ], alias: [ 'json5' ] },
{ name: 'Lua', mime: 'text/x-lua', mode: lua, ext: [ 'lua' ] },
{ name: 'Markdown', mime: 'text/x-markdown', mode: markdown({ defaultCodeLanguage: markdownLanguage }), ext: [ 'markdown', 'md', 'mkd' ] },
{ name: 'MariaDB', mime: 'text/x-mariadb', mode: sql({ dialect: MariaSQL }) },
{ name: 'MS SQL', mime: 'text/x-mssql', mode: sql({ dialect: MSSQL }) },
{ name: 'MySQL', mime: 'text/x-mysql', mode: sql({ dialect: MySQL }) },
{ name: 'Plain Text', mime: 'text/plain', mode: undefined, ext: [ 'txt', 'text', 'conf', 'def', 'list', 'log' ] },
{ name: 'PostgreSQL', mime: 'text/x-pgsql', mode: sql({ dialect: PostgreSQL }) },
{ name: 'Properties', mime: 'text/x-properties', mode: properties, ext: [ 'properties', 'ini', 'in' ], alias: [ 'ini', 'properties' ] },
{ name: 'Python', mime: 'text/x-python', mode: python, ext: [ 'BUILD', 'bzl', 'py', 'pyw' ], file: /^(BUCK|BUILD)$/ },
{ name: 'Ruby', mime: 'text/x-ruby', mode: ruby, ext: [ 'rb' ], alias: [ 'jruby', 'macruby', 'rake', 'rb', 'rbx' ] },
{ name: 'Rust', mime: 'text/x-rustsrc', mode: rustLanguage, ext: [ 'rs' ] },
{ name: 'Sass', mime: 'text/x-sass', mode: cssLanguage, ext: [ 'sass' ] },
{ name: 'SCSS', mime: 'text/x-scss', mode: cssLanguage, ext: [ 'scss' ] },
{ name: 'Shell', mime: 'text/x-sh', mimes: [ 'text/x-sh', 'application/x-sh' ], mode: shell, ext: [ 'sh', 'ksh', 'bash' ], alias: [ 'bash', 'sh', 'zsh' ], file: /^PKGBUILD$/ },
{ name: 'SQL', mime: 'text/x-sql', mode: sql({ dialect: StandardSQL }), ext: [ 'sql' ] },
{ name: 'SQLite', mime: 'text/x-sqlite', mode: sql({ dialect: SQLite }) },
{ name: 'TOML', mime: 'text/x-toml', mode: toml, ext: [ 'toml' ] },
{ name: 'TypeScript', mime: 'application/typescript', mode: typescriptLanguage, ext: [ 'ts' ], alias: [ 'ts' ] },
{ name: 'XML', mime: 'application/xml', mimes: [ 'application/xml', 'text/xml' ], mode: xmlLanguage, ext: [ 'xml', 'xsl', 'xsd', 'svg' ], alias: [ 'rss', 'wsdl', 'xsd' ] },
{ name: 'YAML', mime: 'text/x-yaml', mimes: [ 'text/x-yaml', 'text/yaml' ], mode: yaml, ext: [ 'yaml', 'yml' ], alias: [ 'yml' ] },
];
export const modeToExtension = (m: EditorMode): Extension => {
if (m instanceof LanguageSupport) {
return m;
}
if (m instanceof LezerLanguage) {
return m;
}
return StreamLanguage.define(m);
};
const findModeByFilename = (filename: string): Mode => {
for (let i = 0; i < modes.length; i++) {
const info = modes[i];
if (info.file && info.file.test(filename)) {
return info;
}
}
const dot = filename.lastIndexOf('.');
const ext = dot > -1 && filename.substring(dot + 1, filename.length);
if (ext) {
for (let i = 0; i < modes.length; i++) {
const info = modes[i];
if (info.ext) {
for (let j = 0; j < info.ext.length; j++) {
if (info.ext[j] === ext) {
return info;
}
}
}
}
}
const plainText = modes.find(m => m.mime === 'text/plain');
if (plainText === undefined) {
throw new Error('failed to find \'text/plain\' mode');
}
return plainText;
};
const findLanguageExtensionByMode = (mode: Mode): Extension => {
if (mode.mode === undefined) {
return [];
}
return modeToExtension(mode.mode);
};
const defaultExtensions: Extension = [
ayuMirage,
lineNumbers(),
highlightActiveLineGutter(),
highlightSpecialChars(),
history(),
foldGutter(),
drawSelection(),
EditorState.allowMultipleSelections.of(true),
indentOnInput(),
defaultHighlightStyle.fallback,
bracketMatching(),
closeBrackets(),
autocompletion(),
rectangularSelection(),
highlightActiveLine(),
highlightSelectionMatches(),
keymap.of([
...closeBracketsKeymap,
...defaultKeymap,
...searchKeymap,
...historyKeymap,
...foldKeymap,
...commentKeymap,
...completionKeymap,
...lintKeymap,
defaultTabBinding,
]),
EditorState.tabSize.of(4),
];
const EditorContainer = styled.div<{ overrides?: TwStyle }>`
min-height: 12rem;
${tw`relative`};
& > div {
${props => props.overrides};
&.cm-focused {
outline: none;
}
}
`;
export interface Props {
className?: string;
style?: React.CSSProperties;
overrides?: TwStyle;
initialContent?: string;
extensions?: Extension[];
mode?: EditorMode;
filename?: string;
onModeChanged?: (mode: Mode) => void;
fetchContent?: (callback: () => Promise<string>) => void;
onContentSaved?: () => void;
}
export default ({ className, style, overrides, initialContent, extensions, mode, filename, onModeChanged }: Props) => {
const [ languageConfig ] = useState<Compartment>(new Compartment());
const [ state ] = useState<EditorState>(EditorState.create({
doc: initialContent,
extensions: [
...defaultExtensions,
...(extensions !== undefined ? extensions : []),
languageConfig.of(mode !== undefined ? modeToExtension(mode) : findLanguageExtensionByMode(findModeByFilename(filename || ''))),
],
}));
const [ view, setView ] = useState<EditorView>();
const ref = useCallback((node) => {
if (!node) {
return;
}
const view = new EditorView({
state: state,
parent: node,
});
setView(view);
}, []);
// This useEffect is required to send the proper mode back to the parent element
// due to the initial language being set with EditorState#create, rather than in
// an useEffect like this one, or one watching `filename`.
useEffect(() => {
if (onModeChanged === undefined) {
return;
}
onModeChanged(findModeByFilename(filename || ''));
}, []);
useEffect(() => {
if (view === undefined) {
return;
}
if (mode === undefined) {
return;
}
view.dispatch({
effects: languageConfig.reconfigure(modeToExtension(mode)),
});
}, [ mode ]);
useEffect(() => {
if (view === undefined) {
return;
}
if (filename === undefined) {
return;
}
const mode = findModeByFilename(filename || '');
view.dispatch({
effects: languageConfig.reconfigure(findLanguageExtensionByMode(mode)),
});
console.log(mode);
if (onModeChanged !== undefined) {
console.log(mode);
onModeChanged(mode);
}
}, [ filename ]);
useEffect(() => {
if (view === undefined) {
return;
}
view.dispatch({
changes: { from: 0, insert: initialContent },
});
}, [ initialContent ]);
return (
<EditorContainer className={className} style={style} overrides={overrides} ref={ref}/>
);
};

View file

@ -1,99 +0,0 @@
import React, { useCallback, useState } from 'react';
import styled from 'styled-components/macro';
import tw, { TwStyle } from 'twin.macro';
import { autocompletion, completionKeymap } from '@codemirror/autocomplete';
import { closeBrackets, closeBracketsKeymap } from '@codemirror/closebrackets';
import { defaultKeymap, defaultTabBinding } from '@codemirror/commands';
import { commentKeymap } from '@codemirror/comment';
import { foldGutter, foldKeymap } from '@codemirror/fold';
import { lineNumbers, highlightActiveLineGutter } from '@codemirror/gutter';
import { defaultHighlightStyle } from '@codemirror/highlight';
import { history, historyKeymap } from '@codemirror/history';
import { indentOnInput, LezerLanguage } from '@codemirror/language';
import { lintKeymap } from '@codemirror/lint';
import { bracketMatching } from '@codemirror/matchbrackets';
import { rectangularSelection } from '@codemirror/rectangular-selection';
import { searchKeymap, highlightSelectionMatches } from '@codemirror/search';
import { Extension, EditorState } from '@codemirror/state';
import { StreamLanguage, StreamParser } from '@codemirror/stream-parser';
import { keymap, highlightSpecialChars, drawSelection, highlightActiveLine, EditorView } from '@codemirror/view';
import { ayuMirage } from '@/components/elements/EditorTheme';
const extensions: Extension = [
ayuMirage,
lineNumbers(),
highlightActiveLineGutter(),
highlightSpecialChars(),
history(),
foldGutter(),
drawSelection(),
EditorState.allowMultipleSelections.of(true),
indentOnInput(),
defaultHighlightStyle.fallback,
bracketMatching(),
closeBrackets(),
autocompletion(),
rectangularSelection(),
highlightActiveLine(),
highlightSelectionMatches(),
keymap.of([
...closeBracketsKeymap,
...defaultKeymap,
...searchKeymap,
...historyKeymap,
...foldKeymap,
...commentKeymap,
...completionKeymap,
...lintKeymap,
defaultTabBinding,
]),
EditorState.tabSize.of(4),
];
const EditorContainer = styled.div<{ overrides?: TwStyle }>`
min-height: 12rem;
${tw`relative`};
& > div {
${props => props.overrides};
&.cm-focused {
outline: none;
}
}
`;
export interface Props {
className?: string;
overrides?: TwStyle;
mode: LezerLanguage | StreamParser<any>;
initialContent?: string;
}
export default ({ className, overrides, mode, initialContent }: Props) => {
const [ state ] = useState<EditorState>(EditorState.create({
doc: initialContent,
extensions: [ ...extensions, (mode instanceof LezerLanguage) ? mode : StreamLanguage.define(mode) ],
}));
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [ view, setView ] = useState<EditorView>();
const ref = useCallback((node) => {
if (!node) {
return;
}
const view = new EditorView({
state: state,
parent: node,
});
setView(view);
}, []);
return (
<EditorContainer className={className} overrides={overrides} ref={ref}/>
);
};

View file

@ -1,26 +1,24 @@
import React, { lazy, useEffect, useState } from 'react';
import getFileContents from '@/api/server/files/getFileContents';
import { httpErrorToHuman } from '@/api/http';
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
import saveFileContents from '@/api/server/files/saveFileContents';
import FileManagerBreadcrumbs from '@/components/server/files/FileManagerBreadcrumbs';
import React, { useEffect, useState } from 'react';
import { useHistory, useLocation, useParams } from 'react-router';
import FileNameModal from '@/components/server/files/FileNameModal';
import { dirname } from 'path';
import tw from 'twin.macro';
import { httpErrorToHuman } from '@/api/http';
import getFileContents from '@/api/server/files/getFileContents';
import saveFileContents from '@/api/server/files/saveFileContents';
import Can from '@/components/elements/Can';
import Editor, { modes } from '@/components/elements/Editor';
import ErrorBoundary from '@/components/elements/ErrorBoundary';
import FlashMessageRender from '@/components/FlashMessageRender';
import Button from '@/components/elements/Button';
import PageContentBlock from '@/components/elements/PageContentBlock';
import { ServerError } from '@/components/elements/ScreenBlock';
import tw from 'twin.macro';
import Button from '@/components/elements/Button';
import Select from '@/components/elements/Select';
import modes from '@/modes';
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
import FileManagerBreadcrumbs from '@/components/server/files/FileManagerBreadcrumbs';
import FileNameModal from '@/components/server/files/FileNameModal';
import useFlash from '@/plugins/useFlash';
import { ServerContext } from '@/state/server';
import ErrorBoundary from '@/components/elements/ErrorBoundary';
import { encodePathSegments, hashToPath } from '@/helpers';
import { dirname } from 'path';
const LazyCodemirrorEditor = lazy(() => import(/* webpackChunkName: "editor" */'@/components/elements/CodemirrorEditor'));
export default () => {
const [ error, setError ] = useState('');
@ -28,7 +26,7 @@ export default () => {
const [ loading, setLoading ] = useState(action === 'edit');
const [ content, setContent ] = useState('');
const [ modalVisible, setModalVisible ] = useState(false);
const [ mode, setMode ] = useState('text/plain');
const [ mode, setMode ] = useState<string>('text/plain');
const history = useHistory();
const { hash } = useLocation();
@ -38,6 +36,7 @@ export default () => {
const setDirectory = ServerContext.useStoreActions(actions => actions.files.setDirectory);
const { addError, clearFlashes } = useFlash();
// eslint-disable-next-line prefer-const
let fetchFileContent: null | (() => Promise<string>) = null;
useEffect(() => {
@ -116,11 +115,12 @@ export default () => {
/>
<div css={tw`relative`}>
<SpinnerOverlay visible={loading}/>
<LazyCodemirrorEditor
mode={mode}
filename={hash.replace(/^#/, '')}
onModeChanged={setMode}
<Editor
style={{ height: 'calc(100vh - 20rem)' }}
overrides={tw`rounded h-full`}
initialContent={content}
filename={hash.replace(/^#/, '')}
onModeChanged={m => setMode(m.mime)}
fetchContent={value => {
fetchFileContent = value;
}}