Skip to content

Commit 0be5878

Browse files
authored
Merge pull request pterodactyl#2338 from pterodactyl/feature/codemirror
Replace Ace Editor with CodeMirror
2 parents 79f616f + 9d7f4f9 commit 0be5878

File tree

7 files changed

+325
-118
lines changed

7 files changed

+325
-118
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@
55
"@fortawesome/free-solid-svg-icons": "^5.9.0",
66
"@fortawesome/react-fontawesome": "0.1.4",
77
"axios": "^0.19.2",
8-
"ayu-ace": "^2.0.4",
9-
"brace": "^0.11.1",
108
"chart.js": "^2.8.0",
9+
"codemirror": "^5.57.0",
1110
"date-fns": "^2.14.0",
1211
"debounce": "^1.2.0",
1312
"deepmerge": "^4.2.2",
@@ -57,6 +56,7 @@
5756
"@babel/preset-typescript": "^7.7.4",
5857
"@babel/runtime": "^7.7.5",
5958
"@types/chart.js": "^2.8.5",
59+
"@types/codemirror": "^0.0.98",
6060
"@types/debounce": "^1.2.0",
6161
"@types/events": "^3.0.0",
6262
"@types/node": "^12.6.9",

resources/scripts/components/elements/AceEditor.tsx

