pastebin/js/views/CodeMirrorEditor/index.tsx

105 lines
3.0 KiB
TypeScript

// pastebin.run
// Copyright (C) 2022 Konrad Borowski
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import { indentWithTab } from "@codemirror/commands";
import { Compartment } from "@codemirror/state";
import { keymap } from "@codemirror/view";
import { basicSetup, EditorView } from "codemirror";
import {
Accessor,
createEffect,
createUniqueId,
JSXElement,
onCleanup,
Setter
} from "solid-js";
import CodeView from "../../models/CodeView";
import { getTabIndentationSignal } from "../../options";
import "./codemirror.css";
export default function CodeMirrorEditor({
code,
setCode,
onInput,
form,
setCodeView,
setLabel,
}: {
code: Accessor<string>;
setCode: Setter<string>;
onInput(): void;
form: HTMLFormElement;
setCodeView: Setter<CodeView>;
setLabel: Setter<JSXElement>;
}) {
const [tabIndentationConfiguration] = getTabIndentationSignal();
function getTabIndentationExtension() {
return tabIndentationConfiguration() === "true"
? keymap.of([indentWithTab])
: [];
}
const tabIndentation = new Compartment();
let avoidChangeNotifications = false;
const labelId = createUniqueId();
const view = new EditorView({
doc: code(),
extensions: [
EditorView.contentAttributes.of({ "aria-labelledby": labelId }),
tabIndentation.of(getTabIndentationExtension()),
keymap.of([{ key: "Ctrl-Enter", run: () => true }]),
basicSetup,
EditorView.updateListener.of((v) => {
if (v.docChanged && !avoidChangeNotifications) {
onInput();
}
}),
EditorView.lineWrapping,
],
});
createEffect(() => {
view.dispatch({
effects: tabIndentation.reconfigure(getTabIndentationExtension()),
});
});
const getValue = () => view.state.doc.toString();
setCodeView({
get code() {
return getValue();
},
set code(code: string) {
avoidChangeNotifications = true;
view.dispatch({
changes: { from: 0, to: view.state.doc.length, insert: code },
});
avoidChangeNotifications = false;
},
});
setLabel(
<label id={labelId} onClick={() => view.focus()}>
{"Code: "}
</label>
);
const submitCallback = () => setCode(getValue());
form.addEventListener("submit", submitCallback);
onCleanup(() => {
form.removeEventListener("submit", submitCallback);
submitCallback();
view.destroy();
});
return view.dom;
}