mirror of
https://github.com/home-assistant/frontend.git
synced 2025-10-23 10:39:47 +00:00
Migrate to codemirror 6 (#8382)
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { Editor } from "codemirror";
|
||||
import type { StreamLanguage } from "@codemirror/stream-parser";
|
||||
import type { EditorView, KeyBinding, ViewUpdate } from "@codemirror/view";
|
||||
import {
|
||||
customElement,
|
||||
internalProperty,
|
||||
@@ -15,32 +16,40 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
const modeTag = Symbol("mode");
|
||||
|
||||
const readOnlyTag = Symbol("readOnly");
|
||||
|
||||
const saveKeyBinding: KeyBinding = {
|
||||
key: "Mod-s",
|
||||
run: (view: EditorView) => {
|
||||
fireEvent(view.dom, "editor-save");
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
@customElement("ha-code-editor")
|
||||
export class HaCodeEditor extends UpdatingElement {
|
||||
public codemirror?: Editor;
|
||||
public codemirror?: EditorView;
|
||||
|
||||
@property() public mode?: string;
|
||||
@property() public mode = "yaml";
|
||||
|
||||
@property({ type: Boolean }) public autofocus = false;
|
||||
|
||||
@property({ type: Boolean }) public readOnly = false;
|
||||
|
||||
@property() public rtl = false;
|
||||
|
||||
@property() public error = false;
|
||||
|
||||
@internalProperty() private _value = "";
|
||||
|
||||
@internalProperty() private _langs?: Record<string, StreamLanguage<unknown>>;
|
||||
|
||||
public set value(value: string) {
|
||||
this._value = value;
|
||||
}
|
||||
|
||||
public get value(): string {
|
||||
return this.codemirror ? this.codemirror.getValue() : this._value;
|
||||
}
|
||||
|
||||
public get hasComments(): boolean {
|
||||
return !!this.shadowRoot!.querySelector("span.cm-comment");
|
||||
return this.codemirror ? this.codemirror.state.doc.toString() : this._value;
|
||||
}
|
||||
|
||||
public connectedCallback() {
|
||||
@@ -48,7 +57,6 @@ export class HaCodeEditor extends UpdatingElement {
|
||||
if (!this.codemirror) {
|
||||
return;
|
||||
}
|
||||
this.codemirror.refresh();
|
||||
if (this.autofocus !== false) {
|
||||
this.codemirror.focus();
|
||||
}
|
||||
@@ -62,17 +70,27 @@ export class HaCodeEditor extends UpdatingElement {
|
||||
}
|
||||
|
||||
if (changedProps.has("mode")) {
|
||||
this.codemirror.setOption("mode", this.mode);
|
||||
this.codemirror.dispatch({
|
||||
reconfigure: {
|
||||
[modeTag]: this._mode,
|
||||
},
|
||||
});
|
||||
}
|
||||
if (changedProps.has("autofocus")) {
|
||||
this.codemirror.setOption("autofocus", this.autofocus !== false);
|
||||
if (changedProps.has("readOnly")) {
|
||||
this.codemirror.dispatch({
|
||||
reconfigure: {
|
||||
[readOnlyTag]: !this.readOnly,
|
||||
},
|
||||
});
|
||||
}
|
||||
if (changedProps.has("_value") && this._value !== this.value) {
|
||||
this.codemirror.setValue(this._value);
|
||||
}
|
||||
if (changedProps.has("rtl")) {
|
||||
this.codemirror.setOption("gutters", this._calcGutters());
|
||||
this._setScrollBarDirection();
|
||||
this.codemirror.dispatch({
|
||||
changes: {
|
||||
from: 0,
|
||||
to: this.codemirror.state.doc.length,
|
||||
insert: this._value,
|
||||
},
|
||||
});
|
||||
}
|
||||
if (changedProps.has("error")) {
|
||||
this.classList.toggle("error-state", this.error);
|
||||
@@ -85,159 +103,62 @@ export class HaCodeEditor extends UpdatingElement {
|
||||
this._load();
|
||||
}
|
||||
|
||||
private get _mode() {
|
||||
return this._langs![this.mode];
|
||||
}
|
||||
|
||||
private async _load(): Promise<void> {
|
||||
const loaded = await loadCodeMirror();
|
||||
|
||||
const codeMirror = loaded.codeMirror;
|
||||
this._langs = loaded.langs;
|
||||
|
||||
const shadowRoot = this.attachShadow({ mode: "open" });
|
||||
|
||||
shadowRoot!.innerHTML = `
|
||||
<style>
|
||||
${loaded.codeMirrorCss}
|
||||
.CodeMirror {
|
||||
height: var(--code-mirror-height, auto);
|
||||
direction: var(--code-mirror-direction, ltr);
|
||||
font-family: var(--code-font-family, monospace);
|
||||
}
|
||||
.CodeMirror-scroll {
|
||||
max-height: var(--code-mirror-max-height, --code-mirror-height);
|
||||
}
|
||||
:host(.error-state) .CodeMirror-gutters {
|
||||
shadowRoot!.innerHTML = `<style>
|
||||
:host(.error-state) div.cm-wrap .cm-gutters {
|
||||
border-color: var(--error-state-color, red);
|
||||
}
|
||||
.CodeMirror-focused .CodeMirror-gutters {
|
||||
border-right: 2px solid var(--paper-input-container-focus-color, var(--primary-color));
|
||||
}
|
||||
.CodeMirror-linenumber {
|
||||
color: var(--paper-dialog-color, var(--secondary-text-color));
|
||||
}
|
||||
.rtl .CodeMirror-vscrollbar {
|
||||
right: auto;
|
||||
left: 0px;
|
||||
}
|
||||
.rtl-gutter {
|
||||
width: 20px;
|
||||
}
|
||||
.CodeMirror-gutters {
|
||||
border-right: 1px solid var(--paper-input-container-color, var(--secondary-text-color));
|
||||
background-color: var(--paper-dialog-background-color, var(--primary-background-color));
|
||||
transition: 0.2s ease border-right;
|
||||
}
|
||||
.cm-s-default.CodeMirror {
|
||||
background-color: var(--code-editor-background-color, var(--card-background-color));
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.cm-s-default .CodeMirror-cursor {
|
||||
border-left: 1px solid var(--secondary-text-color);
|
||||
}
|
||||
|
||||
.cm-s-default div.CodeMirror-selected, .cm-s-default.CodeMirror-focused div.CodeMirror-selected {
|
||||
background: rgba(var(--rgb-primary-color), 0.2);
|
||||
}
|
||||
|
||||
.cm-s-default .CodeMirror-line::selection,
|
||||
.cm-s-default .CodeMirror-line>span::selection,
|
||||
.cm-s-default .CodeMirror-line>span>span::selection {
|
||||
background: rgba(var(--rgb-primary-color), 0.2);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-keyword {
|
||||
color: var(--codemirror-keyword, #6262FF);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-operator {
|
||||
color: var(--codemirror-operator, #cda869);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-variable-2 {
|
||||
color: var(--codemirror-variable-2, #690);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-builtin {
|
||||
color: var(--codemirror-builtin, #9B7536);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-atom {
|
||||
color: var(--codemirror-atom, #F90);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-number {
|
||||
color: var(--codemirror-number, #ca7841);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-def {
|
||||
color: var(--codemirror-def, #8DA6CE);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-string {
|
||||
color: var(--codemirror-string, #07a);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-string-2 {
|
||||
color: var(--codemirror-string-2, #bd6b18);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-comment {
|
||||
color: var(--codemirror-comment, #777);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-variable {
|
||||
color: var(--codemirror-variable, #07a);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-tag {
|
||||
color: var(--codemirror-tag, #997643);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-meta {
|
||||
color: var(--codemirror-meta, var(--primary-text-color));
|
||||
}
|
||||
|
||||
.cm-s-default .cm-attribute {
|
||||
color: var(--codemirror-attribute, #d6bb6d);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-property {
|
||||
color: var(--codemirror-property, #905);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-qualifier {
|
||||
color: var(--codemirror-qualifier, #690);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-variable-3 {
|
||||
color: var(--codemirror-variable-3, #07a);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-type {
|
||||
color: var(--codemirror-type, #07a);
|
||||
}
|
||||
</style>`;
|
||||
|
||||
this.codemirror = codeMirror(shadowRoot, {
|
||||
value: this._value,
|
||||
lineNumbers: true,
|
||||
tabSize: 2,
|
||||
mode: this.mode,
|
||||
autofocus: this.autofocus !== false,
|
||||
viewportMargin: Infinity,
|
||||
readOnly: this.readOnly,
|
||||
extraKeys: {
|
||||
Tab: "indentMore",
|
||||
"Shift-Tab": "indentLess",
|
||||
},
|
||||
gutters: this._calcGutters(),
|
||||
const container = document.createElement("span");
|
||||
|
||||
shadowRoot.appendChild(container);
|
||||
|
||||
this.codemirror = new loaded.EditorView({
|
||||
state: loaded.EditorState.create({
|
||||
doc: this._value,
|
||||
extensions: [
|
||||
loaded.lineNumbers(),
|
||||
loaded.keymap.of([
|
||||
...loaded.defaultKeymap,
|
||||
loaded.defaultTabBinding,
|
||||
saveKeyBinding,
|
||||
]),
|
||||
loaded.tagExtension(modeTag, this._mode),
|
||||
loaded.theme,
|
||||
loaded.Prec.fallback(loaded.highlightStyle),
|
||||
loaded.EditorView.updateListener.of((update) =>
|
||||
this._onUpdate(update)
|
||||
),
|
||||
loaded.tagExtension(
|
||||
readOnlyTag,
|
||||
loaded.EditorView.editable.of(!this.readOnly)
|
||||
),
|
||||
],
|
||||
}),
|
||||
root: shadowRoot,
|
||||
parent: container,
|
||||
});
|
||||
this._setScrollBarDirection();
|
||||
this.codemirror!.on("changes", () => this._onChange());
|
||||
}
|
||||
|
||||
private _blockKeyboardShortcuts() {
|
||||
this.addEventListener("keydown", (ev) => ev.stopPropagation());
|
||||
}
|
||||
|
||||
private _onChange(): void {
|
||||
private _onUpdate(update: ViewUpdate): void {
|
||||
if (!update.docChanged) {
|
||||
return;
|
||||
}
|
||||
const newValue = this.value;
|
||||
if (newValue === this._value) {
|
||||
return;
|
||||
@@ -245,16 +166,6 @@ export class HaCodeEditor extends UpdatingElement {
|
||||
this._value = newValue;
|
||||
fireEvent(this, "value-changed", { value: this._value });
|
||||
}
|
||||
|
||||
private _calcGutters(): string[] {
|
||||
return this.rtl ? ["rtl-gutter", "CodeMirror-linenumbers"] : [];
|
||||
}
|
||||
|
||||
private _setScrollBarDirection(): void {
|
||||
if (this.codemirror) {
|
||||
this.codemirror.getWrapperElement().classList.toggle("rtl", this.rtl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
Reference in New Issue
Block a user