220 lines
5.5 KiB
JavaScript
220 lines
5.5 KiB
JavaScript
frappe.ui.form.ControlCode = class ControlCode extends frappe.ui.form.ControlText {
|
|
make_input() {
|
|
if (this.editor) return;
|
|
this.load_lib().then(() => this.make_ace_editor());
|
|
}
|
|
|
|
make_ace_editor() {
|
|
if (this.editor) return;
|
|
this.ace_editor_target = $('<div class="ace-editor-target"></div>').appendTo(
|
|
this.input_area
|
|
);
|
|
|
|
// styling
|
|
this.ace_editor_target.addClass("border rounded");
|
|
this.ace_editor_target.css("height", 300);
|
|
|
|
if (this.df.max_height) {
|
|
this.ace_editor_target.css("max-height", this.df.max_height);
|
|
}
|
|
|
|
// initialize
|
|
const ace = window.ace;
|
|
this.editor = ace.edit(this.ace_editor_target.get(0));
|
|
|
|
if (this.df.max_lines || this.df.min_lines || this.df.max_height) {
|
|
if (this.df.max_lines) this.editor.setOption("maxLines", this.df.max_lines);
|
|
if (this.df.min_lines) this.editor.setOption("minLines", this.df.min_lines);
|
|
} else {
|
|
this.expanded = false;
|
|
this.$expand_button = $(
|
|
`<button class="btn btn-xs btn-default">${this.get_button_label()}</button>`
|
|
)
|
|
.click(() => {
|
|
this.expanded = !this.expanded;
|
|
this.refresh_height();
|
|
this.toggle_label();
|
|
})
|
|
.appendTo(this.$input_wrapper);
|
|
}
|
|
|
|
if (this.disabled) {
|
|
this.editor.setReadOnly(true);
|
|
$(this.ace_editor_target).css("pointer-events", "none");
|
|
}
|
|
|
|
this.editor.setTheme("ace/theme/tomorrow");
|
|
this.editor.setOption("showPrintMargin", false);
|
|
this.editor.setOption("wrap", this.df.wrap);
|
|
this.set_language();
|
|
this.is_setting_content = false;
|
|
|
|
// events
|
|
this.editor.session.on("change", () => {
|
|
if (this.is_setting_content) return;
|
|
change_content();
|
|
});
|
|
|
|
let change_content = frappe.utils.debounce(() => {
|
|
const input_value = this.get_input_value();
|
|
this.parse_validate_and_set_in_model(input_value);
|
|
}, 300);
|
|
|
|
// setup autocompletion when it is set the first time
|
|
Object.defineProperty(this.df, "autocompletions", {
|
|
configurable: true,
|
|
get() {
|
|
return this._autocompletions || [];
|
|
},
|
|
set: (value) => {
|
|
let getter = value;
|
|
if (typeof getter !== "function") {
|
|
getter = () => value;
|
|
}
|
|
if (!this._autocompletions) {
|
|
this._autocompletions = [];
|
|
}
|
|
this._autocompletions.push(getter);
|
|
this.setup_autocompletion();
|
|
},
|
|
});
|
|
}
|
|
|
|
setup_autocompletion(customGetCompletions) {
|
|
if (this._autocompletion_setup) return;
|
|
|
|
const ace = window.ace;
|
|
|
|
let getCompletions = (editor, session, pos, prefix, callback) => {
|
|
if (prefix.length === 0) {
|
|
callback(null, []);
|
|
return;
|
|
}
|
|
const get_autocompletions = () => {
|
|
let getters = this._autocompletions || [];
|
|
let completions = [];
|
|
for (let getter of getters) {
|
|
let values = getter({ editor, session, pos, prefix });
|
|
completions.push(...values);
|
|
}
|
|
return completions;
|
|
};
|
|
let autocompletions = get_autocompletions();
|
|
if (autocompletions.length) {
|
|
callback(
|
|
null,
|
|
autocompletions.map((a) => {
|
|
if (typeof a === "string") {
|
|
a = { value: a };
|
|
}
|
|
return {
|
|
name: "frappe",
|
|
value: a.value,
|
|
score: a.score,
|
|
meta: a.meta,
|
|
caption: a.caption,
|
|
};
|
|
})
|
|
);
|
|
}
|
|
};
|
|
|
|
ace.config.loadModule("ace/ext/language_tools", (langTools) => {
|
|
this.editor.setOptions({
|
|
enableBasicAutocompletion: true,
|
|
enableLiveAutocompletion: true,
|
|
});
|
|
|
|
langTools.addCompleter({
|
|
getCompletions: customGetCompletions || getCompletions,
|
|
});
|
|
});
|
|
this._autocompletion_setup = true;
|
|
}
|
|
|
|
refresh_height() {
|
|
this.ace_editor_target.css("height", this.expanded ? 600 : 300);
|
|
this.editor.resize();
|
|
}
|
|
|
|
toggle_label() {
|
|
this.$expand_button && this.$expand_button.text(this.get_button_label());
|
|
}
|
|
|
|
get_button_label() {
|
|
return this.expanded
|
|
? __("Collapse", null, "Shrink code field.")
|
|
: __("Expand", null, "Enlarge code field.");
|
|
}
|
|
|
|
set_language() {
|
|
const language_map = {
|
|
Javascript: "ace/mode/javascript",
|
|
JS: "ace/mode/javascript",
|
|
Python: "ace/mode/python",
|
|
Py: "ace/mode/python",
|
|
HTML: "ace/mode/html",
|
|
CSS: "ace/mode/css",
|
|
Markdown: "ace/mode/markdown",
|
|
SCSS: "ace/mode/scss",
|
|
JSON: "ace/mode/json",
|
|
Golang: "ace/mode/golang",
|
|
Go: "ace/mode/golang",
|
|
};
|
|
const language = this.df.options;
|
|
|
|
const valid_languages = Object.keys(language_map);
|
|
if (language && !valid_languages.includes(language)) {
|
|
console.warn(
|
|
`Invalid language option provided for field "${
|
|
this.df.label
|
|
}". Valid options are ${valid_languages.join(", ")}.`
|
|
);
|
|
}
|
|
|
|
const ace_language_mode = language_map[language] || "";
|
|
this.editor.session.setMode(ace_language_mode);
|
|
this.editor.setKeyboardHandler("ace/keyboard/vscode");
|
|
}
|
|
|
|
parse(value) {
|
|
if (value == null) {
|
|
value = "";
|
|
}
|
|
return value;
|
|
}
|
|
|
|
set_formatted_input(value) {
|
|
return this.load_lib().then(() => {
|
|
if (!this.editor) return;
|
|
if (!value) value = "";
|
|
if (value === this.get_input_value()) return;
|
|
this.is_setting_content = true;
|
|
this.editor.session.setValue(value);
|
|
this.is_setting_content = false;
|
|
});
|
|
}
|
|
|
|
get_input_value() {
|
|
return this.editor ? this.editor.session.getValue() : "";
|
|
}
|
|
|
|
load_lib() {
|
|
if (this.library_loaded) return this.library_loaded;
|
|
|
|
if (frappe.boot.developer_mode) {
|
|
this.root_lib_path = "/assets/frappe/node_modules/ace-builds/src-noconflict/";
|
|
} else {
|
|
this.root_lib_path = "/assets/frappe/node_modules/ace-builds/src-min-noconflict/";
|
|
}
|
|
|
|
this.library_loaded = new Promise((resolve) => {
|
|
frappe.require(this.root_lib_path + "ace.js", () => {
|
|
window.ace.config.set("basePath", this.root_lib_path);
|
|
resolve();
|
|
});
|
|
});
|
|
|
|
return this.library_loaded;
|
|
}
|
|
};
|