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