antosdk-apps/Antedit/build/debug/main.js

2337 lines
87 KiB
JavaScript
Raw Normal View History

2022-07-04 19:31:17 +02:00
var OS;
(function (OS) {
let API;
(function (API) {
let VFS;
(function (VFS) {
class DiffEditorFileHandle extends OS.API.VFS.BaseFileHandle {
constructor(files) {
super("");
this.path = `${files[0].path} -> ${files[1].path}`;
this.cache = files;
this.basename = `${files[0].basename} -> ${files[1].basename}`;
this.info = {
type: "file",
mime: undefined,
size: 0,
name: this.basename,
path: this.path
};
this.ready = true;
}
meta() {
return new Promise(async (resolve, reject) => {
try {
await Promise.all([this.cache[0].meta(), this.cache[1].meta]);
resolve({
result: this.info,
error: false,
});
}
catch (e) {
reject(e);
}
});
}
_rd(_t) {
return new Promise(async (resolve, reject) => {
try {
this.cache[0].cache = await this.cache[0].read();
this.cache[1].cache = await this.cache[1].read();
resolve(this.cache);
}
catch (e) {
reject(e);
}
});
}
_wr(t, d) {
this.cache = d;
return new Promise((resolve, reject) => {
resolve({
result: true,
error: false,
});
});
}
setPath(s) {
// do nothing
}
}
VFS.DiffEditorFileHandle = DiffEditorFileHandle;
})(VFS = API.VFS || (API.VFS = {}));
})(API = OS.API || (OS.API = {}));
let application;
(function (application) {
class BaseEditorModel {
/**
* Editor mutex
*
* @private
* @type {boolean}
* @memberof BaseEditorModel
*/
//private editormux: boolean;
/**
* Creates an instance of BaseEditorModel.
*
* @param {Antedit} app parent app
* @param {GUI.tag.TabBarTag} tabbar tabbar DOM element
* @param {HTMLElement} editorarea editor container DOM element
* @memberof BaseEditorModel
*/
constructor(app, tabbar, editorarea) {
this.container = editorarea;
this.currfile = "Untitled".asFileHandle();
this.tabbar = tabbar;
this.editorSetup(editorarea);
this.app = app;
// this.editormux = false;
this.onstatuschange = undefined;
this.on("focus", () => {
if (this.onstatuschange)
this.onstatuschange(this.getEditorStatus());
});
this.on("input", () => {
// console.log(this.editormux, this.currfile.dirty);
/*if (this.editormux) {
this.editormux = false;
console.log("set editor mux to false");
return false;
}*/
if (!this.currfile.dirty) {
console.log("dirty", this.currfile.path);
this.currfile.dirty = true;
this.currfile.text += "*";
return this.tabbar.update(undefined);
}
});
this.on("changeCursor", () => {
if (this.onstatuschange)
this.onstatuschange(this.getEditorStatus());
});
this.tabbar.ontabselect = (e) => {
return this.selecteTab($(e.data.item).index());
};
this.tabbar.ontabclose = (e) => {
const it = e.data.item;
if (!it) {
return false;
}
if (!it.data.dirty) {
return this.closeTab(it);
}
this.app.openDialog("YesNoDialog", {
title: __("Close tab"),
text: __("Close without saving ?"),
}).then((d) => {
if (d) {
return this.closeTab(it);
}
return this.focus();
});
return false;
};
}
/**
* Find a tab on the tabbar corresponding to a file handle
*
* @private
* @param {EditorFileHandle} file then file handle to search
* @returns {number}
* @memberof BaseEditorModel
*/
findTabByFile(file) {
const lst = this.tabbar.items;
const its = (() => {
const result = [];
for (let i = 0; i < lst.length; i++) {
const d = lst[i];
if (d.hash() === file.hash()) {
result.push(i);
}
}
return result;
})();
if (its.length === 0) {
return -1;
}
return its[0];
}
/**
* Create new tab when opening a file
*
* @private
* @param {EditorFileHandle} file
* @memberof BaseEditorModel
*/
newTab(file) {
file.text = file.basename ? file.basename : file.path;
if (!file.cache) {
file.cache = "";
}
file.textModel = this.newTextModelFrom(file);
this.currfile.selected = false;
file.selected = true;
//console.log cnt
this.tabbar.push(file);
}
/**
* Close a tab when a file is closed
*
* @param {GUI.tag.ListViewItemTag} it reference to the tab to close
* @returns {boolean}
* @memberof BaseEditorModel
*/
closeTab(it) {
this.tabbar.delete(it);
const cnt = this.tabbar.items.length;
if (cnt === 0) {
this.openFile("Untitled".asFileHandle());
return false;
}
this.tabbar.selected = cnt - 1;
return false;
}
/**
* Select a tab by its index
*
* @private
* @param {number} i tab index
* @returns {void}
* @memberof BaseEditorModel
*/
selecteTab(i) {
//return if i is @tabbar.get "selidx"
const file = this.tabbar.items[i];
if (!file) {
return;
}
//return if file is @currfile
if (this.currfile !== file) {
this.currfile.textModel = this.getTexModel();
this.currfile.selected = false;
this.currfile = file;
}
// this.editormux = true;
this.setTextModel(file.textModel);
this.focus();
}
/**
* Select an opened file, this will select the corresponding tab
*
* @param {(EditorFileHandle | string)} file
* @memberof BaseEditorModel
*/
selectFile(file) {
const i = this.findTabByFile(file.asFileHandle());
if (i !== -1) {
this.tabbar.selected = i;
}
}
/**
* Open a file in new tab. If the file is already opened,
* the just select the tab
*
*
* @param {EditorFileHandle} file file to open
* @returns {void}
* @memberof BaseEditorModel
*/
openFile(file) {
//find tab
const i = this.findTabByFile(file);
if (i !== -1) {
this.tabbar.selected = i;
return;
}
if (file.path.toString() === "Untitled") {
this.newTab(file);
return;
}
file.read()
.then((d) => {
file.cache = d || "";
return this.newTab(file);
})
.catch((e) => {
return this.app.error(__("Unable to open: {0}", file.path), e);
});
}
/**
* write a file
*
* @private
* @param {EditorFileHandle} file
* @memberof BaseEditorModel
*/
write() {
this.currfile.cache = this.getValue();
this.currfile.write("text/plain")
.then((d) => {
this.currfile.dirty = false;
this.currfile.text = this.currfile.basename;
this.tabbar.update(undefined);
})
.catch((e) => this.app.error(__("Unable to save file: {0}", this.currfile.path), e));
}
/**
* Save the current opened file
*
* @return {*} {void}
* @memberof BaseEditorModel
*/
save() {
this.currfile.cache = this.getValue();
if (this.currfile.basename) {
return this.write();
}
return this.saveAs();
}
/**
* Save the current file as another file
*
* @public
* @memberof BaseEditorModel
*/
saveAs() {
this.app.openDialog("FileDialog", {
title: __("Save as"),
file: this.currfile,
}).then((f) => {
let d = f.file.path.asFileHandle();
if (f.file.type === "file") {
d = d.parent();
}
this.currfile.setPath(`${d.path}/${f.name}`);
this.write();
});
}
/**
* Get all dirty file handles in the editor
*
* @return {*} {EditorFileHandle[]}
* @memberof BaseEditorModel
*/
dirties() {
const result = [];
for (let v of Array.from(this.tabbar.items)) {
if (v.dirty) {
result.push(v);
}
}
return result;
}
/**
* Context menu handle for the editor
*
* @memberof BaseEditorModel
*/
set contextmenuHandle(cb) {
this.container.contextmenuHandle = cb;
}
/**
* Close all opened files
*
* @memberof BaseEditorModel
*/
closeAll() {
this.tabbar.items = [];
this.openFile("Untitled".asFileHandle());
this.resetEditor();
}
/**
* Check whether the editor is dirty
*
* @return {*} {boolean}
* @memberof BaseEditorModel
*/
isDirty() {
return this.dirties().length > 0;
}
/**
* Set model tabbar menu context event
*
* @memberof BaseEditorModel
*/
setTabbarCtxMenu(items, handle) {
this.tabbar.contextmenuHandle = (evt, m) => {
m.items = items;
m.onmenuselect = (e) => {
if (handle) {
/**
* get the tab under the cursor
*/
const tab = $(evt.target).closest("afx-list-item");
handle(tab[0], e.data.item.data);
}
};
return m.show(evt);
};
}
}
application.BaseEditorModel = BaseEditorModel;
})(application = OS.application || (OS.application = {}));
})(OS || (OS = {}));
var OS;
(function (OS) {
//declare var monaco: any;
let application;
(function (application) {
/**
* Wrapper model for the ACE text editor
*
* @export
* @class MonacoEditorModel
* @extends {BaseEditorModel}
*/
class MonacoEditorModel extends OS.application.BaseEditorModel {
/**
* Creates an instance of MonacoEditorModel.
* @param {MonacoEditorModel} app MonacoEditorModel instance
* @param {GUI.tag.TabBarTag} tabbar tabbar element
* @param {HTMLElement} editorarea main editor container element
* @memberof MonacoEditorModel
*/
constructor(app, tabbar, editorarea) {
super(app, tabbar, editorarea);
}
/**
* Reset the editor
*
* @protected
* @memberof MonacoEditorModel
*/
resetEditor() {
}
/**
* Get a text model from the current editor session
*
* @protected
* @return {*}
* @memberof MonacoEditorModel
*/
getTexModel() {
return {
model: this.editor.getModel(),
position: this.editor.getPosition()
};
}
/**
* Set text model to current editor session
*
* @protected
* @param {*} model
* @memberof MonacoEditorModel
*/
setTextModel(model) {
this.editor.setModel(model.model);
if (model.position) {
this.editor.setPosition(model.position);
this.editor.revealLineInCenter(model.position.lineNumber);
}
}
/**
* Create new editor model from file
*
* @protected
* @param {EditorFileHandle} file
* @return {*} {*}
* @memberof MonacoEditorModel
*/
newTextModelFrom(file) {
if (Array.isArray(file.cache)) {
return {
model: {
original: this.newTextModelFrom(file.cache[0]).model,
modified: this.newTextModelFrom(file.cache[1]).model
}
};
}
else {
if (file.path.toString() === "Untitled") {
return {
model: monaco.editor.createModel(file.cache, "textplain")
};
}
const uri = monaco.Uri.parse(file.protocol + "://antedit/file/" + file.genealogy.join("/"));
const model = monaco.editor.getModel(uri);
if (model) {
model.setValue(file.cache);
return { model: model };
}
return {
model: monaco.editor.createModel(file.cache, undefined, uri)
};
}
}
/**
* Get language modes
*
* @return {*} {GenericObject<any>[]}
* @memberof MonacoEditorModel
*/
getModes() {
//const list = [];
//return list;
return monaco.languages.getLanguages().map(e => {
const item = e;
if (e.aliases)
item.text = e.aliases[0];
else
item.text = e.id;
return e;
});
}
/**
* Set the editor theme
*
* @param {string} theme theme name
* @memberof MonacoEditorModel
*/
setTheme(theme) {
}
/**
* Set editor language mode
*
* The mode object should be in the following format:
* ```ts
* {
* text: string,
* mode: string
* }
* ```
*
* @param {GenericObject<any>} m language mode object
* @memberof MonacoEditorModel
*/
setMode(m) {
if (this.editor == this._code_editor) {
monaco.editor.setModelLanguage(this.editor.getModel(), m.id);
}
else {
for (const model of this.editor.getModel()) {
monaco.editor.setModelLanguage(model, m.id);
}
}
if (this.onstatuschange)
this.onstatuschange(this.getEditorStatus());
}
/**
* Getter get current editor instance based on current file
*
* @private
* @type {GenericObject<any>}
* @memberof MonacoEditorModel
*/
get editor() {
if (Array.isArray(this.currfile.cache)) {
return this._diff_editor;
}
return this._code_editor;
}
/**
* Setup the editor
*
* @protected
* @param {HTMLElement} el editor container DOM
* @memberof MonacoEditorModel
*/
editorSetup(el) {
// create two editor instancs for code mode and diff mode
this.code_container = $("<div />")
.css("width", "100%")
.css("height", "100%");
this.diff_container = $("<div />")
.css("width", "100%")
.css("height", "100%")
.css("display", "none");
$(el).append(this.code_container);
$(el).append(this.diff_container);
this._code_editor = monaco.editor.create(this.code_container[0], {
value: "",
language: 'textplain'
});
this._diff_editor = monaco.editor.createDiffEditor(this.diff_container[0], {
readOnly: true
});
if (!MonacoEditorModel.modes) {
MonacoEditorModel.modes = {};
monaco.languages.getLanguages().forEach((el) => {
MonacoEditorModel.modes[el.id] = el;
});
}
}
/**
* Register to editor event
*
* @param {string} evt_str event name
* @param {() => void} callback callback function
* @memberof MonacoEditorModel
*/
on(evt_str, callback) {
switch (evt_str) {
case "input":
this._code_editor.onDidChangeModelContent(callback);
break;
case "focus":
this._code_editor.onDidFocusEditorText(callback);
this._diff_editor.getOriginalEditor().onDidFocusEditorText(callback);
this._diff_editor.getModifiedEditor().onDidFocusEditorText(callback);
break;
case "changeCursor":
this._code_editor.onDidChangeCursorPosition(callback);
this._diff_editor.getOriginalEditor().onDidChangeCursorPosition(callback);
this._diff_editor.getModifiedEditor().onDidChangeCursorPosition(callback);
break;
default:
break;
}
}
/**
* Resize the editor
*
* @memberof MonacoEditorModel
*/
resize() {
if (this.editor)
this.editor.layout();
}
/**
* Focus on the editor
*
* @memberof MonacoEditorModel
*/
focus() {
if (Array.isArray(this.currfile.cache)) {
this.code_container.hide();
this.diff_container.show();
}
else {
this.code_container.show();
this.diff_container.hide();
}
if (this.editor) {
this.editor.layout();
this.editor.focus();
}
}
/**
* Get language mode from path
*
* @protected
* @param {string} path
* @return {*} {GenericObject<any>}
* @memberof MonacoEditorModel
*/
getModeForPath(path) {
return {};
}
/**
* Get the editor status
*
* @return {*} {GenericObject<any>}
* @memberof MonacoEditorModel
*/
getEditorStatus() {
let ed = undefined;
if (this.editor == this._code_editor) {
ed = this.editor;
}
else {
ed = this.editor.getOriginalEditor();
if (this.editor.getModifiedEditor().hasTextFocus()) {
ed = this.editor.getModifiedEditor();
}
}
const pos = ed.getPosition();
let mode = undefined;
const model = ed.getModel();
if (model) {
mode = MonacoEditorModel.modes[model.getLanguageId()];
}
return {
row: pos.lineNumber,
column: pos.column,
line: model ? model.getLineCount() : 0,
langmode: {
text: mode ? mode.aliases[0] : "",
mode: mode
},
file: this.currfile.path
};
}
/**
* Get editor value
*
* @return {*} {string}
* @memberof MonacoEditorModel
*/
getValue() {
if (this.editor == this._code_editor)
return this.editor.getValue();
return this.currfile.cache;
}
/**
* Set editor value
*
* @param {string} value
* @memberof MonacoEditorModel
*/
setValue(value) {
if (this.editor == this._code_editor)
this.editor.setValue(value);
}
getEditor() {
return this.editor;
}
}
application.MonacoEditorModel = MonacoEditorModel;
})(application = OS.application || (OS.application = {}));
})(OS || (OS = {}));
var OS;
(function (OS) {
let GUI;
(function (GUI) {
let tag;
(function (tag) {
class AntEditExtensionListItem extends tag.ListViewItemTag {
itemlayout() {
return {
el: "div",
children: [
{ el: "afx-label", ref: "label" },
{ el: "p", ref: "desc", id: "ext-list-item-d-p" },
{
el: "p",
id: "ext-list-item-b-p",
children: [
{
el: "i",
ref: "intall_status"
},
{
el: "afx-button",
ref: "btn_remove"
},
{
el: "afx-button",
ref: "btn_install"
}
]
}
]
};
}
ondatachange() {
const v = this.data;
if (!v) {
return;
}
const label = this.refs.label;
label.iconclass = "bi bi-puzzle";
label.text = `${v.text} - v${v.version}`;
// add description
const p_desc = this.refs.desc;
$(p_desc).text(v.description);
// button install
const btn_install = this.refs.btn_install;
// button remove
const btn_remove = this.refs.btn_remove;
if (v.installed) {
$(btn_remove).show();
btn_remove.iconclass = "bi bi-trash-fill";
btn_install.iconclass = "bi bi-arrow-repeat";
$(this.refs.intall_status).text(__("Installed: v{0} ", v.installed).__());
}
else {
$(btn_remove).hide();
btn_install.iconclass = "fa bi-cloud-download-fill";
$(this.refs.intall_status).text(" ");
}
}
init() {
this.closable = false;
this.data = {};
// button install
const btn_install = this.refs.btn_install;
// button remove
const btn_remove = this.refs.btn_remove;
btn_install.onbtclick = (e) => {
if (!this.data.download || !this.data.install_action) {
return;
}
this.data.install_action(this.data.download, (v) => {
this.data.installed = v;
this.update(undefined);
});
};
btn_remove.onbtclick = (e) => {
if (!this.data.installed || !this.data.uninstall_action) {
return;
}
this.data.uninstall_action(this.data.name, () => {
delete this.data.installed;
this.update(undefined);
});
};
}
reload(d) {
this.data = this.data;
}
}
tag.define("afx-antedit-ext-list-item", AntEditExtensionListItem);
})(tag = GUI.tag || (GUI.tag = {}));
})(GUI = OS.GUI || (OS.GUI = {}));
let application;
(function (application) {
const DEFAULT_REPO = "https://raw.githubusercontent.com/lxsang/antos-antedit-extensions/master/extensions.json";
/**
* A simple yet powerful code/text editor.
*
* Antedit is the default text editor shipped with
* AntOS base system. It is based on the Monaco editor
* which power the VS Code IDE.
*
* @export
* @class Antedit
* @extends {BaseApplication}
*/
class Antedit extends application.BaseApplication {
/**
*Creates an instance of Antedit.
* @param {AppArgumentsType[]} args application arguments
* @memberof Antedit
*/
constructor(args) {
super("Antedit", args);
this.currdir = undefined;
this.diff_buffer = [undefined, undefined];
}
/**
* Main application entry point
*
* @returns {void}
* @memberof Antedit
*/
main() {
this.extensions = {};
this.eum = new EditorModelManager();
this.fileview = this.find("fileview");
this.sidebar = this.find("sidebar");
this.sidebar_container = this.find("sidebar-tab-container");
this.bottombar = this.find("bottombar");
this.langstat = this.find("langstat");
this.editorstat = this.find("editorstat");
this.filestat = this.find("current-file-lbl");
this.extension_list_view = this.find("extension-list");
this.logger = new Logger(this.find("output-tab"));
this.split_mode = true;
this.fileview.fetch = (path) => new Promise(async function (resolve, reject) {
let dir;
if (typeof path === "string") {
dir = path.asFileHandle();
}
else {
dir = path;
}
try {
const d = await dir
.read();
if (d.error) {
return reject(d.error);
}
return resolve(d.result);
}
catch (e) {
return reject(__e(e));
}
});
let file = "Untitled".asFileHandle();
if (this.args && this.args.length > 0) {
this.addRecent(this.args[0].path);
if (this.args[0].type === "dir") {
this.currdir = this.args[0].path.asFileHandle();
}
else {
file = this.args[0].path.asFileHandle();
this.currdir = file.parent();
}
}
if (!this.setting.recent)
this.setting.recent = [];
if (!this.setting.extension_repos)
this.setting.extension_repos = [DEFAULT_REPO];
const wrapper = this.find("wrapper");
$(wrapper).css('visibility', 'hidden');
monaco.editor.setTheme("vs-dark");
// add editor instance
const left_editor = new OS.application.MonacoEditorModel(this, this.find("left-tabbar"), this.find("left-editorarea"));
const right_editor = new OS.application.MonacoEditorModel(this, this.find("right-tabbar"), this.find("right-editorarea"));
left_editor.setTabbarCtxMenu(this.tb_ctxmenu, (tab, data) => this.tabbar_ctx_menu_handle(tab, data, left_editor));
right_editor.setTabbarCtxMenu(this.tb_ctxmenu, (tab, data) => this.tabbar_ctx_menu_handle(tab, data, right_editor));
this.eum.add(left_editor).add(right_editor);
this.eum.onstatuschange = (st) => this.updateStatus(st);
$(wrapper).css('visibility', 'visible');
this.setup();
this.eum.active.openFile(file);
}
/**
* Get the context menu items
*/
get tb_ctxmenu() {
return [
{ text: "__(Close)", id: "close" },
{ text: "__(Close All)", id: "close-all" },
{ text: "__(Move to other side)", id: "mv-side" },
];
}
tabbar_ctx_menu_handle(tab, data, model) {
switch (data.id) {
case "close":
if (!tab) {
return;
}
model.closeTab(tab);
break;
case "close-all":
model.closeAll();
break;
case "mv-side":
if (!tab) {
return;
}
let other_model = this.eum.editors[0];
if (model == other_model) {
other_model = this.eum.editors[1];
}
other_model.openFile(tab.data);
model.closeTab(tab);
if (this.split_mode == false) {
this.toggleSplitMode();
}
break;
default:
break;
}
}
/**
* Set up the text editor
*
* @private
* @returns {void}
* @memberof Antedit
*/
setup() {
this.sidebar_container.selectedIndex = 0;
this.extension_list_view.itemtag = "afx-antedit-ext-list-item";
this.fileview.onfileopen = (e) => {
if (!e.data || !e.data.path) {
return;
}
this.addRecent(e.data.path);
if (e.data.type === "dir") {
return;
}
return this.eum.active.openFile(e.data.path.asFileHandle());
};
this.fileview.onfileselect = (e) => {
if (!e.data || !e.data.path) {
return;
}
if (e.data.type === "dir") {
return;
}
this.eum.active.selectFile(e.data.path);
};
this.on("resize", () => this.eum.resize());
this.on("focus", () => this.eum.active.focus());
this.fileview.contextmenuHandle = (e, m) => {
let file = this
.fileview.selectedFile;
const items = [
{ text: "__(New file)", id: "new" },
{ text: "__(New folder)", id: "newdir" },
{ text: "__(Rename)", id: "rename" },
{ text: "__(Delete)", id: "delete" },
{ text: "__(Upload)", id: "upload" },
];
if (file && file.type === "file") {
items.push({ text: "__(Select for compare)", id: "diff-org" });
items.push({ text: "__(Compare with selected)", id: "diff-mod" });
}
m.items = items;
m.onmenuselect = (e) => {
return this.ctxFileMenuHandle(e);
};
return m.show(e);
};
this.bindKey("ALT-N", () => this.menuAction("new"));
this.bindKey("ALT-O", () => this.menuAction("open"));
this.bindKey("ALT-F", () => this.menuAction("opendir"));
this.bindKey("CTRL-S", () => this.menuAction("save"));
this.bindKey("ALT-W", () => this.menuAction("saveas"));
this.fileview.ondragndrop = (e) => {
if (!e.data.from || !e.data.to) {
return;
}
const src = e.data.from[0].data.path.asFileHandle();
const des = e.data.to.data.path;
return src
.move(`${des}/${src.basename}`)
.then(function (d) {
const p1 = des;
const p2 = src.parent().path;
if (p1.length < p2.length) {
e.data.to.update(p1);
e.data
.from[0].parent.update(p2);
}
else {
e.data
.from[0].parent.update(p2);
e.data.to.update(p1);
}
})
.catch((e) => this.error(__("Unable to move file/folder"), e));
};
this.on("filechange", (data) => {
let { path } = data.file;
if (data.type === "file") {
({ path } = data.file.parent());
}
return this.fileview.update(path);
});
this.find("logger-clear").onbtclick = () => {
this.logger.clear();
};
if (this.setting.showBottomBar === undefined) {
this.setting.showBottomBar = false;
}
const extension = {
name: "Editor",
text: __("Editor")
};
const action = {
name: "langmode",
text: __("Change language mode"),
shortcut: 'CTRL-K'
};
this.eum.addAction(extension, action, async (e) => {
try {
const data = await this.openDialog("SelectionDialog", {
"title": __("Select language"),
data: this.eum.active.getModes()
});
this.eum.active.setMode(data);
}
catch (e) {
console.log(e);
}
});
$(this.find("txt_ext_search")).keyup((e) => this.extension_search(e));
this.loadExtensionMetaData();
this.toggleSideBar();
this.toggleSplitMode();
this.applyAllSetting();
}
/**
* Search an extension from the extension list
*
* @private
* @meberof Antedit
*/
extension_search(e) {
let k;
const search_box = this.find("txt_ext_search");
switch (e.which) {
case 37:
return e.preventDefault();
case 38:
this.extension_list_view.selectPrev();
return e.preventDefault();
case 39:
return e.preventDefault();
case 40:
this.extension_list_view.selectNext();
return e.preventDefault();
case 13:
return e.preventDefault();
default:
var text = search_box.value;
var result = [];
if (text.length === 2) {
this.extension_list_view.data = this.extension_meta_data;
return;
}
if (text.length < 3) {
return;
}
var term = new RegExp(text, "i");
for (k in this.extension_meta_data) {
if (this.extension_meta_data[k].text.match(term)) {
result.push(this.extension_meta_data[k]);
}
}
this.extension_list_view.data = result;
}
}
/**
* Refresh editor extensions list on the side bar
*
* @private
* @memberof Antedit
*/
refreshExtensionRepositories() {
const promises = [];
const meta_file = `${this.meta().path}/extensions/extensions.json`;
for (let url of [meta_file].concat(this.setting.extension_repos)) {
promises.push(url.asFileHandle().read('json'));
}
Promise.all(promises)
.then((results) => {
const meta = {};
for (let el of results.shift()) {
meta[el.name] = el;
}
this.extension_meta_data = [];
for (let result of results) {
for (let ext of result) {
if (meta[ext.name]) {
ext.installed = meta[ext.name].version;
}
ext.install_action = (url, callback) => {
(new Antedit.extensions["EditorExtensionMaker"](this))
.installZip(url)
.then(() => {
this.loadExtensionMetaData();
if (callback) {
callback(ext.version);
}
this.notify(__("Extension '{0}' installed", ext.text));
})
.catch((error) => {
this.error(__("Unable to install '{0}': {1}", ext.text, error.toString()), error);
});
};
ext.uninstall_action = (name, callback) => {
(new Antedit.extensions["EditorExtensionMaker"](this))
.uninstall(name)
.then(() => {
this.loadExtensionMetaData();
if (callback) {
callback();
}
this.notify(__("Extension '{0}' uninstalled", name));
})
.catch((error) => {
this.error(__("Unable to uninstall '{0}': {1}", name, error.toString()), error);
});
};
this.extension_meta_data.push(ext);
}
}
this.extension_list_view.data = this.extension_meta_data;
})
.catch((error) => {
this.error(__("Unable to read extension from repositories: {0}", error.toString()), error);
});
}
/**
* Update the editor status bar
*
* @private
* @memberof Antedit
*/
updateStatus(stat = undefined) {
if (!stat)
stat = this.eum.active.getEditorStatus();
this.editorstat.text = __("Row {0}, col {1}, lines: {2}", stat.row, stat.column, stat.line);
if (stat.langmode)
this.langstat.text = stat.langmode.text;
this.filestat.text = stat.file;
let win = this.scheme;
if (win.apptitle != stat.file)
win.apptitle = stat.file;
}
/**
* Show or hide the SideBar
*
* @memberof Antedit
*/
toggleSideBar() {
if (this.currdir) {
$(this.sidebar).show();
this.fileview.path = this.currdir.path;
this.refreshExtensionRepositories();
}
else {
$(this.sidebar).hide();
}
this.trigger("resize");
}
showOutput(toggle = false) {
if (toggle)
this.showBottomBar(true);
this.bottombar.selectedIndex = 0;
}
openDiff(files) {
const diff_file = new OS.API.VFS.DiffEditorFileHandle(files);
this.eum.active.openFile(diff_file);
}
/**
* Apply [[showBottomBar]] from user setting value
*
* @protected
* @param {string} k
* @memberof Antedit
*/
applySetting(k) {
if (k == "showBottomBar") {
this.showBottomBar(this.setting.showBottomBar);
}
}
/**
* Show or hide the bottom bar and
* save the value to user setting
*
* @param {boolean} v
* @memberof Antedit
*/
showBottomBar(v) {
this.setting.showBottomBar = v;
if (v) {
$(this.bottombar).show();
}
else {
$(this.bottombar).hide();
}
this.trigger("resize");
}
/**
* toggle the bottom bar
*
* @memberof Antedit
*/
toggleBottomBar() {
this.showBottomBar(!this.setting.showBottomBar);
}
/**
* Toogle split mode
*
* #memberof Antedit
**/
toggleSplitMode() {
const right_pannel = this.find("right-panel");
const right_editor = this.eum.editors[1];
const left_editor = this.eum.editors[0];
if (this.split_mode) {
// before hide check if there is dirty files
if (right_editor.isDirty()) {
this.notify(__("Unable to disable split view: Please save changes of modified files on the right panel"));
return;
}
right_editor.closeAll();
$(right_pannel).hide();
this.split_mode = false;
left_editor.focus();
}
else {
$(right_pannel).show();
this.split_mode = true;
right_editor.openFile("Untitled".asFileHandle());
right_editor.focus();
}
this.trigger("resize");
}
/**
* File menu definition
*
* @private
* @returns {GUI.BasicItemType}
* @memberof Antedit
*/
fileMenu() {
const recent = this.setting.recent.map((i) => {
return { text: i };
});
return {
text: __("File"),
nodes: [
{ text: __("New"), dataid: "new", shortcut: "A-N" },
{
text: __("Open Recent"),
dataid: "recent",
nodes: recent,
onchildselect: (e, r) => {
const handle = e.data.item.data.text.asFileHandle();
handle.onready().then((meta) => {
if (!meta) {
return;
}
if (meta.type == "dir") {
this.currdir = handle;
this.toggleSideBar();
}
else {
this.eum.active.openFile(handle);
}
});
}
},
{ text: __("Open"), dataid: "open", shortcut: "A-O" },
{
text: __("Open Folder"),
dataid: "opendir",
shortcut: "A-F",
},
{ text: __("Save"), dataid: "save", shortcut: "C-S" },
{
text: __("Save as"),
dataid: "saveas",
shortcut: "A-W",
},
],
onchildselect: (e, r) => {
return this.menuAction(e.data.item.data.dataid, r);
},
};
}
/**
* Context menu definition
*
* @private
* @param {GUI.TagEventType} e
* @returns {void}
* @memberof Antedit
*/
ctxFileMenuHandle(e) {
const el = e.data.item;
if (!el) {
return;
}
const data = el.data;
if (!data) {
return;
}
let file = this
.fileview.selectedFile;
let dir = this.currdir;
if (file && file.type === "dir") {
dir = file.path.asFileHandle();
}
if (file && file.type === "file") {
dir = file.path.asFileHandle().parent();
}
switch (data.id) {
case "new":
if (!dir) {
return;
}
this.openDialog("PromptDialog", {
title: "__(New file)",
label: "__(File name)",
}).then(async (d) => {
const fp = `${dir.path}/${d}`.asFileHandle();
try {
const r = await fp.write("text/plain");
return this.fileview.update(dir.path);
}
catch (e) {
return this.error(__("Fail to create: {0}", e.stack), __e(e));
}
});
break;
case "newdir":
if (!dir) {
return;
}
this.openDialog("PromptDialog", {
title: "__(New folder)",
label: "__(Folder name)",
}).then(async (d) => {
try {
const r = await dir.mk(d);
return this.fileview.update(dir.path);
}
catch (e) {
return this.error(__("Fail to create: {0}", dir.path), __e(e));
}
});
break;
case "rename":
if (!file) {
return;
}
this.openDialog("PromptDialog", {
title: "__(Rename)",
label: "__(File name)",
value: file.filename,
}).then(async (d) => {
if (d === file.filename) {
return;
}
file = file.path.asFileHandle();
dir = file.parent();
try {
const r = await file.move(`${dir.path}/${d}`);
return this.fileview.update(dir.path);
}
catch (e) {
return this.error(__("Fail to rename: {0}", file.path), __e(e));
}
});
break;
case "delete":
if (!file) {
return;
}
this.openDialog("YesNoDialog", {
title: "__(Delete)",
iconclass: "fa fa-question-circle",
text: __("Do you really want to delete: {0}?", file.filename),
}).then(async (d) => {
if (!d) {
return;
}
file = file.path.asFileHandle();
dir = file.parent();
try {
const r = await file.remove();
return this.fileview.update(dir.path);
}
catch (e) {
return this.error(__("Fail to delete: {0}", file.path), __e(e));
}
});
break;
case "upload":
if (!dir) {
return;
}
dir.upload()
.then((d) => {
this.notify(__("File uploaded to: {0}", dir.path));
return this.fileview.update(dir.path);
})
.catch((e) => this.error(__("Unable to upload file: {e}", e.toString()), __e(e)));
break;
case "diff-org":
if (!file)
return;
this.diff_buffer[0] = file.path.asFileHandle();
break;
case "diff-mod":
if (!file)
return;
if (!this.diff_buffer[0])
return;
this.diff_buffer[1] = file.path.asFileHandle();
this.openDiff(this.diff_buffer);
break;
default:
}
}
/**
* Add a file to recent files setting
*
* @private
* @param {string} file
* @memberof Antedit
*/
addRecent(file) {
if (!this.setting.recent)
this.setting.recent = [];
if (this.setting.recent.includes(file)) {
return;
}
this.setting.recent.unshift(file);
if (this.setting.recent.length > 10)
this.setting.recent = this.setting.recent.slice(0, 10);
}
/**
* Menu action definition
*
* @private
* @param {string} dataid
* @param {Antedit} [r]
* @returns {void}
* @memberof Antedit
*/
menuAction(dataid, r) {
let me = this;
if (r) {
me = r;
}
switch (dataid) {
case "new":
return me.eum.active.openFile("Untitled".asFileHandle());
case "open":
return me
.openDialog("FileDialog", {
title: __("Open file"),
mimes: Array.from(me.meta().mimes).filter((v) => v !== "dir"),
})
.then((f) => {
this.addRecent(f.file.path);
me.eum.active.openFile(f.file.path.asFileHandle());
});
case "opendir":
return me
.openDialog("FileDialog", {
title: __("Open folder"),
mimes: ["dir"],
})
.then(function (f) {
me.addRecent(f.file.path);
me.currdir = f.file.path.asFileHandle();
return me.toggleSideBar();
});
case "save":
return me.eum.active.save();
case "saveas":
return me.eum.active.saveAs();
default:
return console.log(dataid);
}
}
/**
* Cleanup the editor before exiting.
*
* @param {BaseEvent} evt
* @returns {void}
* @memberof Antedit
*/
cleanup(evt) {
let v;
const dirties = this.eum.dirties();
if (dirties.length === 0) {
// cleanup all extension
for (let k in this.extensions) {
if (this.extensions[k] && this.extensions[k].cleanup) {
this.extensions[k].cleanup();
}
}
return;
}
evt.preventDefault();
this.openDialog("YesNoDialog", {
title: "__(Quit)",
text: __("Ignore all unsaved files: {0} ?", (() => {
const result1 = [];
for (v of Array.from(dirties)) {
result1.push(v.filename);
}
return result1;
})().join(", ")),
}).then((d) => {
if (d) {
for (v of Array.from(dirties)) {
v.dirty = false;
}
return this.quit(false);
}
});
}
/**
* Application menu definition
*
* @returns {GUI.BasicItemType[]}
* @memberof Antedit
*/
menu() {
return [
this.fileMenu(),
{
text: "__(View)",
nodes: [
{
text: "__(Toggle bottom bar)",
dataid: "bottombar"
},
{
text: "__(Toggle split view)",
dataid: "splitview"
}
],
onchildselect: (e, r) => {
switch (e.data.item.data.dataid) {
case "bottombar":
return this.toggleBottomBar();
case "splitview":
return this.toggleSplitMode();
break;
default:
break;
}
r;
},
},
];
}
/**
* Load the extension meta data from `extension.json` file
*
* @memberof AntEdit
*/
loadExtensionMetaData() {
this.loadExtensionMetaFromFile(`${this.meta().path}/extensions/extensions.json`)
.catch((e) => {
return this.error(__("Cannot load extension meta data"), e);
});
}
/**
* Load extension meta-data from specific file
*
* @private
* @param {string} path
* @return {*} {Promise<void>}
* @memberof AntEdit
*/
loadExtensionMetaFromFile(path) {
return new Promise((resolve, reject) => {
path
.asFileHandle()
.read("json")
.then((d) => {
for (let extension of d) {
for (let act of extension.actions) {
this.eum.addAction(extension, act, (e_name, a_name) => {
this.loadAndRunExtensionAction(e_name, a_name, extension.root);
});
}
}
resolve();
})
.catch((e) => {
reject(__e(e));
});
});
}
/**
* Load extension then run an action
*
* @param name extension name
* @param action action name
* @memberof AntEdit
*/
loadAndRunExtensionAction(name, action, root) {
//verify if the extension is load
if (!Antedit.extensions[name]) {
//load the extension
let path = `${this.meta().path}/extensions/${name}/main.js`;
if (root)
path = `${root}/main.js`;
this._api
.requires(path, true)
.then(() => this.runExtensionAction(name, action))
.catch((e) => {
return this.error(__("unable to load extension: {0}", name), e);
});
}
else {
this.runExtensionAction(name, action);
}
}
/**
* Run an extension action from the command palette
*
* @private
* @param {string} name extension name
* @param {string} action action name
* @returns {void}
* @memberof AntEdit
*/
runExtensionAction(name, action) {
if (!this.extensions[name]) {
if (!Antedit.extensions[name]) {
return this.error(__("Unable to find extension: {0}", name));
}
this.extensions[name] = new Antedit.extensions[name](this);
}
if (!this.extensions[name][action]) {
return this.error(__("Unable to find action: {0}", action));
}
this.extensions[name].preload()
.then(() => this.extensions[name][action]())
.catch((e) => {
return this.error(__("Unable to preload extension"), e);
});
}
}
application.Antedit = Antedit;
/**
* Helper class to manager several instances
* of editor models
*
* @class EditorModelManager
*/
class EditorModelManager {
/**
* Creates an instance of EditorModelManager.
* @memberof EditorModelManager
*/
constructor() {
this.active_editor = undefined;
this.models = [];
}
get editors() {
return this.models;
}
set contextmenuHandle(cb) {
for (let ed of this.models) {
ed.contextmenuHandle = cb;
}
}
/**
* Get the active editor model
*
* @readonly
* @type {BaseEditorModel}
* @memberof EditorModelManager
*/
get active() {
return this.active_editor;
}
/**
* Add a model to the manager
*
* @param {BaseEditorModel} model
* @memberof EditorModelManager
*/
add(model) {
this.models.push(model);
if (!this.active_editor)
this.active_editor = model;
model.on("focus", () => {
this.active_editor = model;
});
return this;
}
/**
* Add an action to the editor
*
* @param {GenericObject<any>} extension
* @param {GenericObject<any>} action
* @param callback
* @memberof EditorModelManager
*/
addAction(extension, action, callback) {
const ed_action = {
id: `${extension.name}:${action.name}`,
label: `${extension.text.__()}: ${action.text.__()}`,
keybindings: [],
precondition: null,
keybindingContext: null,
contextMenuGroupId: extension.name,
//contextMenuOrder: 1.5,
run: () => callback(extension.name, action.name)
};
if (action.shortcut) {
const keys = action.shortcut.split("-");
let binding = 0;
for (const key of keys) {
switch (key) {
case "CTRL":
binding = binding | monaco.KeyMod.CtrlCmd;
break;
case "ALT":
binding = binding | monaco.KeyMod.Alt;
break;
case "SHIFT":
binding = binding | monaco.KeyMod.Shift;
break;
case "SUPPER":
binding = binding | monaco.KeyMod.WinCtrl;
break;
default:
const k = `Key${key}`;
if (monaco.KeyCode[k]) {
binding = binding | monaco.KeyCode[k];
}
else {
binding = 0;
}
}
}
if (binding != 0)
ed_action.keybindings.push(binding);
}
for (let ed of this.models) {
const editor = ed.getEditor();
if (!editor.getAction(ed_action.id))
editor.addAction(ed_action);
}
}
set onstatuschange(cb) {
for (let ed of this.models) {
ed.onstatuschange = cb;
}
}
dirties() {
let list = [];
for (let ed of this.models) {
list = list.concat(ed.dirties());
}
return list;
}
/**
* Resize all editor
*
* @memberof EditorModelManager
*/
resize() {
for (let ed of this.models) {
ed.resize();
}
}
}
/**
* This class handles log output to the Editor output container
*
* @class Logger
*/
class Logger {
/**
* Creates an instance of Logger.
* @param {HTMLElement} el target container
* @memberof Logger
*/
constructor(el) {
this.target = el;
}
/**
* Log level info
*
* @param {string|FormattedString} s
* @memberof Logger
*/
info(s) {
this.log("info", s, true);
}
/**
* Log level warning
*
* @param {string|FormattedString} s
* @memberof Logger
*/
warn(s) {
this.log("warn", s, true);
}
/**
* Log level error
*
* @param {string|FormattedString} s
* @memberof Logger
*/
error(s) {
this.log("error", s, true);
}
/**
* Log a string to target container
*
* @private
* @param {string} c class name of the appended log element
* @param {string|FormattedString} s log string
* @param {boolean} showtime define whether the logger should insert datetime prefix
* in the log string
* @memberof Logger
*/
log(c, s, showtime) {
let el = $("<pre></pre>")
.attr("class", `code-pad-log-${c}`);
if (showtime) {
let date = new Date();
let prefix = date.getDate() + "/"
+ (date.getMonth() + 1) + "/"
+ date.getFullYear() + " "
+ date.getHours() + ":"
+ date.getMinutes() + ":"
+ date.getSeconds();
el.text(`[${prefix}]: ${s.__()}`);
}
else {
el.text(s.__());
}
$(this.target).append(el);
$(this.target).scrollTop($(this.target)[0].scrollHeight);
}
/**
* Print a log message without prefix
*
* @param {string|FormattedString} s text to print
* @memberof Logger
*/
print(s) {
if (s.match(/warn/i)) {
this.log("warn", s, false);
}
else if (s.match(/error/i)) {
this.log("error", s, false);
}
else {
this.log("info", s, false);
}
}
/**
* Empty the log container
*
* @memberof Logger
*/
clear() {
$(this.target).empty();
}
}
Antedit.Logger = Logger;
Antedit.dependencies = [
"pkg://MonacoCore/path.js",
"pkg://MonacoCore/bundle/app.bundle.js"
];
})(application = OS.application || (OS.application = {}));
})(OS || (OS = {}));
var OS;
(function (OS) {
/**
*
*
* @class EditorBaseExtension
*/
class EditorBaseExtension {
constructor(name, app) {
this.app = app;
this.name = name;
}
/**
*
*
* @returns {Promise<any>}
* @memberof EditorBaseExtension
*/
preload() {
return OS.API.require(OS.application.Antedit.extensions[this.name].dependencies);
}
/**
*
*
* @protected
* @returns {string}
* @memberof EditorBaseExtension
*/
basedir() {
return `${this.app.meta().path}/extensions/${this.name}`;
}
/**
*
*
* @protected
* @param {(string | FormattedString)} m
* @returns {void}
* @memberof EditorBaseExtension
*/
notify(m) {
return this.app.notify(m);
}
/**
*
*
* @protected
* @param {(string | FormattedString)} m
* @param {Error} e
* @returns {void}
* @memberof EditorBaseExtension
*/
error(m, e) {
return this.app.error(m, e);
}
/**
*
*
* @protected
* @return {AnteditLogger} editor logger
* @memberof EditorBaseExtension
*/
logger() {
if (!this.app.setting.showBottomBar) {
this.app.showOutput(true);
}
else {
this.app.showOutput(false);
}
return this.app.logger;
}
/**
*
*
* @protected
* @param {string} file
* @returns {Promise<GenericObject<any>>}
* @memberof EditorBaseExtension
*/
metadata(file) {
return new Promise((resolve, reject) => {
if (!this.app.currdir) {
return reject(OS.API.throwe(__("Current folder is not found")));
}
`${this.app.currdir.path}/${file}`
.asFileHandle()
.read("json")
.then((data) => {
if (!data.root && this.app.currdir) {
data.root = this.app.currdir.path;
}
resolve(data);
})
.catch((e) => {
// try to ask user to select a folder
this.app.openDialog("FileDialog", {
title: __("Select build directory"),
root: this.app.currdir.path,
mimes: ["dir"]
})
.then((d) => {
`${d.file.path}/${file}`
.asFileHandle()
.read("json")
.then((data) => {
if (!data.root) {
data.root = d.file.path;
}
resolve(data);
})
.catch((e1) => reject(e1));
})
.catch((e1) => reject(OS.API.throwe(__("Unable to read meta-data"))));
});
});
}
}
EditorBaseExtension.dependencies = [];
OS.application.Antedit.extensions = {};
OS.application.Antedit.EditorBaseExtension = EditorBaseExtension;
class EditorExtensionMaker extends EditorBaseExtension {
constructor(app) {
super("EditorExtensionMaker", app);
}
create() {
this.logger().clear();
this.app
.openDialog("FileDialog", {
title: "__(New extension at)",
file: { basename: __("ExtensionName") },
mimes: ["dir"],
})
.then((d) => {
return this.mktpl(d.file.path, d.name);
});
}
build(callback) {
this.logger().clear();
this.metadata("extension.json")
.then(async (meta) => {
try {
const jsrc = await OS.API.VFS.cat(meta.javascripts.map(v => `${meta.root}/${v}`), "");
await `${meta.root}/build/debug/main.js`
.asFileHandle()
.setCache(jsrc)
.write("text/plain");
await `${meta.root}/build/debug/extension.json`
.asFileHandle()
.setCache(meta.meta)
.write("object");
await OS.API.VFS.copy(meta.copies.map(v => `${meta.root}/${v}`), `${meta.root}/build/debug`);
this.logger().info(__("Files generated in {0}", `${meta.root}/build/debug`));
if (callback)
callback();
}
catch (e) {
return this.logger().error(__("Unable to build extension:{0}", e.stack));
}
})
.catch((e) => this.logger().error(__("Unable to read meta-data:{0}", e.stack)));
}
run() {
this.logger().clear();
this.metadata("extension.json")
.then(async (meta) => {
if (!meta || !meta.meta || !meta.meta.name)
return this.logger().error(__("Invalid extension meta-data"));
try {
const path = `${meta.root}/build/debug/main.js`;
if (OS.API.shared[path]) {
delete OS.API.shared[path];
}
await OS.API.requires(path);
if (this.app.extensions[meta.meta.name] && this.app.extensions[meta.meta.name].cleanup) {
this.app.extensions[meta.meta.name].cleanup();
}
this.app.extensions[meta.meta.name] = new OS.application.Antedit.extensions[meta.meta.name](this.app);
for (let v of meta.meta.actions) {
this.app.eum.addAction(meta.meta, v, (e_name, a_name) => {
this.app.loadAndRunExtensionAction(e_name, a_name, `${meta.root}/build`);
});
}
this.app.eum.active.getEditor().trigger(meta.meta.name, 'editor.action.quickCommand');
}
catch (e) {
return this.logger().error(__("Unable to run extension:{0}", e.stack));
}
})
.catch((e) => this.logger().error(__("Unable to read meta-data:{0}", e.stack)));
}
release() {
this.logger().clear();
this.metadata("extension.json")
.then(async (meta) => {
this.build(async () => {
try {
await OS.API.VFS.mkar(`${meta.root}/build/debug`, `${meta.root}/build/release/${meta.meta.name}.zip`);
this.logger().info(__("Archive created at {0}", `${meta.root}/build/release/${meta.meta.name}.zip`));
}
catch (e) {
return this.logger().error(__("Unable to create archive: {0}", e.stack));
}
});
})
.catch((e) => this.logger().error(__("Unable to read meta-data: {0}", e.stack)));
}
install() {
this.logger().clear();
this.app
.openDialog("FileDialog", {
title: "__(Select extension archive)",
mimes: [".*/zip"],
})
.then(async (d) => {
try {
await this.installZip(d.file.path);
this.logger().info(__("Extension installed"));
return this.app.loadExtensionMetaData();
}
catch (e) {
return this.logger().error(__("Unable to install extension: {0}", e.stack));
}
});
}
installFromURL() {
this.logger().clear();
this.app
.openDialog("PromptDialog", {
title: __("Enter URI"),
label: __("Please enter extension URI:")
})
.then(async (v) => {
if (!v)
return;
try {
await this.installZip(v);
this.logger().info(__("Extension installed"));
return this.app.loadExtensionMetaData();
}
catch (e) {
return this.app.error(__("Unable to install extension: {0}", v));
}
});
}
/**
*
*
* @private
* @param {string} path
* @param {string} name
* @memberof EditorExtensionMaker
*/
mktpl(path, name) {
const rpath = `${path}/${name}`;
const dirs = [
rpath,
`${rpath}/build`,
`${rpath}/build/release`,
`${rpath}/build/debug`,
];
const files = [
["main.tpl", `${rpath}/${name}.js`],
["meta.tpl", `${rpath}/extension.json`],
];
OS.API.VFS.mkdirAll(dirs, true)
.then(async () => {
try {
await OS.API.VFS.mktpl(files, this.basedir(), (data) => {
return data.format(name, `${path}/${name}`);
});
this.app.currdir = rpath.asFileHandle();
this.app.toggleSideBar();
return this.app.eum.active.openFile(`${rpath}/${name}.js`.asFileHandle());
}
catch (e) {
return this.logger().error(__("Unable to create extension template: {0}", e.stack));
}
})
.catch((e) => this.logger().error(__("Unable to create extension directories: {0}", e.stack)));
}
/**
*
*
* @param {string} name extension name
* @returns {Promise<any>}
* @memberof EditorExtensionMaker
*/
uninstall(name) {
return new Promise(async (resolve, reject) => {
try {
const ext_path = `${this.app.meta().path}/extensions`;
const fp = `${ext_path}/extensions.json`.asFileHandle();
const meta = await fp.read("json");
let ext_meta = undefined;
let ext_index = undefined;
for (let idx in meta) {
if (meta[idx].name === name) {
ext_meta = meta[idx];
ext_index = idx;
break;
}
}
if (ext_meta === undefined) {
return resolve();
}
// remove the directory
await `${ext_path}/${name}`.asFileHandle().remove();
// update the extension file
meta.splice(ext_index, 1);
fp.cache = meta;
await fp.write('object');
resolve();
}
catch (e) {
reject(e);
}
});
}
/**
*
*
* @param {string} path
* @returns {Promise<any>}
* @memberof EditorExtensionMaker
*/
installZip(path) {
return new Promise(async (resolve, reject) => {
try {
await OS.API.requires("os://scripts/jszip.min.js");
const data = await path.asFileHandle().read("binary");
const zip = await JSZip.loadAsync(data);
const d = await zip.file("extension.json").async("uint8array");
const meta = JSON.parse(new TextDecoder("utf-8").decode(d));
// uninstall if exists
await this.uninstall(meta.name);
const pth = this.ext_dir(meta.name);
const dir = [pth];
const files = [];
for (let name in zip.files) {
const file = zip.files[name];
if (file.dir) {
dir.push(pth + "/" + name);
}
else if (name != "extension.json") {
files.push(name);
}
}
if (dir.length > 0) {
await OS.API.VFS.mkdirAll(dir, true);
await this.installFiles(files, zip, meta);
}
else {
await this.installFiles(files, zip, meta);
}
resolve();
}
catch (e) {
reject(__e(e));
}
});
}
ext_dir(en) {
return `${this.app.meta().path}/extensions/${en}`;
}
/**
*
*
* @private
* @param {string[]} files
* @param {*} zip
* @param {GenericObject<any>} meta
* @returns {Promise<any>}
* @memberof EditorExtensionMaker
*/
installFiles(files, zip, meta) {
if (files.length === 0) {
return this.installMeta(meta);
}
return new Promise(async (resolve, reject) => {
try {
const file = files.splice(0, 1)[0];
const path = `${this.ext_dir(meta.name)}/${file}`;
const d = await zip.file(file).async("uint8array");
const r = await path.asFileHandle()
.setCache(new Blob([d], { type: "octet/stream" }))
.write("text/plain");
if (r.error) {
return reject(r.error);
}
await this.installFiles(files, zip, meta);
resolve();
}
catch (e) {
reject(__e(e));
}
});
}
/**
*
*
* @private
* @param {GenericObject<any>} meta
* @returns {Promise<void>}
* @memberof EditorExtensionMaker
*/
installMeta(meta) {
return new Promise(async (resolve, reject) => {
const file = `${this.ext_dir("")}/extensions.json`.asFileHandle();
try {
const data = await file.read("json");
const names = [];
for (let v of data) {
names.push(v.name);
}
const idx = names.indexOf(meta.name);
if (idx >= 0) {
data.splice(idx, 1);
}
data.push(meta);
try {
await file.setCache(data).write("object");
return resolve();
}
catch (e) {
return reject(__e(e));
}
}
catch (e_1) {
// try to create new file
try {
await file.setCache([meta]).write("object");
return resolve();
}
catch (e_2) {
return reject(__e(e_2));
}
}
});
}
}
OS.application.Antedit.extensions.EditorExtensionMaker = EditorExtensionMaker;
})(OS || (OS = {}));