mirror of
https://github.com/khairul169/code-share.git
synced 2025-04-29 00:59:37 +07:00
147 lines
4.0 KiB
TypeScript
147 lines
4.0 KiB
TypeScript
/* eslint-disable react/display-name */
|
|
import ReactCodeMirror, {
|
|
EditorView,
|
|
ReactCodeMirrorRef,
|
|
keymap,
|
|
} from "@uiw/react-codemirror";
|
|
import { javascript } from "@codemirror/lang-javascript";
|
|
import { css } from "@codemirror/lang-css";
|
|
import { html } from "@codemirror/lang-html";
|
|
import { json } from "@codemirror/lang-json";
|
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
import { tokyoNight } from "@uiw/codemirror-theme-tokyo-night";
|
|
import { vscodeKeymap } from "@replit/codemirror-vscode-keymap";
|
|
import prettier from "prettier/standalone";
|
|
import prettierHtmlPlugin from "prettier/plugins/html";
|
|
import prettierCssPlugin from "prettier/plugins/postcss";
|
|
import prettierBabelPlugin from "prettier/plugins/babel";
|
|
import * as prettierPluginEstree from "prettier/plugins/estree";
|
|
import { abbreviationTracker } from "@emmetio/codemirror6-plugin";
|
|
import { useDebounce } from "~/hooks/useDebounce";
|
|
import useCommandKey from "~/hooks/useCommandKey";
|
|
|
|
type Props = {
|
|
lang?: string;
|
|
value: string;
|
|
wordWrap?: boolean;
|
|
onChange: (val: string) => void;
|
|
formatOnSave?: boolean;
|
|
};
|
|
|
|
const CodeEditor = (props: Props) => {
|
|
const codeMirror = useRef<ReactCodeMirrorRef>(null);
|
|
const { lang, value, formatOnSave, wordWrap, onChange } = props;
|
|
const [data, setData] = useState(value);
|
|
const [debounceChange, resetDebounceChange] = useDebounce(onChange, 3000);
|
|
const langMetadata = useMemo(() => getLangMetadata(lang || "plain"), [lang]);
|
|
|
|
const onSave = useCallback(async () => {
|
|
try {
|
|
const cm = codeMirror.current?.view;
|
|
const content = cm ? cm.state.doc.toString() : data;
|
|
const formatter = langMetadata.formatter;
|
|
|
|
if (formatOnSave && cm && formatter != null) {
|
|
const [parser, ...plugins] = formatter;
|
|
const cursor = cm.state.selection.main.head || 0;
|
|
const { formatted, cursorOffset } = await prettier.formatWithCursor(
|
|
content,
|
|
{
|
|
parser,
|
|
plugins,
|
|
cursorOffset: cursor,
|
|
printWidth: 64,
|
|
}
|
|
);
|
|
|
|
setData(formatted);
|
|
onChange(formatted);
|
|
|
|
if (cm) {
|
|
cm.dispatch({
|
|
changes: { from: 0, to: cm?.state.doc.length, insert: formatted },
|
|
});
|
|
cm.dispatch({
|
|
selection: { anchor: cursorOffset },
|
|
});
|
|
}
|
|
} else {
|
|
onChange(content);
|
|
}
|
|
} catch (err) {
|
|
console.log("prettier error", err);
|
|
}
|
|
|
|
setTimeout(() => resetDebounceChange(), 100);
|
|
}, [
|
|
data,
|
|
langMetadata.formatter,
|
|
formatOnSave,
|
|
onChange,
|
|
resetDebounceChange,
|
|
]);
|
|
|
|
useCommandKey("s", onSave);
|
|
|
|
useEffect(() => {
|
|
setData(value);
|
|
}, [value]);
|
|
|
|
const extensions = [...langMetadata.extensions, keymap.of(vscodeKeymap)];
|
|
if (wordWrap) {
|
|
extensions.push(EditorView.lineWrapping);
|
|
}
|
|
|
|
return (
|
|
<ReactCodeMirror
|
|
ref={codeMirror}
|
|
extensions={extensions}
|
|
indentWithTab={false}
|
|
basicSetup={{ defaultKeymap: false }}
|
|
value={data}
|
|
onChange={(val) => {
|
|
setData(val);
|
|
debounceChange(val);
|
|
}}
|
|
height="100%"
|
|
theme={tokyoNight}
|
|
/>
|
|
);
|
|
};
|
|
|
|
function getLangMetadata(lang: string) {
|
|
let extensions: any[] = [];
|
|
let formatter: any = null;
|
|
|
|
switch (lang) {
|
|
case "html":
|
|
extensions = [html({ selfClosingTags: true }), abbreviationTracker()];
|
|
formatter = ["html", prettierHtmlPlugin];
|
|
break;
|
|
case "css":
|
|
extensions = [css()];
|
|
formatter = ["css", prettierCssPlugin];
|
|
break;
|
|
case "json":
|
|
extensions = [json()];
|
|
formatter = ["json", prettierBabelPlugin, prettierPluginEstree];
|
|
break;
|
|
case "jsx":
|
|
case "js":
|
|
case "ts":
|
|
case "tsx":
|
|
extensions = [
|
|
javascript({
|
|
jsx: ["jsx", "tsx"].includes(lang),
|
|
typescript: ["tsx", "ts"].includes(lang),
|
|
}),
|
|
];
|
|
formatter = ["babel", prettierBabelPlugin, prettierPluginEstree];
|
|
break;
|
|
}
|
|
|
|
return { extensions, formatter };
|
|
}
|
|
|
|
export default CodeEditor;
|