mirror of
https://github.com/mickael-kerjean/filestash.git
synced 2025-11-01 19:32:27 +08:00
250 lines
9.5 KiB
JavaScript
250 lines
9.5 KiB
JavaScript
import React from "react";
|
|
import PropTypes from "prop-types";
|
|
import { withRouter } from "react-router";
|
|
import CodeMirror from "codemirror/lib/codemirror";
|
|
import "codemirror/lib/codemirror.css";
|
|
window.CodeMirror = CodeMirror;
|
|
// keybinding
|
|
import "codemirror/keymap/emacs.js";
|
|
// search
|
|
import "codemirror/addon/search/searchcursor.js";
|
|
import "codemirror/addon/search/search.js";
|
|
import "codemirror/addon/comment/comment.js";
|
|
import "codemirror/addon/dialog/dialog.js";
|
|
// code folding
|
|
import "codemirror/addon/fold/foldcode";
|
|
import "codemirror/addon/fold/foldgutter";
|
|
import "codemirror/addon/fold/foldgutter.css";
|
|
// code editing features
|
|
import "codemirror/addon/edit/matchbrackets.js";
|
|
import "codemirror/addon/edit/closebrackets.js";
|
|
import "codemirror/addon/edit/matchtags.js";
|
|
import "codemirror/addon/edit/closetag.js";
|
|
|
|
|
|
import { NgIf, Loader } from "../../components/";
|
|
import { org_shifttab } from "./editor/emacs-org";
|
|
import "./editor.scss";
|
|
|
|
export class EditorClass extends React.Component {
|
|
constructor(props) {
|
|
super(props);
|
|
this.state = {
|
|
loading: null,
|
|
editor: null,
|
|
filename: this.props.filename,
|
|
listeners: [],
|
|
};
|
|
this._refresh = this._refresh.bind(this);
|
|
this.onEdit = this.onEdit.bind(this);
|
|
}
|
|
|
|
_refresh() {
|
|
if (this.state.editor) this.state.editor.refresh();
|
|
}
|
|
|
|
componentDidMount() {
|
|
window.addEventListener("resize", this._refresh);
|
|
this.setState({ loading: null, error: false }, () => {
|
|
window.setTimeout(() => {
|
|
if (this.state.loading === null) this.setState({ loading: true });
|
|
}, 200);
|
|
});
|
|
|
|
this.loadKeybinding()
|
|
.then(() => this.loadMode(this.props.filename))
|
|
.then((res) => new Promise((done) => {
|
|
this.setState({
|
|
loading: false,
|
|
}, () => done(res));
|
|
}))
|
|
.then(loadCodeMirror.bind(this))
|
|
.then(() => {
|
|
this.props.event.subscribe((data) => {
|
|
const [type, value] = data;
|
|
if (type === "goTo") {
|
|
this.state.editor.operation((cm) => {
|
|
this.state.editor.setSelection(
|
|
{ line: value, ch: 0 },
|
|
{ line: value, ch: this.state.editor.getLine(value).length },
|
|
);
|
|
});
|
|
|
|
requestAnimationFrame(() => {
|
|
// For some reasons I ignore, codemirror would give different value for
|
|
// scroll position, depending on when you ask for it. Based on a few
|
|
// debug sessions, I found out the results to be much more accurate when
|
|
// wrapped around an async hack like that
|
|
const pY = this.state.editor.charCoords(
|
|
{ line: value, ch: 0 },
|
|
"local",
|
|
).top;
|
|
this.state.editor.operation((cm) => {
|
|
this.state.editor.scrollTo(null, pY);
|
|
this.state.editor.setSelection(
|
|
{ line: value, ch: 0 },
|
|
{ line: value, ch: this.state.editor.getLine(value).length },
|
|
);
|
|
});
|
|
});
|
|
} else if (type === "refresh") {
|
|
const cursor = this.state.editor.getCursor();
|
|
const selections = this.state.editor.listSelections();
|
|
this.state.editor.setValue(this.props.content);
|
|
this.state.editor.setCursor(cursor);
|
|
if (selections.length > 0) {
|
|
this.state.editor.setSelection(
|
|
selections[0].anchor,
|
|
selections[0].head,
|
|
);
|
|
}
|
|
} else if (type === "fold") {
|
|
this.props.onFoldChange(
|
|
org_shifttab(this.state.editor),
|
|
);
|
|
this.state.editor.refresh();
|
|
}
|
|
});
|
|
});
|
|
|
|
function loadCodeMirror(data) {
|
|
const [CodeMirror, mode] = data;
|
|
|
|
const listeners = [];
|
|
const editor = CodeMirror(document.getElementById("editor"), {
|
|
value: this.props.content,
|
|
lineNumbers: true,
|
|
mode: mode,
|
|
keyMap: ["emacs", "vim"].indexOf(CONFIG["editor"]) === -1 ?
|
|
"sublime" : CONFIG["editor"],
|
|
lineWrapping: true,
|
|
readOnly: !this.props.readonly,
|
|
foldOptions: {
|
|
widget: "...",
|
|
},
|
|
matchBrackets: {},
|
|
autoCloseBrackets: true,
|
|
matchTags: { bothTags: true },
|
|
autoCloseTags: true,
|
|
});
|
|
if (!("ontouchstart" in window)) editor.focus();
|
|
editor.getWrapperElement().setAttribute("mode", mode);
|
|
this.props.onModeChange(mode);
|
|
|
|
editor.on("change", this.onEdit);
|
|
|
|
if (mode === "orgmode") {
|
|
listeners.push(CodeMirror.orgmode.init(editor, (key, value) => {
|
|
if (key === "shifttab") {
|
|
this.props.onFoldChange(value);
|
|
}
|
|
}));
|
|
}
|
|
|
|
|
|
CodeMirror.commands.save = () => {
|
|
this.props.onSave && this.props.onSave();
|
|
};
|
|
editor.addKeyMap({
|
|
"Ctrl-X Ctrl-C": function(cm) {
|
|
window.history.back();
|
|
},
|
|
});
|
|
|
|
return new Promise((done) => {
|
|
this.setState({ editor: editor, listeners: listeners }, done);
|
|
});
|
|
}
|
|
}
|
|
|
|
onEdit(cm) {
|
|
if (this.props.onChange) {
|
|
this.props.onChange(cm.getValue());
|
|
}
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
window.removeEventListener("resize", this._refresh);
|
|
this.state.editor.off("change", this.onEdit);
|
|
this.state.editor.clearHistory();
|
|
this.state.listeners.map((fn) => fn());
|
|
}
|
|
|
|
loadMode(file) {
|
|
let ext = file.split(".").pop();
|
|
let mode = null;
|
|
|
|
// remove emacs mark when a file is opened
|
|
ext = ext
|
|
.replace(/~$/, "")
|
|
.replace(/\#$/, "");
|
|
|
|
if (ext === "org" || ext === "org_archive") mode = "orgmode";
|
|
else if (ext === "sh") mode = "shell";
|
|
else if (ext === "py") mode = "python";
|
|
else if (ext === "html" || ext === "htm") mode = "htmlmixed";
|
|
else if (ext === "css") mode = "css";
|
|
else if (ext === "less" || ext === "scss" || ext === "sass") mode = "sass";
|
|
else if (ext === "js" || ext === "json") mode = "javascript";
|
|
else if (ext === "jsx") mode = "jsx";
|
|
else if (ext === "php" || ext === "php5" || ext === "php4") mode = "php";
|
|
else if (ext === "elm") mode = "elm";
|
|
else if (ext === "erl") mode = "erlang";
|
|
else if (ext === "go") mode = "go";
|
|
else if (ext === "markdown" || ext === "md") mode = "yaml-frontmatter";
|
|
else if (ext === "pl" || ext === "pm") mode = "perl";
|
|
else if (ext === "clj") mode = "clojure";
|
|
else if (ext === "el" || ext === "lisp" || ext === "cl" ||
|
|
ext === "emacs") mode = "commonlisp";
|
|
else if (ext === "Dockerfile") mode = "dockerfile";
|
|
else if (ext === "R") mode = "r";
|
|
else if (ext === "Makefile") mode = "cmake";
|
|
else if (ext === "rb") mode = "ruby";
|
|
else if (ext === "sql") mode = "sql";
|
|
else if (ext === "xml" || ext === "rss" || ext === "svg" || ext === "atom") mode = "xml";
|
|
else if (ext === "yml" || ext === "yaml") mode = "yaml";
|
|
else if (ext === "lua") mode = "lua";
|
|
else if (ext === "csv") mode = "spreadsheet";
|
|
else if (ext === "rs" || ext === "rlib") mode = "rust";
|
|
else if (ext === "latex" || ext === "tex") mode = "stex";
|
|
else if (ext === "diff" || ext === "patch") mode = "diff";
|
|
else if (ext === "sparql") mode = "sparql";
|
|
else if (ext === "properties") mode = "properties";
|
|
else if (ext === "c" || ext === "cpp" || ext === "java" || ext === "h") mode = "clike";
|
|
else mode = "text";
|
|
|
|
return import(/* webpackChunkName: "editor" */"./editor/"+mode)
|
|
.catch(() => import("./editor/text"))
|
|
.then((module) => Promise.resolve([module.default, mode]));
|
|
}
|
|
|
|
loadKeybinding() {
|
|
if (CONFIG["editor"] === "emacs" || !CONFIG["editor"]) {
|
|
return Promise.resolve();
|
|
}
|
|
return import(/* webpackChunkName: "editor" */"./editor/keymap_"+CONFIG["editor"]);
|
|
}
|
|
|
|
render() {
|
|
return (
|
|
<div className="component_editor">
|
|
<NgIf cond={this.state.loading === true}>
|
|
<Loader/>
|
|
</NgIf>
|
|
<NgIf cond={this.state.loading === false}>
|
|
<div id="editor"></div>
|
|
</NgIf>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
EditorClass.propTypes = {
|
|
content: PropTypes.string.isRequired,
|
|
filename: PropTypes.string.isRequired,
|
|
onChange: PropTypes.func,
|
|
onSave: PropTypes.func,
|
|
};
|
|
|
|
export const Editor = withRouter(EditorClass);
|