Lines changed: 0 additions & 84 deletions
This file was deleted.
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
import React, { useCallback, useEffect, useState } from 'react';
2+
import CodeMirror from 'codemirror';
3+
import styled from 'styled-components/macro';
4+
import tw from 'twin.macro';
5+
import modes, { Mode } from '@/modes';
6+
7+
require('codemirror/lib/codemirror.css');
8+
9+
// Themes
10+
require('codemirror/theme/ayu-mirage.css');
11+
12+
// Addons
13+
require('codemirror/addon/edit/closebrackets');
14+
require('codemirror/addon/edit/closetag');
15+
require('codemirror/addon/edit/matchbrackets');
16+
require('codemirror/addon/edit/matchtags');
17+
require('codemirror/addon/edit/trailingspace');
18+
19+
require('codemirror/addon/fold/foldcode');
20+
require('codemirror/addon/fold/foldgutter.css');
21+
require('codemirror/addon/fold/foldgutter');
22+
require('codemirror/addon/fold/brace-fold');
23+
require('codemirror/addon/fold/comment-fold');
24+
require('codemirror/addon/fold/indent-fold');
25+
require('codemirror/addon/fold/markdown-fold');
26+
require('codemirror/addon/fold/xml-fold');
27+
28+
require('codemirror/addon/hint/css-hint');
29+
require('codemirror/addon/hint/html-hint');
30+
require('codemirror/addon/hint/javascript-hint');
31+
require('codemirror/addon/hint/show-hint.css');
32+
require('codemirror/addon/hint/show-hint');
33+
require('codemirror/addon/hint/sql-hint');
34+
require('codemirror/addon/hint/xml-hint');
35+
36+
require('codemirror/addon/mode/simple');
37+
38+
require('codemirror/addon/dialog/dialog.css');
39+
require('codemirror/addon/dialog/dialog');
40+
41+
require('codemirror/addon/scroll/annotatescrollbar');
42+
require('codemirror/addon/scroll/scrollpastend');
43+
require('codemirror/addon/scroll/simplescrollbars.css');
44+
require('codemirror/addon/scroll/simplescrollbars');
45+
46+
require('codemirror/addon/search/jump-to-line');
47+
require('codemirror/addon/search/match-highlighter');
48+
require('codemirror/addon/search/matchesonscrollbar.css');
49+
require('codemirror/addon/search/matchesonscrollbar');
50+
require('codemirror/addon/search/search');
51+
require('codemirror/addon/search/searchcursor');
52+
53+
// Modes
54+
require('codemirror/mode/brainfuck/brainfuck');
55+
require('codemirror/mode/clike/clike');
56+
require('codemirror/mode/css/css');
57+
require('codemirror/mode/dart/dart');
58+
require('codemirror/mode/diff/diff');
59+
require('codemirror/mode/dockerfile/dockerfile');
60+
require('codemirror/mode/erlang/erlang');
61+
require('codemirror/mode/gfm/gfm');
62+
require('codemirror/mode/go/go');
63+
require('codemirror/mode/handlebars/handlebars');
64+
require('codemirror/mode/htmlembedded/htmlembedded');
65+
require('codemirror/mode/htmlmixed/htmlmixed');
66+
require('codemirror/mode/http/http');
67+
require('codemirror/mode/javascript/javascript');
68+
require('codemirror/mode/jsx/jsx');
69+
require('codemirror/mode/julia/julia');
70+
require('codemirror/mode/lua/lua');
71+
require('codemirror/mode/markdown/markdown');
72+
require('codemirror/mode/nginx/nginx');
73+
require('codemirror/mode/perl/perl');
74+
require('codemirror/mode/php/php');
75+
require('codemirror/mode/properties/properties');
76+
require('codemirror/mode/protobuf/protobuf');
77+
require('codemirror/mode/pug/pug');
78+
require('codemirror/mode/python/python');
79+
require('codemirror/mode/rpm/rpm');
80+
require('codemirror/mode/ruby/ruby');
81+
require('codemirror/mode/rust/rust');
82+
require('codemirror/mode/sass/sass');
83+
require('codemirror/mode/shell/shell');
84+
require('codemirror/mode/smarty/smarty');
85+
require('codemirror/mode/sql/sql');
86+
require('codemirror/mode/swift/swift');
87+
require('codemirror/mode/toml/toml');
88+
require('codemirror/mode/twig/twig');
89+
require('codemirror/mode/vue/vue');
90+
require('codemirror/mode/xml/xml');
91+
require('codemirror/mode/yaml/yaml');
92+
93+
const EditorContainer = styled.div`
94+
min-height: 16rem;
95+
height: calc(100vh - 20rem);
96+
${tw`relative`};
97+
98+
> div {
99+
${tw`rounded h-full`};
100+
}
101+
102+
.CodeMirror {
103+
font-size: 12px;
104+
line-height: 1.375rem;
105+
}
106+
107+
.CodeMirror-linenumber {
108+
padding: 1px 12px 0 12px !important;
109+
}
110+
111+
.CodeMirror-foldmarker {
112+
color: #CBCCC6;
113+
text-shadow: none;
114+
margin-left: 0.25rem;
115+
margin-right: 0.25rem;
116+
}
117+
`;
118+
119+
export interface Props {
120+
style?: React.CSSProperties;
121+
initialContent?: string;
122+
mode: string;
123+
filename?: string;
124+
onModeChanged: (mode: string) => void;
125+
fetchContent: (callback: () => Promise<string>) => void;
126+
onContentSaved: () => void;
127+
}
128+
129+
export default ({ style, initialContent, filename, mode, fetchContent, onContentSaved, onModeChanged }: Props) => {
130+
const [ editor, setEditor ] = useState<CodeMirror.Editor>();
131+
132+
const ref = useCallback((node) => {
133+
if (!node) {
134+
return;
135+
}
136+
137+
const e = CodeMirror.fromTextArea(node, {
138+
mode: 'text/plain',
139+
theme: 'ayu-mirage',
140+
141+
indentUnit: 4,
142+
smartIndent: true,
143+
tabSize: 4,
144+
indentWithTabs: true,
145+
146+
lineWrapping: true,
147+
lineNumbers: true,
148+
149+
foldGutter: true,
150+
fixedGutter: true,
151+
152+
scrollbarStyle: 'overlay',
153+
coverGutterNextToScrollbar: false,
154+
155+
readOnly: false,
156+
157+
showCursorWhenSelecting: false,
158+
159+
autofocus: false,
160+
161+
spellcheck: true,
162+
autocorrect: false,
163+
autocapitalize: false,
164+
lint: false,
165+
166+
// This property is actually used, the d.ts file for CodeMirror is incorrect.
167+
// @ts-ignore
168+
autoCloseBrackets: true,
169+
matchBrackets: true,
170+
171+
gutters: [ 'CodeMirror-linenumbers', 'CodeMirror-foldgutter' ],
172+
});
173+
174+
setEditor(e);
175+
}, []);
176+
177+
useEffect(() => {
178+
if (filename === undefined) {
179+
return;
180+
}
181+
182+
const findModeByFilename = (filename: string): Mode|undefined => {
183+
for (let i = 0; i < modes.length; i++) {
184+
const info = modes[i];
185+
186+
if (info.file && info.file.test(filename)) {
187+
return info;
188+
}
189+
}
190+
191+
const dot = filename.lastIndexOf('.');
192+
const ext = dot > -1 && filename.substring(dot + 1, filename.length);
193+
194+
if (ext) {
195+
for (let i = 0; i < modes.length; i++) {
196+
const info = modes[i];
197+
198+
if (info.ext) {
199+
for (let j = 0; j < info.ext.length; j++) {
200+
if (info.ext[j] === ext) {
201+
return info;
202+
}
203+
}
204+
}
205+
}
206+
}
207+
208+
return undefined;
209+
};
210+
211+
onModeChanged(findModeByFilename(filename)?.mime || 'text/plain');
212+
}, [ filename ]);
213+
214+
useEffect(() => {
215+
editor && editor.setOption('mode', mode);
216+
}, [ editor, mode ]);
217+
218+
useEffect(() => {
219+
editor && editor.setValue(initialContent || '');
220+
}, [ editor, initialContent ]);
221+
222+
useEffect(() => {
223+
if (!editor) {
224+
fetchContent(() => Promise.reject(new Error('no editor session has been configured')));
225+
return;
226+
}
227+
228+
editor.addKeyMap({
229+
'Ctrl-S': () => onContentSaved(),
230+
'Cmd-S': () => onContentSaved(),
231+
});
232+
233+
fetchContent(() => Promise.resolve(editor.getValue()));
234+
}, [ editor, fetchContent, onContentSaved ]);
235+
236+
return (
237+
<EditorContainer style={style}>
238+
<textarea ref={ref}/>
239+
</EditorContainer>
240+
);
241+
};

