addon.ts
// 需要引入的插件import "codemirror/addon/search/searchcursor.js";import "codemirror/addon/search/search.js";import "codemirror/addon/scroll/annotatescrollbar.js";import "codemirror/addon/search/matchesonscrollbar.js";import "codemirror/addon/search/jump-to-line.js";import "codemirror/addon/dialog/dialog.js";import "codemirror/mode/groovy/groovy";import "codemirror/mode/javascript/javascript";import "codemirror/addon/dialog/dialog.css";import "codemirror/addon/search/matchesonscrollbar.css";/** 主题 */import "codemirror/theme/eclipse.css";
variable.less
@color: #f9fafc;@nz-code-prefix-cls: ~"nz-code";@nz-code-content-prefix-cls: ~"@{nz-code-prefix-cls}-content";@nz-code-toolbar-prefix-cls: ~"@{nz-code-prefix-cls}-toolbar";@toolbar-default-height: 32px;@default-font-size: 12px;
index.less
@import './codemirror.less';@import './toolbar.less';// @import './eclipse.less';// @import './default.less';
.toolbar.less
@import "./variable.less";.@{nz-code-toolbar-prefix-cls} {position: absolute;top: 0;right: 0;bottom: 0;left: 0;z-index: 10;display: flex;align-items: center;width: 100%;height: @toolbar-default-height;padding: 0 12px;line-height: @toolbar-default-height;background-color: @color;border: 1px solid rgba(216, 216, 216, 1);&-both {justify-content: space-between;.ant-space {justify-content: flex-end;}}&-extra {display: inline-block;}.ant-space {flex: 0 0 100px;}.anticon {cursor: pointer;}}
config.ts
export default {lineNumbers: true,styleActiveLine: true,lineWrapping: true,value: "",theme: "eclipse",foldGutter: true,readOnly: false,smartIndent: true,mode: "text/x-groovy",matchBrackets: true,autoCloseBrackets: true,showCursorWhenSelecting: true,highlightSelectionMatches: {showToken: /\w/,annotateScrollbar: true,},indentUnit: 2,tabSize: 2,autofocus: true,} as CodeMirror.EditorConfiguration;
// context.tsimport { useContext, createContext } from '@alipay/bigfish/react';import { CodeEditorContextProps } from './interface';export const CodeEditorContext = createContext<CodeEditorContextProps>({ints: null,});export const useCodeEditor = () => useContext(CodeEditorContext);
// index.tsximport React, {FC,useRef,useMemo,useState,useEffect,useCallback,} from "@alipay/bigfish/react";import { Spin } from "@alipay/bigfish/antd";import cs from "@alipay/bigfish/util/classnames";import { useFullscreen } from "ahooks";import CodeMirror from "codemirror";import { ErrorBoundary } from "@alipay/360-components";import ToolBar from "./component/ToolBar";import "./addon";import baseConfig from "./config";import { CodeEditorProps } from "./interface";import { CodeEditorContext } from "./context";import "./style/index.less";const CodeEditor: FC<CodeEditorProps> = React.memo<CodeEditorProps>((props) => {const textRef = useRef<HTMLTextAreaElement>(null);const fullRef = useRef<HTMLDivElement | null>(null);const [ints, setInts] = useState<CodeMirror.Editor | null>(null);const intsRef = useRef<CodeMirror.Editor | null>(null);const [isFullscreen, { toggleFull }] = useFullscreen(fullRef);const {style,onLoaded,toolbar,className,spinning,value,extra,options,onChange,} = props;const config = useMemo(() => {return {...baseConfig,value,};}, [value]);const onTextChange = useCallback((cm: CodeMirror.Editor, _changeObj: CodeMirror.EditorChangeLinkedList) => {const localValue = cm.getDoc().getValue();if (typeof onChange === "function" && "value" in props) {onChange(localValue);}},[onChange]);const initCodeInts = useCallback(() => {if (textRef.current) {const codeInts = CodeMirror.fromTextArea(textRef.current as HTMLTextAreaElement,config);if (typeof onLoaded === "function") {onLoaded(codeInts);}setInts(codeInts);intsRef.current = codeInts;intsRef.current.defaultTextHeight();intsRef.current.on("change", onTextChange);}}, [textRef, config]);useEffect(() => {const localValue = ints?.getDoc()?.getValue();const cursor: any = ints?.getDoc().getCursor();if (value && value !== localValue) {ints?.setValue(value);ints?.setCursor(cursor);}}, [value]);useEffect(() => {if (textRef.current) {initCodeInts();}return () => {intsRef.current?.off("change", onTextChange);};}, [textRef, intsRef]);/** options merge */useEffect(() => {if (ints &&options &&Object.prototype.toString.call(options) === "[object Object]") {(Object.keys(options) as (keyof CodeMirror.EditorConfiguration)[]).forEach((key) => {if (ints?.getOption(key) !== options[key]) {ints.setOption(key, options[key]);}});}}, [props.options, ints]);const backToTop = useCallback(() => {if (intsRef.current) {intsRef.current?.scrollTo?.(0, 0);}}, [intsRef]);const cls = useMemo(() => cs("nz-code", className), [className]);const renderToolBar = useCallback(() => {if (!toolbar && !extra) return null;if (typeof toolbar === "boolean" && toolbar) {const toolbarCls = extra? "nz-code-toolbar nz-code-toolbar-both": "nz-code-toolbar";return (<div className={toolbarCls}>{extra && <div className="nz-code-toolbar-extra">{extra}</div>}<ToolBarbackToTop={backToTop}toggleFull={toggleFull}isFullscreen={isFullscreen}/></div>);}return (<div className="nz-code-toolbar"><ToolBar toggleFull={toggleFull} isFullscreen={isFullscreen} /></div>);}, [toolbar, extra]);return (<ErrorBoundary title="代码编辑有误!"><CodeEditorContext.Provider value={{ ints }}><div className={cls} style={style} ref={fullRef}><Spin className="nz-code-content" spinning={spinning}><textarea ref={textRef} autoComplete="off" defaultValue={value} /></Spin>{renderToolBar()}{props.children}</div></CodeEditorContext.Provider></ErrorBoundary>);});CodeEditor.defaultProps = {spinning: false,toolbar: true,};export { useCodeEditor } from "./context";export default CodeEditor;
// interface.tsimport { ReactNode } from '@alipay/bigfish/react';export interface CodeEditorContextProps {ints: CodeMirror.Editor | null;}export interface RenderToolbar {(): JSX.Element;}export interface ToolbarConfig {/** 全屏 */fullScreen?: boolean;/** 切换主题 */theme?: boolean;/** 简单配置 */setting?: boolean;/** 回到顶部 */top?: boolean;}export interface CodeEditorProps {spinning?: boolean;className?: string;value?: string;style?: React.CSSProperties;/** 自定义配置 */options?: CodeMirror.EditorConfiguration;onChange?(val?: string): void;/** 初始化完成 */onLoaded?(editor: CodeMirror.Editor): void;children?: JSX.Element | JSX.Element[];toolbar?: boolean | RenderToolbar | ToolbarConfig;/** 工具栏其他辅助操作 */extra?: ReactNode;}
