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