resources/scripts/components/server/files/FileEditContainer.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,15 @@ import modes from '@/modes';
1717
import useFlash from '@/plugins/useFlash';
1818
import { ServerContext } from '@/state/server';
1919

20-
const LazyAceEditor = lazy(() => import(/* webpackChunkName: "editor" */'@/components/elements/AceEditor'));
20+
const LazyCodemirrorEditor = lazy(() => import(/* webpackChunkName: "editor" */'@/components/elements/CodemirrorEditor'));
2121

2222
export default () => {
2323
const [ error, setError ] = useState('');
2424
const { action } = useParams();
2525
const [ loading, setLoading ] = useState(action === 'edit');
2626
const [ content, setContent ] = useState('');
2727
const [ modalVisible, setModalVisible ] = useState(false);
28-
const [ mode, setMode ] = useState('plain_text');
28+
const [ mode, setMode ] = useState('text/plain');
2929

3030
const history = useHistory();
3131
const { hash } = useLocation();
@@ -82,6 +82,11 @@ export default () => {
8282
);
8383
}
8484

85+
const actualModes: React.ReactChild[] = [];
86+
for (let i = 0; i < modes.length; i++) {
87+
actualModes.push(<option key={modes[i].mime} value={modes[i].mime}>{modes[i].name}</option>);
88+
}
89+
8590
return (
8691
<PageContentBlock>
8792
<FlashMessageRender byKey={'files:view'} css={tw`mb-4`}/>
@@ -108,7 +113,7 @@ export default () => {
108113
/>
109114
<div css={tw`relative`}>
110115
<SpinnerOverlay visible={loading}/>
111-
<LazyAceEditor
116+
<LazyCodemirrorEditor
112117
mode={mode}
113118
filename={hash.replace(/^#/, '')}
114119
onModeChanged={setMode}
@@ -122,9 +127,7 @@ export default () => {
122127
<div css={tw`flex justify-end mt-4`}>
123128
<div css={tw`flex-1 sm:flex-none rounded bg-neutral-900 mr-4`}>
124129
<Select value={mode} onChange={e => setMode(e.currentTarget.value)}>
125-
{Object.keys(modes).map(key => (
126-
<option key={key} value={key}>{modes[key]}</option>
127-
))}
130+
{actualModes}
128131
</Select>
129132
</div>
130133
{action === 'edit' ?

0 commit comments

Comments
 (0)