Merge branch 'bugfixes' of https://github.com/Sir3lit/panel into bugfixes

This commit is contained in:
Charles Morgan 2020-09-17 00:07:30 -04:00
commit 98ed96d08a
No known key found for this signature in database
GPG key ID: D71946CD723249BD
25 changed files with 382 additions and 909 deletions

View file

@ -1,84 +0,0 @@
import React, { useCallback, useEffect, useState } from 'react';
import ace, { Editor } from 'brace';
import styled from 'styled-components/macro';
import tw from 'twin.macro';
import modes from '@/modes';
// @ts-ignore
require('brace/ext/modelist');
require('ayu-ace/mirage');
const EditorContainer = styled.div`
min-height: 16rem;
height: calc(100vh - 20rem);
${tw`relative`};
#editor {
${tw`rounded h-full`};
}
`;
Object.keys(modes).forEach(mode => require(`brace/mode/${mode}`));
const modelist = ace.acequire('ace/ext/modelist');
export interface Props {
style?: React.CSSProperties;
initialContent?: string;
mode: string;
filename?: string;
onModeChanged: (mode: string) => void;
fetchContent: (callback: () => Promise<string>) => void;
onContentSaved: () => void;
}
export default ({ style, initialContent, filename, mode, fetchContent, onContentSaved, onModeChanged }: Props) => {
const [ editor, setEditor ] = useState<Editor>();
const ref = useCallback(node => {
if (node) setEditor(ace.edit('editor'));
}, []);
useEffect(() => {
if (modelist && filename) {
onModeChanged(modelist.getModeForPath(filename).mode.replace(/^ace\/mode\//, ''));
}
}, [ filename ]);
useEffect(() => {
editor && editor.session.setMode(`ace/mode/${mode}`);
}, [ editor, mode ]);
useEffect(() => {
editor && editor.session.setValue(initialContent || '');
}, [ editor, initialContent ]);
useEffect(() => {
if (!editor) {
fetchContent(() => Promise.reject(new Error('no editor session has been configured')));
return;
}
editor.setTheme('ace/theme/ayu-mirage');
editor.$blockScrolling = Infinity;
editor.container.style.lineHeight = '1.375rem';
editor.container.style.fontWeight = '500';
editor.renderer.updateFontSize();
editor.renderer.setShowPrintMargin(false);
editor.session.setTabSize(4);
editor.session.setUseSoftTabs(true);
editor.commands.addCommand({
name: 'Save',
bindKey: { win: 'Ctrl-s', mac: 'Command-s' },
exec: () => onContentSaved(),
});
fetchContent(() => Promise.resolve(editor.session.getValue()));
}, [ editor, fetchContent, onContentSaved ]);
return (
<EditorContainer style={style}>
<div id={'editor'} ref={ref}/>
</EditorContainer>
);
};

View file

@ -0,0 +1,217 @@
import React, { useCallback, useEffect, useState } from 'react';
import CodeMirror from 'codemirror';
import styled from 'styled-components/macro';
import tw from 'twin.macro';
import modes, { Mode } 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,
foldGutter: true,
fixedGutter: true,
scrollbarStyle: 'overlay',
coverGutterNextToScrollbar: false,
readOnly: false,
showCursorWhenSelecting: false,
autofocus: false,
spellcheck: true,
autocorrect: false,
autocapitalize: false,
lint: false,
// This property is actually used, the d.ts file for CodeMirror is incorrect.
// @ts-ignore
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

@ -12,6 +12,7 @@ import tw from 'twin.macro';
import { Textarea } from '@/components/elements/Input';
import getServerBackups from '@/api/swr/getServerBackups';
import { ServerContext } from '@/state/server';
import styled from 'styled-components/macro';
interface Values {
name: string;
@ -44,7 +45,7 @@ const ModalContent = ({ ...props }: RequiredModalProps) => {
prefixing the path with an exclamation point.
`}
>
<FormikField as={Textarea} name={'ignored'} css={tw`h-32`}/>
<FormikField as={Textarea} name={'ignored'} rows={6}/>
</FormikFieldWrapper>
</div>
<div css={tw`flex justify-end`}>

View file

@ -171,7 +171,9 @@ const FileDropdownMenu = ({ file }: { file: FileObject }) => {
<Row onClick={doArchive} icon={faFileArchive} title={'Archive'}/>
</Can>
}
<Row onClick={doDownload} icon={faFileDownload} title={'Download'}/>
{file.isFile &&
<Row onClick={doDownload} icon={faFileDownload} title={'Download'}/>
}
<Can action={'file.delete'}>
<Row onClick={() => setShowConfirmation(true)} icon={faTrashAlt} title={'Delete'} $danger/>
</Can>

View file

@ -17,7 +17,7 @@ import modes from '@/modes';
import useFlash from '@/plugins/useFlash';
import { ServerContext } from '@/state/server';
const LazyAceEditor = lazy(() => import(/* webpackChunkName: "editor" */'@/components/elements/AceEditor'));
const LazyCodemirrorEditor = lazy(() => import(/* webpackChunkName: "editor" */'@/components/elements/CodemirrorEditor'));
export default () => {
const [ error, setError ] = useState('');
@ -25,7 +25,7 @@ export default () => {
const [ loading, setLoading ] = useState(action === 'edit');
const [ content, setContent ] = useState('');
const [ modalVisible, setModalVisible ] = useState(false);
const [ mode, setMode ] = useState('plain_text');
const [ mode, setMode ] = useState('text/plain');
const history = useHistory();
const { hash } = useLocation();
@ -108,7 +108,7 @@ export default () => {
/>
<div css={tw`relative`}>
<SpinnerOverlay visible={loading}/>
<LazyAceEditor
<LazyCodemirrorEditor
mode={mode}
filename={hash.replace(/^#/, '')}
onModeChanged={setMode}
@ -122,8 +122,10 @@ export default () => {
<div css={tw`flex justify-end mt-4`}>
<div css={tw`flex-1 sm:flex-none rounded bg-neutral-900 mr-4`}>
<Select value={mode} onChange={e => setMode(e.currentTarget.value)}>
{Object.keys(modes).map(key => (
<option key={key} value={key}>{modes[key]}</option>
{modes.map(mode => (
<option key={`${mode.name}_${mode.mime}`} value={mode.mime}>
{mode.name}
</option>
))}
</Select>
</div>